diff --git a/docs/reference-configuration.adoc b/docs/reference-configuration.adoc index af7c1b21..f9193da9 100644 --- a/docs/reference-configuration.adoc +++ b/docs/reference-configuration.adoc @@ -430,6 +430,12 @@ accessLog: | `false` | Enable/Disable Access logging +| `include-messages` +| `false` +| Include request params and response result/error (i.e., a JSON) in access log. +It's an expensive operation, use it for debugging only. +Note that for errors it provides only error message, not the error response itself. + | `filename` | `access_log.jsonl` | Path to the access log file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogConfig.kt index e105a691..1046fdd4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogConfig.kt @@ -1,7 +1,8 @@ package io.emeraldpay.dshackle.config class AccessLogConfig( - val enabled: Boolean = false + val enabled: Boolean = false, + val includeMessages: Boolean = false ) { var filename: String = "./access_log.jsonl" diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogReader.kt index 55422082..bdc0e3b9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AccessLogReader.kt @@ -15,7 +15,8 @@ class AccessLogReader : YamlConfigReader(), ConfigReader { if (!enabled) { AccessLogConfig.disabled() } else { - val config = AccessLogConfig(true) + val includeMessages = getValueAsBool(node, "include-messages") ?: false + val config = AccessLogConfig(true, includeMessages) getValueAsString(node, "filename")?.let { config.filename = it } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriter.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriter.kt index dd6e022f..c7ac8e2f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriter.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriter.kt @@ -63,6 +63,9 @@ class AccessLogWriter( } log.info("Writing Access Log to ${filename.absolutePath}") scheduler.schedule(runner, START_SLEEP_MS, TimeUnit.MILLISECONDS) + + //propagate current config to the Event Builder, so it knows which details to include + EventsBuilder.accessLogConfig = config } private fun flushRunner() { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt index 4f6daefe..8d6c55f8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/Events.kt @@ -108,7 +108,9 @@ class Events { val succeed: Boolean, val rpcError: Int? = null, val payloadSizeBytes: Long, - val nativeCall: NativeCallItemDetails + val nativeCall: NativeCallItemDetails, + val responseBody: String? = null, + val errorMessage: String? = null ) : ChainBase(blockchain, "NativeCall", id, channel) @JsonInclude(JsonInclude.Include.NON_NULL) @@ -120,7 +122,8 @@ class Events { // info about the initial request, that may include several native calls val request: StreamRequestDetails, val payloadSizeBytes: Long, - val nativeSubscribe: NativeSubscribeItemDetails + val nativeSubscribe: NativeSubscribeItemDetails, + val responseBody: String? = null, ) : ChainBase(blockchain, "NativeSubscribe", id, channel) @JsonInclude(JsonInclude.Include.NON_NULL) @@ -159,7 +162,8 @@ class Events { data class NativeCallItemDetails( val method: String, val id: Int, - val payloadSizeBytes: Long + val payloadSizeBytes: Long, + val requestParams: String? = null ) data class NativeCallReplyDetails( diff --git a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt index 17bd3227..dcddaa34 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/monitoring/accesslog/EventsBuilder.kt @@ -17,6 +17,9 @@ package io.emeraldpay.dshackle.monitoring.accesslog import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.Global +import io.emeraldpay.dshackle.config.AccessLogConfig +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.grpc.Chain import io.grpc.Attributes import io.grpc.Grpc @@ -36,6 +39,10 @@ class EventsBuilder { companion object { private val log = LoggerFactory.getLogger(EventsBuilder::class.java) + + // A reference to the config for current _running instance_. + // Initialized by AccessLogWriter + var accessLogConfig: AccessLogConfig = AccessLogConfig.default() } interface StartingHttp2Request { @@ -306,7 +313,10 @@ class EventsBuilder { Events.NativeCallItemDetails( item.method, item.id, - item.payload.size().toLong() + item.payload.size().toLong(), + if (accessLogConfig.includeMessages) { + if (item.payload != null && !item.payload.isEmpty && item.payload.isValidUtf8) item.payload.toStringUtf8() else "" + } else null ) ) } @@ -323,7 +333,11 @@ class EventsBuilder { nativeCall = item, payloadSizeBytes = item.payloadSizeBytes, id = UUID.randomUUID(), - channel = Events.Channel.GRPC + channel = Events.Channel.GRPC, + responseBody = if (accessLogConfig.includeMessages) { + if (msg.payload != null && !msg.payload.isEmpty && msg.payload.isValidUtf8) msg.payload.toStringUtf8() else "" + } else null, + errorMessage = if (accessLogConfig.includeMessages) msg.errorMessage else null, ) } @@ -341,7 +355,13 @@ class EventsBuilder { nativeCall = item, payloadSizeBytes = item.payloadSizeBytes, id = UUID.randomUUID(), - channel = channel + channel = channel, + responseBody = if (accessLogConfig.includeMessages) (reply.result?.let { String(it) } ?: "") else null, + errorMessage = if (accessLogConfig.includeMessages) { + reply.error?.let { + it.upstreamError?.message ?: it.message + } ?: "" + } else null ) } } @@ -373,7 +393,8 @@ class EventsBuilder { nativeSubscribe = item!!, payloadSizeBytes = msg.payload?.size()?.toLong() ?: 0L, id = UUID.randomUUID(), - channel = Events.Channel.GRPC + channel = Events.Channel.GRPC, + responseBody = if (accessLogConfig.includeMessages) (msg.payload?.toStringUtf8() ?: "") else null, ) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriterSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriterSpec.groovy index bde9ce73..38dedfb9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriterSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/monitoring/accesslog/AccessLogWriterSpec.groovy @@ -17,7 +17,7 @@ class AccessLogWriterSpec extends Specification { File accessLog = new File(dir, "accesslog.jsonl") println("Write access log to $accessLog.absolutePath") MainConfig config = new MainConfig() - config.accessLogConfig = new AccessLogConfig(true).tap { + config.accessLogConfig = new AccessLogConfig(true, false).tap { it.filename = accessLog.absolutePath } AccessLogWriter logWriter = new AccessLogWriter(config)