fix(otel): fail closed when configured proxies are invalid (#118612)

This commit is contained in:
Peter Steinberger
2026-08-03 03:37:58 -07:00
committed by GitHub
parent 06957a69d8
commit 56f02c4956
3 changed files with 78 additions and 24 deletions
@@ -119,10 +119,7 @@ export function resolveOtelHttpAgentOptions(params: {
const agent = createNodeProxyAgent({ mode: "env", targetUrl: url, agentOptions });
return agent ? () => agent : undefined;
} catch {
logger.warn(
`diagnostics-otel: env proxy agent unavailable for OTLP ${signalIdentifier.toLowerCase()} exporter; falling back to default Node agent`,
);
return undefined;
throw new Error("Configured telemetry proxy is invalid or unsupported; refusing direct export");
}
}
+75 -18
View File
@@ -1869,32 +1869,89 @@ describe("diagnostics-otel service", () => {
}
});
test("falls back to default OTLP agents when env proxy agent creation fails", async () => {
test.each([
["traces", { traces: true }, "unsupported proxy protocol"],
["metrics", { metrics: true }, "invalid proxy URL"],
["logs", { logs: true }, "unsupported proxy protocol"],
] as const)(
"refuses direct %s export when the configured proxy cannot initialize",
async (_signal, signals, errorMessage) => {
createNodeProxyAgentMock.mockImplementation(() => {
throw new Error(errorMessage);
});
await expect(
startOtelService({ endpoint: "https://collector.example.com/otlp", ...signals }),
).rejects.toThrow(
"Configured telemetry proxy is invalid or unsupported; refusing direct export",
);
expect(traceExporterCtor).not.toHaveBeenCalled();
expect(metricExporterCtor).not.toHaveBeenCalled();
expect(logExporterCtor).not.toHaveBeenCalled();
},
);
test("redacts proxy credentials from telemetry startup failures", async () => {
const proxyPassword = "qa-otel-proxy-password-sentinel";
createNodeProxyAgentMock.mockImplementation(() => {
throw new Error("unsupported proxy protocol");
throw new Error(`Invalid proxy URL: "https://operator:${proxyPassword}@proxy.example.com"`);
});
const { ctx } = await startOtelService({
const failure = await startOtelService({
endpoint: "https://collector.example.com/otlp",
traces: true,
metrics: true,
logs: true,
});
}).catch((error: unknown) => error);
expect(firstExporterOptions(traceExporterCtor).httpAgentOptions).toBeUndefined();
expect(firstExporterOptions(metricExporterCtor).httpAgentOptions).toBeUndefined();
expect(firstExporterOptions(logExporterCtor).httpAgentOptions).toBeUndefined();
expect(ctx.logger.warn).toHaveBeenCalledWith(
"diagnostics-otel: env proxy agent unavailable for OTLP traces exporter; falling back to default Node agent",
);
expect(ctx.logger.warn).toHaveBeenCalledWith(
"diagnostics-otel: env proxy agent unavailable for OTLP metrics exporter; falling back to default Node agent",
);
expect(ctx.logger.warn).toHaveBeenCalledWith(
"diagnostics-otel: env proxy agent unavailable for OTLP logs exporter; falling back to default Node agent",
);
expect(failure).toBeInstanceOf(Error);
expect(failure).toMatchObject({
message: "Configured telemetry proxy is invalid or unsupported; refusing direct export",
});
expect(failure).not.toHaveProperty("cause");
expect(String(failure)).not.toContain(proxyPassword);
expect(traceExporterCtor).not.toHaveBeenCalled();
});
test.each([
{
disabledSignal: "traces",
enabledSignal: "metrics",
disabledEndpoint: "tracesEndpoint",
signals: { traces: false, metrics: true },
},
{
disabledSignal: "metrics",
enabledSignal: "traces",
disabledEndpoint: "metricsEndpoint",
signals: { traces: true, metrics: false },
},
] as const)(
"does not resolve proxy settings for disabled $disabledSignal export",
async ({ disabledSignal, enabledSignal, disabledEndpoint, signals }) => {
createNodeProxyAgentMock.mockImplementation(({ targetUrl }: { targetUrl: string }) => {
if (targetUrl.includes(`disabled-${disabledSignal}.example.com`)) {
throw new Error("invalid disabled-signal proxy");
}
return nodeProxyAgent;
});
await startOtelService({
endpoint: "https://collector.example.com/otlp",
...signals,
configure: (ctx) => {
ctx.config.diagnostics!.otel![disabledEndpoint] =
`https://disabled-${disabledSignal}.example.com/otlp`;
},
});
expect(createNodeProxyAgentCalls()).toEqual([
expect.objectContaining({
targetUrl: `https://collector.example.com/otlp/v1/${enabledSignal}`,
}),
]);
},
);
test("leaves OTLP HTTP exporters on their default agents when env proxy is bypassed", async () => {
await startOtelService({
endpoint: "https://collector.example.com/otlp",
+2 -2
View File
@@ -175,12 +175,12 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
path: "v1/metrics",
});
const traceHttpAgentOptions = resolveOtelHttpAgentOptions({
url: traceUrl,
url: tracesEnabled ? traceUrl : undefined,
signalIdentifier: "TRACES",
logger: ctx.logger,
});
const metricHttpAgentOptions = resolveOtelHttpAgentOptions({
url: metricUrl,
url: metricsEnabled ? metricUrl : undefined,
signalIdentifier: "METRICS",
logger: ctx.logger,
});