Repository navigation
feat: add OpenTelemetry instrumentation and improve tool lifecycle hooks #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||||
| import { NodeSDK } from "@opentelemetry/sdk-node"; | ||||||||||||||||||||||||||||
| import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; | ||||||||||||||||||||||||||||
| import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"; | ||||||||||||||||||||||||||||
| import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"; | ||||||||||||||||||||||||||||
| import { W3CTraceContextPropagator } from "@opentelemetry/core"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const sdk = new NodeSDK({ | ||||||||||||||||||||||||||||
| serviceName: "my-service", | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| serviceName: "my-service", | |
| serviceName: process.env.OTEL_SERVICE_NAME ?? "my-service", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting contextManager to undefined disables automatic asynchronous context propagation. This means that trace.getActiveSpan() and otelContext.active() will not work correctly across async/await calls, which will break the tracing instrumentation implemented in logger.ts and e2b.ts. To fix this, you should remove this line entirely to let the SDK use the default AsyncLocalStorageContextManager.
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contextManager: undefined configuration at line 16 in instrumentation.ts is concerning. Explicitly setting the context manager to undefined may cause issues with context propagation in async operations. Either remove this line to use the default context manager, or explicitly configure an appropriate context manager (e.g., AsyncHooksContextManager) for proper trace context propagation.
| contextManager: undefined, |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instrumentation file exports the SDK instance but never calls sdk.shutdown(). The NodeSDK should be properly shut down when the application terminates to flush pending telemetry data. Consider adding a shutdown handler or documenting that the consuming application must handle SDK lifecycle management.
| const shutdown = () => { | |
| sdk | |
| .shutdown() | |
| .catch((error) => { | |
| console.error("Error shutting down OpenTelemetry SDK", error); | |
| }); | |
| }; | |
| process.once("SIGINT", shutdown); | |
| process.once("SIGTERM", shutdown); | |
| process.once("beforeExit", shutdown); |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { PinoLogger } from '@mastra/loggers' | ||||||||||||||||||||||||||||||||||||||||||
| import { PinoLogger } from "@mastra/loggers"; | ||||||||||||||||||||||||||||||||||||||||||
| import { FileTransport } from "@mastra/loggers/file"; | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
| import { FileTransport } from "@mastra/loggers/file"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cast (span as any) is unnecessary and bypasses type safety. The spanContext() method is a standard part of the Span interface from @opentelemetry/api. You can call it directly on the span object.
| const sc = (span as any).spanContext ? (span as any).spanContext() : undefined | |
| const sc = span.spanContext(); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unexpected any. Specify a different type.
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unexpected any. Specify a different type.
| const sc = (span as any).spanContext ? (span as any).spanContext() : undefined | |
| return { traceId: sc?.traceId, spanId: sc?.spanId } | |
| const sc = span.spanContext() | |
| return { traceId: sc.traceId, spanId: sc.spanId } |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BaggageToObject helper in logger.ts uses (b as any) type assertion without proper type guards. At line 31, the code casts an unknown type to any and calls .getAllEntries() without verifying that the object has this method. This can cause runtime errors if baggage is null, undefined, or an unexpected type. The function should include proper type checking before accessing methods.
| const obj: Record<string, string> = {} | |
| for (const [k, v] of (b as any).getAllEntries()) { | |
| // Ensure the value has a callable getAllEntries method before using it | |
| const maybeHasEntries = b as { getAllEntries?: unknown } | |
| if (typeof maybeHasEntries.getAllEntries !== 'function') { | |
| return {} | |
| } | |
| const baggageWithEntries = maybeHasEntries as { | |
| getAllEntries: () => Iterable<[string, { value: unknown }]> | |
| } | |
| const obj: Record<string, string> = {} | |
| for (const [k, v] of baggageWithEntries.getAllEntries()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BaggageToObject function takes b as unknown and then casts it to any. This can be made more type-safe by typing b as Baggage | undefined | null and avoiding the any cast, as getAllEntries() is a standard method on the Baggage interface.
| const BaggageToObject = (b: unknown) => { | |
| if (b === null || b === undefined) { | |
| return {} | |
| } | |
| const obj: Record<string, string> = {} | |
| for (const [k, v] of (b as any).getAllEntries()) { | |
| obj[k] = String(v.value) | |
| } | |
| return obj | |
| } | |
| const BaggageToObject = (b: Baggage | undefined | null) => { | |
| if (!b) { | |
| return {} | |
| } | |
| const obj: Record<string, string> = {} | |
| for (const [k, v] of b.getAllEntries()) { | |
| obj[k] = String(v.value) | |
| } | |
| return obj | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log formatter, which is responsible for calling attachTraceAndBaggage to enrich all logs with trace context, is commented out. This means that no logs will be automatically correlated with traces, which undermines a key benefit of this instrumentation. Please uncomment this block to enable automatic log enrichment.
| // log(object) { | |
| // return attachTraceAndBaggage(object) | |
| // }, | |
| log(object) { | |
| return attachTraceAndBaggage(object) | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is significant code duplication across the logWorkflowStart, logWorkflowEnd, logStepStart, and other logging functions. Each one re-defines a data type and manually calls attachTraceAndBaggage. If you enable the Pino log formatter (as suggested in another comment), it will handle this automatically for all logs, and you can remove the manual enrichment from each of these functions, greatly simplifying the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
serviceNameis hardcoded. It's a best practice to make this configurable via an environment variable to easily identify the service in different observability environments (e.g., development, staging, production).