Skip to content

Add custom telemetry exporter support #1602

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

Merged
merged 5 commits into from
Jan 13, 2025
Merged

Add custom telemetry exporter support #1602

merged 5 commits into from
Jan 13, 2025

Conversation

ericallam
Copy link
Member

@ericallam ericallam commented Jan 11, 2025

No description provided.

Copy link

changeset-bot bot commented Jan 11, 2025

🦋 Changeset detected

Latest commit: d3bd091

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
trigger.dev Patch
@trigger.dev/core Patch
@trigger.dev/build Patch
@trigger.dev/sdk Patch
@internal/redis-worker Patch
@internal/zod-worker Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
references-nextjs-realtime Patch
@internal/testcontainers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Jan 11, 2025

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 eslint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/cli-v3/src/config.ts

Oops! Something went wrong! :(

ESLint: 8.45.0

ESLint couldn't find the config "custom" to extend from. Please check that the name of the config is correct.

The config "custom" was referenced from the config file in "/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

Walkthrough

The pull request introduces support for OpenTelemetry (otel) exporters across the project. The changes enable configuration and integration of telemetry data collection by adding an exporters property to various configuration types and components. This enhancement allows developers to specify custom span exporters, improving observability and monitoring capabilities. The modifications span multiple files, including core configuration, tracing SDK, and a reference Next.js project, with a specific focus on integrating LangSmith telemetry.

Changes

File Change Summary
packages/core/src/v3/config.ts Added optional telemetry property with instrumentations and exporters to TriggerConfig type; marked existing instrumentations as deprecated.
packages/core/src/v3/otel/tracingSDK.ts Updated TracingSDKConfig to include exporters?: SpanExporter[] and added logic for processing exporters in the constructor.
packages/cli-v3/src/entryPoints/dev-run-worker.ts Updated TracingSDK instantiation to include exporters from config.telemetry.
packages/cli-v3/src/entryPoints/deploy-run-worker.ts Updated TracingSDK instantiation to include exporters from config.telemetry.
packages/cli-v3/src/config.ts Modified logic in getInstrumentedPackageNames to check for config.telemetry?.instrumentations.
packages/core/src/v3/semanticInternalAttributes.ts Added new entry SPAN_ATTEMPT to SemanticInternalAttributes.
packages/core/src/v3/workers/taskExecutor.ts Added SPAN_ATTEMPT attribute to the span attributes in the execute method of TaskExecutor.
references/nextjs-realtime/package.json Added @opentelemetry/exporter-trace-otlp-http and langsmith dependencies.
references/nextjs-realtime/src/trigger/ai.ts Imported AISDKExporter and initialized telemetrySettings for logging; updated streamText function to include experimental_telemetry.
references/nextjs-realtime/trigger.config.ts Imported AISDKExporter and added it to the telemetry configuration in defineConfig.

Sequence Diagram

sequenceDiagram
    participant Config as Trigger Configuration
    participant TracingSDK as Tracing SDK
    participant Exporter as Span Exporter
    
    Config->>TracingSDK: Provide exporters
    TracingSDK->>Exporter: Create SpanProcessor
    TracingSDK->>Exporter: Add to TraceProvider
    Exporter-->>TracingSDK: Process and export spans
Loading

Possibly related PRs

Poem

🐰 Telemetry's dance, a rabbit's delight,
Spans hopping through traces, so clear and bright
OpenTelemetry's magic unfurled
Exporters spinning stories untold
Monitoring's warren, now open and free! 🔍

Finishing Touches

  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
references/nextjs-realtime/trigger.config.ts (1)

48-51: Consider environment-based debug configuration.

The AISDKExporter is configured with debug: true, which might generate excessive logs in production.

Consider using an environment variable to control debug mode:

const exporter = new AISDKExporter({
-  debug: true,
+  debug: process.env.NODE_ENV !== 'production',
  client,
});
.changeset/rich-trainers-glow.md (1)

1-6: Enhance the changeset description with more details.

Since this is a significant feature addition (OpenTelemetry exporter support), consider expanding the description to include:

  • Purpose and benefits of adding otel exporter support
  • Key components affected
  • Any breaking changes or migration steps
packages/core/src/v3/otel/tracingSDK.ts (1)

157-159: Consider performance and error handling improvements.

While the implementation is functional, consider these enhancements:

  1. Add validation for the exporters array
  2. Consider using BatchSpanProcessor for better performance
  3. Add error handling for individual exporter initialization

Example implementation:

-    for (const exporter of config.exporters ?? []) {
-      traceProvider.addSpanProcessor(new SimpleSpanProcessor(exporter));
-    }
+    const exporters = config.exporters ?? [];
+    if (exporters.length > 0) {
+      try {
+        for (const exporter of exporters) {
+          if (!exporter) {
+            console.warn("Skipping null or undefined exporter");
+            continue;
+          }
+          
+          const processor = getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
+            ? new BatchSpanProcessor(exporter)
+            : new SimpleSpanProcessor(exporter);
+            
+          traceProvider.addSpanProcessor(processor);
+        }
+      } catch (error) {
+        console.error("Error initializing additional exporters:", error);
+      }
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46a37fd and 2e9960b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • .changeset/rich-trainers-glow.md (1 hunks)
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts (1 hunks)
  • packages/core/src/v3/config.ts (1 hunks)
  • packages/core/src/v3/otel/tracingSDK.ts (2 hunks)
  • references/nextjs-realtime/package.json (1 hunks)
  • references/nextjs-realtime/src/trigger/ai.ts (2 hunks)
  • references/nextjs-realtime/trigger.config.ts (1 hunks)
🔇 Additional comments (4)
packages/cli-v3/src/entryPoints/dev-run-worker.ts (1)

132-132: LGTM!

The integration of exporters into the TracingSDK configuration is clean and follows best practices by providing a default empty array.

references/nextjs-realtime/package.json (1)

30-30: Verify langsmith package version and security.

Please verify the following:

  1. Is ^0.2.15 the latest stable version of langsmith?
  2. Are there any known vulnerabilities in this version?
✅ Verification successful

✓ langsmith@^0.2.15 version is appropriate

The package is using the latest available version (0.2.15) and no security advisories were found in the npm ecosystem.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check latest version and security advisories for langsmith

# Get latest version from npm
echo "Latest version from npm:"
npm view langsmith version

# Check for known vulnerabilities
echo "Checking for vulnerabilities:"
npm audit langsmith@0.2.15

Length of output: 541


Script:

#!/bin/bash
# Check for security advisories using GitHub CLI
echo "Checking for security advisories:"
gh api \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  /repos/langchain-ai/langsmith-sdk/security/advisories

# Also check npm advisory database
echo "Checking npm advisory database:"
curl -s https://registry.npmjs.org/-/npm/v1/security/advisories/search?package=langsmith

Length of output: 590

packages/core/src/v3/otel/tracingSDK.ts (2)

88-88: LGTM! Type definition looks good.

The optional exporters array is correctly typed and placed in the TracingSDKConfig interface.


157-159: Verify impact on existing tracing functionality.

The addition of multiple exporters could impact performance and resource usage. Please ensure:

  1. Existing tracing functionality remains unaffected
  2. Memory usage is acceptable with multiple exporters
  3. No duplicate spans are being exported
✅ Verification successful

Multiple exporters implementation is safe and well-designed

The implementation follows OpenTelemetry best practices:

  • Each exporter gets its own SimpleSpanProcessor, preventing conflicts
  • Proper shutdown handling is implemented
  • Memory usage is optimized as spans are processed immediately
  • No risk of duplicate spans as each processor handles its own export path
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing span processor usage and potential conflicts

# Look for span processor configurations
echo "Checking span processor usage:"
rg -l "SpanProcessor|TracerProvider" --type ts

# Check for potential memory leak patterns
echo "Checking for proper cleanup:"
ast-grep --pattern 'shutdown() {
  $$$
  this._traceProvider.shutdown()
  $$$
}'

Length of output: 511


Script:

#!/bin/bash
# Check for exporter configurations and processor types
echo "Checking exporter configurations:"
rg "new \w*SpanProcessor|addSpanProcessor" -A 2 --type ts

echo -e "\nChecking shutdown patterns:"
rg "shutdown.*Provider|Provider.*shutdown" --type ts

echo -e "\nChecking for batch processors:"
rg "BatchSpanProcessor" --type ts

Length of output: 2783

Comment on lines 6 to 44
class LangsmithSpanExporterWrapper {
constructor(
private underlyingExporter: any,
private transformSpan: (span: any) => any | undefined = (span) => {
if (span.attributes["$span.partial"]) {
// Skip partial spans
return;
}

// Check if this is an attempt span
if (span.name.startsWith("Attempt ")) {
// Create a new span that wraps the original but modifies spanContext
const spanContext = span.spanContext();

return {
...span,
spanContext: () => spanContext,
parentSpanId: undefined,
};
}
return span;
}
) {}

export(spans: any[], resultCallback: (result: any) => void): void {
const modifiedSpans = spans.map(this.transformSpan);
this.underlyingExporter.export(modifiedSpans.filter(Boolean), resultCallback);
}

shutdown(): Promise<void> {
return this.underlyingExporter.shutdown();
}

forceFlush?(): Promise<void> {
return this.underlyingExporter.forceFlush
? this.underlyingExporter.forceFlush()
: Promise.resolve();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety and error handling to the span exporter wrapper.

The implementation has several areas that could be improved:

  1. Use proper types instead of any
  2. Add error handling in the export method
  3. Consider memory efficiency for large span arrays

Consider this implementation:

+import type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
+
-class LangsmithSpanExporterWrapper {
+class LangsmithSpanExporterWrapper implements SpanExporter {
   constructor(
-    private underlyingExporter: any,
-    private transformSpan: (span: any) => any | undefined = (span) => {
+    private underlyingExporter: SpanExporter,
+    private transformSpan: (span: ReadableSpan) => ReadableSpan | undefined = (span) => {
     if (span.attributes["$span.partial"]) {
       return;
     }

     if (span.name.startsWith("Attempt ")) {
       const spanContext = span.spanContext();
       return {
         ...span,
         spanContext: () => spanContext,
         parentSpanId: undefined,
       };
     }
     return span;
   }
 ) {}

-  export(spans: any[], resultCallback: (result: any) => void): void {
+  export(spans: ReadableSpan[], resultCallback: (result: any) => void): void {
     try {
-      const modifiedSpans = spans.map(this.transformSpan);
+      const modifiedSpans = spans.map(this.transformSpan).filter(Boolean);
-      this.underlyingExporter.export(modifiedSpans.filter(Boolean), resultCallback);
+      this.underlyingExporter.export(modifiedSpans, resultCallback);
+    } catch (error) {
+      resultCallback({ code: 1, error: error instanceof Error ? error : new Error(String(error)) });
     }
   }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +102 to +109
const telemetrySettings = AISDKExporter.getSettings();

logger.info("Telemetry settings", { telemetrySettings });

const result = streamText({
model: openai(model),
prompt,
tools: {
getWeather: weatherTask.tool,
},
maxSteps: 10,
experimental_telemetry: telemetrySettings,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle experimental telemetry feature gracefully.

The code uses experimental telemetry features without proper error handling or feature detection.

Consider adding error handling:

-    const telemetrySettings = AISDKExporter.getSettings();
+    let telemetrySettings;
+    try {
+      telemetrySettings = AISDKExporter.getSettings();
+      logger.info("Telemetry settings", { telemetrySettings });
+    } catch (error) {
+      logger.warn("Failed to get telemetry settings", { error });
+    }

-    logger.info("Telemetry settings", { telemetrySettings });

     const result = streamText({
       model: openai(model),
       prompt,
-      experimental_telemetry: telemetrySettings,
+      experimental_telemetry: telemetrySettings ?? undefined,
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const telemetrySettings = AISDKExporter.getSettings();
logger.info("Telemetry settings", { telemetrySettings });
const result = streamText({
model: openai(model),
prompt,
tools: {
getWeather: weatherTask.tool,
},
maxSteps: 10,
experimental_telemetry: telemetrySettings,
let telemetrySettings;
try {
telemetrySettings = AISDKExporter.getSettings();
logger.info("Telemetry settings", { telemetrySettings });
} catch (error) {
logger.warn("Failed to get telemetry settings", { error });
}
const result = streamText({
model: openai(model),
prompt,
experimental_telemetry: telemetrySettings ?? undefined,
});

Comment on lines 59 to 77
exporters?: Array<any>;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety for exporters configuration.

Using Array<any> reduces type safety and could lead to runtime errors.

Consider using proper OpenTelemetry types:

+import type { SpanExporter } from '@opentelemetry/sdk-trace-base';

-  exporters?: Array<any>;
+  exporters?: Array<SpanExporter>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exporters?: Array<any>;
import type { SpanExporter } from '@opentelemetry/sdk-trace-base';
exporters?: Array<SpanExporter>;

Copy link

pkg-pr-new bot commented Jan 11, 2025

@trigger.dev/build

npm i https://pkg.pr.new/triggerdotdev/trigger.dev/@trigger.dev/build@1602

@trigger.dev/core

npm i https://pkg.pr.new/triggerdotdev/trigger.dev/@trigger.dev/core@1602

@trigger.dev/rsc

npm i https://pkg.pr.new/triggerdotdev/trigger.dev/@trigger.dev/rsc@1602

trigger.dev

npm i https://pkg.pr.new/triggerdotdev/trigger.dev@1602

@trigger.dev/sdk

npm i https://pkg.pr.new/triggerdotdev/trigger.dev/@trigger.dev/sdk@1602

@trigger.dev/react-hooks

npm i https://pkg.pr.new/triggerdotdev/trigger.dev/@trigger.dev/react-hooks@1602

commit: d3bd091

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/core/src/v3/semanticInternalAttributes.ts (1)

54-54: LGTM! Consider grouping related attributes.

The new SPAN_ATTEMPT attribute follows the established naming conventions and integrates well with the existing telemetry system.

Consider grouping this attribute with other span-related attributes (SPAN_PARTIAL, SPAN_ID) to improve code organization and maintainability. Here's a suggested reorganization:

  MACHINE_PRESET_MEMORY: "ctx.machine.memory",
  MACHINE_PRESET_CENTS_PER_MS: "ctx.machine.centsPerMs",
+ SPAN_ATTEMPT: "$span.attempt",
  SPAN_PARTIAL: "$span.partial",
  SPAN_ID: "$span.span_id",
- SPAN_ATTEMPT: "$span.attempt",
packages/cli-v3/src/config.ts (1)

320-323: Improve readability and add deprecation warning

While the logic is correct, consider these improvements:

  1. The complex nullish coalescing chain could be simplified for better readability
  2. Add a deprecation warning for config.instrumentations to guide users toward the new telemetry.instrumentations
  3. Consider using proper type definitions instead of casting to any

Here's a suggested refactoring:

-  if (config.instrumentations ?? config.telemetry?.instrumentations) {
-    for (const instrumentation of config.telemetry?.instrumentations ??
-      config.instrumentations ??
-      []) {
+  const useDeprecatedPath = Boolean(config.instrumentations);
+  if (useDeprecatedPath) {
+    logger.warn(
+      "The 'instrumentations' option is deprecated. Please use 'telemetry.instrumentations' instead."
+    );
+  }
+
+  const instrumentations = config.telemetry?.instrumentations ?? config.instrumentations;
+  if (instrumentations) {
+    for (const instrumentation of instrumentations) {

Also, consider defining a proper type for the instrumentation object instead of using any:

interface TelemetryInstrumentation {
  getModuleDefinitions?: () => Array<InstrumentationModuleDefinition>;
}
packages/core/src/v3/otel/tracingSDK.ts (1)

116-117: Consider environment-specific service names

The service name fallback to "trigger.dev" might be too generic. Consider using a more specific default based on the application context.

-            getEnvVar("OTEL_SERVICE_NAME") ?? "trigger.dev",
+            getEnvVar("OTEL_SERVICE_NAME") ?? `trigger.dev.${process.env.NODE_ENV ?? "development"}`,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f967c63 and e0c6a33.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .changeset/rich-trainers-glow.md (1 hunks)
  • packages/cli-v3/src/config.ts (1 hunks)
  • packages/cli-v3/src/entryPoints/deploy-run-worker.ts (1 hunks)
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts (1 hunks)
  • packages/core/src/v3/config.ts (2 hunks)
  • packages/core/src/v3/otel/tracingSDK.ts (5 hunks)
  • packages/core/src/v3/semanticInternalAttributes.ts (1 hunks)
  • packages/core/src/v3/workers/taskExecutor.ts (1 hunks)
  • references/nextjs-realtime/package.json (2 hunks)
  • references/nextjs-realtime/src/trigger/ai.ts (2 hunks)
  • references/nextjs-realtime/trigger.config.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/core/src/v3/workers/taskExecutor.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • .changeset/rich-trainers-glow.md
  • references/nextjs-realtime/src/trigger/ai.ts
  • references/nextjs-realtime/trigger.config.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
  • packages/core/src/v3/config.ts
  • packages/cli-v3/src/entryPoints/deploy-run-worker.ts
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (buildjet-8vcpu-ubuntu-2204 - npm)
  • GitHub Check: units / 🧪 Unit Tests
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
references/nextjs-realtime/package.json (1)

17-17: Verify OpenTelemetry and LangSmith package versions

The specified versions might be outdated or incorrect:

  • @opentelemetry/exporter-trace-otlp-http@^0.57.0 seems to be ahead of the latest stable version
  • langsmith@^0.2.15 seems to be ahead of the latest stable version

Run this script to check the latest stable versions:

Also applies to: 31-31

packages/core/src/v3/otel/tracingSDK.ts (1)

89-89: LGTM! Configuration change looks good

The addition of optional exporters to TracingSDKConfig is well-typed and maintains backward compatibility.

Comment on lines +263 to +307
class ExternalSpanExporterWrapper {
constructor(
private underlyingExporter: SpanExporter,
private externalTraceId: string
) {}

private transformSpan(span: ReadableSpan): ReadableSpan | undefined {
if (span.attributes[SemanticInternalAttributes.SPAN_PARTIAL]) {
// Skip partial spans
return;
}

const spanContext = span.spanContext();

return {
...span,
spanContext: () => ({ ...spanContext, traceId: this.externalTraceId }),
parentSpanId: span.attributes[SemanticInternalAttributes.SPAN_ATTEMPT]
? undefined
: span.parentSpanId,
};
}

export(spans: any[], resultCallback: (result: any) => void): void {
try {
const modifiedSpans = spans.map(this.transformSpan.bind(this));
this.underlyingExporter.export(
modifiedSpans.filter(Boolean) as ReadableSpan[],
resultCallback
);
} catch (e) {
console.error(e);
}
}

shutdown(): Promise<void> {
return this.underlyingExporter.shutdown();
}

forceFlush?(): Promise<void> {
return this.underlyingExporter.forceFlush
? this.underlyingExporter.forceFlush()
: Promise.resolve();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and error handling in ExternalSpanExporterWrapper

The wrapper implementation has several areas for improvement:

  1. Uses any[] type instead of proper span types
  2. Basic error handling that only logs errors
  3. Missing documentation explaining the wrapper's purpose

Consider these improvements:

+/**
+ * Wraps a SpanExporter to transform spans before export:
+ * - Replaces trace IDs with a consistent external ID
+ * - Filters out partial spans
+ * - Handles parent span relationships
+ */
 class ExternalSpanExporterWrapper {
   constructor(
     private underlyingExporter: SpanExporter,
     private externalTraceId: string
   ) {}

   private transformSpan(span: ReadableSpan): ReadableSpan | undefined {
     if (span.attributes[SemanticInternalAttributes.SPAN_PARTIAL]) {
       // Skip partial spans
       return;
     }

     const spanContext = span.spanContext();

     return {
       ...span,
       spanContext: () => ({ ...spanContext, traceId: this.externalTraceId }),
       parentSpanId: span.attributes[SemanticInternalAttributes.SPAN_ATTEMPT]
         ? undefined
         : span.parentSpanId,
     };
   }

-  export(spans: any[], resultCallback: (result: any) => void): void {
+  export(
+    spans: ReadableSpan[],
+    resultCallback: (result: ExportResult) => void
+  ): void {
     try {
       const modifiedSpans = spans.map(this.transformSpan.bind(this));
       this.underlyingExporter.export(
         modifiedSpans.filter(Boolean) as ReadableSpan[],
         resultCallback
       );
     } catch (e) {
-      console.error(e);
+      console.error("Error in ExternalSpanExporterWrapper.export:", e);
+      resultCallback({ code: ExportResultCode.FAILED, error: e as Error });
     }
   }

   shutdown(): Promise<void> {
-    return this.underlyingExporter.shutdown();
+    return this.underlyingExporter.shutdown().catch((e) => {
+      console.error("Error in ExternalSpanExporterWrapper.shutdown:", e);
+      throw e;
+    });
   }

   forceFlush?(): Promise<void> {
-    return this.underlyingExporter.forceFlush
+    return this.underlyingExporter.forceFlush?.()
+      .catch((e) => {
+        console.error("Error in ExternalSpanExporterWrapper.forceFlush:", e);
+        throw e;
+      })
       ? this.underlyingExporter.forceFlush()
       : Promise.resolve();
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +160 to +177
const externalTraceId = crypto.randomUUID();

for (const exporter of config.exporters ?? []) {
traceProvider.addSpanProcessor(
getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
scheduledDelayMillis: parseInt(
getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
),
exportTimeoutMillis: parseInt(
getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
),
maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
})
: new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation and error handling for exporters configuration

The exporter initialization has several potential issues:

  1. Missing validation for the exporters array
  2. No error handling for invalid environment variable values
  3. Potential memory pressure from unbounded queue sizes

Consider these improvements:

+    // Validate exporters
+    if (config.exporters?.length) {
+      if (!Array.isArray(config.exporters)) {
+        throw new Error("exporters must be an array");
+      }
+      
+      for (const exporter of config.exporters) {
+        if (!exporter.export || typeof exporter.export !== "function") {
+          throw new Error("Invalid exporter: missing export function");
+        }
+      }
+    }

+    // Parse and validate env vars with defaults
+    const batchSize = Math.min(
+      parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
+      100 // Maximum safe batch size
+    );
+    const queueSize = Math.min(
+      parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
+      1000 // Maximum safe queue size
+    );

     const externalTraceId = crypto.randomUUID();

     for (const exporter of config.exporters ?? []) {
       traceProvider.addSpanProcessor(
         getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
           ? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
-              maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
+              maxExportBatchSize: batchSize,
               scheduledDelayMillis: parseInt(
                 getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
               ),
               exportTimeoutMillis: parseInt(
                 getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
               ),
-              maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
+              maxQueueSize: queueSize,
             })
           : new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
       );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const externalTraceId = crypto.randomUUID();
for (const exporter of config.exporters ?? []) {
traceProvider.addSpanProcessor(
getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
scheduledDelayMillis: parseInt(
getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
),
exportTimeoutMillis: parseInt(
getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
),
maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
})
: new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
);
}
// Validate exporters
if (config.exporters?.length) {
if (!Array.isArray(config.exporters)) {
throw new Error("exporters must be an array");
}
for (const exporter of config.exporters) {
if (!exporter.export || typeof exporter.export !== "function") {
throw new Error("Invalid exporter: missing export function");
}
}
}
// Parse and validate env vars with defaults
const batchSize = Math.min(
parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
100 // Maximum safe batch size
);
const queueSize = Math.min(
parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
1000 // Maximum safe queue size
);
const externalTraceId = crypto.randomUUID();
for (const exporter of config.exporters ?? []) {
traceProvider.addSpanProcessor(
getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
maxExportBatchSize: batchSize,
scheduledDelayMillis: parseInt(
getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
),
exportTimeoutMillis: parseInt(
getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
),
maxQueueSize: queueSize,
})
: new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
);
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/src/v3/otel/tracingSDK.ts (1)

116-117: Consider a more specific default service name.

The default service name "trigger.dev" is too generic. Consider using a more specific default that includes the package or component name for better observability.

-            getEnvVar("OTEL_SERVICE_NAME") ?? "trigger.dev",
+            getEnvVar("OTEL_SERVICE_NAME") ?? "trigger.dev-core",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0c6a33 and d3bd091.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .changeset/rich-trainers-glow.md (1 hunks)
  • packages/cli-v3/src/config.ts (1 hunks)
  • packages/cli-v3/src/entryPoints/deploy-run-worker.ts (1 hunks)
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts (1 hunks)
  • packages/core/src/v3/config.ts (2 hunks)
  • packages/core/src/v3/otel/tracingSDK.ts (5 hunks)
  • packages/core/src/v3/semanticInternalAttributes.ts (1 hunks)
  • packages/core/src/v3/workers/taskExecutor.ts (1 hunks)
  • references/nextjs-realtime/package.json (2 hunks)
  • references/nextjs-realtime/src/trigger/ai.ts (2 hunks)
  • references/nextjs-realtime/trigger.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/core/src/v3/semanticInternalAttributes.ts
  • packages/cli-v3/src/entryPoints/dev-run-worker.ts
  • packages/core/src/v3/workers/taskExecutor.ts
  • .changeset/rich-trainers-glow.md
  • references/nextjs-realtime/trigger.config.ts
  • packages/cli-v3/src/entryPoints/deploy-run-worker.ts
  • references/nextjs-realtime/package.json
  • packages/cli-v3/src/config.ts
  • packages/core/src/v3/config.ts
  • references/nextjs-realtime/src/trigger/ai.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
🔇 Additional comments (3)
packages/core/src/v3/otel/tracingSDK.ts (3)

23-23: LGTM! Type changes are well-defined.

The addition of ReadableSpan import and optional exporters property to TracingSDKConfig are appropriate for the new functionality.

Also applies to: 89-89


160-177: Previous review comment about validation is still applicable.

The suggestion to add validation for exporters and improve error handling for environment variables is still relevant.

Extract batch processing configuration to reduce duplication.

The batch processing configuration is duplicated from the main exporter setup. Consider extracting it into a shared configuration object.

+    const batchConfig = {
+      maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
+      scheduledDelayMillis: parseInt(
+        getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
+      ),
+      exportTimeoutMillis: parseInt(
+        getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
+      ),
+      maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
+    };

     for (const exporter of config.exporters ?? []) {
       traceProvider.addSpanProcessor(
         getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
-          ? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
-              maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
-              scheduledDelayMillis: parseInt(
-                getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
-              ),
-              exportTimeoutMillis: parseInt(
-                getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
-              ),
-              maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
-            })
+          ? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), batchConfig)
           : new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
       );
     }

263-307: Previous review comment about type safety and error handling is still applicable.

The suggestion to improve type safety, error handling, and add documentation to the ExternalSpanExporterWrapper class is still relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant