Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion pkg/workflow/observability_otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var otlpLog = logger.New("workflow:observability_otlp")

var sentryEndpointExpressionPattern = regexp.MustCompile(`(?i)^\$\{\{\s*secrets\.` + regexp.QuoteMeta(constants.OTELSentryEndpointSecretName) + `\s*\}\}$`)
var otlpResourceAttributeSecretRefPattern = regexp.MustCompile(`\$\{\{\s*(secrets|vars)\.`)
var otelServiceNameKeyPattern = regexp.MustCompile(`(?m)^\s*OTEL_SERVICE_NAME:`)

func normalizeOTLPHeadersForEndpoint(raw any, endpoint string) string {
if raw == nil {
Expand Down Expand Up @@ -732,7 +733,14 @@ func (c *Compiler) injectOTLPConfig(workflowData *WorkflowData) {
// OTEL_EXPORTER_OTLP_ENDPOINT is set to the first endpoint for backward
// compatibility (MCP gateway, legacy scripts). OTEL_SERVICE_NAME is
// workflow-specific when WorkflowID is available.
otlpEnvLines := fmt.Sprintf(" OTEL_EXPORTER_OTLP_ENDPOINT: %s\n OTEL_SERVICE_NAME: %s", firstEndpoint, serviceName)
// If the user has already defined OTEL_SERVICE_NAME in their env block,
// we respect their value and skip injection to avoid duplicate key errors.
otlpEnvLines := " OTEL_EXPORTER_OTLP_ENDPOINT: " + firstEndpoint
if otelServiceNameKeyPattern.MatchString(workflowData.Env) {
otlpLog.Printf("Skipping OTEL_SERVICE_NAME injection: already defined by user")
} else {
otlpEnvLines += "\n OTEL_SERVICE_NAME: " + serviceName
}
otlpEnvLines += "\n OTEL_RESOURCE_ATTRIBUTES: '" + escapeYAMLSingleQuoted(otelResourceAttributes(workflowData)) + "'"

// 3. Inject per-endpoint headers env vars.
Expand Down
17 changes: 17 additions & 0 deletions pkg/workflow/observability_otlp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,23 @@ func TestInjectOTLPConfig(t *testing.T) {
assert.Equal(t, 1, strings.Count(wd.Env, "env:"), "should have exactly one env: key")
})

t.Run("preserves user-defined OTEL_SERVICE_NAME and does not inject duplicate", func(t *testing.T) {
c := newCompiler()
wd := &WorkflowData{
ParsedFrontmatter: &FrontmatterConfig{
Observability: &ObservabilityConfig{
OTLP: &OTLPConfig{Endpoint: "https://traces.example.com"},
},
},
Env: "env:\n OTEL_SERVICE_NAME: my-service",
}
c.injectOTLPConfig(wd)

assert.Contains(t, wd.Env, "OTEL_SERVICE_NAME: my-service", "user-defined service name should be preserved")
assert.Equal(t, 1, strings.Count(wd.Env, "OTEL_SERVICE_NAME:"), "OTEL_SERVICE_NAME should appear exactly once")
assert.Contains(t, wd.Env, "OTEL_EXPORTER_OTLP_ENDPOINT: https://traces.example.com", "endpoint should still be injected")
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The test verifies OTEL_SERVICE_NAME is preserved and not duplicated, but doesn't assert that OTEL_RESOURCE_ATTRIBUTES is still injected when the user supplies their own service name.

💡 Suggested addition
assert.Contains(t, wd.Env, "OTEL_RESOURCE_ATTRIBUTES:", "resource attributes should still be injected when user defines OTEL_SERVICE_NAME")

This guards against a future regression where skipping OTEL_SERVICE_NAME injection accidentally skips the OTEL_RESOURCE_ATTRIBUTES line too. The current code is correct, but a test assertion makes the invariant explicit.

@copilot please address this.

t.Run("OTEL_SERVICE_NAME includes sanitized workflow ID when available", func(t *testing.T) {
c := newCompiler()
wd := &WorkflowData{
Expand Down
Loading