fix(telemetry): implement bounded structural truncation and decouple traces

This commit is contained in:
Spencer
2026-04-10 18:22:10 +00:00
parent 82e8d67a78
commit c6d3849d0c
25 changed files with 244 additions and 95 deletions
@@ -98,6 +98,7 @@ export function createMockConfig(
getMcpServers: vi.fn().mockReturnValue({}),
}),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
getGitService: vi.fn(),
validatePathAccess: vi.fn().mockReturnValue(undefined),
getShellExecutionConfig: vi.fn().mockReturnValue({
@@ -2966,6 +2966,11 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
description: 'Protocol for OTLP exporters.',
enum: ['grpc', 'http'],
},
traces: {
type: 'boolean',
description:
'Whether detailed traces with large attributes are captured.',
},
logPrompts: {
type: 'boolean',
description: 'Whether prompts are logged in telemetry payloads.',
+2
View File
@@ -241,6 +241,7 @@ describe('gemini.tsx main function cleanup', () => {
getCoreTools: vi.fn(() => []),
getTelemetryEnabled: vi.fn(() => false),
getTelemetryLogPromptsEnabled: vi.fn(() => false),
getTelemetryTracesEnabled: () => false,
getFileFilteringRespectGitIgnore: vi.fn(() => true),
getOutputFormat: vi.fn(() => 'text'),
getUsageStatisticsEnabled: vi.fn(() => false),
@@ -325,6 +326,7 @@ describe('gemini.tsx main function cleanup', () => {
getCoreTools: vi.fn(() => []),
getTelemetryEnabled: vi.fn(() => false),
getTelemetryLogPromptsEnabled: vi.fn(() => false),
getTelemetryTracesEnabled: () => false,
getFileFilteringRespectGitIgnore: vi.fn(() => true),
getOutputFormat: vi.fn(() => 'text'),
getUsageStatisticsEnabled: vi.fn(() => false),
@@ -87,6 +87,7 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
getAccessibility: vi.fn().mockReturnValue({}),
getTelemetryEnabled: vi.fn().mockReturnValue(false),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
getTelemetryOtlpEndpoint: vi.fn().mockReturnValue(''),
getTelemetryOtlpProtocol: vi.fn().mockReturnValue('grpc'),
getTelemetryTarget: vi.fn().mockReturnValue(''),
@@ -308,6 +308,8 @@ describe('useGeminiStream', () => {
sandbox: false,
targetDir: '/test/dir',
debugMode: false,
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: vi.fn().mockReturnValue(false),
question: undefined,
coreTools: [],
toolDiscoveryCommand: undefined,
@@ -1559,6 +1559,8 @@ export const useGeminiStream = (
operation: options?.isContinuation
? GeminiCliOperation.SystemPrompt
: GeminiCliOperation.UserPrompt,
logPrompts: config.getTelemetryLogPromptsEnabled(),
tracesEnabled: config.getTelemetryTracesEnabled(),
sessionId: config.getSessionId(),
},
async ({ metadata: spanMetadata }) => {
+1
View File
@@ -194,6 +194,7 @@ class DelegateInvocation extends BaseToolInvocation<
{
operation: GeminiCliOperation.AgentCall,
logPrompts: this.context.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.context.config.getTelemetryTracesEnabled(),
sessionId: this.context.config.getSessionId(),
attributes: {
[GEN_AI_AGENT_NAME]: this.definition.name,
+6
View File
@@ -202,6 +202,7 @@ export interface PlanSettings {
export interface TelemetrySettings {
enabled?: boolean;
traces?: boolean;
target?: TelemetryTarget;
otlpEndpoint?: string;
otlpProtocol?: 'grpc' | 'http';
@@ -1050,6 +1051,7 @@ export class Config implements McpContext, AgentLoopContext {
this.accessibility = params.accessibility ?? {};
this.telemetrySettings = {
enabled: params.telemetry?.enabled ?? false,
traces: params.telemetry?.traces ?? false,
target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
otlpProtocol: params.telemetry?.otlpProtocol,
@@ -2660,6 +2662,10 @@ export class Config implements McpContext, AgentLoopContext {
return this.telemetrySettings.enabled ?? false;
}
getTelemetryTracesEnabled(): boolean {
return this.telemetrySettings.traces ?? false;
}
getTelemetryLogPromptsEnabled(): boolean {
return this.telemetrySettings.logPrompts ?? true;
}
@@ -153,6 +153,7 @@ describe('GeminiChat', () => {
promptId: 'test-session-id',
getSessionId: () => 'test-session-id',
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
getUsageStatisticsEnabled: () => true,
getDebugMode: () => false,
getContentGeneratorConfig: vi.fn().mockImplementation(() => ({
@@ -96,6 +96,7 @@ describe('GeminiChat Network Retries', () => {
promptId: 'test-session-id',
getSessionId: () => 'test-session-id',
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
getUsageStatisticsEnabled: () => true,
getDebugMode: () => false,
getContentGeneratorConfig: vi.fn().mockReturnValue({
@@ -73,6 +73,7 @@ describe('LoggingContentGenerator', () => {
authType: 'API_KEY',
}),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(true),
getTelemetryTracesEnabled: () => false,
refreshUserQuotaIfStale: vi.fn().mockResolvedValue(undefined),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
} as unknown as Config;
@@ -361,6 +361,7 @@ export class LoggingContentGenerator implements ContentGenerator {
{
operation: GeminiCliOperation.LLMCall,
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.config.getTelemetryTracesEnabled(),
sessionId: this.config.getSessionId(),
attributes: {
[GEN_AI_REQUEST_MODEL]: req.model,
@@ -452,6 +453,7 @@ export class LoggingContentGenerator implements ContentGenerator {
{
operation: GeminiCliOperation.LLMCall,
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.config.getTelemetryTracesEnabled(),
sessionId: this.config.getSessionId(),
attributes: {
[GEN_AI_REQUEST_MODEL]: req.model,
@@ -607,6 +609,7 @@ export class LoggingContentGenerator implements ContentGenerator {
{
operation: GeminiCliOperation.LLMCall,
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.config.getTelemetryTracesEnabled(),
sessionId: this.config.getSessionId(),
attributes: {
[GEN_AI_REQUEST_MODEL]: req.model,
@@ -858,6 +858,7 @@ describe('Plan Mode Denial Consistency', () => {
getEnableHooks: vi.fn().mockReturnValue(false),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.PLAN), // Key: Plan Mode
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: () => false,
setApprovalMode: vi.fn(),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
@@ -178,6 +178,7 @@ describe('Scheduler (Orchestrator)', () => {
setApprovalMode: vi.fn(),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: () => false,
getSessionId: vi.fn().mockReturnValue('test-session-id'),
} as unknown as Mocked<Config>;
@@ -1517,6 +1518,7 @@ describe('Scheduler MCP Progress', () => {
setApprovalMode: vi.fn(),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: () => false,
getSessionId: vi.fn().mockReturnValue('test-session-id'),
} as unknown as Mocked<Config>;
+1
View File
@@ -196,6 +196,7 @@ export class Scheduler {
{
operation: GeminiCliOperation.ScheduleToolCalls,
logPrompts: this.context.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.context.config.getTelemetryTracesEnabled(),
sessionId: this.context.config.getSessionId(),
},
async ({ metadata: spanMetadata }) => {
@@ -71,6 +71,7 @@ function createMockConfig(overrides: Partial<Config> = {}): Config {
getEnableHooks: () => true,
getExperiments: () => {},
getTelemetryLogPromptsEnabled: () => false,
getTelemetryTracesEnabled: () => false,
getPolicyEngine: () =>
({
check: async () => ({ decision: 'allow' }),
@@ -218,6 +218,7 @@ describe('Scheduler Parallel Execution', () => {
setApprovalMode: vi.fn(),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(false),
getTelemetryTracesEnabled: () => false,
getSessionId: vi.fn().mockReturnValue('test-session-id'),
} as unknown as Mocked<Config>;
@@ -84,6 +84,7 @@ export class ToolExecutor {
{
operation: GeminiCliOperation.ToolCall,
logPrompts: this.config.getTelemetryLogPromptsEnabled(),
tracesEnabled: this.config.getTelemetryTracesEnabled(),
sessionId: this.config.getSessionId(),
attributes: {
[GEN_AI_TOOL_NAME]: toolName,
+5
View File
@@ -60,6 +60,10 @@ export async function resolveTelemetrySettings(options: {
parseBooleanEnvFlag(env['GEMINI_TELEMETRY_ENABLED']) ??
settings.enabled;
const traces =
parseBooleanEnvFlag(env['GEMINI_TELEMETRY_TRACES_ENABLED']) ??
settings.traces;
const rawTarget =
argv.telemetryTarget ??
env['GEMINI_TELEMETRY_TARGET'] ??
@@ -110,6 +114,7 @@ export async function resolveTelemetrySettings(options: {
return {
enabled,
traces,
target,
otlpEndpoint,
otlpProtocol,
@@ -37,6 +37,7 @@ describe('conseca-logger', () => {
getTelemetryEnabled: vi.fn().mockReturnValue(true),
getSessionId: vi.fn().mockReturnValue('test-session-id'),
getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(true),
getTelemetryTracesEnabled: () => false,
isInteractive: vi.fn().mockReturnValue(true),
getExperiments: vi.fn().mockReturnValue({ experimentIds: [] }),
getContentGeneratorConfig: vi.fn().mockReturnValue({ authType: 'oauth' }),
@@ -216,6 +216,7 @@ describe('loggers', () => {
getTelemetryEnabled: () => true,
getUsageStatisticsEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getFileFilteringAllowBuildArtifacts: () => false,
getDebugMode: () => true,
@@ -313,6 +314,7 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
getExperiments: () => undefined,
@@ -352,6 +354,7 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => false,
getTelemetryTracesEnabled: () => false,
getTargetDir: () => 'target-dir',
getUsageStatisticsEnabled: () => true,
isInteractive: () => false,
@@ -392,6 +395,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -596,6 +600,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -756,6 +761,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -834,6 +840,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true, // Enabled
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -923,6 +930,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => false, // Disabled
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -978,6 +986,7 @@ describe('loggers', () => {
getSessionId: () => 'test-session-id',
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -1140,6 +1149,7 @@ describe('loggers', () => {
getCoreTools: () => ['ls', 'read-file'],
getApprovalMode: () => 'default',
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getFileFilteringAllowBuildArtifacts: () => false,
getDebugMode: () => true,
@@ -1170,6 +1180,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -1829,6 +1840,7 @@ describe('loggers', () => {
getUsageStatisticsEnabled: () => true,
getTelemetryEnabled: () => true,
getTelemetryLogPromptsEnabled: () => true,
getTelemetryTracesEnabled: () => false,
isInteractive: () => false,
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
@@ -2423,6 +2435,7 @@ describe('loggers', () => {
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
getTelemetryLogPromptsEnabled: () => false,
getTelemetryTracesEnabled: () => false,
getContentGeneratorConfig: () => undefined,
} as unknown as Config;
@@ -26,6 +26,7 @@ import type { Config } from '../config/config.js';
function createMockConfig(logPromptsEnabled: boolean): Config {
return {
getTelemetryLogPromptsEnabled: () => logPromptsEnabled,
getTelemetryTracesEnabled: () => false,
getSessionId: () => 'test-session-id',
getExperiments: () => undefined,
getExperimentsAsync: async () => undefined,
+19 -4
View File
@@ -62,13 +62,28 @@ describe('truncateForTelemetry', () => {
expect(result).toBe('👋🌍...[TRUNCATED: original length 10]');
});
it('should stringify and truncate objects if exceeding maxLength', () => {
it('should stringify and structurally truncate objects if exceeding limits', () => {
const obj = { message: 'hello world', nested: { a: 1 } };
const stringified = JSON.stringify(obj);
const result = truncateForTelemetry(obj, 10);
expect(result).toBe(
stringified.substring(0, 10) +
`...[TRUNCATED: original length ${stringified.length}]`,
JSON.stringify({
message: 'hello worl...[TRUNCATED: original length 11]',
nested: { a: 1 },
}),
);
});
it('should structurally truncate arrays and depth', () => {
const obj = {
arr: [1, 2, 3],
deep: { level1: { level2: { level3: { a: 1 } } } },
};
const result = truncateForTelemetry(obj, 100, 2, 3);
expect(result).toBe(
JSON.stringify({
arr: [1, 2, '[TRUNCATED: Array of length 3]'],
deep: { level1: { level2: '[TRUNCATED: Max Depth Reached]' } },
}),
);
});
+168 -91
View File
@@ -14,7 +14,6 @@ import {
import { debugLogger } from '../utils/debugLogger.js';
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
import { truncateString } from '../utils/textUtils.js';
import {
GEN_AI_AGENT_DESCRIPTION,
GEN_AI_AGENT_NAME,
@@ -54,27 +53,78 @@ export const spanRegistry = new FinalizationRegistry((endSpan: () => void) => {
*/
export function truncateForTelemetry(
value: unknown,
maxLength = 10000,
maxStringLength = 10000,
maxArrayLength = 100,
maxDepth = 4,
): AttributeValue | undefined {
if (typeof value === 'string') {
return truncateString(
value,
maxLength,
`...[TRUNCATED: original length ${value.length}]`,
) as AttributeValue;
const truncateObj = (v: unknown, depth: number): unknown => {
if (typeof v === 'string') {
const graphemes = Array.from(v);
if (graphemes.length > maxStringLength) {
return (
graphemes.slice(0, maxStringLength).join('') +
`...[TRUNCATED: original length ${graphemes.length}]`
);
}
return v;
}
if (
typeof v === 'number' ||
typeof v === 'boolean' ||
v === null ||
v === undefined
) {
return v;
}
if (typeof v === 'object') {
if (depth >= maxDepth) {
return `[TRUNCATED: Max Depth Reached]`;
}
if (Array.isArray(v)) {
if (v.length > maxArrayLength) {
const truncatedArray = v
.slice(0, maxArrayLength)
.map((item) => truncateObj(item, depth + 1));
truncatedArray.push(`[TRUNCATED: Array of length ${v.length}]`);
return truncatedArray;
}
return v.map((item) => truncateObj(item, depth + 1));
}
const newObj: Record<string, unknown> = {};
let numKeys = 0;
const MAX_KEYS = 100;
for (const key in v) {
if (!Object.prototype.hasOwnProperty.call(v, key)) continue;
if (numKeys >= MAX_KEYS) {
newObj['__truncated'] = `[TRUNCATED: Object with >${MAX_KEYS} keys]`;
break;
}
const descriptor = Object.getOwnPropertyDescriptor(v, key);
if (descriptor) {
newObj[key] = truncateObj(descriptor.value, depth + 1);
}
numKeys++;
}
return newObj;
}
return undefined;
};
const truncated = truncateObj(value, 0);
if (
typeof truncated === 'string' ||
typeof truncated === 'number' ||
typeof truncated === 'boolean'
) {
return truncated as AttributeValue;
}
if (typeof value === 'object' && value !== null) {
const stringified = safeJsonStringify(value);
return truncateString(
stringified,
maxLength,
`...[TRUNCATED: original length ${stringified.length}]`,
) as AttributeValue;
if (truncated === null || truncated === undefined) {
return undefined;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return value as AttributeValue;
}
return undefined;
return safeJsonStringify(truncated) as AttributeValue;
}
function isAsyncIterable<T>(value: T): value is T & AsyncIterable<unknown> {
@@ -125,13 +175,14 @@ export async function runInDevTraceSpan<R>(
operation: GeminiCliOperation;
logPrompts?: boolean;
sessionId: string;
tracesEnabled?: boolean;
},
fn: ({ metadata }: { metadata: SpanMetadata }) => Promise<R>,
): Promise<R> {
const { operation, logPrompts, sessionId, ...restOfSpanOpts } = opts;
const { operation, logPrompts, sessionId, tracesEnabled, ...restOfSpanOpts } =
opts;
const tracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
return tracer.startActiveSpan(operation, restOfSpanOpts, async (span) => {
if (tracesEnabled === false) {
const meta: SpanMetadata = {
name: operation,
attributes: {
@@ -141,87 +192,113 @@ export async function runInDevTraceSpan<R>(
[GEN_AI_CONVERSATION_ID]: sessionId,
},
};
let spanEnded = false;
const endSpan = () => {
if (spanEnded) {
return;
}
spanEnded = true;
try {
if (logPrompts !== false) {
if (meta.input !== undefined) {
const truncated = truncateForTelemetry(meta.input);
if (truncated !== undefined) {
span.setAttribute(GEN_AI_INPUT_MESSAGES, truncated);
return fn({ metadata: meta });
}
const spanOptsWithSession: SpanOptions = {
...restOfSpanOpts,
attributes: {
...restOfSpanOpts.attributes,
[GEN_AI_CONVERSATION_ID]: sessionId,
},
};
const tracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
return tracer.startActiveSpan(
operation,
spanOptsWithSession,
async (span) => {
const meta: SpanMetadata = {
name: operation,
attributes: {
[GEN_AI_OPERATION_NAME]: operation,
[GEN_AI_AGENT_NAME]: SERVICE_NAME,
[GEN_AI_AGENT_DESCRIPTION]: SERVICE_DESCRIPTION,
[GEN_AI_CONVERSATION_ID]: sessionId,
},
};
let spanEnded = false;
const endSpan = () => {
if (spanEnded) {
return;
}
spanEnded = true;
try {
if (logPrompts !== false) {
if (meta.input !== undefined) {
const truncated = truncateForTelemetry(meta.input);
if (truncated !== undefined) {
span.setAttribute(GEN_AI_INPUT_MESSAGES, truncated);
}
}
if (meta.output !== undefined) {
const truncated = truncateForTelemetry(meta.output);
if (truncated !== undefined) {
span.setAttribute(GEN_AI_OUTPUT_MESSAGES, truncated);
}
}
}
if (meta.output !== undefined) {
const truncated = truncateForTelemetry(meta.output);
for (const [key, value] of Object.entries(meta.attributes)) {
const truncated = truncateForTelemetry(value);
if (truncated !== undefined) {
span.setAttribute(GEN_AI_OUTPUT_MESSAGES, truncated);
span.setAttribute(key, truncated);
}
}
}
for (const [key, value] of Object.entries(meta.attributes)) {
const truncated = truncateForTelemetry(value);
if (truncated !== undefined) {
span.setAttribute(key, truncated);
if (meta.error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: getErrorMessage(meta.error),
});
if (meta.error instanceof Error) {
span.recordException(meta.error);
}
} else {
span.setStatus({ code: SpanStatusCode.OK });
}
}
if (meta.error) {
} catch (e) {
// Log the error but don't rethrow, to ensure span.end() is called.
diag.error('Error setting span attributes in endSpan', e);
span.setStatus({
code: SpanStatusCode.ERROR,
message: getErrorMessage(meta.error),
message: `Error in endSpan: ${getErrorMessage(e)}`,
});
if (meta.error instanceof Error) {
span.recordException(meta.error);
}
} else {
span.setStatus({ code: SpanStatusCode.OK });
} finally {
span.end();
}
} catch (e) {
// Log the error but don't rethrow, to ensure span.end() is called.
diag.error('Error setting span attributes in endSpan', e);
span.setStatus({
code: SpanStatusCode.ERROR,
message: `Error in endSpan: ${getErrorMessage(e)}`,
});
};
let isStream = false;
try {
const result = await fn({ metadata: meta });
if (isAsyncIterable(result)) {
isStream = true;
const streamWrapper = (async function* () {
try {
yield* result;
} catch (e: unknown) {
meta.error = e;
throw e;
} finally {
endSpan();
}
})();
const finalResult = Object.assign(streamWrapper, result);
spanRegistry.register(finalResult, endSpan);
return finalResult;
}
return result;
} catch (e: unknown) {
meta.error = e;
throw e;
} finally {
span.end();
if (!isStream) {
endSpan();
}
}
};
let isStream = false;
try {
const result = await fn({ metadata: meta });
if (isAsyncIterable(result)) {
isStream = true;
const streamWrapper = (async function* () {
try {
yield* result;
} catch (e: unknown) {
meta.error = e;
throw e;
} finally {
endSpan();
}
})();
const finalResult = Object.assign(streamWrapper, result);
spanRegistry.register(finalResult, endSpan);
return finalResult;
}
return result;
} catch (e: unknown) {
meta.error = e;
throw e;
} finally {
if (!isStream) {
endSpan();
}
}
});
},
);
}
/**
+4
View File
@@ -3561,6 +3561,10 @@
"description": "Protocol for OTLP exporters.",
"enum": ["grpc", "http"]
},
"traces": {
"type": "boolean",
"description": "Whether detailed traces with large attributes are captured."
},
"logPrompts": {
"type": "boolean",
"description": "Whether prompts are logged in telemetry payloads."