diff --git a/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/wrangler.jsonc index 5ef4f1ff11f6..0b7b36047973 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/wrangler.jsonc +++ b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/wrangler.jsonc @@ -8,11 +8,10 @@ "SENTRY_DSN": "https://username@domain/123", "SENTRY_ENVIRONMENT": "qa", "SENTRY_TRACES_SAMPLE_RATE": "1.0", - "SENTRY_TUNNEL": "http://localhost:3031/" + "SENTRY_TUNNEL": "http://localhost:3031/", }, "assets": { "binding": "ASSETS", - "directory": "./dist" - } + "directory": "./dist", + }, } - diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json index aeca3156526c..34dd2a7757da 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json @@ -12,12 +12,12 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-http": "^0.211.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-node": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-http": "^0.212.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-node": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json index ca99076f8b7a..96fe46062bcc 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json @@ -12,15 +12,15 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-http": "^0.211.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-node": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-http": "^0.212.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-node": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", - "@opentelemetry/sdk-node": "^0.211.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.211.0", + "@opentelemetry/sdk-node": "^0.212.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.212.0", "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", "@types/express": "4.17.17", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json index 7da50bef037a..210ed53d2733 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json @@ -14,12 +14,12 @@ "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-http": "^0.211.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-node": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-http": "^0.212.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-node": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@types/express": "^4.17.21", "@types/node": "^18.19.1", diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json index 334a8eb6876a..fc60b4c85b90 100644 --- a/dev-packages/node-core-integration-tests/package.json +++ b/dev-packages/node-core-integration-tests/package.json @@ -27,12 +27,12 @@ "@nestjs/core": "^11", "@nestjs/platform-express": "^11", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/instrumentation-http": "0.211.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-http": "0.212.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/core": "10.39.0", "@sentry/node-core": "10.39.0", diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs new file mode 100644 index 000000000000..cef786b8988f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/agent-scenario.mjs @@ -0,0 +1,70 @@ +import { ChatAnthropic } from '@langchain/anthropic'; +import { HumanMessage, SystemMessage } from '@langchain/core/messages'; +import { createReactAgent } from '@langchain/langgraph/prebuilt'; +import * as Sentry from '@sentry/node'; +import express from 'express'; + +function startMockAnthropicServer() { + const app = express(); + app.use(express.json()); + + app.post('/v1/messages', (req, res) => { + const model = req.body.model; + + // Simulate basic response + res.json({ + id: 'msg_react_agent_123', + type: 'message', + role: 'assistant', + content: [ + { + type: 'text', + text: 'Mock response from Anthropic!', + }, + ], + model: model, + stop_reason: 'end_turn', + stop_sequence: null, + usage: { + input_tokens: 10, + output_tokens: 15, + }, + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); +} + +async function run() { + const server = await startMockAnthropicServer(); + const baseUrl = `http://localhost:${server.address().port}`; + + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + // Create mocked LLM instance + const llm = new ChatAnthropic({ + model: 'claude-3-5-sonnet-20241022', + apiKey: 'mock-api-key', + clientOptions: { + baseURL: baseUrl, + }, + }); + + // Create a simple react agent with no tools + const agent = createReactAgent({ llm, tools: [] }); + + // Test: basic invocation + await agent.invoke({ + messages: [new SystemMessage('You are a helpful assistant.'), new HumanMessage('What is the weather today?')], + }); + }); + + await Sentry.flush(2000); + + server.close(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs index be512ed2f773..cb68a6f7683e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument-with-pii.mjs @@ -7,4 +7,11 @@ Sentry.init({ tracesSampleRate: 1.0, sendDefaultPii: true, transport: loggingTransport, + beforeSendTransaction: event => { + // Filter out mock express server transactions + if (event.transaction.includes('/v1/messages')) { + return null; + } + return event; + }, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs index 06cc1a32e93e..b4ce44f3e91a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/instrument.mjs @@ -7,4 +7,11 @@ Sentry.init({ tracesSampleRate: 1.0, sendDefaultPii: false, transport: loggingTransport, + beforeSendTransaction: event => { + // Filter out mock express server transactions + if (event.transaction.includes('/v1/messages')) { + return null; + } + return event; + }, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 5905d592ee7a..45b40fc0340a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -223,6 +223,46 @@ describe('LangGraph integration', () => { }); }); + const EXPECTED_TRANSACTION_REACT_AGENT = { + transaction: 'main', + spans: expect.arrayContaining([ + // create_agent span + expect.objectContaining({ + data: expect.objectContaining({ + [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.langgraph', + }), + description: expect.stringContaining('create_agent'), + op: 'gen_ai.create_agent', + origin: 'auto.ai.langgraph', + status: 'ok', + }), + // invoke_agent span + expect.objectContaining({ + data: expect.objectContaining({ + [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'invoke_agent', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.invoke_agent', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ai.langgraph', + }), + description: expect.stringContaining('invoke_agent'), + op: 'gen_ai.invoke_agent', + origin: 'auto.ai.langgraph', + status: 'ok', + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'agent-scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('should instrument LangGraph createReactAgent with default PII settings', async () => { + await createRunner() + .ignore('event') + .expect({ transaction: EXPECTED_TRANSACTION_REACT_AGENT }) + .start() + .completed(); + }); + }); + // Test for thread_id (conversation ID) support const EXPECTED_TRANSACTION_THREAD_ID = { transaction: 'langgraph-thread-id-test', diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 453aa291510e..e0f1e8de720f 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -66,7 +66,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/instrumentation-aws-sdk": "0.66.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/core": "10.39.0", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d76917bf0cdd..b052ac9dbd13 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -157,7 +157,7 @@ export type { GoogleGenAIResponse } from './tracing/google-genai/types'; export { createLangChainCallbackHandler } from './tracing/langchain'; export { LANGCHAIN_INTEGRATION_NAME } from './tracing/langchain/constants'; export type { LangChainOptions, LangChainIntegration } from './tracing/langchain/types'; -export { instrumentStateGraphCompile, instrumentLangGraph } from './tracing/langgraph'; +export { instrumentStateGraphCompile, instrumentCreateReactAgent, instrumentLangGraph } from './tracing/langgraph'; export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants'; export type { LangGraphOptions, LangGraphIntegration, CompiledGraph } from './tracing/langgraph/types'; export type { OpenAiClient, OpenAiOptions, InstrumentedMethod } from './tracing/openai/types'; diff --git a/packages/core/src/tracing/langchain/types.ts b/packages/core/src/tracing/langchain/types.ts index 7379de764817..e2aa08f4631c 100644 --- a/packages/core/src/tracing/langchain/types.ts +++ b/packages/core/src/tracing/langchain/types.ts @@ -30,6 +30,14 @@ export interface LangChainSerialized { kwargs?: Record; } +/** + * Subset of the 'llm' param passed to createReactAgent + */ +export interface BaseChatModel { + lc_namespace: string[]; + modelName: string; +} + /** * LangChain message structure * Supports both regular messages and LangChain serialized format diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index 6a9c39a7ddda..dbf172429cd1 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -10,16 +10,17 @@ import { GEN_AI_OPERATION_NAME_ATTRIBUTE, GEN_AI_PIPELINE_NAME_ATTRIBUTE, GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE, + GEN_AI_REQUEST_MODEL_ATTRIBUTE, GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; import { truncateGenAiMessages } from '../ai/messageTruncation'; import { extractSystemInstructions } from '../ai/utils'; -import type { LangChainMessage } from '../langchain/types'; +import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; import { startSpan } from '../trace'; import { LANGGRAPH_ORIGIN } from './constants'; import type { CompiledGraph, LangGraphOptions } from './types'; -import { extractToolsFromCompiledGraph, setResponseAttributes } from './utils'; +import { extractLLMFromParams, extractToolsFromCompiledGraph, setResponseAttributes } from './utils'; /** * Instruments StateGraph's compile method to create spans for agent creation and invocation @@ -94,9 +95,11 @@ function instrumentCompiledGraphInvoke( graphInstance: CompiledGraph, compileOptions: Record, options: LangGraphOptions, + llm?: BaseChatModel | null, ): (...args: unknown[]) => Promise { return new Proxy(originalInvoke, { apply(target, thisArg, args: unknown[]): Promise { + const modelName = llm?.modelName; return startSpan( { op: 'gen_ai.invoke_agent', @@ -116,6 +119,9 @@ function instrumentCompiledGraphInvoke( span.setAttribute(GEN_AI_AGENT_NAME_ATTRIBUTE, graphName); span.updateName(`invoke_agent ${graphName}`); } + if (modelName) { + span.setAttribute(GEN_AI_REQUEST_MODEL_ATTRIBUTE, modelName); + } // Extract thread_id from the config (second argument) // LangGraph uses config.configurable.thread_id for conversation/session linking @@ -179,6 +185,60 @@ function instrumentCompiledGraphInvoke( }) as (...args: unknown[]) => Promise; } +/** + * Instruments createReactAgent to create spans for agent creation and invocation + * + * Creates a `gen_ai.create_agent` span when createReactAgent() is called + */ +export function instrumentCreateReactAgent( + originalCreateReactAgent: (...args: unknown[]) => CompiledGraph, + options: LangGraphOptions, +): (...args: unknown[]) => CompiledGraph { + return new Proxy(originalCreateReactAgent, { + apply(target, thisArg, args: unknown[]): CompiledGraph { + const llm = extractLLMFromParams(args); + return startSpan( + { + op: 'gen_ai.create_agent', + name: 'create_agent', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent', + [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'create_agent', + }, + }, + span => { + try { + const compiledGraph = Reflect.apply(target, thisArg, args); + const compiledOptions = args.length > 0 ? (args[0] as Record) : {}; + const originalInvoke = compiledGraph.invoke; + if (originalInvoke && typeof originalInvoke === 'function') { + compiledGraph.invoke = instrumentCompiledGraphInvoke( + originalInvoke.bind(compiledGraph) as (...args: unknown[]) => Promise, + compiledGraph, + compiledOptions, + options, + llm, + ) as typeof originalInvoke; + } + + return compiledGraph; + } catch (error) { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(error, { + mechanism: { + handled: false, + type: 'auto.ai.langgraph.error', + }, + }); + throw error; + } + }, + ); + }, + }) as (...args: unknown[]) => CompiledGraph; +} + /** * Directly instruments a StateGraph instance to add tracing spans * diff --git a/packages/core/src/tracing/langgraph/utils.ts b/packages/core/src/tracing/langgraph/utils.ts index 4b1990058924..f2c64052c071 100644 --- a/packages/core/src/tracing/langgraph/utils.ts +++ b/packages/core/src/tracing/langgraph/utils.ts @@ -8,10 +8,25 @@ import { GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE, } from '../ai/gen-ai-attributes'; -import type { LangChainMessage } from '../langchain/types'; +import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; import type { CompiledGraph, LangGraphTool } from './types'; +/** + * Extract LLM model object from createReactAgent params + */ +export function extractLLMFromParams(args: unknown[]): null | BaseChatModel { + const arg = args[0]; + return typeof arg === 'object' && + !!arg && + 'llm' in arg && + !!arg.llm && + typeof arg.llm === 'object' && + typeof (arg.llm as BaseChatModel).modelName === 'string' + ? (arg.llm as BaseChatModel) + : null; +} + /** * Extract tool calls from messages */ @@ -139,7 +154,9 @@ export function setResponseAttributes(span: Span, inputMessages: LangChainMessag } // Get new messages (delta between input and output) + /* v8 ignore start - coverage gets confused by this somehow */ const inputCount = inputMessages?.length ?? 0; + /* v8 ignore stop */ const newMessages = outputMessages.length > inputCount ? outputMessages.slice(inputCount) : []; if (newMessages.length === 0) { diff --git a/packages/core/test/lib/utils/langraph-utils.test.ts b/packages/core/test/lib/utils/langraph-utils.test.ts new file mode 100644 index 000000000000..bd8d5c7193ec --- /dev/null +++ b/packages/core/test/lib/utils/langraph-utils.test.ts @@ -0,0 +1,255 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type { Span } from '../../../src'; +import { + extractLLMFromParams, + extractModelMetadata, + extractTokenUsageFromMessage, + extractToolCalls, + extractToolsFromCompiledGraph, + setResponseAttributes, +} from '../../../src/tracing/langgraph/utils'; + +describe('extractLLMFromParams', () => { + it('handles invalid args or missing llm object', () => { + // @ts-expect-error should be arguments array, at least. + expect(extractLLMFromParams({})).toBe(null); + expect(extractLLMFromParams([])).toBe(null); + expect(extractLLMFromParams([null])).toBe(null); + expect(extractLLMFromParams([{}])).toBe(null); + expect(extractLLMFromParams([{ llm: false }])).toBe(null); + expect(extractLLMFromParams([{ llm: 123 }])).toBe(null); + expect(extractLLMFromParams([{ llm: {} }])).toBe(null); + }); + it('extracts llm object if found', () => { + expect(extractLLMFromParams([{ llm: { modelName: 'model-name-1' } }])).toStrictEqual({ modelName: 'model-name-1' }); + }); +}); + +describe('extractToolCalls', () => { + it('returns null for missing/empty messages', () => { + expect(extractToolCalls(null)).toBe(null); + expect(extractToolCalls([])).toBe(null); + expect(extractToolCalls([{}])).toBe(null); + expect(extractToolCalls([{ tool_calls: null }])).toBe(null); + expect(extractToolCalls([{ tool_calls: [] }])).toBe(null); + }); + it('extracts tool call from messages array', () => { + expect( + extractToolCalls([ + { tool_calls: [{ name: 'tool a' }] }, + { tool_calls: [{ name: 'tool b' }, { name: 'tool c' }] }, + ]), + ).toStrictEqual([{ name: 'tool a' }, { name: 'tool b' }, { name: 'tool c' }]); + }); +}); + +describe('extractTokenUsageFromMessage', () => { + it('extracts from usage_metadata', () => { + const inputs = [{}, { input_tokens: 10 }]; + const outputs = [{}, { output_tokens: 20 }]; + const totals = [{}, { total_tokens: 30 }]; + for (const i of inputs) { + for (const o of outputs) { + for (const t of totals) { + expect( + extractTokenUsageFromMessage({ + usage_metadata: { + ...i, + ...o, + ...t, + }, + }), + ).toStrictEqual({ + inputTokens: i?.input_tokens ?? 0, + outputTokens: o?.output_tokens ?? 0, + totalTokens: t?.total_tokens ?? 0, + }); + } + } + } + }); + it('falls back to response_metadata', () => { + const inputs = [{}, { promptTokens: 10 }]; + const outputs = [{}, { completionTokens: 20 }]; + const totals = [{}, { totalTokens: 30 }]; + for (const i of inputs) { + for (const o of outputs) { + for (const t of totals) { + expect( + extractTokenUsageFromMessage({ + response_metadata: { + // @ts-expect-error using old tokenUsage field + tokenUsage: { + ...i, + ...o, + ...t, + }, + }, + }), + ).toStrictEqual({ + inputTokens: i?.promptTokens ?? 0, + outputTokens: o?.completionTokens ?? 0, + totalTokens: t?.totalTokens ?? 0, + }); + } + } + } + }); +}); + +describe('extractModelMetadata', () => { + let attributes: Record = {}; + const span = { + setAttribute(key: string, value: unknown) { + attributes[key] = value; + }, + } as unknown as Span; + beforeEach(() => (attributes = {})); + + it('handles lacking metadata ok', () => { + extractModelMetadata(span, {}); + expect(attributes).toStrictEqual({}); + }); + + it('extracts response model name from metadata', () => { + extractModelMetadata(span, { + response_metadata: { + model_name: 'model-name', + finish_reason: 'stop', + }, + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.model': 'model-name', + 'gen_ai.response.finish_reasons': ['stop'], + }); + }); +}); + +describe('extractToolsFromCompiledGraph', () => { + it('returns null if no tools found', () => { + expect(extractToolsFromCompiledGraph({})).toBe(null); + expect( + extractToolsFromCompiledGraph({ + builder: { nodes: { tools: { runnable: {} } } }, + }), + ).toBe(null); + expect( + extractToolsFromCompiledGraph({ + // @ts-expect-error Wants LangGraphTool[] + builder: { nodes: { tools: { runnable: { tools: 'not an array' } } } }, + }), + ).toBe(null); + expect( + extractToolsFromCompiledGraph({ + builder: { nodes: { tools: { runnable: { tools: [] } } } }, + }), + ).toBe(null); + }); + it('returns the tools found', () => { + expect( + extractToolsFromCompiledGraph({ + builder: { + nodes: { + tools: { + runnable: { + tools: [ + {}, + { lc_kwargs: { name: 'name' } }, + { lc_kwargs: { name: 'name', description: 'desc' } }, + { lc_kwargs: { name: 'name', description: 'desc', schema: 'schema' } }, + ], + }, + }, + }, + }, + }), + ).toStrictEqual([ + { name: undefined, description: undefined, schema: undefined }, + { name: 'name', description: undefined, schema: undefined }, + { name: 'name', description: 'desc', schema: undefined }, + { name: 'name', description: 'desc', schema: 'schema' }, + ]); + }); +}); + +describe('setResponseAttribute', () => { + let attributes: Record = {}; + const span = { + setAttribute(key: string, value: unknown) { + attributes[key] = value; + }, + } as unknown as Span; + beforeEach(() => (attributes = {})); + + it('handles lack of messages', () => { + setResponseAttributes(span, [], undefined); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, undefined); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, {}); + expect(attributes).toStrictEqual({}); + + setResponseAttributes(span, null, { messages: null }); + expect(attributes).toStrictEqual({}); + + // no new messages + setResponseAttributes(span, [{}], { messages: [{}] }); + expect(attributes).toStrictEqual({}); + setResponseAttributes(span, [], { messages: [] }); + expect(attributes).toStrictEqual({}); + + // @ts-expect-error cover excessive type safety case + setResponseAttributes(span, { length: undefined }, []); + expect(attributes).toStrictEqual({}); + }); + + it('extracts tool calls', () => { + setResponseAttributes(span, [], { + messages: [{ tool_calls: [{ name: 'tool a' }] }], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.response.tool_calls': JSON.stringify([{ name: 'tool a' }]), + }); + }); + + it('extracts token usage', () => { + setResponseAttributes(span, [], { + messages: [ + { + usage_metadata: { + input_tokens: 1, + output_tokens: 2, + total_tokens: 3, + }, + }, + ], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.usage.input_tokens': 1, + 'gen_ai.usage.output_tokens': 2, + 'gen_ai.usage.total_tokens': 3, + }); + }); + + it('extracts model metadata', () => { + setResponseAttributes(span, [], { + messages: [ + { + response_metadata: { + model_name: 'model-name-1', + finish_reason: 'stop', + }, + }, + ], + }); + expect(attributes).toStrictEqual({ + 'gen_ai.response.text': '[{"role":"user"}]', + 'gen_ai.response.model': 'model-name-1', + 'gen_ai.response.finish_reasons': ['stop'], + }); + }); +}); diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index d4c34796048c..59ff95a7a995 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,8 +45,8 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/instrumentation-nestjs-core": "0.57.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/core": "10.39.0", diff --git a/packages/node-core/package.json b/packages/node-core/package.json index d272789bfccf..aeae61f8f476 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -105,11 +105,11 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@types/node": "^18.19.1" }, diff --git a/packages/node/package.json b/packages/node/package.json index 622cd2b4ecc4..4fbcbf1ed3fa 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -66,9 +66,9 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/instrumentation-amqplib": "0.58.0", "@opentelemetry/instrumentation-connect": "0.54.0", "@opentelemetry/instrumentation-dataloader": "0.28.0", @@ -77,7 +77,7 @@ "@opentelemetry/instrumentation-generic-pool": "0.54.0", "@opentelemetry/instrumentation-graphql": "0.58.0", "@opentelemetry/instrumentation-hapi": "0.57.0", - "@opentelemetry/instrumentation-http": "0.211.0", + "@opentelemetry/instrumentation-http": "0.212.0", "@opentelemetry/instrumentation-ioredis": "0.59.0", "@opentelemetry/instrumentation-kafkajs": "0.20.0", "@opentelemetry/instrumentation-knex": "0.55.0", @@ -91,8 +91,8 @@ "@opentelemetry/instrumentation-redis": "0.59.0", "@opentelemetry/instrumentation-tedious": "0.30.0", "@opentelemetry/instrumentation-undici": "0.21.0", - "@opentelemetry/resources": "^2.5.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/resources": "^2.5.1", + "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@prisma/instrumentation": "7.2.0", "@sentry/core": "10.39.0", diff --git a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts index d275e1b9d39b..85244aa9bc6c 100644 --- a/packages/node/src/integrations/tracing/langgraph/instrumentation.ts +++ b/packages/node/src/integrations/tracing/langgraph/instrumentation.ts @@ -6,7 +6,7 @@ import { InstrumentationNodeModuleFile, } from '@opentelemetry/instrumentation'; import type { CompiledGraph, LangGraphOptions } from '@sentry/core'; -import { getClient, instrumentStateGraphCompile, SDK_VERSION } from '@sentry/core'; +import { getClient, instrumentCreateReactAgent, instrumentStateGraphCompile, SDK_VERSION } from '@sentry/core'; const supportedVersions = ['>=0.0.0 <2.0.0']; @@ -18,6 +18,7 @@ type LangGraphInstrumentationOptions = InstrumentationConfig & LangGraphOptions; interface PatchedModuleExports { [key: string]: unknown; StateGraph?: abstract new (...args: unknown[]) => unknown; + createReactAgent?: (...args: unknown[]) => CompiledGraph; } /** @@ -31,28 +32,72 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase exports, - [ - new InstrumentationNodeModuleFile( - /** - * In CJS, LangGraph packages re-export from dist/index.cjs files. - * Patching only the root module sometimes misses the real implementation or - * gets overwritten when that file is loaded. We add a file-level patch so that - * _patch runs again on the concrete implementation - */ - '@langchain/langgraph/dist/index.cjs', - supportedVersions, - this._patch.bind(this), - exports => exports, - ), - ], - ); - return module; + public init(): InstrumentationModuleDefinition[] { + return [ + new InstrumentationNodeModuleDefinition( + '@langchain/langgraph', + supportedVersions, + this._patch.bind(this), + exports => exports, + [ + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/prebuilt/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + ], + ), + new InstrumentationNodeModuleDefinition( + '@langchain/langgraph/prebuilt', + supportedVersions, + this._patch.bind(this), + exports => exports, + [ + new InstrumentationNodeModuleFile( + /** + * ESM builds use dist/prebuilt/index.js (without .cjs extension) + * This catches ESM imports that resolve through the main package, + * using the package.json submodule export + */ + '@langchain/langgraph/dist/prebuilt/index.js', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + new InstrumentationNodeModuleFile( + /** + * In CJS, LangGraph packages re-export from dist/prebuilt/index.cjs files. + * Patching only the root module sometimes misses the real implementation or + * gets overwritten when that file is loaded. We add a file-level patch so that + * _patch runs again on the concrete implementation + */ + '@langchain/langgraph/dist/prebuilt/index.cjs', + supportedVersions, + this._patch.bind(this), + exports => exports, + ), + ], + ), + ]; } /** @@ -83,6 +128,17 @@ export class SentryLangGraphInstrumentation extends InstrumentationBase CompiledGraph, options), + writable: true, + enumerable: true, + configurable: true, + }); + } + return exports; } } diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts index ff58698a7931..9a6029826567 100644 --- a/packages/node/test/sdk/client.test.ts +++ b/packages/node/test/sdk/client.test.ts @@ -1,5 +1,5 @@ import { ProxyTracer } from '@opentelemetry/api'; -import * as opentelemetryInstrumentationPackage from '@opentelemetry/instrumentation'; +import type * as opentelemetryInstrumentationPackage from '@opentelemetry/instrumentation'; import type { Event, EventHint, Log } from '@sentry/core'; import { getCurrentScope, getGlobalScope, getIsolationScope, Scope, SDK_VERSION } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; @@ -285,18 +285,23 @@ describe('NodeClient', () => { }); it('registers instrumentations provided with `openTelemetryInstrumentations`', () => { - const registerInstrumentationsSpy = vi - .spyOn(opentelemetryInstrumentationPackage, 'registerInstrumentations') - .mockImplementationOnce(() => () => undefined); - const instrumentationsArray = ['foobar'] as unknown as opentelemetryInstrumentationPackage.Instrumentation[]; + const mockInstrumentation = { + setTracerProvider: vi.fn(), + setMeterProvider: vi.fn(), + enable: vi.fn(), + disable: vi.fn(), + getConfig: vi.fn(() => ({})), + setConfig: vi.fn(), + getModuleDefinitions: vi.fn(() => []), + } as unknown as opentelemetryInstrumentationPackage.Instrumentation; + + const instrumentationsArray = [mockInstrumentation]; new NodeClient(getDefaultNodeClientOptions({ openTelemetryInstrumentations: instrumentationsArray })); - expect(registerInstrumentationsSpy).toHaveBeenCalledWith( - expect.objectContaining({ - instrumentations: instrumentationsArray, - }), - ); + // Verify that the instrumentation was registered by checking if its methods were called + /* eslint-disable-next-line @typescript-eslint/unbound-method */ + expect(mockInstrumentation.setTracerProvider).toHaveBeenCalled(); }); describe('log capture', () => { diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 9c2e560aa1e1..04103862aad5 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -50,9 +50,9 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/context-async-hooks": "^2.5.1", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0" }, "scripts": { diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 95bec21e5ddb..cb16ecce3b0c 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/browser": "10.39.0", "@sentry/cli": "^2.58.4", diff --git a/packages/remix/package.json b/packages/remix/package.json index 2b0b03c869c2..e917799ea477 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.211.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.39.0", "@remix-run/router": "^1.23.2", "@sentry/cli": "^2.58.2", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 0565e88f5055..733e0e54a452 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -40,13 +40,13 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/resources": "^2.5.1", "@sentry/core": "10.39.0" }, "devDependencies": { "@edge-runtime/types": "4.0.0", - "@opentelemetry/core": "^2.5.0", - "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/core": "^2.5.1", + "@opentelemetry/sdk-trace-base": "^2.5.1", "@opentelemetry/semantic-conventions": "^1.39.0", "@sentry/opentelemetry": "10.39.0" }, diff --git a/yarn.lock b/yarn.lock index e1d15fdb0360..a553dcf69ca2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6446,20 +6446,27 @@ dependencies: "@opentelemetry/api" "^1.3.0" +"@opentelemetry/api-logs@0.212.0": + version "0.212.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz#ec66a0951b84b1f082e13fd8a027b9f9d65a3f7a" + integrity sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg== + dependencies: + "@opentelemetry/api" "^1.3.0" + "@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/context-async-hooks@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz#0e6bf31f0dbdd159731f7dbcd266d20f028a6915" - integrity sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw== +"@opentelemetry/context-async-hooks@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz#457b8f9c1e219bf6e22b549d90f773db0a38fe06" + integrity sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw== -"@opentelemetry/core@2.5.0", "@opentelemetry/core@^2.0.0", "@opentelemetry/core@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.5.0.tgz#3b2ac6cf471ed9a85eea836048a4de77a2e549d3" - integrity sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ== +"@opentelemetry/core@2.5.1", "@opentelemetry/core@^2.0.0", "@opentelemetry/core@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.5.1.tgz#b5d830ab499bc13e29f6efa88a165630f25d2ad2" + integrity sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA== dependencies: "@opentelemetry/semantic-conventions" "^1.29.0" @@ -6538,13 +6545,13 @@ "@opentelemetry/instrumentation" "^0.211.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-http@0.211.0": - version "0.211.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.211.0.tgz#2f12f83f0c21d37917fd9710fb5b755f28858cf6" - integrity sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA== +"@opentelemetry/instrumentation-http@0.212.0": + version "0.212.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.212.0.tgz#e9b08e500afb6f8feb0ffeeadf81cf8af77e9457" + integrity sha512-t2nt16Uyv9irgR+tqnX96YeToOStc3X5js7Ljn3EKlI2b4Fe76VhMkTXtsTQ0aId6AsYgefrCRnXSCo/Fn/vww== dependencies: - "@opentelemetry/core" "2.5.0" - "@opentelemetry/instrumentation" "0.211.0" + "@opentelemetry/core" "2.5.1" + "@opentelemetry/instrumentation" "0.212.0" "@opentelemetry/semantic-conventions" "^1.29.0" forwarded-parse "2.1.2" @@ -6671,13 +6678,13 @@ "@opentelemetry/instrumentation" "^0.211.0" "@opentelemetry/semantic-conventions" "^1.24.0" -"@opentelemetry/instrumentation@0.211.0", "@opentelemetry/instrumentation@^0.211.0": - version "0.211.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz#d45e20eafa75b5d3e8a9745a6205332893c55f37" - integrity sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q== +"@opentelemetry/instrumentation@0.212.0", "@opentelemetry/instrumentation@^0.212.0": + version "0.212.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz#238b6e3e2131217ff4acfe7e8e7b6ce1f0ac0ba0" + integrity sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg== dependencies: - "@opentelemetry/api-logs" "0.211.0" - import-in-the-middle "^2.0.0" + "@opentelemetry/api-logs" "0.212.0" + import-in-the-middle "^2.0.6" require-in-the-middle "^8.0.0" "@opentelemetry/instrumentation@^0.207.0": @@ -6689,26 +6696,35 @@ import-in-the-middle "^2.0.0" require-in-the-middle "^8.0.0" +"@opentelemetry/instrumentation@^0.211.0": + version "0.211.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz#d45e20eafa75b5d3e8a9745a6205332893c55f37" + integrity sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q== + dependencies: + "@opentelemetry/api-logs" "0.211.0" + import-in-the-middle "^2.0.0" + require-in-the-middle "^8.0.0" + "@opentelemetry/redis-common@^0.38.2": version "0.38.2" resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz#cefa4f3e79db1cd54f19e233b7dfb56621143955" integrity sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA== -"@opentelemetry/resources@2.5.0", "@opentelemetry/resources@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.5.0.tgz#e7a575b2c534961a9db5153f9498931c786a607a" - integrity sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g== +"@opentelemetry/resources@2.5.1", "@opentelemetry/resources@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.5.1.tgz#90ccc27cea02b543f20a7db9834852ec11784c1a" + integrity sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ== dependencies: - "@opentelemetry/core" "2.5.0" + "@opentelemetry/core" "2.5.1" "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/sdk-trace-base@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz#4b96ae2494a4de5e3bfb36ef7459b30a1ce3332a" - integrity sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ== +"@opentelemetry/sdk-trace-base@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz#4f55f37e18ac3f971936d4717b6bfd43cfd72d61" + integrity sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw== dependencies: - "@opentelemetry/core" "2.5.0" - "@opentelemetry/resources" "2.5.0" + "@opentelemetry/core" "2.5.1" + "@opentelemetry/resources" "2.5.1" "@opentelemetry/semantic-conventions" "^1.29.0" "@opentelemetry/semantic-conventions@^1.24.0", "@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.33.1", "@opentelemetry/semantic-conventions@^1.34.0", "@opentelemetry/semantic-conventions@^1.36.0", "@opentelemetry/semantic-conventions@^1.37.0", "@opentelemetry/semantic-conventions@^1.39.0":