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
2 changes: 2 additions & 0 deletions apps/desktop/src/app/DesktopConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export const DesktopConfig = Config.all({
desktopLanHostOverride: trimmedString("T3CODE_DESKTOP_LAN_HOST"),
desktopHttpsEndpointUrls: commaSeparatedStrings("T3CODE_DESKTOP_HTTPS_ENDPOINTS"),
otlpTracesUrl: trimmedString("T3CODE_OTLP_TRACES_URL"),
otlpMetricsUrl: trimmedString("T3CODE_OTLP_METRICS_URL"),
otlpLogsUrl: trimmedString("T3CODE_OTLP_LOGS_URL"),
otlpExportIntervalMs: Config.Int("T3CODE_OTLP_EXPORT_INTERVAL_MS").pipe(
Config.withDefault(10_000),
),
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/app/DesktopEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe("DesktopEnvironment", () => {
VITE_DEV_SERVER_URL: "http://localhost:5173",
T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH: " /remote/server.mjs ",
T3CODE_OTLP_TRACES_URL: " http://127.0.0.1:4318/v1/traces ",
T3CODE_OTLP_METRICS_URL: " http://127.0.0.1:4318/v1/metrics ",
T3CODE_OTLP_LOGS_URL: " http://127.0.0.1:4318/v1/logs ",
T3CODE_OTLP_EXPORT_INTERVAL_MS: "2500",
T3CODE_OTLP_HEADERS: "authorization=Basic%20abc%3D%3D,x-tenant=t3",
T3CODE_OTLP_PROTOCOL: "http/protobuf",
Expand Down Expand Up @@ -86,6 +88,8 @@ describe("DesktopEnvironment", () => {
assert.deepEqual(environment.configuredBackendPort, Option.some(4949));
assert.deepEqual(environment.commitHashOverride, Option.some("0123456789abcdef"));
assert.deepEqual(environment.otlpTracesUrl, Option.some("http://127.0.0.1:4318/v1/traces"));
assert.deepEqual(environment.otlpMetricsUrl, Option.some("http://127.0.0.1:4318/v1/metrics"));
assert.deepEqual(environment.otlpLogsUrl, Option.some("http://127.0.0.1:4318/v1/logs"));
assert.equal(environment.otlpExportIntervalMs, 2500);
assert.deepEqual(
environment.otlpHeaders,
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/app/DesktopEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export class DesktopEnvironment extends Context.Service<
readonly configuredBackendPort: Option.Option<number>;
readonly commitHashOverride: Option.Option<string>;
readonly otlpTracesUrl: Option.Option<string>;
readonly otlpMetricsUrl: Option.Option<string>;
readonly otlpLogsUrl: Option.Option<string>;
readonly otlpExportIntervalMs: number;
readonly otlpHeaders: Option.Option<Record<string, string>>;
readonly otlpProtocol: OtlpProtocol;
Expand Down Expand Up @@ -227,6 +229,8 @@ const make = Effect.fn("desktop.environment.make")(function* (
configuredBackendPort: config.configuredBackendPort,
commitHashOverride: config.commitHashOverride,
otlpTracesUrl: config.otlpTracesUrl,
otlpMetricsUrl: config.otlpMetricsUrl,
otlpLogsUrl: config.otlpLogsUrl,
otlpExportIntervalMs: config.otlpExportIntervalMs,
otlpHeaders: config.otlpHeaders,
otlpProtocol: config.otlpProtocol,
Expand Down
103 changes: 103 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 @@ -60,6 +61,9 @@ function makeEnvironmentLayer(
readonly resourcesPath?: string;
readonly appVersion?: string;
readonly processArch?: NodeJS.Architecture;
readonly otlpTracesUrl?: string;
readonly otlpMetricsUrl?: string;
readonly otlpLogsUrl?: string;
},
) {
return DesktopEnvironment.layer({
Expand All @@ -82,6 +86,9 @@ function makeEnvironmentLayer(
T3CODE_MODE: "desktop",
T3CODE_DESKTOP_LAN_HOST: "192.168.1.50",
VITE_DEV_SERVER_URL: options?.devServerUrl,
T3CODE_OTLP_TRACES_URL: options?.otlpTracesUrl,
T3CODE_OTLP_METRICS_URL: options?.otlpMetricsUrl,
T3CODE_OTLP_LOGS_URL: options?.otlpLogsUrl,
}),
),
),
Expand Down Expand Up @@ -730,13 +737,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 @@ -749,10 +758,103 @@ describe("DesktopBackendConfiguration", () => {

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

it.effect("resolveWsl carries environment-configured observability endpoints", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});

yield* Effect.gen(function* () {
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
const config = yield* configuration.resolveWsl({ port: 5050, distro: null });

// No settings.json exists here: the endpoints come from the desktop
// process's env, which a WSL child cannot inherit, so the bootstrap
// has to carry them or log export stays off inside the distro.
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");
assert.notInclude(config.env.WSLENV ?? "", "T3CODE_OTLP_LOGS_URL");
}).pipe(
Effect.provide(
DesktopBackendConfiguration.layer.pipe(
Layer.provideMerge(serverExposureLayer),
Layer.provideMerge(DesktopAppSettings.layerTest()),
Layer.provideMerge(DesktopWslServerTree.layerTest()),
Layer.provideMerge(
DesktopWslEnvironment.layerTest({
isAvailable: true,
windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
getDistroIp: () => Option.some("172.27.0.99"),
}),
),
Layer.provideMerge(
makeEnvironmentLayer(baseDir, {
platform: "win32",
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 ",
}),
),
),
),
);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("environment observability endpoints win over the persisted settings file", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});

yield* Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;

yield* fileSystem.makeDirectory(environment.path.dirname(environment.serverSettingsPath), {
recursive: true,
});
yield* fileSystem.writeFileString(
environment.serverSettingsPath,
yield* encodePersistedServerObservabilitySettingsDocument({
observability: {
otlpTracesUrl: "http://persisted:4318/v1/traces",
otlpMetricsUrl: "http://persisted:4318/v1/metrics",
otlpLogsUrl: "http://persisted:4318/v1/logs",
},
}),
);

const config = yield* configuration.resolvePrimary;
assert.equal(config.bootstrap.otlpLogsUrl, "http://env:4318/v1/logs");
// Only the logs endpoint is set in env, so the other two still come
// from the settings file rather than being dropped together.
assert.equal(config.bootstrap.otlpTracesUrl, "http://persisted:4318/v1/traces");
assert.equal(config.bootstrap.otlpMetricsUrl, "http://persisted:4318/v1/metrics");
}).pipe(
Effect.provide(
DesktopBackendConfiguration.layer.pipe(
Layer.provideMerge(serverExposureLayer),
Layer.provideMerge(DesktopAppSettings.layerTest()),
Layer.provideMerge(DesktopWslServerTree.layerTest()),
Layer.provideMerge(DesktopWslEnvironment.layerTest()),
Layer.provideMerge(
makeEnvironmentLayer(baseDir, { otlpLogsUrl: "http://env:4318/v1/logs" }),
),
),
),
);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("logs structured context when persisted observability settings cannot be read", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down Expand Up @@ -799,6 +901,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
25 changes: 24 additions & 1 deletion 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 @@ -212,9 +214,26 @@ const readPersistedBackendObservabilitySettings = Effect.gen(function* () {
return {
otlpTracesUrl: Option.fromNullishOr(parsed.otlpTracesUrl),
otlpMetricsUrl: Option.fromNullishOr(parsed.otlpMetricsUrl),
otlpLogsUrl: Option.fromNullishOr(parsed.otlpLogsUrl),
};
});

// The bootstrap is the only channel that carries an OTLP endpoint to every
// backend. A Windows-native child inherits the desktop process's env, but a
// WSL child gets nothing across wsl.exe that WSLENV does not declare, and
// WSLENV translation of URL-shaped values is unreliable, so the endpoints are
// deliberately not forwarded that way. Env beats the persisted settings file,
// matching the precedence resolveServerConfig and DesktopObservability apply.
const readBackendObservabilitySettings = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const persisted = yield* readPersistedBackendObservabilitySettings;
return {
otlpTracesUrl: Option.orElse(environment.otlpTracesUrl, () => persisted.otlpTracesUrl),
otlpMetricsUrl: Option.orElse(environment.otlpMetricsUrl, () => persisted.otlpMetricsUrl),
otlpLogsUrl: Option.orElse(environment.otlpLogsUrl, () => persisted.otlpLogsUrl),
} satisfies BackendObservabilitySettings;
});

interface SharedBootstrapInput {
readonly bootstrapToken: string;
readonly observabilitySettings: BackendObservabilitySettings;
Expand Down Expand Up @@ -488,6 +507,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 Expand Up @@ -804,7 +827,7 @@ export const make = Effect.gen(function* () {
// restart cycle without having to bounce the desktop process.
const sharedInputs = Effect.gen(function* () {
const bootstrapToken = yield* getOrCreateBootstrapToken;
const observabilitySettings = yield* readPersistedBackendObservabilitySettings.pipe(
const observabilitySettings = yield* readBackendObservabilitySettings.pipe(
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment),
);
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const makeCliTestServerConfig = (baseDir: string) =>
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otlpHeaders: undefined,
Expand Down
44 changes: 44 additions & 0 deletions apps/server/src/cli/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otlpHeaders: undefined,
Expand Down Expand Up @@ -398,6 +399,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 @@ -438,6 +440,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 @@ -604,6 +607,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 @@ -635,11 +639,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 Expand Up @@ -845,4 +851,42 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => {
expect(resolved.otlpProtocol).toBe("http/protobuf");
}),
);

it.effect("reads the OTLP logs URL from env", () =>
Effect.gen(function* () {
const { join } = yield* Path.Path;
const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-otlp-logs-url-base");

const resolved = yield* resolveServerConfig(
{
mode: Option.some("web"),
port: Option.some(3773),
host: Option.none(),
baseDir: Option.some(baseDir),
cwd: Option.none(),
devUrl: Option.none(),
noBrowser: Option.none(),
bootstrapFd: Option.none(),
autoBootstrapProjectFromCwd: Option.none(),
logWebSocketEvents: Option.none(),
tailscaleServeEnabled: Option.none(),
tailscaleServePort: Option.none(),
},
Option.none(),
).pipe(
Effect.provide(
Layer.mergeAll(
ConfigProvider.layer(
ConfigProvider.fromEnv({
env: { T3CODE_OTLP_LOGS_URL: "http://collector.internal:4318/v1/logs" },
}),
),
NetService.layer,
),
),
);

expect(resolved.otlpLogsUrl).toBe("http://collector.internal:4318/v1/logs");
}),
);
});
8 changes: 7 additions & 1 deletion apps/server/src/cli/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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.withDefault(10_000),
),
Expand Down Expand Up @@ -227,7 +231,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 @@ -393,6 +397,8 @@ export const resolveServerConfig = (
env.otlpMetricsUrl ??
bootstrap?.otlpMetricsUrl ??
persistedObservabilitySettings.otlpMetricsUrl,
otlpLogsUrl:
env.otlpLogsUrl ?? bootstrap?.otlpLogsUrl ?? persistedObservabilitySettings.otlpLogsUrl,
otlpExportIntervalMs: env.otlpExportIntervalMs,
otlpServiceName: env.otlpServiceName,
otlpHeaders: env.otlpHeaders,
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/cli/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ const makePairServerConfig = Effect.fn(function* (input: {
traceMaxFiles: 10,
otlpTracesUrl: undefined,
otlpMetricsUrl: undefined,
otlpLogsUrl: undefined,
otlpExportIntervalMs: 10_000,
otlpServiceName: "t3-server",
otlpHeaders: undefined,
Expand Down
Loading
Loading