import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability";import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Option from "effect/Option";import * as References from "effect/References";import * as Tracer from "effect/Tracer";import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; import * as DesktopBackendOutputLogModule from "./DesktopBackendOutputLog.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts"; const DESKTOP_TRACE_BATCH_WINDOW_MS = 200; export { DesktopBackendOutputLog } from "./DesktopBackendOutputLog.ts"; export type DesktopLogAnnotations = Record<string, unknown>; export interface DesktopComponentLogger { readonly annotate: <A, E, R>( effect: Effect.Effect<A, E, R>, annotations?: DesktopLogAnnotations, ) => Effect.Effect<A, E, R>; readonly logDebug: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>; readonly logInfo: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>; readonly logWarning: ( message: string, annotations?: DesktopLogAnnotations, ) => Effect.Effect<void>; readonly logError: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>;} export function makeComponentLogger(component: string): DesktopComponentLogger { const annotate: DesktopComponentLogger["annotate"] = (effect, annotations) => effect.pipe( Effect.annotateLogs({ component, ...annotations, }), ); return { annotate, logDebug: (message, annotations) => annotate(Effect.logDebug(message), annotations), logInfo: (message, annotations) => annotate(Effect.logInfo(message), annotations), logWarning: (message, annotations) => annotate(Effect.logWarning(message), annotations), logError: (message, annotations) => annotate(Effect.logError(message), annotations), };} const readPersistedOtlpTracesUrl = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const environment = yield* DesktopEnvironment.DesktopEnvironment; const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe(Effect.option); if (Option.isNone(raw)) { return Option.none(); } const parsed = parsePersistedServerObservabilitySettings(raw.value); return Option.fromNullishOr(parsed.otlpTracesUrl);}); const resolveOtlpTracesUrl = Effect.gen(function* () { const environment = yield* DesktopEnvironment.DesktopEnvironment; if (Option.isSome(environment.otlpTracesUrl)) { return environment.otlpTracesUrl; } return yield* readPersistedOtlpTracesUrl;}); const desktopLoggerLayer = Layer.mergeAll( Logger.layer([Logger.consolePretty(), Logger.tracerLogger], { mergeWithExisting: false }), Layer.succeed(References.MinimumLogLevel, "Info"),); const tracerLayer = Layer.unwrap( Effect.gen(function* () { const environment = yield* DesktopEnvironment.DesktopEnvironment; const otlpTracesUrl = yield* resolveOtlpTracesUrl; const tracePath = environment.path.join(environment.logDir, "desktop.trace.ndjson"); const sink = yield* makeTraceSink({ filePath: tracePath, maxBytes: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_BYTES, maxFiles: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_FILES, batchWindowMs: DESKTOP_TRACE_BATCH_WINDOW_MS, }); const delegate = Option.isNone(otlpTracesUrl) ? undefined : yield* OtlpTracer.make({ url: otlpTracesUrl.value, exportInterval: `${environment.otlpExportIntervalMs} millis`, resource: { serviceName: "desktop", attributes: { "service.runtime": "desktop", "service.mode": environment.isDevelopment ? "development" : "packaged", }, }, }); const tracer = yield* makeLocalFileTracer({ filePath: tracePath, maxBytes: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_BYTES, maxFiles: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_FILES, batchWindowMs: DESKTOP_TRACE_BATCH_WINDOW_MS, sink, ...(delegate ? { delegate } : {}), }); return Layer.succeed(Tracer.Tracer, tracer); }),).pipe(Layer.provideMerge(OtlpSerialization.layerJson)); export const layer = Layer.mergeAll( DesktopBackendOutputLogModule.layer, desktopLoggerLayer, tracerLayer, Layer.succeed(Tracer.MinimumTraceLevel, "Info"), Layer.succeed(References.TracerTimingEnabled, true),);
import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability";
import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Logger from "effect/Logger";
import * as Option from "effect/Option";
import * as References from "effect/References";
import * as Tracer from "effect/Tracer";
import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
import * as DesktopBackendOutputLogModule from "./DesktopBackendOutputLog.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
const DESKTOP_TRACE_BATCH_WINDOW_MS = 200;
export { DesktopBackendOutputLog } from "./DesktopBackendOutputLog.ts";
export type DesktopLogAnnotations = Record<string, unknown>;
export interface DesktopComponentLogger {
readonly annotate: <A, E, R>(
effect: Effect.Effect<A, E, R>,
annotations?: DesktopLogAnnotations,
) => Effect.Effect<A, E, R>;
readonly logDebug: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>;
readonly logInfo: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>;
readonly logWarning: (
message: string,
annotations?: DesktopLogAnnotations,
) => Effect.Effect<void>;
readonly logError: (message: string, annotations?: DesktopLogAnnotations) => Effect.Effect<void>;
}
export function makeComponentLogger(component: string): DesktopComponentLogger {
const annotate: DesktopComponentLogger["annotate"] = (effect, annotations) =>
effect.pipe(
Effect.annotateLogs({
component,
...annotations,
}),
);
return {
annotate,
logDebug: (message, annotations) => annotate(Effect.logDebug(message), annotations),
logInfo: (message, annotations) => annotate(Effect.logInfo(message), annotations),
logWarning: (message, annotations) => annotate(Effect.logWarning(message), annotations),
logError: (message, annotations) => annotate(Effect.logError(message), annotations),
};
}
const readPersistedOtlpTracesUrl = Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe(Effect.option);
if (Option.isNone(raw)) {
return Option.none();
}
const parsed = parsePersistedServerObservabilitySettings(raw.value);
return Option.fromNullishOr(parsed.otlpTracesUrl);
});
const resolveOtlpTracesUrl = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
if (Option.isSome(environment.otlpTracesUrl)) {
return environment.otlpTracesUrl;
}
return yield* readPersistedOtlpTracesUrl;
});
const desktopLoggerLayer = Layer.mergeAll(
Logger.layer([Logger.consolePretty(), Logger.tracerLogger], { mergeWithExisting: false }),
Layer.succeed(References.MinimumLogLevel, "Info"),
);
const tracerLayer = Layer.unwrap(
Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const otlpTracesUrl = yield* resolveOtlpTracesUrl;
const tracePath = environment.path.join(environment.logDir, "desktop.trace.ndjson");
const sink = yield* makeTraceSink({
filePath: tracePath,
maxBytes: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_BYTES,
maxFiles: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_FILES,
batchWindowMs: DESKTOP_TRACE_BATCH_WINDOW_MS,
});
const delegate = Option.isNone(otlpTracesUrl)
? undefined
: yield* OtlpTracer.make({
url: otlpTracesUrl.value,
exportInterval: `${environment.otlpExportIntervalMs} millis`,
resource: {
serviceName: "desktop",
attributes: {
"service.runtime": "desktop",
"service.mode": environment.isDevelopment ? "development" : "packaged",
},
},
});
const tracer = yield* makeLocalFileTracer({
filePath: tracePath,
maxBytes: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_BYTES,
maxFiles: DesktopBackendOutputLogModule.DESKTOP_LOG_FILE_MAX_FILES,
batchWindowMs: DESKTOP_TRACE_BATCH_WINDOW_MS,
sink,
...(delegate ? { delegate } : {}),
});
return Layer.succeed(Tracer.Tracer, tracer);
}),
).pipe(Layer.provideMerge(OtlpSerialization.layerJson));
export const layer = Layer.mergeAll(
DesktopBackendOutputLogModule.layer,
desktopLoggerLayer,
tracerLayer,
Layer.succeed(Tracer.MinimumTraceLevel, "Info"),
Layer.succeed(References.TracerTimingEnabled, true),
);