Repository navigation
Expand file tree
/
Copy pathserverLogger.ts
More file actions
80 lines (72 loc) · 3.3 KB
/
Copy pathserverLogger.ts
File metadata and controls
80 lines (72 loc) · 3.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as SharedObservability from "@t3tools/shared/observability";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Logger from "effect/Logger";
import * as Redactable from "effect/Redactable";
import * as References from "effect/References";
import * as OtlpExporter from "effect/observability/OtlpExporter";
import * as OtlpLogger from "effect/observability/OtlpLogger";
import * as ServerConfig from "./config.ts";
// HTTP errors logged with --log-ws-events carry the request URL and its connection ticket.
const redactingCredentials = <Output>(logger: Logger.Logger<unknown, Output>) =>
Logger.make<unknown, Output>((options) =>
logger.log({ ...options, cause: SharedObservability.redactCauseCredentials(options.cause) }),
);
// consolePretty collapses Cause fields via util.inspect; render them with Cause.pretty like standalone arguments.
const consolePretty = (): Logger.Logger<unknown, void> => {
const pretty = redactingCredentials(Logger.consolePretty());
return Logger.make((options) =>
pretty.log({
...options,
message: Array.isArray(options.message)
? options.message.map(prettyCauseFields)
: prettyCauseFields(options.message),
}),
);
};
function prettyCauseFields(value: unknown): unknown {
if (typeof value !== "object" || value === null) return value;
const prototype: unknown = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) return value;
// A message with its own redaction hook already decides how it prints.
if (Redactable.isRedactable(value)) return value;
// Copy descriptors to preserve symbols without invoking getters.
const descriptors = Object.getOwnPropertyDescriptors(value);
let changed = false;
for (const descriptor of Object.values(descriptors)) {
if ("value" in descriptor && Cause.isCause(descriptor.value)) {
descriptor.value = Cause.pretty(descriptor.value);
changed = true;
}
}
return changed ? Object.create(prototype, descriptors) : value;
}
export const layer = Effect.gen(function* () {
const config = yield* ServerConfig.ServerConfig;
const hostName = yield* HostProcess.Hostname;
const layerMinimumLogLevel = Layer.succeed(References.MinimumLogLevel, config.logLevel);
const logs = config.otlpLogsExport;
const otlpLogger =
config.otlpLogsUrl === undefined
? undefined
: OtlpLogger.make({
url: config.otlpLogsUrl,
exportInterval: `${logs.exportIntervalMs} millis`,
headers: logs.headers,
resource: ServerConfig.otlpResource(config, hostName),
}).pipe(Effect.map(redactingCredentials));
// Logger.layer replaces the whole set; name every logger here.
// OTLP includes traced messages as log records, so also using tracerLogger would duplicate in-span exports.
const layerLogger = Logger.layer(
otlpLogger === undefined
? [consolePretty(), Logger.tracerLogger]
: [consolePretty(), otlpLogger],
{ mergeWithExisting: false },
).pipe(
Layer.provide(OtlpExporter.layerFlusher),
Layer.provide(SharedObservability.layerOtlpSerialization(logs.protocol)),
);
return Layer.mergeAll(layerLogger, layerMinimumLogLevel);
}).pipe(Layer.unwrap);