fix: preserve inherited native exporter headers

This commit is contained in:
Zhichao Li
2026-08-03 10:50:51 -07:00
parent 0e19835124
commit 08fd4957af
5 changed files with 22 additions and 8 deletions
@@ -38,7 +38,11 @@ export interface IAgentHostNativeOTelConfig {
/** Trace destination. In DB mode this is the Agent Host HTTP/JSON loopback. */
readonly traces?: { readonly endpoint: string; readonly protocol: 'http/json' | 'http/protobuf' | 'grpc' };
/** User-owned OTLP destination used directly by native SDK logs and metrics. */
readonly external?: { readonly endpoint: string; readonly protocol: 'http/json' | 'http/protobuf' | 'grpc' };
readonly external?: {
readonly endpoint: string;
readonly protocol: 'http/json' | 'http/protobuf' | 'grpc';
readonly headers?: Readonly<Record<string, string>>;
};
readonly captureContent: boolean;
}
@@ -269,6 +269,9 @@ export function buildClaudeTelemetryEnv(config: IAgentHostNativeOTelConfig | und
env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = config.external.protocol;
env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = resolveSignalEndpoint(config.external.endpoint, 'metrics');
env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = config.external.protocol;
if (config.external.headers && Object.keys(config.external.headers).length > 0) {
env.OTEL_EXPORTER_OTLP_HEADERS = Object.entries(config.external.headers).map(([key, value]) => `${key}=${value}`).join(',');
}
}
if (traceContext) {
env.TRACEPARENT = traceContext.traceparent;
@@ -86,12 +86,15 @@ export function codexTelemetryOverrides(config: IAgentHostNativeOTelConfig | und
];
}
function codexExporter(config: { endpoint: string; protocol: 'http/json' | 'http/protobuf' | 'grpc' }): string {
function codexExporter(config: { endpoint: string; protocol: 'http/json' | 'http/protobuf' | 'grpc'; headers?: Readonly<Record<string, string>> }): string {
const headers = config.headers && Object.keys(config.headers).length > 0
? `, headers = { ${Object.entries(config.headers).map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`).join(', ')} }`
: '';
if (config.protocol === 'grpc') {
return `{ otlp-grpc = { endpoint = ${JSON.stringify(config.endpoint)} } }`;
return `{ otlp-grpc = { endpoint = ${JSON.stringify(config.endpoint)}${headers} } }`;
}
const protocol = config.protocol === 'http/json' ? 'json' : 'binary';
return `{ otlp-http = { endpoint = ${JSON.stringify(config.endpoint)}, protocol = ${JSON.stringify(protocol)} } }`;
return `{ otlp-http = { endpoint = ${JSON.stringify(config.endpoint)}, protocol = ${JSON.stringify(protocol)}${headers} } }`;
}
function resolveSignalEndpoint(endpoint: string, signal: 'logs' | 'metrics'): string {
@@ -195,7 +195,11 @@ export class AgentHostOTelService extends Disposable implements IAgentHostOTelSe
const protocol = this._config.otlpProtocol === 'grpc'
? 'grpc'
: this._config.otlpProtocol === 'http/protobuf' ? 'http/protobuf' : 'http/json';
const external = this._config.otlpEndpoint ? { endpoint: this._config.otlpEndpoint, protocol } as const : undefined;
const external = this._config.otlpEndpoint ? {
endpoint: this._config.otlpEndpoint,
protocol,
...(this._config.headers ? { headers: this._config.headers } : {}),
} as const : undefined;
if (!this._config.dbSpanExporter) {
return { traces: external, external, captureContent: this._config.captureContent === true };
}
@@ -31,13 +31,13 @@ suite('CodexLaunchConfig', () => {
test('routes traces to loopback and logs/metrics directly to the external sink', () => {
const config = buildCodexLaunchConfig('openai', {}, undefined, [], {
traces: { endpoint: 'http://127.0.0.1:4567/v1/traces', protocol: 'http/json' },
external: { endpoint: 'http://collector:4318', protocol: 'http/protobuf' },
external: { endpoint: 'http://collector:4318', protocol: 'http/protobuf', headers: { authorization: 'Bearer test' } },
captureContent: false,
});
assert.ok(config.args.includes('otel.log_user_prompt=false'));
assert.ok(config.args.includes('otel.trace_exporter={ otlp-http = { endpoint = "http://127.0.0.1:4567/v1/traces", protocol = "json" } }'));
assert.ok(config.args.includes('otel.exporter={ otlp-http = { endpoint = "http://collector:4318/v1/logs", protocol = "binary" } }'));
assert.ok(config.args.includes('otel.metrics_exporter={ otlp-http = { endpoint = "http://collector:4318/v1/metrics", protocol = "binary" } }'));
assert.ok(config.args.includes('otel.exporter={ otlp-http = { endpoint = "http://collector:4318/v1/logs", protocol = "binary", headers = { "authorization" = "Bearer test" } } }'));
assert.ok(config.args.includes('otel.metrics_exporter={ otlp-http = { endpoint = "http://collector:4318/v1/metrics", protocol = "binary", headers = { "authorization" = "Bearer test" } } }'));
});
test('identifies provider-compatible threads', () => {