Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const PersistedServerObservabilitySettingsDocument = Schema.Struct({
observability: Schema.Struct({
otlpTracesUrl: Schema.String,
otlpMetricsUrl: Schema.String,
otlpLogsUrl: Schema.String,
}),
});

Expand Down Expand Up @@ -374,13 +375,15 @@ describe("DesktopBackendConfiguration", () => {
observability: {
otlpTracesUrl: " http://127.0.0.1:4318/v1/traces ",
otlpMetricsUrl: " http://127.0.0.1:4318/v1/metrics ",
otlpLogsUrl: " http://127.0.0.1:4318/v1/logs ",
},
}),
);

const config = yield* configuration.resolvePrimary;
assert.equal(config.bootstrap.otlpTracesUrl, "http://127.0.0.1:4318/v1/traces");
assert.equal(config.bootstrap.otlpMetricsUrl, "http://127.0.0.1:4318/v1/metrics");
assert.equal(config.bootstrap.otlpLogsUrl, "http://127.0.0.1:4318/v1/logs");
}),
),
);
Expand All @@ -393,6 +396,7 @@ describe("DesktopBackendConfiguration", () => {

assert.isUndefined(config.bootstrap.otlpTracesUrl);
assert.isUndefined(config.bootstrap.otlpMetricsUrl);
assert.isUndefined(config.bootstrap.otlpLogsUrl);
}),
),
);
Expand Down Expand Up @@ -443,6 +447,7 @@ describe("DesktopBackendConfiguration", () => {

assert.isUndefined(config.bootstrap.otlpTracesUrl);
assert.isUndefined(config.bootstrap.otlpMetricsUrl);
assert.isUndefined(config.bootstrap.otlpLogsUrl);

const error = messages
.flatMap((message) => (Array.isArray(message) ? message : [message]))
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ export class DesktopBackendConfiguration extends Context.Service<
interface BackendObservabilitySettings {
readonly otlpTracesUrl: Option.Option<string>;
readonly otlpMetricsUrl: Option.Option<string>;
readonly otlpLogsUrl: Option.Option<string>;
}

const emptyBackendObservabilitySettings: BackendObservabilitySettings = {
otlpTracesUrl: Option.none(),
otlpMetricsUrl: Option.none(),
otlpLogsUrl: Option.none(),
};

const DESKTOP_BACKEND_ENV_NAMES = [
Expand Down Expand Up @@ -202,6 +204,7 @@ const readPersistedBackendObservabilitySettings = Effect.gen(function* () {
return {
otlpTracesUrl: Option.fromNullishOr(parsed.otlpTracesUrl),
otlpMetricsUrl: Option.fromNullishOr(parsed.otlpMetricsUrl),
otlpLogsUrl: Option.fromNullishOr(parsed.otlpLogsUrl),
};
});

Expand Down Expand Up @@ -361,6 +364,10 @@ const buildObservabilityFragment = (observabilitySettings: BackendObservabilityS
onNone: () => ({}),
onSome: (otlpMetricsUrl) => ({ otlpMetricsUrl }),
}),
...Option.match(observabilitySettings.otlpLogsUrl, {
onNone: () => ({}),
onSome: (otlpLogsUrl) => ({ otlpLogsUrl }),
}),
});

const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolvePrimary")(
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ const makeCliTestServerConfig = (baseDir: string) =>
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpMetricsExportIntervalMs: 10_000,
otlpLogsExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otelEnvironment: OtelEnvironment.none,
mode: "web",
Expand Down
31 changes: 31 additions & 0 deletions apps/server/src/cli/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpMetricsExportIntervalMs: 10_000,
otlpLogsExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otelEnvironment: OtelEnvironment.none,
devAllowedOrigins: [],
Expand Down Expand Up @@ -300,6 +302,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
tailscaleServePort: 443,
otlpTracesUrl: "http://localhost:4318/v1/traces",
otlpMetricsUrl: "http://localhost:4318/v1/metrics",
otlpLogsUrl: "http://localhost:4318/v1/logs",
}),
);
const derivedPaths = yield* deriveServerPaths(baseDir, undefined);
Expand Down Expand Up @@ -340,6 +343,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
...defaultObservabilityConfig,
otlpTracesUrl: "http://localhost:4318/v1/traces",
otlpMetricsUrl: "http://localhost:4318/v1/metrics",
otlpLogsUrl: "http://localhost:4318/v1/logs",
mode: "desktop",
port: 4888,
cwd: process.cwd(),
Expand Down Expand Up @@ -534,6 +538,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {

expect(resolved.otlpTracesUrl).toBe("https://collector.example.com/v1/traces");
expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics");
expect(resolved.otlpLogsUrl).toBe("https://collector.example.com/v1/logs");
expect(resolved.otlpServiceName).toBe("t3");
}),
);
Expand All @@ -548,11 +553,13 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
OTEL_SERVICE_NAME: "t3",
T3CODE_OTLP_TRACES_URL: "",
T3CODE_OTLP_METRICS_URL: " ",
T3CODE_OTLP_LOGS_URL: "",
T3CODE_OTLP_SERVICE_NAME: "",
});

expect(resolved.otlpTracesUrl).toBe("https://collector.example.com/v1/traces");
expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics");
expect(resolved.otlpLogsUrl).toBe("https://collector.example.com/v1/logs");
expect(resolved.otlpServiceName).toBe("t3");
}),
);
Expand All @@ -563,11 +570,13 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com",
OTEL_SERVICE_NAME: "t3",
T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces",
T3CODE_OTLP_LOGS_URL: "http://localhost:4318/v1/logs",
T3CODE_OTLP_SERVICE_NAME: "t3-local",
});

expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics");
expect(resolved.otlpLogsUrl).toBe("http://localhost:4318/v1/logs");
expect(resolved.otlpServiceName).toBe("t3-local");
}),
);
Expand Down Expand Up @@ -618,6 +627,23 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
}),
);

it.effect("keeps a span's schedule off a T3 Code log endpoint", () =>
Effect.gen(function* () {
// Log records batch on their own variable with their own default, so a
// log endpoint that came from a T3 Code name keeps T3 Code's interval
// instead of inheriting the span delay standing next to it.
const resolved = yield* resolveWithEnv({
OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com",
OTEL_BSP_SCHEDULE_DELAY: "7000",
T3CODE_OTLP_LOGS_URL: "http://localhost:4318/v1/logs",
});

expect(resolved.otelEnvironment.logs.settings).toBeUndefined();
expect(resolved.otlpExportIntervalMs).toBe(7_000);
expect(resolved.otlpLogsExportIntervalMs).toBe(10_000);
}),
);

it.effect("does not report a signal as declined while it is exporting", () =>
Effect.gen(function* () {
// grpc turns off the export these variables asked for, and says nothing
Expand All @@ -631,6 +657,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
expect(resolved.otelEnvironment.traces.declined).toBeUndefined();
expect(resolved.otelEnvironment.metrics.declined).toContain("grpc");
expect(resolved.otelEnvironment.logs.declined).toContain("grpc");
}),
);

Expand All @@ -644,6 +671,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {

expect(resolved.otlpTracesUrl).toBeUndefined();
expect(resolved.otlpMetricsUrl).toBeUndefined();
expect(resolved.otlpLogsUrl).toBeUndefined();
}),
);

Expand All @@ -661,6 +689,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
observability: {
otlpTracesUrl: "http://localhost:4318/v1/traces",
otlpMetricsUrl: "http://localhost:4318/v1/metrics",
otlpLogsUrl: "http://localhost:4318/v1/logs",
},
})}\n`,
);
Expand Down Expand Up @@ -692,11 +721,13 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {

expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
expect(resolved.otlpMetricsUrl).toBe("http://localhost:4318/v1/metrics");
expect(resolved.otlpLogsUrl).toBe("http://localhost:4318/v1/logs");
expect(resolved).toEqual({
logLevel: "Info",
...defaultObservabilityConfig,
otlpTracesUrl: "http://localhost:4318/v1/traces",
otlpMetricsUrl: "http://localhost:4318/v1/metrics",
otlpLogsUrl: "http://localhost:4318/v1/logs",
mode: "desktop",
port: 4888,
cwd: process.cwd(),
Expand Down
24 changes: 19 additions & 5 deletions apps/server/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ const EnvServerConfig = Config.all({
Config.option,
Config.map(Option.getOrUndefined),
),
otlpLogsUrl: Config.string("T3CODE_OTLP_LOGS_URL").pipe(
Config.option,
Config.map(Option.getOrUndefined),
),
otlpExportIntervalMs: Config.int("T3CODE_OTLP_EXPORT_INTERVAL_MS").pipe(
Config.option,
Config.map(Option.getOrUndefined),
Expand Down Expand Up @@ -216,7 +220,7 @@ const loadPersistedObservabilitySettings = Effect.fn(function* (settingsPath: st
const fs = yield* FileSystem.FileSystem;
const exists = yield* fs.exists(settingsPath).pipe(Effect.orElseSucceed(() => false));
if (!exists) {
return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined };
return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined, otlpLogsUrl: undefined };
}

const raw = yield* fs.readFileString(settingsPath).pipe(Effect.orElseSucceed(() => ""));
Expand Down Expand Up @@ -380,10 +384,14 @@ export const resolveServerConfig = (
bootstrap?.otlpMetricsUrl ??
persistedObservabilitySettings.otlpMetricsUrl,
);
const namedLogsUrl = named(
env.otlpLogsUrl ?? bootstrap?.otlpLogsUrl ?? persistedObservabilitySettings.otlpLogsUrl,
);
const otelEnvironment = {
...otel,
traces: namedTracesUrl === undefined ? otel.traces : OtelEnvironment.noSignal,
metrics: namedMetricsUrl === undefined ? otel.metrics : OtelEnvironment.noSignal,
logs: namedLogsUrl === undefined ? otel.logs : OtelEnvironment.noSignal,
} satisfies OtelEnvironment.OtelEnvironment;

const config: ServerConfig.ServerConfig["Service"] = {
Expand All @@ -399,14 +407,20 @@ export const resolveServerConfig = (
otlpMetricsUrl: otelEnvironment.disabled
? undefined
: (namedMetricsUrl ?? otelEnvironment.metrics.settings?.url),
// T3 Code has one interval variable and it deliberately covers both
// signals. The per-signal part is the fallback under it: the environment
// names a trace delay and a metric interval separately, so a signal that
// took its endpoint elsewhere must not inherit the other one's.
otlpLogsUrl: otelEnvironment.disabled
? undefined
: (namedLogsUrl ?? otelEnvironment.logs.settings?.url),
// T3 Code has one interval variable and it deliberately covers every
// signal. The per-signal part is the fallback under it: the environment
// names a span delay, a metric interval, and a log record delay
// separately, so a signal that took its endpoint elsewhere must not
// inherit another one's.
otlpExportIntervalMs:
env.otlpExportIntervalMs ?? otelEnvironment.traces.settings?.exportIntervalMs ?? 10_000,
otlpMetricsExportIntervalMs:
env.otlpExportIntervalMs ?? otelEnvironment.metrics.settings?.exportIntervalMs ?? 10_000,
otlpLogsExportIntervalMs:
env.otlpExportIntervalMs ?? otelEnvironment.logs.settings?.exportIntervalMs ?? 10_000,
otlpServiceName:
named(env.otlpServiceName) ?? otelEnvironment.resource.serviceName ?? "t3-server",
otelEnvironment,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/cli/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,10 @@ const makePairServerConfig = Effect.fn(function* (input: {
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpMetricsExportIntervalMs: 10_000,
otlpLogsExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otelEnvironment: OtelEnvironment.none,
mode: "web",
Expand Down
21 changes: 21 additions & 0 deletions apps/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ export class ServerConfig extends Context.Service<
readonly traceMaxFiles: number;
readonly otlpTracesUrl: string | undefined;
readonly otlpMetricsUrl: string | undefined;
readonly otlpLogsUrl: string | undefined;
readonly otlpExportIntervalMs: number;
readonly otlpMetricsExportIntervalMs: number;
readonly otlpLogsExportIntervalMs: number;
readonly otlpServiceName: string;
/**
* What the standard `OTEL_*` variables asked for. The endpoints above are
Expand Down Expand Up @@ -106,6 +108,23 @@ export const make = (config: ServerConfig["Service"]) => ServerConfig.of(config)

export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerConfig, make(config));

/**
* The OTLP resource every exported signal is tagged with. Traces, metrics, and
* logs read it from here so no two of them can disagree about which process
* produced them.
*/
export const otlpResource = (config: ServerConfig["Service"]) => ({
serviceName: config.otlpServiceName,
...(config.otelEnvironment.resource.serviceVersion === undefined
? {}
: { serviceVersion: config.otelEnvironment.resource.serviceVersion }),
attributes: {
...config.otelEnvironment.resource.attributes,
"service.runtime": "t3-server",
"service.mode": config.mode,
},
});

export const deriveServerPaths = Effect.fn(function* (
baseDir: ServerConfig["Service"]["baseDir"],
devUrl: ServerConfig["Service"]["devUrl"],
Expand Down Expand Up @@ -186,8 +205,10 @@ const makeTest = Effect.fn("ServerConfig.makeTest")(function* (
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpMetricsExportIntervalMs: 10_000,
otlpLogsExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otelEnvironment: OtelEnvironment.none,
cwd,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/environment/ServerEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) {
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpMetricsExportIntervalMs: 10_000,
otlpLogsExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otelEnvironment: OtelEnvironment.none,
cwd: process.cwd(),
Expand Down
20 changes: 6 additions & 14 deletions apps/server/src/observability/Layers/Observability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ export const ObservabilityLive = Layer.unwrap(
);
}

// One variable can decline both signals, and saying so twice reads like
// two separate problems.
// One variable can decline every signal, and saying so three times reads
// like three separate problems.
const declined = new Set(
[otel.traces.declined, otel.metrics.declined].filter((reason) => reason !== undefined),
[otel.traces.declined, otel.metrics.declined, otel.logs.declined].filter(
(reason) => reason !== undefined,
),
);
for (const reason of declined) {
yield* Effect.logWarning(reason);
Expand All @@ -57,17 +59,7 @@ export const ObservabilityLive = Layer.unwrap(
);
}

const otlpResource = {
serviceName: config.otlpServiceName,
...(otel.resource.serviceVersion === undefined
? {}
: { serviceVersion: otel.resource.serviceVersion }),
attributes: {
...otel.resource.attributes,
"service.runtime": "t3-server",
"service.mode": config.mode,
},
};
const otlpResource = ServerConfig.otlpResource(config);

const traceReferencesLayer = Layer.mergeAll(
Layer.succeed(Tracer.MinimumTraceLevel, config.traceMinLevel),
Expand Down
Loading
Loading