Skip to content

Commit 0f70dda

Browse files
authored
Merge branch 'master' into szegedi/profiler-system-info
2 parents c09e149 + 2f03518 commit 0f70dda

File tree

72 files changed

+5443
-90
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+5443
-90
lines changed

.github/workflows/platform.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ jobs:
285285
runs-on: ubuntu-latest
286286
steps:
287287
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
288+
- uses: ./.github/actions/testagent/start
288289
- uses: ./.github/actions/node
289290
with:
290291
version: ${{ matrix.version }}

LICENSE-3rdparty.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ require,@datadog/libdatadog,Apache license 2.0,Copyright 2024 Datadog Inc.
33
require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
44
require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
55
require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
6+
require,@datadog/openfeature-node-server,Apache license 2.0,Copyright 2024 Datadog Inc.
67
require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
78
require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
89
require,@datadog/wasm-js-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.
910
require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
11+
require,@opentelemetry/api-logs,Apache license 2.0,Copyright OpenTelemetry Authors
1012
require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
13+
require,@opentelemetry/resources,Apache license 2.0,Copyright OpenTelemetry Authors
1114
require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
1215
require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
1316
require,dc-polyfill,MIT,Copyright 2023 Datadog Inc.
@@ -41,6 +44,8 @@ dev,@types/tap,MIT,Copyright (c) Microsoft Corp.
4144
dev,@eslint/eslintrc,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
4245
dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
4346
dev,@msgpack/msgpack,ISC,Copyright 2019 The MessagePack Community
47+
dev,@openfeature/core,Apache-2.0,Copyright OpenFeature Authors
48+
dev,@openfeature/server-sdk,Apache-2.0,Copyright OpenFeature Authors
4449
dev,@stylistic/eslint-plugin,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
4550
dev,axios,MIT,Copyright 2014-present Matt Zabriskie
4651
dev,benchmark,MIT,Copyright 2010-2016 Mathias Bynens Robert Kieffer John-David Dalton

benchmark/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ const exec = cmd => execSync(cmd, { stdio: [0, 1, 2] })
66
exec('node benchmark/core')
77
exec('node benchmark/scope')
88
exec('node benchmark/dd-trace')
9+
exec('node benchmark/openfeature')

benchmark/openfeature.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict'
2+
3+
const benchmark = require('./benchmark')
4+
const proxyquire = require('proxyquire')
5+
const { createSingleExposureEvent, createExposureEventArray } = require('./stubs/exposure-events')
6+
7+
const Config = require('../packages/dd-trace/src/config')
8+
const ExposuresWriter = proxyquire('../packages/dd-trace/src/openfeature/writers/exposures', {
9+
'../../exporters/common/request': () => {}
10+
})
11+
12+
const config = new Config({ service: 'benchmark', version: '1.0.0', env: 'test' })
13+
const suite = benchmark('openfeature')
14+
15+
let writer
16+
let singleEvent
17+
let eventArray
18+
19+
suite
20+
.add('ExposuresWriter#append (single event)', {
21+
onStart () {
22+
writer = new ExposuresWriter(config)
23+
writer.setEnabled(true)
24+
singleEvent = createSingleExposureEvent()
25+
},
26+
fn () {
27+
writer.append(singleEvent)
28+
}
29+
})
30+
.add('ExposuresWriter#append (event array)', {
31+
onStart () {
32+
writer = new ExposuresWriter(config)
33+
writer.setEnabled(true)
34+
eventArray = createExposureEventArray(10)
35+
},
36+
fn () {
37+
writer.append(eventArray)
38+
}
39+
})
40+
.add('ExposuresWriter#append (disabled, single event)', {
41+
onStart () {
42+
writer = new ExposuresWriter(config)
43+
writer.setEnabled(false)
44+
singleEvent = createSingleExposureEvent()
45+
},
46+
fn () {
47+
writer.append(singleEvent)
48+
// Clear buffer periodically to prevent unbounded growth during benchmarking
49+
if (writer._pendingEvents.length >= 1000) {
50+
writer._pendingEvents = []
51+
}
52+
}
53+
})
54+
.add('ExposuresWriter#append (disabled, event array)', {
55+
onStart () {
56+
writer = new ExposuresWriter(config)
57+
writer.setEnabled(false)
58+
eventArray = createExposureEventArray(10)
59+
},
60+
fn () {
61+
writer.append(eventArray)
62+
// Clear buffer periodically to prevent unbounded growth during benchmarking
63+
if (writer._pendingEvents.length >= 1000) {
64+
writer._pendingEvents = []
65+
}
66+
}
67+
})
68+
.add('ExposuresWriter#makePayload', {
69+
onStart () {
70+
writer = new ExposuresWriter(config)
71+
eventArray = createExposureEventArray(100)
72+
},
73+
fn () {
74+
writer.makePayload(eventArray)
75+
}
76+
})
77+
78+
suite.run()

benchmark/stubs/exposure-events.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict'
2+
3+
function createSingleExposureEvent () {
4+
return {
5+
timestamp: Date.now(),
6+
allocation: { key: 'allocation-123' },
7+
flag: { key: 'test-flag' },
8+
variant: { key: 'variant-a' },
9+
subject: {
10+
id: 'user-123',
11+
type: 'user',
12+
attributes: { plan: 'premium' }
13+
}
14+
}
15+
}
16+
17+
function createExposureEventArray (count = 10) {
18+
return Array(count).fill(null).map((_, i) => ({
19+
timestamp: Date.now(),
20+
allocation: { key: `allocation-${i}` },
21+
flag: { key: `test-flag-${i}` },
22+
variant: { key: 'variant-a' },
23+
subject: {
24+
id: `user-${i}`,
25+
type: 'user',
26+
attributes: { plan: 'premium' }
27+
}
28+
}))
29+
}
30+
31+
module.exports = {
32+
createSingleExposureEvent,
33+
createExposureEventArray
34+
}

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ services:
180180
image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.33.1
181181
ports:
182182
- "127.0.0.1:9126:9126"
183+
- "127.0.0.1:4318:4318"
183184
environment:
184185
- LOG_LEVEL=DEBUG
185186
- TRACE_LANGUAGE=javascript

docs/API.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,44 @@ The following attributes are available to override Datadog-specific options:
379379
* `resource.name`: The resource name to be used for this span. The operation name will be used if this is not provided.
380380
* `span.type`: The span type to be used for this span. Will fallback to `custom` if not provided.
381381

382+
<h3 id="opentelemetry-logs">OpenTelemetry Logs</h3>
383+
384+
dd-trace-js includes experimental support for OpenTelemetry logs, designed as a drop-in replacement for the OpenTelemetry SDK. This support is primarily intended for logging libraries rather than direct user configuration. Enable it by setting `DD_LOGS_OTEL_ENABLED=true` and use the [OpenTelemetry Logs API](https://open-telemetry.github.io/opentelemetry-js/modules/_opentelemetry_api-logs.html) to emit structured log data:
385+
386+
```javascript
387+
require('dd-trace').init()
388+
const { logs } = require('@opentelemetry/api-logs')
389+
const express = require('express')
390+
391+
const app = express()
392+
const logger = logs.getLogger('my-service', '1.0.0')
393+
394+
app.get('/api/users/:id', (req, res) => {
395+
logger.emit({
396+
severityText: 'INFO',
397+
severityNumber: 9,
398+
body: `Processing user request for ID: ${req.params.id}`,
399+
})
400+
res.json({ id: req.params.id, name: 'John Doe' })
401+
})
402+
403+
app.listen(3000)
404+
```
405+
406+
#### Supported Configuration
407+
408+
The Datadog SDK supports many of the configurations supported by the OpenTelemetry SDK. The following environment variables are supported:
409+
410+
- `DD_LOGS_OTEL_ENABLED` - Enable OpenTelemetry logs (default: `false`)
411+
- `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` - OTLP endpoint URL for logs (default: `http://localhost:4318`)
412+
- `OTEL_EXPORTER_OTLP_LOGS_HEADERS` - Optional headers in JSON format for logs (default: `{}`)
413+
- `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` - OTLP protocol for logs (default: `http/protobuf`)
414+
- `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` - Request timeout in milliseconds for logs (default: `10000`)
415+
- `OTEL_BSP_SCHEDULE_DELAY` - Batch timeout in milliseconds (default: `5000`)
416+
- `OTEL_BSP_MAX_EXPORT_BATCH_SIZE` - Maximum logs per batch (default: `512`)
417+
418+
Logs are exported via OTLP over HTTP. The protocol can be configured using `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` or `OTEL_EXPORTER_OTLP_PROTOCOL` environment variables. Supported protocols are `http/protobuf` (default) and `http/json`. For complete OTLP exporter configuration options, see the [OpenTelemetry OTLP Exporter documentation](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/).
419+
382420
<h2 id="advanced-configuration">Advanced Configuration</h2>
383421

384422
<h3 id="tracer-settings">Tracer settings</h3>

index.d.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ interface Tracer extends opentracing.Tracer {
139139
*/
140140
llmobs: tracer.llmobs.LLMObs;
141141

142+
/**
143+
* OpenFeature Provider with Remote Config integration.
144+
*
145+
* Extends DatadogNodeServerProvider with Remote Config integration for dynamic flag configuration.
146+
* Enable with DD_FLAGGING_PROVIDER_ENABLED=true.
147+
*
148+
* @beta This feature is in preview and not ready for production use
149+
*/
150+
openfeature: tracer.OpenFeatureProvider;
151+
142152
/**
143153
* AI Guard SDK
144154
*/
@@ -199,7 +209,7 @@ interface Plugins {
199209
"ioredis": tracer.plugins.ioredis;
200210
"iovalkey": tracer.plugins.iovalkey;
201211
"jest": tracer.plugins.jest;
202-
"kafkajs": tracer.plugins.kafkajs
212+
"kafkajs": tracer.plugins.kafkajs;
203213
"knex": tracer.plugins.knex;
204214
"koa": tracer.plugins.koa;
205215
"langchain": tracer.plugins.langchain;
@@ -623,6 +633,21 @@ declare namespace tracer {
623633
*/
624634
maxContentSize?: number
625635
}
636+
637+
/**
638+
* Configuration for Feature Flagging & Experimentation.
639+
*
640+
* @beta This feature is in preview and not ready for production use
641+
*/
642+
flaggingProvider?: {
643+
/**
644+
* Whether to enable the feature flagging provider.
645+
* Requires Remote Config to be properly configured.
646+
*
647+
* @default false
648+
*/
649+
enabled?: boolean
650+
}
626651
};
627652

628653
/**
@@ -1098,6 +1123,65 @@ declare namespace tracer {
10981123
eventTrackingV2: EventTrackingV2
10991124
}
11001125

1126+
/**
1127+
* Flagging Provider (OpenFeature-compatible).
1128+
*
1129+
* Wraps @datadog/openfeature-node-server with Remote Config integration for dynamic flag configuration.
1130+
* Implements the OpenFeature Provider interface for flag evaluation.
1131+
*
1132+
* @beta This feature is in preview and not ready for production use
1133+
*/
1134+
export interface OpenFeatureProvider {
1135+
/**
1136+
* Metadata about this provider.
1137+
*/
1138+
metadata: { name: string; [key: string]: any };
1139+
1140+
/**
1141+
* Resolves a boolean flag value.
1142+
*
1143+
* @param flagKey The key of the flag to evaluate
1144+
* @param defaultValue The default value to return if evaluation fails
1145+
* @param context Evaluation context (e.g., user attributes)
1146+
* @param logger Optional logger instance
1147+
* @returns Promise resolving to evaluation result with value and reason
1148+
*/
1149+
resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: object, logger: object): Promise<{ value: boolean; reason?: string; [key: string]: any }>;
1150+
1151+
/**
1152+
* Resolves a string flag value.
1153+
*
1154+
* @param flagKey The key of the flag to evaluate
1155+
* @param defaultValue The default value to return if evaluation fails
1156+
* @param context Evaluation context (e.g., user attributes)
1157+
* @param logger Optional logger instance
1158+
* @returns Promise resolving to evaluation result with value and reason
1159+
*/
1160+
resolveStringEvaluation(flagKey: string, defaultValue: string, context: object, logger: object): Promise<{ value: string; reason?: string; [key: string]: any }>;
1161+
1162+
/**
1163+
* Resolves a number flag value.
1164+
*
1165+
* @param flagKey The key of the flag to evaluate
1166+
* @param defaultValue The default value to return if evaluation fails
1167+
* @param context Evaluation context (e.g., user attributes)
1168+
* @param logger Optional logger instance
1169+
* @returns Promise resolving to evaluation result with value and reason
1170+
*/
1171+
resolveNumberEvaluation(flagKey: string, defaultValue: number, context: object, logger: object): Promise<{ value: number; reason?: string; [key: string]: any }>;
1172+
1173+
/**
1174+
* Resolves an object flag value.
1175+
*
1176+
* @param flagKey The key of the flag to evaluate
1177+
* @param defaultValue The default value to return if evaluation fails
1178+
* @param context Evaluation context (e.g., user attributes)
1179+
* @param logger Optional logger instance
1180+
* @returns Promise resolving to evaluation result with value and reason
1181+
*/
1182+
resolveObjectEvaluation<T = any>(flagKey: string, defaultValue: T, context: object, logger: object): Promise<{ value: T; reason?: string; [key: string]: any }>;
1183+
}
1184+
11011185
export namespace aiguard {
11021186

11031187
/**

integration-tests/appsec/iast-esbuild/esbuild.common-config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ module.exports = {
1313
'@datadog/native-iast-taint-tracking',
1414
'@datadog/native-iast-rewriter',
1515

16+
// @openfeature/core is a peer dependency of @openfeature/server-sdk
17+
// which is used by @datadog/openfeature-node-server
18+
'@openfeature/core',
19+
1620
// required if you encounter graphql errors during the build step
1721
// see https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/#bundling
1822
'graphql/language/visitor',

integration-tests/helpers/fake-agent.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ function buildExpressServer (agent) {
277277
app.use(bodyParser.raw({ limit: Infinity, type: 'application/msgpack' }))
278278
app.use(bodyParser.json({ limit: Infinity, type: 'application/json' }))
279279

280+
app.get('/info', (req, res) => {
281+
res.json({
282+
endpoints: ['/evp_proxy/v2']
283+
})
284+
})
285+
280286
app.put('/v0.4/traces', (req, res) => {
281287
if (req.body.length === 0) return res.status(200).send()
282288
res.status(200).send({ rate_by_service: { 'service:,env:': 1 } })
@@ -405,6 +411,14 @@ function buildExpressServer (agent) {
405411
})
406412
})
407413

414+
app.post('/evp_proxy/v2/api/v2/exposures', (req, res) => {
415+
res.status(200).send()
416+
agent.emit('exposures', {
417+
headers: req.headers,
418+
payload: req.body
419+
})
420+
})
421+
408422
// Ensure that any failure inside of Express isn't swallowed and returned as a 500, but instead crashes the test
409423
app.use((err, req, res, next) => {
410424
if (!err) next()

0 commit comments

Comments
 (0)