| author | |
| committer | |
| log | fa1e1715538f6f8ac076761f7821f68479f616ab |
| tree | e22554ddb368fcd2d2d12a649fa3662ca59afd86 |
| parent | c14f6015bfe479d313355cb234af1a5c16dbb15f |
| signature | Signed by PGP key B5690EEEBB952194 |
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>12 files changed, 104 insertions(+), 49 deletions(-)
apps/server/src/bin.test.ts+4-3| ... | @@ -15,6 +15,7 @@ import { | ... | @@ -15,6 +15,7 @@ import { |
| 15 | } from "@t3tools/contracts"; | 15 | } from "@t3tools/contracts"; |
| 16 | import * as NetService from "@t3tools/shared/Net"; | 16 | import * as NetService from "@t3tools/shared/Net"; |
| 17 | import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; | 17 | import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; |
| 18 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; | ||
| 18 | import { assert, it } from "@effect/vitest"; | 19 | import { assert, it } from "@effect/vitest"; |
| 19 | import * as Effect from "effect/Effect"; | 20 | import * as Effect from "effect/Effect"; |
| 20 | import * as DateTime from "effect/DateTime"; | 21 | import * as DateTime from "effect/DateTime"; |
| ... | @@ -101,10 +102,10 @@ const makeCliTestServerConfig = (baseDir: string) => | ... | @@ -101,10 +102,10 @@ const makeCliTestServerConfig = (baseDir: string) => |
| 101 | otlpTracesUrl: undefined, | 102 | otlpTracesUrl: undefined, |
| 102 | otlpMetricsUrl: undefined, | 103 | otlpMetricsUrl: undefined, |
| 103 | otlpLogsUrl: undefined, | 104 | otlpLogsUrl: undefined, |
| 104 | otlpExportIntervalMs: 10_000, | 105 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 106 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 107 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 105 | otlpServiceName: "t3-server", | 108 | otlpServiceName: "t3-server", |
| 106 | otlpHeaders: undefined, | ||
| 107 | otlpProtocol: "http/json", | ||
| 108 | mode: "web", | 109 | mode: "web", |
| 109 | port: 0, | 110 | port: 0, |
| 110 | host: "127.0.0.1", | 111 | host: "127.0.0.1", |
apps/server/src/cli/config.test.ts+12-7| ... | @@ -17,6 +17,7 @@ import { | ... | @@ -17,6 +17,7 @@ import { |
| 17 | type DesktopBackendBootstrap as DesktopBackendBootstrapValue, | 17 | type DesktopBackendBootstrap as DesktopBackendBootstrapValue, |
| 18 | } from "@t3tools/contracts"; | 18 | } from "@t3tools/contracts"; |
| 19 | import * as NetService from "@t3tools/shared/Net"; | 19 | import * as NetService from "@t3tools/shared/Net"; |
| 20 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; | ||
| 20 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 21 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 21 | import { deriveServerPaths } from "../config.ts"; | 22 | import { deriveServerPaths } from "../config.ts"; |
| 22 | import { resolveServerConfig } from "./config.ts"; | 23 | import { resolveServerConfig } from "./config.ts"; |
| ... | @@ -51,10 +52,10 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -51,10 +52,10 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 51 | otlpTracesUrl: undefined, | 52 | otlpTracesUrl: undefined, |
| 52 | otlpMetricsUrl: undefined, | 53 | otlpMetricsUrl: undefined, |
| 53 | otlpLogsUrl: undefined, | 54 | otlpLogsUrl: undefined, |
| 54 | otlpExportIntervalMs: 10_000, | 55 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 56 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 57 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 55 | otlpServiceName: "t3-server", | 58 | otlpServiceName: "t3-server", |
| 56 | otlpHeaders: undefined, | ||
| 57 | otlpProtocol: "http/json", | ||
| 58 | devAllowedOrigins: [], | 59 | devAllowedOrigins: [], |
| 59 | } as const; | 60 | } as const; |
| 60 | 61 | ||
| ... | @@ -764,7 +765,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -764,7 +765,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 764 | ), | 765 | ), |
| 765 | ); | 766 | ); |
| 766 | 767 | ||
| 767 | expect(resolved.otlpHeaders).toEqual({ | 768 | expect(resolved.otlpTracesExport.headers).toEqual({ |
| 768 | authorization: "Basic abc==", | 769 | authorization: "Basic abc==", |
| 769 | "x-tenant": "t3", | 770 | "x-tenant": "t3", |
| 770 | }); | 771 | }); |
| ... | @@ -808,7 +809,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -808,7 +809,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 808 | ), | 809 | ), |
| 809 | ); | 810 | ); |
| 810 | 811 | ||
| 811 | expect(resolved.otlpHeaders).toEqual({ | 812 | expect(resolved.otlpTracesExport.headers).toEqual({ |
| 812 | authorization: "Bearer abc==", | 813 | authorization: "Bearer abc==", |
| 813 | "x-tenant": "t3", | 814 | "x-tenant": "t3", |
| 814 | }); | 815 | }); |
| ... | @@ -816,7 +817,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -816,7 +817,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 816 | }), | 817 | }), |
| 817 | ); | 818 | ); |
| 818 | 819 | ||
| 819 | it.effect("reads the OTLP protocol from env", () => | 820 | it.effect("gives every signal the protocol named without one", () => |
| 820 | Effect.gen(function* () { | 821 | Effect.gen(function* () { |
| 821 | const { join } = yield* Path.Path; | 822 | const { join } = yield* Path.Path; |
| 822 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-protocol-base"); | 823 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-protocol-base"); |
| ... | @@ -848,7 +849,11 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -848,7 +849,11 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 848 | ), | 849 | ), |
| 849 | ); | 850 | ); |
| 850 | 851 | ||
| 851 | expect(resolved.otlpProtocol).toBe("http/protobuf"); | 852 | expect([ |
| 853 | resolved.otlpTracesExport.protocol, | ||
| 854 | resolved.otlpMetricsExport.protocol, | ||
| 855 | resolved.otlpLogsExport.protocol, | ||
| 856 | ]).toEqual(["http/protobuf", "http/protobuf", "http/protobuf"]); | ||
| 852 | }), | 857 | }), |
| 853 | ); | 858 | ); |
| 854 | 859 |
apps/server/src/cli/config.ts+16-4| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import * as NetService from "@t3tools/shared/Net"; | 1 | import * as NetService from "@t3tools/shared/Net"; |
| 2 | import { OtlpHeadersFromString, OtlpProtocol } from "@t3tools/shared/observability"; | 2 | import { |
| 3 | OtlpHeadersFromString, | ||
| 4 | OtlpProtocol, | ||
| 5 | type SignalExport, | ||
| 6 | } from "@t3tools/shared/observability"; | ||
| 3 | import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; | 7 | import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; |
| 4 | import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; | 8 | import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; |
| 5 | import * as Config from "effect/Config"; | 9 | import * as Config from "effect/Config"; |
| ... | @@ -382,6 +386,14 @@ export const resolveServerConfig = ( | ... | @@ -382,6 +386,14 @@ export const resolveServerConfig = ( |
| 382 | ); | 386 | ); |
| 383 | const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel); | 387 | const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel); |
| 384 | 388 | ||
| 389 | // T3 Code's own OTLP variables name no signal, so the one answer they give | ||
| 390 | // is the answer for all three. | ||
| 391 | const signalExport: SignalExport = { | ||
| 392 | protocol: env.otlpProtocol, | ||
| 393 | headers: env.otlpHeaders, | ||
| 394 | exportIntervalMs: env.otlpExportIntervalMs, | ||
| 395 | }; | ||
| 396 | |||
| 385 | const config: ServerConfig.ServerConfig["Service"] = { | 397 | const config: ServerConfig.ServerConfig["Service"] = { |
| 386 | logLevel, | 398 | logLevel, |
| 387 | traceMinLevel: env.traceMinLevel, | 399 | traceMinLevel: env.traceMinLevel, |
| ... | @@ -399,10 +411,10 @@ export const resolveServerConfig = ( | ... | @@ -399,10 +411,10 @@ export const resolveServerConfig = ( |
| 399 | persistedObservabilitySettings.otlpMetricsUrl, | 411 | persistedObservabilitySettings.otlpMetricsUrl, |
| 400 | otlpLogsUrl: | 412 | otlpLogsUrl: |
| 401 | env.otlpLogsUrl ?? bootstrap?.otlpLogsUrl ?? persistedObservabilitySettings.otlpLogsUrl, | 413 | env.otlpLogsUrl ?? bootstrap?.otlpLogsUrl ?? persistedObservabilitySettings.otlpLogsUrl, |
| 402 | otlpExportIntervalMs: env.otlpExportIntervalMs, | 414 | otlpTracesExport: signalExport, |
| 415 | otlpMetricsExport: signalExport, | ||
| 416 | otlpLogsExport: signalExport, | ||
| 403 | otlpServiceName: env.otlpServiceName, | 417 | otlpServiceName: env.otlpServiceName, |
| 404 | otlpHeaders: env.otlpHeaders, | ||
| 405 | otlpProtocol: env.otlpProtocol, | ||
| 406 | mode, | 418 | mode, |
| 407 | port, | 419 | port, |
| 408 | cwd, | 420 | cwd, |
apps/server/src/cli/pair.ts+4-3| ... | @@ -15,6 +15,7 @@ import { | ... | @@ -15,6 +15,7 @@ import { |
| 15 | PortSchema, | 15 | PortSchema, |
| 16 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 17 | import { resolveWorktreeT3Home } from "@t3tools/shared/devHome"; | 17 | import { resolveWorktreeT3Home } from "@t3tools/shared/devHome"; |
| 18 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; | ||
| 18 | import { | 19 | import { |
| 19 | buildTailscaleHttpsBaseUrl, | 20 | buildTailscaleHttpsBaseUrl, |
| 20 | DEFAULT_TAILSCALE_SERVE_PORT, | 21 | DEFAULT_TAILSCALE_SERVE_PORT, |
| ... | @@ -321,10 +322,10 @@ const makePairServerConfig = Effect.fn(function* (input: { | ... | @@ -321,10 +322,10 @@ const makePairServerConfig = Effect.fn(function* (input: { |
| 321 | otlpTracesUrl: undefined, | 322 | otlpTracesUrl: undefined, |
| 322 | otlpMetricsUrl: undefined, | 323 | otlpMetricsUrl: undefined, |
| 323 | otlpLogsUrl: undefined, | 324 | otlpLogsUrl: undefined, |
| 324 | otlpExportIntervalMs: 10_000, | 325 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 326 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 327 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 325 | otlpServiceName: "t3-server", | 328 | otlpServiceName: "t3-server", |
| 326 | otlpHeaders: undefined, | ||
| 327 | otlpProtocol: "http/json", | ||
| 328 | mode: "web", | 329 | mode: "web", |
| 329 | port: state.port, | 330 | port: state.port, |
| 330 | host: state.host, | 331 | host: state.host, |
apps/server/src/config.ts+12-7| ... | @@ -17,7 +17,7 @@ import type * as Redacted from "effect/Redacted"; | ... | @@ -17,7 +17,7 @@ import type * as Redacted from "effect/Redacted"; |
| 17 | import * as Schema from "effect/Schema"; | 17 | import * as Schema from "effect/Schema"; |
| 18 | 18 | ||
| 19 | import { sweepStalePendingAttachments } from "./attachmentStore.ts"; | 19 | import { sweepStalePendingAttachments } from "./attachmentStore.ts"; |
| 20 | import { OtlpProtocol } from "@t3tools/shared/observability"; | 20 | import { DEFAULT_SIGNAL_EXPORT, type SignalExport } from "@t3tools/shared/observability"; |
| 21 | 21 | ||
| 22 | export const DEFAULT_PORT = 3773; | 22 | export const DEFAULT_PORT = 3773; |
| 23 | 23 | ||
| ... | @@ -73,10 +73,15 @@ export class ServerConfig extends Context.Service< | ... | @@ -73,10 +73,15 @@ export class ServerConfig extends Context.Service< |
| 73 | readonly otlpTracesUrl: string | undefined; | 73 | readonly otlpTracesUrl: string | undefined; |
| 74 | readonly otlpMetricsUrl: string | undefined; | 74 | readonly otlpMetricsUrl: string | undefined; |
| 75 | readonly otlpLogsUrl: string | undefined; | 75 | readonly otlpLogsUrl: string | undefined; |
| 76 | readonly otlpExportIntervalMs: number; | 76 | /** |
| 77 | * How each signal is exported. Read instead of a process-wide setting so | ||
| 78 | * the wire format, credential, and schedule travel with the endpoint they | ||
| 79 | * were configured beside. | ||
| 80 | */ | ||
| 81 | readonly otlpTracesExport: SignalExport; | ||
| 82 | readonly otlpMetricsExport: SignalExport; | ||
| 83 | readonly otlpLogsExport: SignalExport; | ||
| 77 | readonly otlpServiceName: string; | 84 | readonly otlpServiceName: string; |
| 78 | readonly otlpHeaders: Readonly<Record<string, string>> | undefined; | ||
| 79 | readonly otlpProtocol: OtlpProtocol; | ||
| 80 | readonly mode: RuntimeMode; | 85 | readonly mode: RuntimeMode; |
| 81 | readonly port: number; | 86 | readonly port: number; |
| 82 | readonly host: string | undefined; | 87 | readonly host: string | undefined; |
| ... | @@ -212,10 +217,10 @@ const makeTest = Effect.fn("ServerConfig.makeTest")(function* ( | ... | @@ -212,10 +217,10 @@ const makeTest = Effect.fn("ServerConfig.makeTest")(function* ( |
| 212 | otlpTracesUrl: undefined, | 217 | otlpTracesUrl: undefined, |
| 213 | otlpMetricsUrl: undefined, | 218 | otlpMetricsUrl: undefined, |
| 214 | otlpLogsUrl: undefined, | 219 | otlpLogsUrl: undefined, |
| 215 | otlpExportIntervalMs: 10_000, | 220 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 221 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 222 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 216 | otlpServiceName: "t3-server", | 223 | otlpServiceName: "t3-server", |
| 217 | otlpHeaders: undefined, | ||
| 218 | otlpProtocol: "http/json", | ||
| 219 | cwd, | 224 | cwd, |
| 220 | baseDir, | 225 | baseDir, |
| 221 | ...derivedPaths, | 226 | ...derivedPaths, |
apps/server/src/environment/ServerEnvironment.test.ts+5-3| ... | @@ -10,6 +10,8 @@ import * as Option from "effect/Option"; | ... | @@ -10,6 +10,8 @@ import * as Option from "effect/Option"; |
| 10 | import * as PlatformError from "effect/PlatformError"; | 10 | import * as PlatformError from "effect/PlatformError"; |
| 11 | import * as Schema from "effect/Schema"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | 12 | ||
| 13 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; | ||
| 14 | |||
| 13 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; | 15 | import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; |
| 14 | import { | 16 | import { |
| 15 | PUBLISH_AGENT_ACTIVITY_SECRET, | 17 | PUBLISH_AGENT_ACTIVITY_SECRET, |
| ... | @@ -54,10 +56,10 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) { | ... | @@ -54,10 +56,10 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) { |
| 54 | otlpTracesUrl: undefined, | 56 | otlpTracesUrl: undefined, |
| 55 | otlpMetricsUrl: undefined, | 57 | otlpMetricsUrl: undefined, |
| 56 | otlpLogsUrl: undefined, | 58 | otlpLogsUrl: undefined, |
| 57 | otlpExportIntervalMs: 10_000, | 59 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 60 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 61 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 58 | otlpServiceName: "t3-server", | 62 | otlpServiceName: "t3-server", |
| 59 | otlpHeaders: undefined, | ||
| 60 | otlpProtocol: "http/json", | ||
| 61 | cwd: process.cwd(), | 63 | cwd: process.cwd(), |
| 62 | baseDir, | 64 | baseDir, |
| 63 | mode: "web", | 65 | mode: "web", |
apps/server/src/http.ts+1-1| ... | @@ -322,7 +322,7 @@ export const otlpTracesProxyRouteLayer = HttpRouter.add( | ... | @@ -322,7 +322,7 @@ export const otlpTracesProxyRouteLayer = HttpRouter.add( |
| 322 | const request = yield* HttpServerRequest.HttpServerRequest; | 322 | const request = yield* HttpServerRequest.HttpServerRequest; |
| 323 | const config = yield* ServerConfig.ServerConfig; | 323 | const config = yield* ServerConfig.ServerConfig; |
| 324 | const otlpTracesUrl = config.otlpTracesUrl; | 324 | const otlpTracesUrl = config.otlpTracesUrl; |
| 325 | const otlpHeaders = config.otlpHeaders; | 325 | const otlpHeaders = config.otlpTracesExport.headers; |
| 326 | const browserTraceCollector = yield* BrowserTraceCollector.BrowserTraceCollector; | 326 | const browserTraceCollector = yield* BrowserTraceCollector.BrowserTraceCollector; |
| 327 | const httpClient = yield* HttpClient.HttpClient; | 327 | const httpClient = yield* HttpClient.HttpClient; |
| 328 | const serialization = yield* OtlpSerialization.OtlpSerialization; | 328 | const serialization = yield* OtlpSerialization.OtlpSerialization; |
apps/server/src/observability/Layers/Observability.ts+10-6| ... | @@ -20,7 +20,11 @@ import * as BrowserTraceCollector from "../BrowserTraceCollector.ts"; | ... | @@ -20,7 +20,11 @@ import * as BrowserTraceCollector from "../BrowserTraceCollector.ts"; |
| 20 | export const ObservabilityLive = Layer.unwrap( | 20 | export const ObservabilityLive = Layer.unwrap( |
| 21 | Effect.gen(function* () { | 21 | Effect.gen(function* () { |
| 22 | const config = yield* ServerConfig.ServerConfig; | 22 | const config = yield* ServerConfig.ServerConfig; |
| 23 | const serializationLayer = otlpSerializationLayer(config.otlpProtocol); | 23 | const traces = config.otlpTracesExport; |
| 24 | const metrics = config.otlpMetricsExport; | ||
| 25 | // The trace serializer stays in the returned context because the browser | ||
| 26 | // trace forwarder exports on the same signal. | ||
| 27 | const serializationLayer = otlpSerializationLayer(traces.protocol); | ||
| 24 | const resource = ServerConfig.otlpResource(config); | 28 | const resource = ServerConfig.otlpResource(config); |
| 25 | const attribution = yield* ResourceAttribution.ResourceAttribution; | 29 | const attribution = yield* ResourceAttribution.ResourceAttribution; |
| 26 | 30 | ||
| ... | @@ -51,8 +55,8 @@ export const ObservabilityLive = Layer.unwrap( | ... | @@ -51,8 +55,8 @@ export const ObservabilityLive = Layer.unwrap( |
| 51 | ? undefined | 55 | ? undefined |
| 52 | : yield* OtlpTracer.make({ | 56 | : yield* OtlpTracer.make({ |
| 53 | url: config.otlpTracesUrl, | 57 | url: config.otlpTracesUrl, |
| 54 | exportInterval: `${config.otlpExportIntervalMs} millis`, | 58 | exportInterval: `${traces.exportIntervalMs} millis`, |
| 55 | headers: config.otlpHeaders, | 59 | headers: traces.headers, |
| 56 | resource, | 60 | resource, |
| 57 | }); | 61 | }); |
| 58 | 62 | ||
| ... | @@ -77,10 +81,10 @@ export const ObservabilityLive = Layer.unwrap( | ... | @@ -77,10 +81,10 @@ export const ObservabilityLive = Layer.unwrap( |
| 77 | ? Layer.empty | 81 | ? Layer.empty |
| 78 | : OtlpMetrics.layer({ | 82 | : OtlpMetrics.layer({ |
| 79 | url: config.otlpMetricsUrl, | 83 | url: config.otlpMetricsUrl, |
| 80 | exportInterval: `${config.otlpExportIntervalMs} millis`, | 84 | exportInterval: `${metrics.exportIntervalMs} millis`, |
| 81 | headers: config.otlpHeaders, | 85 | headers: metrics.headers, |
| 82 | resource, | 86 | resource, |
| 83 | }).pipe(Layer.provideMerge(serializationLayer)); | 87 | }).pipe(Layer.provide(otlpSerializationLayer(metrics.protocol))); |
| 84 | 88 | ||
| 85 | return Layer.mergeAll(ServerLoggerLive, traceReferencesLayer, tracerLayer, metricsLayer); | 89 | return Layer.mergeAll(ServerLoggerLive, traceReferencesLayer, tracerLayer, metricsLayer); |
| 86 | }), | 90 | }), |
apps/server/src/server.test.ts+6-6| ... | @@ -218,7 +218,7 @@ import { | ... | @@ -218,7 +218,7 @@ import { |
| 218 | transferBudgetViolations, | 218 | transferBudgetViolations, |
| 219 | } from "../integration/TransferBudgetReport.integration.ts"; | 219 | } from "../integration/TransferBudgetReport.integration.ts"; |
| 220 | import { symlinksSupported } from "@t3tools/shared/testing/symlinks"; | 220 | import { symlinksSupported } from "@t3tools/shared/testing/symlinks"; |
| 221 | import { otlpSerializationLayer } from "@t3tools/shared/observability"; | 221 | import { DEFAULT_SIGNAL_EXPORT, otlpSerializationLayer } from "@t3tools/shared/observability"; |
| 222 | 222 | ||
| 223 | const defaultProjectId = ProjectId.make("project-default"); | 223 | const defaultProjectId = ProjectId.make("project-default"); |
| 224 | const defaultThreadId = ThreadId.make("thread-default"); | 224 | const defaultThreadId = ThreadId.make("thread-default"); |
| ... | @@ -578,10 +578,10 @@ const buildAppUnderTest = (options?: { | ... | @@ -578,10 +578,10 @@ const buildAppUnderTest = (options?: { |
| 578 | otlpTracesUrl: undefined, | 578 | otlpTracesUrl: undefined, |
| 579 | otlpMetricsUrl: undefined, | 579 | otlpMetricsUrl: undefined, |
| 580 | otlpLogsUrl: undefined, | 580 | otlpLogsUrl: undefined, |
| 581 | otlpExportIntervalMs: 10_000, | 581 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 582 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 583 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 582 | otlpServiceName: "t3-server", | 584 | otlpServiceName: "t3-server", |
| 583 | otlpHeaders: undefined, | ||
| 584 | otlpProtocol: "http/json", | ||
| 585 | mode: "desktop", | 585 | mode: "desktop", |
| 586 | port: 0, | 586 | port: 0, |
| 587 | host: "127.0.0.1", | 587 | host: "127.0.0.1", |
| ... | @@ -1076,7 +1076,7 @@ const buildAppUnderTest = (options?: { | ... | @@ -1076,7 +1076,7 @@ const buildAppUnderTest = (options?: { |
| 1076 | ...options?.layers?.browserTraceCollector, | 1076 | ...options?.layers?.browserTraceCollector, |
| 1077 | }), | 1077 | }), |
| 1078 | ), | 1078 | ), |
| 1079 | Layer.provide(otlpSerializationLayer(config.otlpProtocol)), | 1079 | Layer.provide(otlpSerializationLayer(config.otlpTracesExport.protocol)), |
| 1080 | Layer.provide( | 1080 | Layer.provide( |
| 1081 | Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({ | 1081 | Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({ |
| 1082 | publish: (event) => Effect.succeed({ ...(event as any), sequence: 1 }), | 1082 | publish: (event) => Effect.succeed({ ...(event as any), sequence: 1 }), |
| ... | @@ -5299,7 +5299,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -5299,7 +5299,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 5299 | yield* buildAppUnderTest({ | 5299 | yield* buildAppUnderTest({ |
| 5300 | config: { | 5300 | config: { |
| 5301 | otlpTracesUrl: collector.url, | 5301 | otlpTracesUrl: collector.url, |
| 5302 | otlpProtocol: "http/protobuf", | 5302 | otlpTracesExport: { ...DEFAULT_SIGNAL_EXPORT, protocol: "http/protobuf" }, |
| 5303 | }, | 5303 | }, |
| 5304 | layers: { | 5304 | layers: { |
| 5305 | browserTraceCollector: { | 5305 | browserTraceCollector: { |
apps/server/src/serverLogger.test.ts+11-6| ... | @@ -8,6 +8,8 @@ import * as Tracer from "effect/Tracer"; | ... | @@ -8,6 +8,8 @@ import * as Tracer from "effect/Tracer"; |
| 8 | import * as HttpClient from "effect/unstable/http/HttpClient"; | 8 | import * as HttpClient from "effect/unstable/http/HttpClient"; |
| 9 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; | 9 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; |
| 10 | 10 | ||
| 11 | import { DEFAULT_SIGNAL_EXPORT } from "@t3tools/shared/observability"; | ||
| 12 | |||
| 11 | import * as ServerConfig from "./config.ts"; | 13 | import * as ServerConfig from "./config.ts"; |
| 12 | import { ServerLoggerLive } from "./serverLogger.ts"; | 14 | import { ServerLoggerLive } from "./serverLogger.ts"; |
| 13 | 15 | ||
| ... | @@ -51,10 +53,10 @@ const configLayer = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) = | ... | @@ -51,10 +53,10 @@ const configLayer = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) = |
| 51 | otlpTracesUrl: undefined, | 53 | otlpTracesUrl: undefined, |
| 52 | otlpMetricsUrl: undefined, | 54 | otlpMetricsUrl: undefined, |
| 53 | otlpLogsUrl: undefined, | 55 | otlpLogsUrl: undefined, |
| 54 | otlpExportIntervalMs: 10_000, | 56 | otlpTracesExport: DEFAULT_SIGNAL_EXPORT, |
| 57 | otlpMetricsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 58 | otlpLogsExport: DEFAULT_SIGNAL_EXPORT, | ||
| 55 | otlpServiceName: "t3-server", | 59 | otlpServiceName: "t3-server", |
| 56 | otlpHeaders: undefined, | ||
| 57 | otlpProtocol: "http/json", | ||
| 58 | cwd: baseDir, | 60 | cwd: baseDir, |
| 59 | baseDir, | 61 | baseDir, |
| 60 | ...derivedPaths, | 62 | ...derivedPaths, |
| ... | @@ -155,12 +157,15 @@ describe("ServerLoggerLive", () => { | ... | @@ -155,12 +157,15 @@ describe("ServerLoggerLive", () => { |
| 155 | }), | 157 | }), |
| 156 | ); | 158 | ); |
| 157 | 159 | ||
| 158 | it.effect("sends the headers and wire format the rest of OTLP export already uses", () => | 160 | it.effect("sends the headers and wire format the log signal asked for", () => |
| 159 | Effect.gen(function* () { | 161 | Effect.gen(function* () { |
| 160 | const requests = yield* logThrough({ | 162 | const requests = yield* logThrough({ |
| 161 | otlpLogsUrl: "https://collector.example.com/v1/logs", | 163 | otlpLogsUrl: "https://collector.example.com/v1/logs", |
| 162 | otlpProtocol: "http/protobuf", | 164 | otlpLogsExport: { |
| 163 | otlpHeaders: { "x-scope": "logs" }, | 165 | ...DEFAULT_SIGNAL_EXPORT, |
| 166 | protocol: "http/protobuf", | ||
| 167 | headers: { "x-scope": "logs" }, | ||
| 168 | }, | ||
| 164 | }); | 169 | }); |
| 165 | 170 | ||
| 166 | assert.lengthOf(requests, 1); | 171 | assert.lengthOf(requests, 1); |
apps/server/src/serverLogger.ts+4-3| ... | @@ -12,13 +12,14 @@ export const ServerLoggerLive = Effect.gen(function* () { | ... | @@ -12,13 +12,14 @@ export const ServerLoggerLive = Effect.gen(function* () { |
| 12 | const config = yield* ServerConfig; | 12 | const config = yield* ServerConfig; |
| 13 | const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel); | 13 | const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel); |
| 14 | 14 | ||
| 15 | const logs = config.otlpLogsExport; | ||
| 15 | const otlpLogger = | 16 | const otlpLogger = |
| 16 | config.otlpLogsUrl === undefined | 17 | config.otlpLogsUrl === undefined |
| 17 | ? undefined | 18 | ? undefined |
| 18 | : OtlpLogger.make({ | 19 | : OtlpLogger.make({ |
| 19 | url: config.otlpLogsUrl, | 20 | url: config.otlpLogsUrl, |
| 20 | exportInterval: `${config.otlpExportIntervalMs} millis`, | 21 | exportInterval: `${logs.exportIntervalMs} millis`, |
| 21 | headers: config.otlpHeaders, | 22 | headers: logs.headers, |
| 22 | resource: otlpResource(config), | 23 | resource: otlpResource(config), |
| 23 | }); | 24 | }); |
| 24 | 25 | ||
| ... | @@ -41,7 +42,7 @@ export const ServerLoggerLive = Effect.gen(function* () { | ... | @@ -41,7 +42,7 @@ export const ServerLoggerLive = Effect.gen(function* () { |
| 41 | { mergeWithExisting: false }, | 42 | { mergeWithExisting: false }, |
| 42 | ).pipe( | 43 | ).pipe( |
| 43 | Layer.provide(OtlpExporter.layerFlusher), | 44 | Layer.provide(OtlpExporter.layerFlusher), |
| 44 | Layer.provide(otlpSerializationLayer(config.otlpProtocol)), | 45 | Layer.provide(otlpSerializationLayer(logs.protocol)), |
| 45 | ); | 46 | ); |
| 46 | 47 | ||
| 47 | return Layer.mergeAll(loggerLayer, minimumLogLevelLayer); | 48 | return Layer.mergeAll(loggerLayer, minimumLogLevelLayer); |
packages/shared/src/observability.ts+19| ... | @@ -16,6 +16,25 @@ export type OtlpProtocol = typeof OtlpProtocol.Type; | ... | @@ -16,6 +16,25 @@ export type OtlpProtocol = typeof OtlpProtocol.Type; |
| 16 | export const otlpSerializationLayer = (protocol: OtlpProtocol) => | 16 | export const otlpSerializationLayer = (protocol: OtlpProtocol) => |
| 17 | protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson; | 17 | protocol === "http/protobuf" ? OtlpSerialization.layerProtobuf : OtlpSerialization.layerJson; |
| 18 | 18 | ||
| 19 | /** | ||
| 20 | * How one signal is exported, once whichever source named that signal's | ||
| 21 | * endpoint has been resolved. Held per signal rather than per process, so a | ||
| 22 | * wire format or a credential cannot be paired by hand with an endpoint that | ||
| 23 | * came from somewhere else. | ||
| 24 | */ | ||
| 25 | export interface SignalExport { | ||
| 26 | readonly protocol: OtlpProtocol; | ||
| 27 | readonly headers: Readonly<Record<string, string>> | undefined; | ||
| 28 | readonly exportIntervalMs: number; | ||
| 29 | } | ||
| 30 | |||
| 31 | /** What T3 Code exports with when nothing configured a signal. */ | ||
| 32 | export const DEFAULT_SIGNAL_EXPORT: SignalExport = { | ||
| 33 | protocol: "http/json", | ||
| 34 | headers: undefined, | ||
| 35 | exportIntervalMs: 10_000, | ||
| 36 | }; | ||
| 37 | |||
| 19 | const FLUSH_BUFFER_THRESHOLD = 256; | 38 | const FLUSH_BUFFER_THRESHOLD = 256; |
| 20 | const textEncoder = new TextEncoder(); | 39 | const textEncoder = new TextEncoder(); |
| 21 | 40 |