-
Notifications
You must be signed in to change notification settings - Fork 372
WIP: add support for OTLP trace export (http/json) #7531
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
Draft
ida613
wants to merge
17
commits into
master
Choose a base branch
from
ida613/otlp-traces-support
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,758
−7
Draft
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
72cf0d2
add trace_service.proto
ida613 3cada29
cursor
ida613 d1fa5c1
update config
ida613 797ac07
add support for grpc
ida613 5c31c87
update default protocol to http/json
ida613 9662041
only support http/json for now
ida613 bcb6c39
Delete packages/dd-trace/src/opentelemetry/otlp/otlp_grpc_exporter_ba…
ida613 d001714
revert otlp_transformer_base
ida613 ad3ea0b
Merge branch 'ida613/otlp-traces-support' of github.com:DataDog/dd-tr…
ida613 b9d226a
revert otlp transformer base
ida613 f8f503c
revert test files
ida613 e0c15d6
use protobuf types for span kind
ida613 62016b2
update config according to requirements
ida613 6f70c93
remove dual export
ida613 37ce718
update resource attributes and implement sampling to drop traces with…
ida613 bb767b2
integration test
ida613 05ac79c
fix linter error
ida613 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| 'use strict' | ||
|
|
||
| const assert = require('node:assert/strict') | ||
|
|
||
| const { fork } = require('child_process') | ||
| const { join } = require('path') | ||
| const { FakeAgent, sandboxCwd, useSandbox } = require('./helpers') | ||
|
|
||
| function waitForOtlpTraces (agent, timeout = 10000) { | ||
| return new Promise((resolve, reject) => { | ||
| const timer = setTimeout(() => reject(new Error('Timeout waiting for OTLP traces')), timeout) | ||
| agent.once('otlp-traces', (msg) => { | ||
| clearTimeout(timer) | ||
| resolve(msg) | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| describe('OTLP Trace Export', () => { | ||
| let agent | ||
| let cwd | ||
| const timeout = 10000 | ||
|
|
||
| useSandbox() | ||
|
|
||
| before(async () => { | ||
| cwd = sandboxCwd() | ||
| agent = await new FakeAgent().start() | ||
| }) | ||
|
|
||
| after(async () => { | ||
| await agent.stop() | ||
| }) | ||
|
|
||
| it('should export traces in OTLP JSON format', async () => { | ||
| const tracesPromise = waitForOtlpTraces(agent, timeout) | ||
|
|
||
| const proc = fork(join(cwd, 'opentelemetry/otlp-traces.js'), { | ||
| cwd, | ||
| env: { | ||
| DD_TRACE_AGENT_PORT: agent.port, | ||
| OTEL_TRACES_EXPORTER: 'otlp', | ||
| OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: `http://127.0.0.1:${agent.port}/v1/traces`, | ||
| DD_SERVICE: 'otlp-test-service', | ||
| DD_ENV: 'test', | ||
| DD_VERSION: '1.0.0', | ||
| }, | ||
| }) | ||
|
|
||
| const exitPromise = new Promise((resolve, reject) => { | ||
| const timer = setTimeout(() => reject(new Error('Process timed out')), timeout) | ||
| proc.on('error', reject) | ||
| proc.on('exit', (code) => { | ||
| clearTimeout(timer) | ||
| if (code !== 0) { | ||
| reject(new Error(`Process exited with status code ${code}`)) | ||
| } else { | ||
| resolve() | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| const { headers, payload } = await tracesPromise | ||
| await exitPromise | ||
|
|
||
| assert.strictEqual(headers['content-type'], 'application/json') | ||
|
|
||
| // Validate ExportTraceServiceRequest top-level structure | ||
| assert.ok(payload.resourceSpans, 'payload should have resourceSpans') | ||
| assert.strictEqual(payload.resourceSpans.length, 1) | ||
|
|
||
| const resourceSpan = payload.resourceSpans[0] | ||
|
|
||
| // Validate resource attributes | ||
| const resource = resourceSpan.resource | ||
| assert.ok(resource, 'resourceSpan should have resource') | ||
| assert.ok(Array.isArray(resource.attributes), 'resource should have attributes array') | ||
|
|
||
| const resourceAttrs = Object.fromEntries( | ||
| resource.attributes.map(({ key, value }) => [key, value]) | ||
| ) | ||
| assert.deepStrictEqual(resourceAttrs['service.name'], { stringValue: 'otlp-test-service' }) | ||
| assert.deepStrictEqual(resourceAttrs['deployment.environment'], { stringValue: 'test' }) | ||
| assert.deepStrictEqual(resourceAttrs['service.version'], { stringValue: '1.0.0' }) | ||
|
|
||
| // Validate scopeSpans | ||
| assert.ok(Array.isArray(resourceSpan.scopeSpans), 'resourceSpan should have scopeSpans') | ||
| assert.strictEqual(resourceSpan.scopeSpans.length, 1) | ||
|
|
||
| const scopeSpan = resourceSpan.scopeSpans[0] | ||
| assert.strictEqual(scopeSpan.scope.name, 'dd-trace-js') | ||
| assert.ok(scopeSpan.scope.version, 'scope should have a version') | ||
|
|
||
| // Validate spans | ||
| const spans = scopeSpan.spans | ||
| assert.strictEqual(spans.length, 3, 'should have 3 spans') | ||
|
|
||
| // Sort by name for stable ordering | ||
| spans.sort((a, b) => a.name.localeCompare(b.name)) | ||
|
|
||
| const [dbSpan, errSpan, webSpan] = spans | ||
|
|
||
| // All spans should share the same traceId | ||
| assert.deepStrictEqual(dbSpan.traceId, webSpan.traceId, 'all spans should share a traceId') | ||
| assert.deepStrictEqual(errSpan.traceId, webSpan.traceId, 'all spans should share a traceId') | ||
|
|
||
| // Root span (web.request) should not have parentSpanId | ||
| assert.strictEqual(webSpan.parentSpanId, undefined, 'root span should not have parentSpanId') | ||
|
|
||
| // Child spans should have parentSpanId equal to root span's spanId | ||
| assert.deepStrictEqual(dbSpan.parentSpanId, webSpan.spanId, 'child span should reference parent') | ||
| assert.deepStrictEqual(errSpan.parentSpanId, webSpan.spanId, 'error span should reference parent') | ||
|
|
||
| // Validate span names | ||
| assert.strictEqual(webSpan.name, 'web.request') | ||
| assert.strictEqual(dbSpan.name, 'db.query') | ||
| assert.strictEqual(errSpan.name, 'error.operation') | ||
|
|
||
| // Validate span kind (server=2, client=3 per OTLP proto SpanKind enum) | ||
| assert.strictEqual(webSpan.kind, 2, 'web.request should be SERVER kind') | ||
| assert.strictEqual(dbSpan.kind, 3, 'db.query should be CLIENT kind') | ||
|
|
||
| // Validate timing fields | ||
| for (const span of spans) { | ||
| assert.ok(span.startTimeUnixNano > 0, 'span should have a positive startTimeUnixNano') | ||
| assert.ok(span.endTimeUnixNano > 0, 'span should have a positive endTimeUnixNano') | ||
| assert.ok(span.endTimeUnixNano >= span.startTimeUnixNano, 'endTime should be >= startTime') | ||
| } | ||
|
|
||
| // Validate error span status | ||
| assert.strictEqual(errSpan.status.code, 2, 'error span should have STATUS_CODE_ERROR') | ||
| assert.strictEqual(errSpan.status.message, 'test error message') | ||
|
|
||
| // Validate non-error span status | ||
| assert.strictEqual(webSpan.status.code, 0, 'non-error span should have STATUS_CODE_UNSET') | ||
|
|
||
| // Validate span attributes include service.name and resource.name | ||
| const webAttrs = Object.fromEntries( | ||
| webSpan.attributes.map(({ key, value }) => [key, value]) | ||
| ) | ||
| assert.deepStrictEqual(webAttrs['service.name'], { stringValue: 'otlp-test-service' }) | ||
| assert.ok(webAttrs['resource.name'], 'span should have resource.name attribute') | ||
|
|
||
| // Validate custom tags appear as attributes | ||
| assert.deepStrictEqual(webAttrs['http.method'], { stringValue: 'GET' }) | ||
| assert.deepStrictEqual(webAttrs['http.url'], { stringValue: '/api/test' }) | ||
|
|
||
| const dbAttrs = Object.fromEntries( | ||
| dbSpan.attributes.map(({ key, value }) => [key, value]) | ||
| ) | ||
| assert.deepStrictEqual(dbAttrs['db.type'], { stringValue: 'postgres' }) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| 'use strict' | ||
|
|
||
| const tracer = require('dd-trace').init() | ||
|
|
||
| const rootSpan = tracer.startSpan('web.request', { | ||
| tags: { 'span.kind': 'server', 'http.method': 'GET', 'http.url': '/api/test' }, | ||
| }) | ||
|
|
||
| const childSpan = tracer.startSpan('db.query', { | ||
| childOf: rootSpan, | ||
| tags: { 'span.kind': 'client', 'db.type': 'postgres' }, | ||
| }) | ||
| childSpan.finish() | ||
|
|
||
| const errorSpan = tracer.startSpan('error.operation', { | ||
| childOf: rootSpan, | ||
| }) | ||
| errorSpan.setTag('error', true) | ||
| errorSpan.setTag('error.message', 'test error message') | ||
| errorSpan.finish() | ||
|
|
||
| rootSpan.finish() | ||
|
|
||
| // Allow time for the HTTP export request to complete | ||
| setTimeout(() => process.exit(0), 1500) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
this config var is not exactly in the requirement doc. This was added as the counterpart to
config.otelMetricsEnabledandconfig.otelLogsEnabled, and is equals to OTEL_TRACES_EXPORTER==otlp