From 097881d7069b26ddbc1f935593ef716d8cd23c83 Mon Sep 17 00:00:00 2001 From: TheMeinerLP Date: Sat, 20 Jun 2026 15:28:13 +0200 Subject: [PATCH 1/3] build: add OpenTelemetry and JSON-logging dependencies Add the version-catalog entries (settings.gradle.kts) and wire them into the backend: micronaut-tracing-opentelemetry http/jdbc, the OTLP exporter, logstash-logback-encoder, the OpenTelemetry logback MDC appender and janino. Also disable the AOT replaceLogbackXml optimization so logback.xml is parsed at runtime, which the env-driven JSON/plain switch and ${...} substitution in the optimized (Docker/prod) jar depend on. --- backend/build.gradle.kts | 17 ++++++++++++++++- settings.gradle.kts | 11 +++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index dfed056..09b0cfe 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -42,6 +42,19 @@ dependencies { implementation(mn.jackson.databind) implementation(mn.jackson.datatype.jsr310) + // Distributed tracing (OpenTelemetry). Spans/export are only active when + // OTEL_TRACES_EXPORTER=otlp is set (prod/Docker) — see application.yml. + implementation(mn.micronaut.tracing.opentelemetry.http) + implementation(mn.micronaut.tracing.opentelemetry.jdbc) + implementation(libs.opentelemetry.exporter.otlp) + + // Structured JSON logging for Grafana Loki + trace/log correlation. + // logstash encoder renders JSON; the OTel MDC appender injects trace_id/span_id. + implementation(libs.logstash.logback.encoder) + implementation(libs.opentelemetry.logback.mdc) + // Enables the // conditional in logback.xml. + runtimeOnly(libs.janino) + testImplementation(mn.micronaut.test.rest.assured) testImplementation(mn.junit.jupiter.api) testImplementation(mn.junit.jupiter.params) @@ -76,7 +89,9 @@ micronaut { optimizeClassLoading = true deduceEnvironment = true optimizeNetty = true - replaceLogbackXml = true + // Keep logback.xml parsed at runtime so the env-driven JSON/plain switch + // and ${...} substitutions work in the optimized (Docker/prod) jar. + replaceLogbackXml = false } } diff --git a/settings.gradle.kts b/settings.gradle.kts index e7b6d30..5ebf888 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,10 @@ dependencyResolutionManagement { version("jackson", "2.22.0") version("jakarta-annotation", "3.0.0") + version("logstash-logback-encoder", "8.1") + version("opentelemetry-instrumentation-alpha", "2.20.1-alpha") + version("janino", "3.1.12") + library( "jetbrains.annotations", "org.jetbrains", @@ -39,6 +43,13 @@ dependencyResolutionManagement { library("jackson-databind-nullable", "org.openapitools", "jackson-databind-nullable").version("0.2.10") library("jakarta-annotation-api", "jakarta.annotation", "jakarta.annotation-api").versionRef("jakarta-annotation") + // Observability — JSON logging + OpenTelemetry (see backend/build.gradle.kts). + // Version managed by the Micronaut platform BOM (opentelemetry-bom). + library("opentelemetry-exporter-otlp", "io.opentelemetry", "opentelemetry-exporter-otlp").withoutVersion() + library("logstash-logback-encoder", "net.logstash.logback", "logstash-logback-encoder").versionRef("logstash-logback-encoder") + library("opentelemetry-logback-mdc", "io.opentelemetry.instrumentation", "opentelemetry-logback-mdc-1.0").versionRef("opentelemetry-instrumentation-alpha") + library("janino", "org.codehaus.janino", "janino").versionRef("janino") + plugin("micronaut.application", "io.micronaut.application").versionRef("micronaut") plugin("micronaut.aot", "io.micronaut.aot").versionRef("micronaut") From 75c32e46e053ca5a125edb9135dcea3546ae750b Mon Sep 17 00:00:00 2001 From: TheMeinerLP Date: Sat, 20 Jun 2026 15:28:21 +0200 Subject: [PATCH 2/3] feat(logging): structured JSON logging for Grafana Loki Switch logback to emit structured JSON on stdout when LOG_JSON=true (set in the Docker/prod container) so Loki can index the fields; local dev keeps the human-readable pattern. Wrap the appender with the OpenTelemetry MDC appender so each line carries trace_id/span_id for log <-> trace correlation. --- backend/src/main/resources/logback.xml | 33 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml index 546c784..eaa2771 100644 --- a/backend/src/main/resources/logback.xml +++ b/backend/src/main/resources/logback.xml @@ -1,10 +1,33 @@ + + + - - %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - + + + + + {"service":"${OTEL_SERVICE_NAME:-otis}"} + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + - + - \ No newline at end of file + From e35b7d8362680fe7368d7dc3e52212094cd6e352 Mon Sep 17 00:00:00 2001 From: TheMeinerLP Date: Sat, 20 Jun 2026 15:28:21 +0200 Subject: [PATCH 3/3] feat(tracing): configure OpenTelemetry OTLP export Add the otel config block: tracing is disabled by default (exporter "none") and activates when OTEL_TRACES_EXPORTER=otlp is set, exporting spans over OTLP gRPC to the configured endpoint. Metrics stay on Prometheus/Micrometer, and health/prometheus/swagger paths are excluded from tracing. --- backend/src/main/resources/application.yml | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 7b4c913..7697a95 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -21,6 +21,29 @@ jpa: auto: update show_sql: false +# OpenTelemetry — tracing is disabled unless OTEL_TRACES_EXPORTER=otlp is set +# (do this in the Docker/prod container). Metrics stay on Prometheus/Micrometer. +otel: + service: + name: ${OTEL_SERVICE_NAME:otis} + traces: + exporter: ${OTEL_TRACES_EXPORTER:none} + metrics: + exporter: none + logs: + exporter: none + exporter: + otlp: + protocol: grpc + endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:`http://localhost:4317`} + exclusions: + - /health + - /health/.* + - /prometheus + - /swagger + - /swagger/.* + - /swagger-ui/.* + # OpenAPI router: static-resources: