1import * as SharedObservability from "@t3tools/shared/observability";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Logger from "effect/Logger";
5import * as References from "effect/References";
6import * as OtlpExporter from "effect/observability/OtlpExporter";
7import * as OtlpLogger from "effect/observability/OtlpLogger";
8
9import * as ServerConfig from "./config.ts";
10
11export 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);