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/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: diff --git a/backend/src/main/resources/logback.xml b/backend/src/main/resources/logback.xml index 546c784..1bbeb63 100644 --- a/backend/src/main/resources/logback.xml +++ b/backend/src/main/resources/logback.xml @@ -1,10 +1,34 @@ - - - %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n - + + + + + + + + timestamp + message + logger + thread + level + + + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + - + \ No newline at end of file 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")