Add OpenTelemetry tracing support for Redis commands

- Add RedisSpan interface in rbcs-api for opaque span handles
- Extend TelemetryController with startRedisSpan/endRedisSpan methods
- Implement Redis tracing in rbcs-server-otel via OtelController and RedisOtelSpan
- Instrument RedisCacheHandler to create spans around GET and SET commands
- Add uses directive in rbcs-server-redis module-info for ServiceLoader discovery

Redis spans are created as CLIENT spans with attributes:
db.system=redis, db.operation=GET|SET, server.address, server.port
This commit is contained in:
opencode
2026-05-21 00:48:37 +00:00
committed by Walter Oggioni
parent 316f9e61b0
commit f154bbd33c
6 changed files with 136 additions and 41 deletions
@@ -2,10 +2,13 @@ package net.woggioni.rbcs.server.otel
import io.netty.channel.ChannelHandler
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender
import io.opentelemetry.instrumentation.netty.v4_1.NettyServerTelemetry
import io.opentelemetry.instrumentation.runtimetelemetry.RuntimeTelemetry
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk
import net.woggioni.rbcs.api.RedisSpan
import net.woggioni.rbcs.api.TelemetryController
import net.woggioni.rbcs.common.createLogger
import net.woggioni.rbcs.common.info
@@ -14,6 +17,10 @@ class OtelController : TelemetryController {
private val log = createLogger<OtelController>()
private val tracer by lazy {
GlobalOpenTelemetry.getTracer("net.woggioni.rbcs.server.redis", "0.5.0")
}
override fun initialize() {
log.info { "Initializing OpenTelemetry SDK with auto-configuration" }
@@ -36,4 +43,24 @@ class OtelController : TelemetryController {
override fun createHandler(): ChannelHandler {
return NettyServerTelemetry.create(GlobalOpenTelemetry.get()).createCombinedHandler()
}
override fun startRedisSpan(command: String, key: String): RedisSpan? {
val span = tracer.spanBuilder(command)
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.system", "redis")
.setAttribute("db.operation", command)
.startSpan()
return RedisOtelSpan(span)
}
override fun endRedisSpan(span: RedisSpan?) {
(span as? RedisOtelSpan)?.delegate?.end()
}
override fun endRedisSpan(span: RedisSpan?, error: Throwable) {
val s = (span as? RedisOtelSpan)?.delegate ?: return
s.recordException(error)
s.setStatus(StatusCode.ERROR)
s.end()
}
}
@@ -0,0 +1,17 @@
package net.woggioni.rbcs.server.otel
import io.opentelemetry.api.trace.Span
import net.woggioni.rbcs.api.RedisSpan
internal class RedisOtelSpan(
val delegate: Span,
) : RedisSpan {
override fun setAttribute(key: String, value: String) {
delegate.setAttribute(key, value)
}
override fun setAttribute(key: String, value: Long) {
delegate.setAttribute(key, value)
}
}