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
76 changes: 76 additions & 0 deletions packages/core/src/telemetry/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ import { TelemetryTarget } from './index.js';

import * as os from 'node:os';
import * as path from 'node:path';
import { promises as fs } from 'node:fs';
import {
resetDebugLoggingState,
setDebugLogSession,
} from '../utils/debugLogger.js';

vi.mock('@opentelemetry/exporter-trace-otlp-grpc');
vi.mock('@opentelemetry/exporter-logs-otlp-grpc');
Expand Down Expand Up @@ -143,6 +148,77 @@ describe('Telemetry SDK', () => {
);
});

it('should route OpenTelemetry diagnostics to debug log instead of console output', async () => {
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
const consoleWarnSpy = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});
const mkdirSpy = vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined);
const appendFileSpy = vi
.spyOn(fs, 'appendFile')
.mockResolvedValue(undefined);
const unlinkSpy = vi.spyOn(fs, 'unlink').mockResolvedValue(undefined);
const symlinkSpy = vi.spyOn(fs, 'symlink').mockResolvedValue(undefined);
const previousDebugLogFileEnv = process.env['QWEN_DEBUG_LOG_FILE'];
try {
process.env['QWEN_DEBUG_LOG_FILE'] = '1';
setDebugLogSession({ getSessionId: () => 'otel-diag-test-session' });

diag.error(
JSON.stringify({
message:
'Error: PeriodicExportingMetricReader: metrics export failed (error Error: connect ECONNREFUSED)',
}),
);

diag.error('A different OpenTelemetry diagnostic');
diag.warn('An OpenTelemetry warning');

await vi.waitFor(() => {
expect(appendFileSpy).toHaveBeenCalledTimes(3);
});

expect(consoleErrorSpy).not.toHaveBeenCalled();
expect(consoleWarnSpy).not.toHaveBeenCalled();
expect(mkdirSpy).toHaveBeenCalled();
expect(appendFileSpy).toHaveBeenCalledWith(
expect.stringContaining('otel-diag-test-session'),
expect.stringContaining(
'[ERROR] [OTEL] {"message":"Error: PeriodicExportingMetricReader: metrics export failed (error Error: connect ECONNREFUSED)"}',
),
'utf8',
);
expect(appendFileSpy).toHaveBeenCalledWith(
expect.stringContaining('otel-diag-test-session'),
expect.stringContaining(
'[ERROR] [OTEL] A different OpenTelemetry diagnostic',
),
'utf8',
);
expect(appendFileSpy).toHaveBeenCalledWith(
expect.stringContaining('otel-diag-test-session'),
expect.stringContaining('[WARN] [OTEL] An OpenTelemetry warning'),
'utf8',
);
} finally {
consoleErrorSpy.mockRestore();
consoleWarnSpy.mockRestore();
mkdirSpy.mockRestore();
appendFileSpy.mockRestore();
unlinkSpy.mockRestore();
symlinkSpy.mockRestore();
setDebugLogSession(null);
resetDebugLoggingState();
if (previousDebugLogFileEnv === undefined) {
delete process.env['QWEN_DEBUG_LOG_FILE'];
} else {
process.env['QWEN_DEBUG_LOG_FILE'] = previousDebugLogFileEnv;
}
}
});

it('should use HTTP exporters with signal-specific paths when protocol is http', () => {
vi.spyOn(mockConfig, 'getTelemetryEnabled').mockReturnValue(true);
vi.spyOn(mockConfig, 'getTelemetryOtlpProtocol').mockReturnValue('http');
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/telemetry/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
import { DiagLogLevel, diag } from '@opentelemetry/api';
import type { DiagLogger } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
Expand All @@ -30,8 +31,21 @@ import {
import { createDebugLogger } from '../utils/debugLogger.js';
import { LogToSpanProcessor } from './log-to-span-processor.js';

// For troubleshooting, set the log level to DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.WARN);
function createTelemetryDiagLogger(): DiagLogger {
const debugLogger = createDebugLogger('OTEL');
return {
error: (message, ...args) => debugLogger.error(message, ...args),
warn: (message, ...args) => debugLogger.warn(message, ...args),
info: (message, ...args) => debugLogger.info(message, ...args),
debug: (message, ...args) => debugLogger.debug(message, ...args),
verbose: (message, ...args) => debugLogger.debug(message, ...args),
};
}

// For troubleshooting, set the log level to DiagLogLevel.DEBUG.
// OTel SDK diagnostics must not write to console because console output can be
// surfaced in user-visible UI. Keep diagnostics in the debug log instead.
diag.setLogger(createTelemetryDiagLogger(), DiagLogLevel.WARN);

/**
* Standard OTLP HTTP signal-specific paths per the OpenTelemetry specification.
Expand Down
Loading