Skip to content
  • There are no suggestions because the search field is empty.

SDK logging — How to implement Rest API logging if the Java SDK is used as a library in a client project (before SDK refactoring in Q3/Q4 2026)?

1. Overview

This guide explains how to implement REST API request/response logging for the Oddin SDK when consuming it as a library (JAR dependency). Since you cannot modify the SDK's internal classes, the approach uses Fuel HTTP interceptors to capture all HTTP traffic made by the SDK, including response payloads and timing information. How it works The Oddin SDK uses Fuel as its HTTP client. Fuel provides a global interceptor mechanism through FuelManager.instance that allows you to hook into every HTTP request and response — including those made internally by the SDK. By registering interceptors at application startup (before initializing the SDK), you can transparently capture:

  • The HTTP method and URL of every SDK API call
  • Timestamps and response times (network latency)
  • Response status codes and bodies
  • Error details for failed requests

ℹ️ Note: This approach requires no changes to the SDK source code. All configuration is done in your application.

2. Prerequisites

Your application must include the following dependencies alongside the Oddin SDK. Gradle (build.gradle) groovy

dependencies {
    // SLF4J API
    implementation 'org.slf4j:slf4j-api:1.7.36'

    // Logback (logging implementation)
    implementation 'ch.qos.logback:logback-classic:1.2.11'
    implementation 'ch.qos.logback:logback-core:1.2.11'

    // KotlinLogging (SLF4J facade for Kotlin)
    implementation 'io.github.microutils:kotlin-logging:3.0.5'
}

Maven (pom.xml) xml

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>
<dependency>
    <groupId>io.github.microutils</groupId>
    <artifactId>kotlin-logging-jvm</artifactId>
    <version>3.0.5</version>
</dependency>

3. Implementing the Fuel Interceptors

Register the following interceptors early in your application's initialization, before the Oddin SDK makes any HTTP calls. This is typically done in your main function or application bootstrap class.

3.1 Required Imports

kotlin

import com.github.kittinunf.fuel.core.FuelManager
import mu.KotlinLogging
import java.util.*
3.2 Logger Declaration

Create a logger instance named to match the SDK's ApiClient class. This naming is important — it allows the Logback configuration (Section 4) to route these logs to a dedicated file. kotlin

private val apiLogger = KotlinLogging.logger("com.oddin.oddsfeedsdk.api.ApiClient")
3.3 Request Interceptor (Timing)

This interceptor stamps each outgoing request with the current timestamp so the response interceptor can calculate network latency. kotlin

FuelManager.instance.addRequestInterceptor { next ->
    { request ->
        request.tag = System.currentTimeMillis()
        next(request)
    }
}
3.4 Response Interceptor (Logging)

This interceptor captures the response, calculates timing, and logs the full details. kotlin

FuelManager.instance.addResponseInterceptor { next ->
    { request, response ->
        val result = next(request, response)
        val startTime = request.tag as? Long
        val networkMillis = if (startTime != null)
            System.currentTimeMillis() - startTime else -1
        val responseBody = String(response.data)
        val statusCode = response.statusCode

        if (statusCode in 200..299) {
            apiLogger.info {
                "API Request: ${request.method} ${request.url}" +
                " - Timestamp: ${Date()}" +
                " - Response Time: ${networkMillis}ms" +
                " - Status: $statusCode" +
                " - Response Body: $responseBody"
            }
        } else {
            apiLogger.warn {
                "API Request: ${request.method} ${request.url}" +
                " - Timestamp: ${Date()}" +
                " - Response Time: ${networkMillis}ms" +
                " - Status: $statusCode" +
                " - Failed" +
                " - Response Body: $responseBody"
            }
        }

        result
    }
}
3.5 Complete Example

Here is a full example of how the interceptor setup looks in a single initialization block: kotlin

import com.github.kittinunf.fuel.core.FuelManager
import mu.KotlinLogging
import java.util.*

private val apiLogger = KotlinLogging.logger("com.oddin.oddsfeedsdk.api.ApiClient")

fun setupApiLogging() {
    // Stamp each request with a start timestamp
    FuelManager.instance.addRequestInterceptor { next ->
        { request ->
            request.tag = System.currentTimeMillis()
            next(request)
        }
    }

    // Log every response with timing and payload
    FuelManager.instance.addResponseInterceptor { next ->
        { request, response ->
            val result = next(request, response)
            val startTime = request.tag as? Long
            val networkMillis = if (startTime != null)
                System.currentTimeMillis() - startTime else -1
            val responseBody = String(response.data)
            val statusCode = response.statusCode

            if (statusCode in 200..299) {
                apiLogger.info {
                    "API Request: ${request.method} ${request.url}" +
                    " - Timestamp: ${Date()}" +
                    " - Response Time: ${networkMillis}ms" +
                    " - Status: $statusCode" +
                    " - Response Body: $responseBody"
                }
            } else {
                apiLogger.warn {
                    "API Request: ${request.method} ${request.url}" +
                    " - Timestamp: ${Date()}" +
                    " - Response Time: ${networkMillis}ms" +
                    " - Status: $statusCode" +
                    " - Failed" +
                    " - Response Body: $responseBody"
                }
            }

            result
        }
    }
}

fun main() {
    // Call this BEFORE initializing the Oddin SDK
    setupApiLogging()

    // Now initialize the SDK as usual
    // val sdk = OddsFeedSdk(...)
}

4. Isolating Logs into a Dedicated File

By default, the interceptor logs will appear in your standard console output mixed with your application logs. To route them to a dedicated, automatically rotating compressed file, configure Logback as described below.

4.1 Logback Configuration

Create or edit the file src/main/resources/logback.xml in your application with the following content: xml

<configuration>
    <!-- 1. Standard Console Appender for normal application logs -->
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{ISO8601} [%level] - %logger{36}:%line - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 2. Rolling File Appender for API logs -->
    <appender name="API_LOG_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/api-requests.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/api-requests-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>500MB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{ISO8601} [%level] - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 3. Route SDK API logs to the file appender only -->
    <logger name="com.oddin.oddsfeedsdk.api.ApiClient"
            level="INFO" additivity="false">
        <appender-ref ref="API_LOG_FILE" />
    </logger>

    <!-- 4. Default root logger (console) -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
4.2 How It Works
  • Logger name matching: Because the interceptor's logger is named "com.oddin.oddsfeedsdk.api.ApiClient", the Logback <logger> element with that exact name captures all its output.
  • additivity="false": This prevents the API logs from propagating up to the root logger, so they will not appear in your console output.
  • Rolling policy: Log files rotate daily and are compressed to .gz. Files larger than 10 MB trigger an intra-day rollover. History is retained for 30 days with a 500 MB total cap.
  • Log location: Logs are written to logs/api-requests.log relative to your application's working directory.

5. Log Output Format

Once configured, each API call made by the SDK will produce a log entry like the following: Successful request

2025-07-15T14:32:01.123+00:00 [INFO] - API Request: GET
    <https://api.oddin.gg/v1/sports/en/sport.xml>
    - Timestamp: Tue Jul 15 14:32:00 UTC 2025
    - Response Time: 142ms
    - Status: 200
    - Response Body: <?xml version="1.0" ...>...

Failed request

2025-07-15T14:32:05.456+00:00 [WARN] - API Request: POST
    <https://api.oddin.gg/v1/some/endpoint>
    - Timestamp: Tue Jul 15 14:32:05 UTC 2025
    - Response Time: 89ms
    - Status: 403
    - Failed
    - Response Body: {"error":"Forbidden"}

6. Summary of Steps

  1. Add the required dependencies (SLF4J, Logback, KotlinLogging) to your build.gradle or pom.xml.
  2. Create a setupApiLogging() function that registers the Fuel request and response interceptors (see Section 3.5).
  3. Call setupApiLogging() before initializing the Oddin SDK.
  4. Add the logback.xml configuration to src/main/resources/ to isolate logs into a dedicated rotating file (see Section 4.1).

ℹ️ No changes to the SDK source code are required. All logging is implemented externally through Fuel's global interceptor mechanism and Logback's configuration.