Skip to content

feat: add OTLP integration support#1229

Open
giortzisg wants to merge 9 commits intomasterfrom
feat/otlp
Open

feat: add OTLP integration support#1229
giortzisg wants to merge 9 commits intomasterfrom
feat/otlp

Conversation

@giortzisg
Copy link
Contributor

@giortzisg giortzisg commented Mar 18, 2026

Description

This PR adds the OpenTelemetry integration for OTLP, while also separating the error linking from the span exporter. The PR also includes some deviations from the Spec, so that the package follows closely the OTel conventions.

The main changes:

  • Adds a public sentryotel.NewErrorLinkingIntegration() for OTel users to link errors with traces.
  • Decouple error linking to work without the sentry span map.
  • Add the sentryotlp package where users can create a NewTraceExporter to send to sentry's otlp endpoint using the otlptracehttp exporter.
  • Keep otlp http client configuration options for the TraceExporter

Deviations from the spec:

  • There is no setup_otlp_traces_exporter flag since users need to manually provide the exporter to the TracerProvider
  • No collector_url. For users that are already using the collector, the only thing they need to set up is the ErrorLinkingIntegration. Having a WithCollectorURL option mixes concerns, since the sentryotlp trace exporter should only forward to sentry. Using the collector with Sentry requires changing the otlp http configuration on the collector and not the integration level.

Issues

Changelog Entry Instructions

To add a custom changelog entry, uncomment the section above. Supports:

  • Single entry: just write text
  • Multiple entries: use bullet points
  • Nested bullets: indent 4+ spaces

For more details: custom changelog entries

Reminders

@giortzisg giortzisg requested a review from sl0thentr0py March 18, 2026 13:31
@giortzisg giortzisg self-assigned this Mar 18, 2026
@linear-code
Copy link

linear-code bot commented Mar 18, 2026

GO-121 Add OTLP support

@giortzisg giortzisg changed the title Feat/otlp feat: add OTLP integration support Mar 18, 2026
@github-actions
Copy link

github-actions bot commented Mar 18, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • Add OTLP integration support by giortzisg in #1229
  • Add RemoveAttribute api on the scope. by giortzisg in #1224
  • Deprecate Scope.SetExtra, Scope.SetExtras, and Scope.RemoveExtra in favor of Scope.SetAttributes and Scope.RemoveAttribute by giortzisg in #1224
    • The recommended migration path is to use SetAttributes to attach values to logs and metrics. Note that attributes do not appear on error events; if you only capture errors, use SetTag or SetContext instead.
    • Before:
    scope.SetExtra("key.string", "str")
    scope.SetExtra("key.int", 42)
    • After (for error events) — use tags and contexts:
    scope.SetTag("key.string", "str")
    scope.SetContext("my_data", sentry.Context{"key.int": 42})
    • After (for logs and metrics) — use attributes:
    scope.SetAttributes(
        attribute.String("key.string", "str"),
        attribute.Int("key.int", 42),
    )
  • Add support for homogenous arrays by giortzisg in #1203
  • Add support for client reports by giortzisg in #1192
  • Add org id propagation in sentry_baggage by giortzisg in #1210
  • Add OrgID and StrictTraceContinuation client options. by giortzisg in #1210
  • Add the option to set attributes on the scope by giortzisg in #1208

Bug Fixes 🐛

  • (serialization) Pre-serialize mutable event fields to prevent race panics by giortzisg in #1214
  • Use HEROKU_BUILD_COMMIT with HEROKU_SLUG_COMMIT as fallback by ericapisani in #1220

Internal Changes 🔧

Ai

  • Add AGENTS.md and testing guidelines by giortzisg in #1216
  • Add dotagents configuration by giortzisg in #1211

Other

  • (deps) Bump github.com/gofiber/fiber/v2 from 2.52.11 to 2.52.12 in /fiber by dependabot in #1209
  • Bump getsentry/craft to 2.24.1 by giortzisg in #1225
  • Handle independent go module versions for integrations by giortzisg in #1217

🤖 This preview updates automatically when you update the PR.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Event processor overwrites entire trace context losing fields
    • The event processor now updates trace_id and span_id in the existing event.Contexts["trace"] map (creating it only when absent), preserving SDK-populated trace fields.

Create PR

Or push these changes by commenting:

@cursor push 38eedc31b4
Preview (38eedc31b4)
diff --git a/otel/internal/common/event_processor.go b/otel/internal/common/event_processor.go
--- a/otel/internal/common/event_processor.go
+++ b/otel/internal/common/event_processor.go
@@ -27,9 +27,12 @@
 	if event.Contexts == nil {
 		event.Contexts = make(map[string]map[string]any)
 	}
-	event.Contexts["trace"] = map[string]any{
-		"trace_id": otelSpanContext.TraceID().String(),
-		"span_id":  otelSpanContext.SpanID().String(),
+	traceContext, found := event.Contexts["trace"]
+	if !found || traceContext == nil {
+		traceContext = make(map[string]any)
+		event.Contexts["trace"] = traceContext
 	}
+	traceContext["trace_id"] = otelSpanContext.TraceID().String()
+	traceContext["span_id"] = otelSpanContext.SpanID().String()
 	return event
 }

diff --git a/otel/internal/common/event_processor_test.go b/otel/internal/common/event_processor_test.go
--- a/otel/internal/common/event_processor_test.go
+++ b/otel/internal/common/event_processor_test.go
@@ -22,7 +22,7 @@
 		{name: "without existing trace context"},
 		{
 			name:          "with existing trace context",
-			existingTrace: map[string]any{"trace_id": "123", "parent_span_id": "456"},
+			existingTrace: map[string]any{"trace_id": "123", "parent_span_id": "456", "op": "http.server"},
 		},
 	}
 
@@ -43,10 +43,12 @@
 			}))
 
 			got := linkTraceContextToErrorEvent(event, &sentry.EventHint{Context: ctx})
-			assert.Equal(t, map[string]any{
-				"trace_id": traceID.String(),
-				"span_id":  spanID.String(),
-			}, got.Contexts["trace"])
+			assert.Equal(t, traceID.String(), got.Contexts["trace"]["trace_id"])
+			assert.Equal(t, spanID.String(), got.Contexts["trace"]["span_id"])
+			if tt.existingTrace != nil {
+				assert.Equal(t, "456", got.Contexts["trace"]["parent_span_id"])
+				assert.Equal(t, "http.server", got.Contexts["trace"]["op"])
+			}
 		})
 	}
 }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment on lines +17 to +19
func (errorLinkingIntegration) Name() string {
return "OtelErrorLinking"
}
Copy link

Choose a reason for hiding this comment

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

Bug: Initializing multiple Sentry clients with NewErrorLinkingIntegration() registers the same global event processor multiple times, causing redundant event processing and performance overhead.
Severity: MEDIUM

Suggested Fix

To prevent duplicate registration, the SetupOnce method should be made idempotent. This can be achieved by using a global flag, such as a sync.Once or a boolean, to ensure that sentry.AddGlobalEventProcessor() is only called the first time SetupOnce is executed across all client initializations.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: otel/error_linking.go#L17-L19

Potential issue: When multiple Sentry clients are initialized with
`NewErrorLinkingIntegration()`, the integration's `SetupOnce` method is called for each
client. This method unconditionally calls `sentry.AddGlobalEventProcessor()`, appending
the same processor to the global `globalEventProcessors` slice multiple times. Because
the check for installed integrations is per-client, not global, this duplication is not
prevented. As a result, every event is processed redundantly by each registered instance
of the processor, leading to unnecessary performance overhead.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.


func (errorLinkingIntegration) SetupOnce(_ *sentry.Client) {
sentry.AddGlobalEventProcessor(common.NewEventProcessor())
}
Copy link

Choose a reason for hiding this comment

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

Integration uses global processor instead of per-client processor

Medium Severity

errorLinkingIntegration.SetupOnce calls sentry.AddGlobalEventProcessor instead of using the provided *sentry.Client parameter (which is discarded with _). The established convention for Sentry integrations (e.g. modulesIntegration) is to use client.AddEventProcessor. Because SetupOnce is called on every sentry.Init, each initialization appends another copy of the processor to the global list, causing it to run multiple times per event. Using client.AddEventProcessor would scope the processor to the specific client and prevent unbounded accumulation.

Fix in Cursor Fix in Web

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.

Add OTLP support

1 participant