| 1 | import * as SharedObservability from "@t3tools/shared/observability"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; |
| 4 | import * as Logger from "effect/Logger"; |
| 5 | import * as References from "effect/References"; |
| 6 | import * as OtlpExporter from "effect/observability/OtlpExporter"; |
| 7 | import * as OtlpLogger from "effect/observability/OtlpLogger"; |
| 8 | |
| 9 | import * as ServerConfig from "./config.ts"; |
| 10 | |
| 11 | export const layer = Effect.gen(function* () { |
| 12 | const config = yield* ServerConfig.ServerConfig; |
| 13 | const layerMinimumLogLevel = Layer.succeed(References.MinimumLogLevel, config.logLevel); |
| 14 | |
| 15 | const logs = config.otlpLogsExport; |
| 16 | const otlpLogger = |
| 17 | config.otlpLogsUrl === undefined |
| 18 | ? undefined |
| 19 | : OtlpLogger.make({ |
| 20 | url: config.otlpLogsUrl, |
| 21 | exportInterval: `${logs.exportIntervalMs} millis`, |
| 22 | headers: logs.headers, |
| 23 | resource: ServerConfig.otlpResource(config), |
| 24 | }); |
| 25 | |
| 26 | // `Logger.layer` writes the whole logger set rather than adding to it, so |
| 27 | // every logger the server wants has to be named in this one call. |
| 28 | // |
| 29 | // `Logger.tracerLogger` reaches a collector by attaching each message to the |
| 30 | // active span as a span event, which covers only messages logged inside a |
| 31 | // recorded span and files them under traces. The OTLP logger carries the same |
| 32 | // messages as log records stamped with their trace and span ids, so it is a |
| 33 | // superset: keeping both would export every in-span message twice. |
| 34 | // |
| 35 | // Recording events on spans is also the shape OpenTelemetry is deprecating, |
| 36 | // in favor of the log-based events this logger emits: |
| 37 | // https://opentelemetry.io/blog/2026/deprecating-span-events/ |
| 38 | const layerLogger = Logger.layer( |
| 39 | otlpLogger === undefined |
| 40 | ? [Logger.consolePretty(), Logger.tracerLogger] |
| 41 | : [Logger.consolePretty(), otlpLogger], |
| 42 | { mergeWithExisting: false }, |
| 43 | ).pipe( |
| 44 | Layer.provide(OtlpExporter.layerFlusher), |
| 45 | Layer.provide(SharedObservability.layerOtlpSerialization(logs.protocol)), |
| 46 | ); |
| 47 | |
| 48 | return Layer.mergeAll(layerLogger, layerMinimumLogLevel); |
| 49 | }).pipe(Layer.unwrap); |