📝 Logging

Konfiguracja logowania requestów i odpowiedzi HTTP.

Logging Interceptor

import rip.nerd.kitsunenet.interceptor.KNETLoggingInterceptor

val loggingInterceptor = KNETLoggingInterceptor(
    level = LogLevel.BODY,   // NONE, BASIC, HEADERS, BODY
    tag = "KNET"
)

val client = KNETClient.builder()
    .addInterceptor(loggingInterceptor)
    .build()

Log levels

Level Loguje Użycie
NONE Nic Production
BASIC Method, URL, status, czas Monitoring
HEADERS BASIC + nagłówki Debug auth
BODY HEADERS + body Full debug
// Przykład output BODY level:
// --> POST https://api.example.com/users
// Content-Type: application/json
// Authorization: Bearer ***
//
// {"name":"Jan","email":"jan@example.com"}
// --> END POST
//
// <-- 201 Created (245ms)
// Content-Type: application/json
//
// {"id":1,"name":"Jan","email":"jan@example.com"}
// <-- END HTTP

Custom logger

val loggingInterceptor = KNETLoggingInterceptor(
    level = LogLevel.BODY,
    logger = { tag, message ->
        // Custom logging (np. Timber)
        Timber.tag(tag).d(message)

        // Lub do pliku
        logFile.appendText("$message\n")

        // Lub do crashlytics
        if (message.contains("Error")) {
            FirebaseCrashlytics.getInstance().log(message)
        }
    }
)

Redakcja sensitive data

val loggingInterceptor = KNETLoggingInterceptor(
    level = LogLevel.BODY,
    redactHeaders = setOf(
        "Authorization",
        "Cookie",
        "X-API-Key",
        "X-Auth-Token"
    ),
    redactBodyFields = setOf(
        "password",
        "credit_card",
        "ssn",
        "secret"
    )
)

// Output:
// Authorization: ██████████
// {"email":"jan@example.com","password":"██████████"}

Filtrowanie

val loggingInterceptor = KNETLoggingInterceptor(
    level = LogLevel.BODY,
    filter = { request ->
        // Nie loguj health checks
        !request.url.contains("/health") &&
        !request.url.contains("/ping") &&
        // Nie loguj dużych body
        (request.data?.toString()?.length ?: 0) < 10000
    },
    maxBodyLength = 5000  // Truncate długich body
)

Request Logger (zaawansowany)

import rip.nerd.kitsunenet.logging.KNETRequestLogger

val logger = KNETRequestLogger.Builder()
    .enableRequestLogging(true)
    .enableResponseLogging(true)
    .enableTimingLogging(true)
    .enableErrorLogging(true)
    .formatJson(true)  // Pretty print JSON
    .maxBodySize(10_000)
    .redactPatterns(listOf(
        Regex("\"password\"\\s*:\\s*\"[^\"]+\""),
        Regex("Bearer\\s+\\S+")
    ))
    .output(LogOutput.LOGCAT)  // LOGCAT, FILE, CUSTOM
    .build()

val client = KNETClient.builder()
    .addInterceptor(logger.interceptor())
    .build()

File logging

val logger = KNETRequestLogger.Builder()
    .output(LogOutput.FILE)
    .logDirectory(File(context.filesDir, "logs"))
    .maxFileSize(5 * 1024 * 1024)  // 5MB per file
    .maxFiles(10)  // Keep last 10 files
    .build()

// Logs zapisywane do:
// /data/data/app/files/logs/knet_2024-01-15.log

// Export logów
logger.exportLogs { logFiles ->
    // Wyślij do support/debugging
    uploadLogs(logFiles)
}

Debug vs Production

val loggingInterceptor = if (BuildConfig.DEBUG) {
    KNETLoggingInterceptor(
        level = LogLevel.BODY,
        tag = "KNET-DEBUG"
    )
} else {
    // W production - tylko błędy
    KNETLoggingInterceptor(
        level = LogLevel.NONE,
        logger = { tag, message ->
            // Loguj tylko do crashlytics przy błędach
        }
    )
}

val client = KNETClient.builder()
    .addInterceptor(loggingInterceptor)
    .build()

cURL export

val loggingInterceptor = KNETLoggingInterceptor(
    level = LogLevel.BODY,
    exportCurl = true  // Dodaje cURL command do logów
)

// Output:
// --> POST https://api.example.com/users
// curl -X POST 'https://api.example.com/users' \
//   -H 'Content-Type: application/json' \
//   -H 'Authorization: Bearer token' \
//   -d '{"name":"Jan"}'

📚 Zobacz też