commit 63c01db19377f84266fae19d5f768597d47fc976
parent 4b3cf170c669079f644c33b809c528e780b1abd7
Author: Antoine A <>
Date: Wed, 28 May 2025 10:49:35 +0200
common: improve API logging
Diffstat:
5 files changed, 62 insertions(+), 19 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2023-2024 Taler Systems S.A.
+ * Copyright (C) 2023-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -34,7 +34,7 @@ import com.github.ajalt.clikt.core.main
val logger: Logger = LoggerFactory.getLogger("libeufin-bank")
/** Set up web server handlers for the Taler corebank API */
-fun Application.corebankWebApp(db: Database, ctx: BankConfig) = talerApi(logger) {
+fun Application.corebankWebApp(db: Database, ctx: BankConfig) = talerApi(LoggerFactory.getLogger("libeufin-bank-api")) {
coreBankApi(db, ctx)
conversionApi(db, ctx)
bankIntegrationApi(db, ctx)
diff --git a/common/src/main/kotlin/api/server.kt b/common/src/main/kotlin/api/server.kt
@@ -30,9 +30,13 @@ import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.forwardedheaders.*
import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.plugins.callid.*
import io.ktor.server.request.*
+import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.utils.io.*
+import io.ktor.util.pipeline.*
+import io.ktor.http.content.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import org.postgresql.util.PSQLState
@@ -46,10 +50,27 @@ import java.util.zip.DataFormatException
import java.util.zip.Inflater
/**
- * This plugin checks for body length limit and inflates the requests that have "Content-Encoding: deflate"
+ * This plugin apply Taler specific logic
+ * It checks for body length limit and inflates the requests that have "Content-Encoding: deflate"
+ * It logs incoming requests and their details
*/
-fun bodyLimitPlugin(logger: Logger): ApplicationPlugin<Unit> {
- return createApplicationPlugin("BodyLimitAndDecompression") {
+fun talerPlugin(logger: Logger): ApplicationPlugin<Unit> {
+ return createApplicationPlugin("TalerPlugin") {
+ onCall { call ->
+ // Log incoming transaction
+ val requestCall = buildString {
+ val path = call.request.path()
+ append(call.request.httpMethod.value)
+ append(' ')
+ append(call.request.path())
+ val query = call.request.queryString()
+ if (query.isNotEmpty()) {
+ append('?')
+ append(query)
+ }
+ }
+ logger.info(requestCall)
+ }
onCallReceive { call ->
// Check content length if present and wellformed
val contentLenght = call.request.headers[HttpHeaders.ContentLength]?.toIntOrNull()
@@ -96,6 +117,9 @@ fun bodyLimitPlugin(logger: Logger): ApplicationPlugin<Unit> {
TalerErrorCode.GENERIC_COMPRESSION_INVALID
)
}
+ logger.trace {
+ "request ${bytes.sliceArray(0 until read).asUtf8()}"
+ }
ByteReadChannel(bytes, 0, read)
}
}
@@ -104,18 +128,21 @@ fun bodyLimitPlugin(logger: Logger): ApplicationPlugin<Unit> {
/** Set up web server handlers for a Taler API */
fun Application.talerApi(logger: Logger, routes: Routing.() -> Unit) {
+ install(CallId) {
+ generate(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+ verify { true }
+ }
install(CallLogging) {
+ callIdMdc("call-id")
level = Level.INFO
this.logger = logger
format { call ->
val status = call.response.status()
- val httpMethod = call.request.httpMethod.value
- val path = call.request.path()
val msg = call.logMsg()
if (msg != null) {
- "${status?.value} $httpMethod $path ${call.processingTimeMillis()}ms: $msg"
+ "${status?.value} ${call.processingTimeMillis()}ms: $msg"
} else {
- "${status?.value} $httpMethod $path ${call.processingTimeMillis()}ms"
+ "${status?.value} ${call.processingTimeMillis()}ms"
}
}
}
@@ -129,7 +156,7 @@ fun Application.talerApi(logger: Logger, routes: Routing.() -> Unit) {
allowMethod(HttpMethod.Delete)
allowCredentials = true
}
- install(bodyLimitPlugin(logger))
+ install(talerPlugin(logger))
install(IgnoreTrailingSlash)
install(ContentNegotiation) {
json(Json {
@@ -157,8 +184,7 @@ fun Application.talerApi(logger: Logger, routes: Routing.() -> Unit) {
)
}
exception<Exception> { call, cause ->
- // TODO nexus specific error code ?!
- logger.trace("request failed", cause)
+ logger.debug("failure", cause)
when (cause) {
is ApiException -> call.err(cause, null)
is SQLException -> {
@@ -221,6 +247,21 @@ fun Application.talerApi(logger: Logger, routes: Routing.() -> Unit) {
}
}
}
+ val phase = PipelinePhase("phase")
+ sendPipeline.insertPhaseBefore(ApplicationSendPipeline.Engine, phase)
+ sendPipeline.intercept(phase) { response ->
+ if (logger.isTraceEnabled) {
+ val content = when (response) {
+ is OutgoingContent.ByteArrayContent -> String(response.bytes())
+ is OutgoingContent.NoContent -> null
+ else -> error("")
+ }
+ if (content != null) {
+ logger.trace("response ${content}")
+ }
+ }
+
+ }
routing { routes() }
}
diff --git a/common/src/main/kotlin/helpers.kt b/common/src/main/kotlin/helpers.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2024-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -168,6 +168,10 @@ inline fun Logger.debug(lambda: () -> String) {
if (isDebugEnabled) debug(lambda())
}
+inline fun Logger.trace(lambda: () -> String) {
+ if (isTraceEnabled) trace(lambda())
+}
+
/* ----- KTOR ----- */
fun ApplicationCall.uuidPath(name: String): UUID {
diff --git a/common/src/main/kotlin/log.kt b/common/src/main/kotlin/log.kt
@@ -68,13 +68,12 @@ class TalerLogger(private val loggerName: String): LegacyAbstractLogger() {
append(" - ")
append(MessageFormatter.basicArrayFormat(messagePattern, arguments))
- /*throwable.let { t ->
- append("\n")
+ throwable?.let { t ->
append("${t.javaClass.simpleName}: ${t.message}")
t.stackTrace.take(10).forEach { stackElement ->
append("\n\tat $stackElement")
}
- }*/
+ }
}
System.err.println(logEntry)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -1,7 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2023 Stanisci and Dold.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2023-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -45,7 +44,7 @@ data class IbanAccountMetadata(
val name: String
)
-fun Application.nexusApi(db: Database, cfg: NexusConfig) = talerApi(logger) {
+fun Application.nexusApi(db: Database, cfg: NexusConfig) = talerApi(LoggerFactory.getLogger("libeufin-nexus-api")) {
wireGatewayApi(db, cfg)
revenueApi(db, cfg)
}