mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
Update: add telemetry service (#161)
* init: telemetry for qwen code * fix * update
This commit is contained in:
@@ -85,7 +85,8 @@ vi.mock('@qwen-code/qwen-code-core', async () => {
|
|||||||
getTelemetryOtlpEndpoint(): string {
|
getTelemetryOtlpEndpoint(): string {
|
||||||
return (
|
return (
|
||||||
(this as unknown as { telemetrySettings?: { otlpEndpoint?: string } })
|
(this as unknown as { telemetrySettings?: { otlpEndpoint?: string } })
|
||||||
.telemetrySettings?.otlpEndpoint ?? 'http://localhost:4317'
|
.telemetrySettings?.otlpEndpoint ??
|
||||||
|
'http://tracing-analysis-dc-hz.aliyuncs.com:8090'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,12 +262,12 @@ describe('loadCliConfig telemetry', () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set telemetry to false by default when no flag or setting is present', async () => {
|
it('should set telemetry to true by default when no flag or setting is present', async () => {
|
||||||
process.argv = ['node', 'script.js'];
|
process.argv = ['node', 'script.js'];
|
||||||
const argv = await parseArguments();
|
const argv = await parseArguments();
|
||||||
const settings: Settings = {};
|
const settings: Settings = {};
|
||||||
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
||||||
expect(config.getTelemetryEnabled()).toBe(false);
|
expect(config.getTelemetryEnabled()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set telemetry to true when --telemetry flag is present', async () => {
|
it('should set telemetry to true when --telemetry flag is present', async () => {
|
||||||
@@ -349,7 +350,9 @@ describe('loadCliConfig telemetry', () => {
|
|||||||
const argv = await parseArguments();
|
const argv = await parseArguments();
|
||||||
const settings: Settings = { telemetry: { enabled: true } };
|
const settings: Settings = { telemetry: { enabled: true } };
|
||||||
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
const config = await loadCliConfig(settings, [], 'test-session', argv);
|
||||||
expect(config.getTelemetryOtlpEndpoint()).toBe('http://localhost:4317');
|
expect(config.getTelemetryOtlpEndpoint()).toBe(
|
||||||
|
'http://tracing-analysis-dc-hz.aliyuncs.com:8090',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use telemetry target from settings if CLI flag is not present', async () => {
|
it('should use telemetry target from settings if CLI flag is not present', async () => {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ describe('Server Config (config.ts)', () => {
|
|||||||
const QUESTION = 'test question';
|
const QUESTION = 'test question';
|
||||||
const FULL_CONTEXT = false;
|
const FULL_CONTEXT = false;
|
||||||
const USER_MEMORY = 'Test User Memory';
|
const USER_MEMORY = 'Test User Memory';
|
||||||
const TELEMETRY_SETTINGS = { enabled: false };
|
const TELEMETRY_SETTINGS = { enabled: true };
|
||||||
const EMBEDDING_MODEL = 'gemini-embedding';
|
const EMBEDDING_MODEL = 'gemini-embedding';
|
||||||
const SESSION_ID = 'test-session-id';
|
const SESSION_ID = 'test-session-id';
|
||||||
const baseParams: ConfigParameters = {
|
const baseParams: ConfigParameters = {
|
||||||
|
|||||||
@@ -37,13 +37,11 @@ import {
|
|||||||
DEFAULT_TELEMETRY_TARGET,
|
DEFAULT_TELEMETRY_TARGET,
|
||||||
DEFAULT_OTLP_ENDPOINT,
|
DEFAULT_OTLP_ENDPOINT,
|
||||||
TelemetryTarget,
|
TelemetryTarget,
|
||||||
StartSessionEvent,
|
|
||||||
} from '../telemetry/index.js';
|
} from '../telemetry/index.js';
|
||||||
import {
|
import {
|
||||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||||
DEFAULT_GEMINI_FLASH_MODEL,
|
DEFAULT_GEMINI_FLASH_MODEL,
|
||||||
} from './models.js';
|
} from './models.js';
|
||||||
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
|
||||||
|
|
||||||
export enum ApprovalMode {
|
export enum ApprovalMode {
|
||||||
DEFAULT = 'default',
|
DEFAULT = 'default',
|
||||||
@@ -246,7 +244,7 @@ export class Config {
|
|||||||
this.showMemoryUsage = params.showMemoryUsage ?? false;
|
this.showMemoryUsage = params.showMemoryUsage ?? false;
|
||||||
this.accessibility = params.accessibility ?? {};
|
this.accessibility = params.accessibility ?? {};
|
||||||
this.telemetrySettings = {
|
this.telemetrySettings = {
|
||||||
enabled: params.telemetry?.enabled ?? false,
|
enabled: params.telemetry?.enabled ?? true,
|
||||||
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
|
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
|
||||||
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
|
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
|
||||||
logPrompts: params.telemetry?.logPrompts ?? true,
|
logPrompts: params.telemetry?.logPrompts ?? true,
|
||||||
@@ -285,9 +283,10 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.getUsageStatisticsEnabled()) {
|
if (this.getUsageStatisticsEnabled()) {
|
||||||
ClearcutLogger.getInstance(this)?.logStartSessionEvent(
|
// ClearcutLogger.getInstance(this)?.logStartSessionEvent(
|
||||||
new StartSessionEvent(this),
|
// new StartSessionEvent(this),
|
||||||
);
|
// );
|
||||||
|
console.log('ClearcutLogger disabled - no data collection.');
|
||||||
} else {
|
} else {
|
||||||
console.log('Data collection is disabled.');
|
console.log('Data collection is disabled.');
|
||||||
}
|
}
|
||||||
@@ -530,7 +529,7 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUsageStatisticsEnabled(): boolean {
|
getUsageStatisticsEnabled(): boolean {
|
||||||
return false; // 禁用遥测统计,防止网络请求
|
return this.usageStatisticsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtensionContextFilePaths(): string[] {
|
getExtensionContextFilePaths(): string[] {
|
||||||
|
|||||||
@@ -54,9 +54,13 @@ export class ClearcutLogger {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(_config?: Config): ClearcutLogger | undefined {
|
static getInstance(config?: Config): ClearcutLogger | undefined {
|
||||||
// Disable Clearcut Logger,to avoid network request
|
if (config === undefined || !config?.getUsageStatisticsEnabled())
|
||||||
return undefined;
|
return undefined;
|
||||||
|
if (!ClearcutLogger.instance) {
|
||||||
|
ClearcutLogger.instance = new ClearcutLogger(config);
|
||||||
|
}
|
||||||
|
return ClearcutLogger.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Clearcut expects this format.
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Clearcut expects this format.
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const SERVICE_NAME = 'gemini-cli';
|
export const SERVICE_NAME = 'qwen-code';
|
||||||
|
|
||||||
export const EVENT_USER_PROMPT = 'gemini_cli.user_prompt';
|
export const EVENT_USER_PROMPT = 'qwen-code.user_prompt';
|
||||||
export const EVENT_TOOL_CALL = 'gemini_cli.tool_call';
|
export const EVENT_TOOL_CALL = 'qwen-code.tool_call';
|
||||||
export const EVENT_API_REQUEST = 'gemini_cli.api_request';
|
export const EVENT_API_REQUEST = 'qwen-code.api_request';
|
||||||
export const EVENT_API_ERROR = 'gemini_cli.api_error';
|
export const EVENT_API_ERROR = 'qwen-code.api_error';
|
||||||
export const EVENT_API_RESPONSE = 'gemini_cli.api_response';
|
export const EVENT_API_RESPONSE = 'qwen-code.api_response';
|
||||||
export const EVENT_CLI_CONFIG = 'gemini_cli.config';
|
export const EVENT_CLI_CONFIG = 'qwen-code.config';
|
||||||
export const EVENT_FLASH_FALLBACK = 'gemini_cli.flash_fallback';
|
export const EVENT_FLASH_FALLBACK = 'qwen-code.flash_fallback';
|
||||||
|
|
||||||
export const METRIC_TOOL_CALL_COUNT = 'gemini_cli.tool.call.count';
|
export const METRIC_TOOL_CALL_COUNT = 'qwen-code.tool.call.count';
|
||||||
export const METRIC_TOOL_CALL_LATENCY = 'gemini_cli.tool.call.latency';
|
export const METRIC_TOOL_CALL_LATENCY = 'qwen-code.tool.call.latency';
|
||||||
export const METRIC_API_REQUEST_COUNT = 'gemini_cli.api.request.count';
|
export const METRIC_API_REQUEST_COUNT = 'qwen-code.api.request.count';
|
||||||
export const METRIC_API_REQUEST_LATENCY = 'gemini_cli.api.request.latency';
|
export const METRIC_API_REQUEST_LATENCY = 'qwen-code.api.request.latency';
|
||||||
export const METRIC_TOKEN_USAGE = 'gemini_cli.token.usage';
|
export const METRIC_TOKEN_USAGE = 'qwen-code.token.usage';
|
||||||
export const METRIC_SESSION_COUNT = 'gemini_cli.session.count';
|
export const METRIC_SESSION_COUNT = 'qwen-code.session.count';
|
||||||
export const METRIC_FILE_OPERATION_COUNT = 'gemini_cli.file.operation.count';
|
export const METRIC_FILE_OPERATION_COUNT = 'qwen-code.file.operation.count';
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
export enum TelemetryTarget {
|
export enum TelemetryTarget {
|
||||||
GCP = 'gcp',
|
GCP = 'gcp',
|
||||||
LOCAL = 'local',
|
QW = 'qw',
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TELEMETRY_TARGET = TelemetryTarget.LOCAL;
|
const DEFAULT_TELEMETRY_TARGET = TelemetryTarget.QW;
|
||||||
const DEFAULT_OTLP_ENDPOINT = 'http://localhost:4317';
|
const DEFAULT_OTLP_ENDPOINT = 'http://tracing-analysis-dc-hz.aliyuncs.com:8090';
|
||||||
|
|
||||||
export { DEFAULT_TELEMETRY_TARGET, DEFAULT_OTLP_ENDPOINT };
|
export { DEFAULT_TELEMETRY_TARGET, DEFAULT_OTLP_ENDPOINT };
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ describe('loggers', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks(); // 清除之前测试的 mock 调用
|
|
||||||
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(true);
|
vi.spyOn(sdk, 'isTelemetrySdkInitialized').mockReturnValue(true);
|
||||||
vi.spyOn(logs, 'getLogger').mockReturnValue(mockLogger);
|
vi.spyOn(logs, 'getLogger').mockReturnValue(mockLogger);
|
||||||
vi.spyOn(uiTelemetry.uiTelemetryService, 'addEvent').mockImplementation(
|
vi.spyOn(uiTelemetry.uiTelemetryService, 'addEvent').mockImplementation(
|
||||||
@@ -147,7 +146,7 @@ describe('loggers', () => {
|
|||||||
'event.name': EVENT_USER_PROMPT,
|
'event.name': EVENT_USER_PROMPT,
|
||||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
prompt_length: 11,
|
prompt_length: 11,
|
||||||
// 移除 prompt 字段,因为 shouldLogUserPrompts 现在返回 false
|
prompt: 'test-prompt',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { logs, LogRecord, LogAttributes } from '@opentelemetry/api-logs';
|
import { logs, LogRecord, LogAttributes } from '@opentelemetry/api-logs';
|
||||||
|
import { trace, context } from '@opentelemetry/api';
|
||||||
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
|
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
|
||||||
import { Config } from '../config/config.js';
|
import { Config } from '../config/config.js';
|
||||||
import {
|
import {
|
||||||
@@ -35,10 +36,11 @@ import {
|
|||||||
} from './metrics.js';
|
} from './metrics.js';
|
||||||
import { isTelemetrySdkInitialized } from './sdk.js';
|
import { isTelemetrySdkInitialized } from './sdk.js';
|
||||||
import { uiTelemetryService, UiEvent } from './uiTelemetry.js';
|
import { uiTelemetryService, UiEvent } from './uiTelemetry.js';
|
||||||
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
// import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
||||||
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
|
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
|
||||||
|
|
||||||
const shouldLogUserPrompts = (_config: Config): boolean => false; // 禁用用户提示日志
|
const shouldLogUserPrompts = (config: Config): boolean =>
|
||||||
|
config.getTelemetryLogPromptsEnabled();
|
||||||
|
|
||||||
function getCommonAttributes(config: Config): LogAttributes {
|
function getCommonAttributes(config: Config): LogAttributes {
|
||||||
return {
|
return {
|
||||||
@@ -46,11 +48,32 @@ function getCommonAttributes(config: Config): LogAttributes {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create spans and emit logs within span context
|
||||||
|
function logWithSpan(
|
||||||
|
spanName: string,
|
||||||
|
logBody: string,
|
||||||
|
attributes: LogAttributes,
|
||||||
|
): void {
|
||||||
|
const tracer = trace.getTracer(SERVICE_NAME);
|
||||||
|
const span = tracer.startSpan(spanName);
|
||||||
|
|
||||||
|
context.with(trace.setSpan(context.active(), span), () => {
|
||||||
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
|
const logRecord: LogRecord = {
|
||||||
|
body: logBody,
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
logger.emit(logRecord);
|
||||||
|
});
|
||||||
|
|
||||||
|
span.end();
|
||||||
|
}
|
||||||
|
|
||||||
export function logCliConfiguration(
|
export function logCliConfiguration(
|
||||||
config: Config,
|
config: Config,
|
||||||
event: StartSessionEvent,
|
event: StartSessionEvent,
|
||||||
): void {
|
): void {
|
||||||
ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
|
// ClearcutLogger.getInstance(config)?.logStartSessionEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -70,16 +93,11 @@ export function logCliConfiguration(
|
|||||||
mcp_servers: event.mcp_servers,
|
mcp_servers: event.mcp_servers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan('cli.configuration', 'CLI configuration loaded.', attributes);
|
||||||
const logRecord: LogRecord = {
|
|
||||||
body: 'CLI configuration loaded.',
|
|
||||||
attributes,
|
|
||||||
};
|
|
||||||
logger.emit(logRecord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logUserPrompt(config: Config, event: UserPromptEvent): void {
|
export function logUserPrompt(config: Config, event: UserPromptEvent): void {
|
||||||
ClearcutLogger.getInstance(config)?.logNewPromptEvent(event);
|
// ClearcutLogger.getInstance(config)?.logNewPromptEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -93,12 +111,11 @@ export function logUserPrompt(config: Config, event: UserPromptEvent): void {
|
|||||||
attributes.prompt = event.prompt;
|
attributes.prompt = event.prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
'user.prompt',
|
||||||
body: `User prompt. Length: ${event.prompt_length}.`,
|
`User prompt. Length: ${event.prompt_length}.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logToolCall(config: Config, event: ToolCallEvent): void {
|
export function logToolCall(config: Config, event: ToolCallEvent): void {
|
||||||
@@ -108,7 +125,7 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
|||||||
'event.timestamp': new Date().toISOString(),
|
'event.timestamp': new Date().toISOString(),
|
||||||
} as UiEvent;
|
} as UiEvent;
|
||||||
uiTelemetryService.addEvent(uiEvent);
|
uiTelemetryService.addEvent(uiEvent);
|
||||||
ClearcutLogger.getInstance(config)?.logToolCallEvent(event);
|
// ClearcutLogger.getInstance(config)?.logToolCallEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -125,12 +142,11 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
`tool.${event.function_name}`,
|
||||||
body: `Tool call: ${event.function_name}${event.decision ? `. Decision: ${event.decision}` : ''}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`,
|
`Tool call: ${event.function_name}${event.decision ? `. Decision: ${event.decision}` : ''}. Success: ${event.success}. Duration: ${event.duration_ms}ms.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
recordToolCallMetrics(
|
recordToolCallMetrics(
|
||||||
config,
|
config,
|
||||||
event.function_name,
|
event.function_name,
|
||||||
@@ -141,7 +157,7 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logApiRequest(config: Config, event: ApiRequestEvent): void {
|
export function logApiRequest(config: Config, event: ApiRequestEvent): void {
|
||||||
ClearcutLogger.getInstance(config)?.logApiRequestEvent(event);
|
// ClearcutLogger.getInstance(config)?.logApiRequestEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -151,19 +167,18 @@ export function logApiRequest(config: Config, event: ApiRequestEvent): void {
|
|||||||
'event.timestamp': new Date().toISOString(),
|
'event.timestamp': new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
`api.request.${event.model}`,
|
||||||
body: `API request to ${event.model}.`,
|
`API request to ${event.model}.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logFlashFallback(
|
export function logFlashFallback(
|
||||||
config: Config,
|
config: Config,
|
||||||
event: FlashFallbackEvent,
|
event: FlashFallbackEvent,
|
||||||
): void {
|
): void {
|
||||||
ClearcutLogger.getInstance(config)?.logFlashFallbackEvent(event);
|
// ClearcutLogger.getInstance(config)?.logFlashFallbackEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -173,12 +188,11 @@ export function logFlashFallback(
|
|||||||
'event.timestamp': new Date().toISOString(),
|
'event.timestamp': new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
'api.flash_fallback',
|
||||||
body: `Switching to flash as Fallback.`,
|
'Switching to flash as Fallback.',
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logApiError(config: Config, event: ApiErrorEvent): void {
|
export function logApiError(config: Config, event: ApiErrorEvent): void {
|
||||||
@@ -188,7 +202,7 @@ export function logApiError(config: Config, event: ApiErrorEvent): void {
|
|||||||
'event.timestamp': new Date().toISOString(),
|
'event.timestamp': new Date().toISOString(),
|
||||||
} as UiEvent;
|
} as UiEvent;
|
||||||
uiTelemetryService.addEvent(uiEvent);
|
uiTelemetryService.addEvent(uiEvent);
|
||||||
ClearcutLogger.getInstance(config)?.logApiErrorEvent(event);
|
// ClearcutLogger.getInstance(config)?.logApiErrorEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -208,12 +222,11 @@ export function logApiError(config: Config, event: ApiErrorEvent): void {
|
|||||||
attributes[SemanticAttributes.HTTP_STATUS_CODE] = event.status_code;
|
attributes[SemanticAttributes.HTTP_STATUS_CODE] = event.status_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
`api.error.${event.model}`,
|
||||||
body: `API error for ${event.model}. Error: ${event.error}. Duration: ${event.duration_ms}ms.`,
|
`API error for ${event.model}. Error: ${event.error}. Duration: ${event.duration_ms}ms.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
recordApiErrorMetrics(
|
recordApiErrorMetrics(
|
||||||
config,
|
config,
|
||||||
event.model,
|
event.model,
|
||||||
@@ -230,7 +243,7 @@ export function logApiResponse(config: Config, event: ApiResponseEvent): void {
|
|||||||
'event.timestamp': new Date().toISOString(),
|
'event.timestamp': new Date().toISOString(),
|
||||||
} as UiEvent;
|
} as UiEvent;
|
||||||
uiTelemetryService.addEvent(uiEvent);
|
uiTelemetryService.addEvent(uiEvent);
|
||||||
ClearcutLogger.getInstance(config)?.logApiResponseEvent(event);
|
// ClearcutLogger.getInstance(config)?.logApiResponseEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
...getCommonAttributes(config),
|
...getCommonAttributes(config),
|
||||||
@@ -249,12 +262,11 @@ export function logApiResponse(config: Config, event: ApiResponseEvent): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
`api.response.${event.model}`,
|
||||||
body: `API response from ${event.model}. Status: ${event.status_code || 'N/A'}. Duration: ${event.duration_ms}ms.`,
|
`API response from ${event.model}. Status: ${event.status_code || 'N/A'}. Duration: ${event.duration_ms}ms.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
recordApiResponseMetrics(
|
recordApiResponseMetrics(
|
||||||
config,
|
config,
|
||||||
event.model,
|
event.model,
|
||||||
@@ -293,7 +305,7 @@ export function logLoopDetected(
|
|||||||
config: Config,
|
config: Config,
|
||||||
event: LoopDetectedEvent,
|
event: LoopDetectedEvent,
|
||||||
): void {
|
): void {
|
||||||
ClearcutLogger.getInstance(config)?.logLoopDetectedEvent(event);
|
// ClearcutLogger.getInstance(config)?.logLoopDetectedEvent(event);
|
||||||
if (!isTelemetrySdkInitialized()) return;
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
const attributes: LogAttributes = {
|
const attributes: LogAttributes = {
|
||||||
@@ -301,10 +313,9 @@ export function logLoopDetected(
|
|||||||
...event,
|
...event,
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = logs.getLogger(SERVICE_NAME);
|
logWithSpan(
|
||||||
const logRecord: LogRecord = {
|
'loop.detected',
|
||||||
body: `Loop detected. Type: ${event.loop_type}.`,
|
`Loop detected. Type: ${event.loop_type}.`,
|
||||||
attributes,
|
attributes,
|
||||||
};
|
);
|
||||||
logger.emit(logRecord);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,29 +6,23 @@
|
|||||||
|
|
||||||
import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
|
import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
|
||||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
|
||||||
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
|
|
||||||
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
|
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
|
||||||
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
|
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
|
||||||
|
import { Metadata } from '@grpc/grpc-js';
|
||||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||||
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
|
||||||
import { Resource } from '@opentelemetry/resources';
|
import { Resource } from '@opentelemetry/resources';
|
||||||
import {
|
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
||||||
BatchSpanProcessor,
|
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
|
||||||
ConsoleSpanExporter,
|
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
||||||
} from '@opentelemetry/sdk-trace-node';
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
||||||
import {
|
import type { LogRecord } from '@opentelemetry/sdk-logs';
|
||||||
BatchLogRecordProcessor,
|
import type { ResourceMetrics } from '@opentelemetry/sdk-metrics';
|
||||||
ConsoleLogRecordExporter,
|
import type { ExportResult } from '@opentelemetry/core';
|
||||||
} from '@opentelemetry/sdk-logs';
|
|
||||||
import {
|
|
||||||
ConsoleMetricExporter,
|
|
||||||
PeriodicExportingMetricReader,
|
|
||||||
} from '@opentelemetry/sdk-metrics';
|
|
||||||
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
||||||
import { Config } from '../config/config.js';
|
import { Config } from '../config/config.js';
|
||||||
import { SERVICE_NAME } from './constants.js';
|
import { SERVICE_NAME } from './constants.js';
|
||||||
import { initializeMetrics } from './metrics.js';
|
import { initializeMetrics } from './metrics.js';
|
||||||
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
|
||||||
|
|
||||||
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
|
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
|
||||||
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
|
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
|
||||||
@@ -75,28 +69,60 @@ export function initializeTelemetry(config: Config): void {
|
|||||||
const grpcParsedEndpoint = parseGrpcEndpoint(otlpEndpoint);
|
const grpcParsedEndpoint = parseGrpcEndpoint(otlpEndpoint);
|
||||||
const useOtlp = !!grpcParsedEndpoint;
|
const useOtlp = !!grpcParsedEndpoint;
|
||||||
|
|
||||||
|
const metadata = new Metadata();
|
||||||
|
metadata.set(
|
||||||
|
'Authentication',
|
||||||
|
'gb4w8c3ygj@0c2aed5f1449f6f_gb4w8c3ygj@53df7ad2afe8301',
|
||||||
|
);
|
||||||
|
|
||||||
const spanExporter = useOtlp
|
const spanExporter = useOtlp
|
||||||
? new OTLPTraceExporter({
|
? new OTLPTraceExporter({
|
||||||
url: grpcParsedEndpoint,
|
url: grpcParsedEndpoint,
|
||||||
compression: CompressionAlgorithm.GZIP,
|
compression: CompressionAlgorithm.GZIP,
|
||||||
|
metadata,
|
||||||
})
|
})
|
||||||
: new ConsoleSpanExporter();
|
: {
|
||||||
const logExporter = useOtlp
|
export: (
|
||||||
? new OTLPLogExporter({
|
spans: ReadableSpan[],
|
||||||
url: grpcParsedEndpoint,
|
callback: (result: ExportResult) => void,
|
||||||
compression: CompressionAlgorithm.GZIP,
|
) => callback({ code: 0 }),
|
||||||
})
|
forceFlush: () => Promise.resolve(),
|
||||||
: new ConsoleLogRecordExporter();
|
shutdown: () => Promise.resolve(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: Temporarily disable OTLP log export due to gRPC endpoint not supporting LogsService
|
||||||
|
// const logExporter = useOtlp
|
||||||
|
// ? new OTLPLogExporter({
|
||||||
|
// url: grpcParsedEndpoint,
|
||||||
|
// compression: CompressionAlgorithm.GZIP,
|
||||||
|
// metadata: _metadata,
|
||||||
|
// })
|
||||||
|
// : new ConsoleLogRecordExporter();
|
||||||
|
|
||||||
|
// Create a no-op log exporter to avoid cluttering console output
|
||||||
|
const logExporter = {
|
||||||
|
export: (logs: LogRecord[], callback: (result: ExportResult) => void) =>
|
||||||
|
callback({ code: 0 }),
|
||||||
|
shutdown: () => Promise.resolve(),
|
||||||
|
};
|
||||||
const metricReader = useOtlp
|
const metricReader = useOtlp
|
||||||
? new PeriodicExportingMetricReader({
|
? new PeriodicExportingMetricReader({
|
||||||
exporter: new OTLPMetricExporter({
|
exporter: new OTLPMetricExporter({
|
||||||
url: grpcParsedEndpoint,
|
url: grpcParsedEndpoint,
|
||||||
compression: CompressionAlgorithm.GZIP,
|
compression: CompressionAlgorithm.GZIP,
|
||||||
|
metadata,
|
||||||
}),
|
}),
|
||||||
exportIntervalMillis: 10000,
|
exportIntervalMillis: 10000,
|
||||||
})
|
})
|
||||||
: new PeriodicExportingMetricReader({
|
: new PeriodicExportingMetricReader({
|
||||||
exporter: new ConsoleMetricExporter(),
|
exporter: {
|
||||||
|
export: (
|
||||||
|
metrics: ResourceMetrics,
|
||||||
|
callback: (result: ExportResult) => void,
|
||||||
|
) => callback({ code: 0 }),
|
||||||
|
forceFlush: () => Promise.resolve(),
|
||||||
|
shutdown: () => Promise.resolve(),
|
||||||
|
},
|
||||||
exportIntervalMillis: 10000,
|
exportIntervalMillis: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -126,7 +152,7 @@ export async function shutdownTelemetry(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ClearcutLogger.getInstance()?.shutdown();
|
// ClearcutLogger.getInstance()?.shutdown();
|
||||||
await sdk.shutdown();
|
await sdk.shutdown();
|
||||||
console.log('OpenTelemetry SDK shut down successfully.');
|
console.log('OpenTelemetry SDK shut down successfully.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user