dd-trace 5.48.1 → 5.49.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -4
- package/README.md +2 -2
- package/package.json +4 -6
- package/packages/datadog-esbuild/index.js +4 -3
- package/packages/datadog-instrumentations/src/playwright.js +28 -14
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +0 -1
- package/packages/datadog-shimmer/src/shimmer.js +3 -128
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/config.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +34 -16
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/writers.js +9 -5
- package/packages/dd-trace/src/llmobs/index.js +23 -9
- package/packages/dd-trace/src/llmobs/sdk.js +0 -7
- package/packages/dd-trace/src/llmobs/writers/base.js +66 -38
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +9 -6
- package/packages/dd-trace/src/llmobs/writers/{spans/base.js → spans.js} +20 -12
- package/packages/dd-trace/src/llmobs/writers/util.js +60 -0
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +0 -8
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +0 -23
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +0 -17
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -20,6 +20,7 @@ require,limiter,MIT,Copyright 2011 John Hurliman
|
|
|
20
20
|
require,lodash.sortby,MIT,Copyright JS Foundation and other contributors
|
|
21
21
|
require,lru-cache,ISC,Copyright (c) 2010-2022 Isaac Z. Schlueter and Contributors
|
|
22
22
|
require,module-details-from-path,MIT,Copyright 2016 Thomas Watson Steen
|
|
23
|
+
require,mutexify,MIT,Copyright (c) 2014 Mathias Buus
|
|
23
24
|
require,opentracing,MIT,Copyright 2016 Resonance Labs Inc
|
|
24
25
|
require,path-to-regexp,MIT,Copyright 2014 Blake Embrey
|
|
25
26
|
require,pprof-format,MIT,Copyright 2022 Stephen Belanger
|
|
@@ -31,7 +32,6 @@ require,semifies,Apache license 2.0,Copyright Authors
|
|
|
31
32
|
require,shell-quote,mit,Copyright (c) 2013 James Halliday
|
|
32
33
|
require,source-map,BSD-3-Clause,Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
|
33
34
|
require,ttl-set,MIT,Copyright (c) 2024 Thomas Watson
|
|
34
|
-
dev,@apollo/server,MIT,Copyright (c) 2016-2020 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.)
|
|
35
35
|
dev,@babel/helpers,MIT,Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
|
36
36
|
dev,@types/node,MIT,Copyright Authors
|
|
37
37
|
dev,@eslint/eslintrc,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
@@ -39,7 +39,6 @@ dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.open
|
|
|
39
39
|
dev,@msgpack/msgpack,ISC,Copyright 2019 The MessagePack Community
|
|
40
40
|
dev,@stylistic/eslint-plugin-js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
41
41
|
dev,autocannon,MIT,Copyright 2016 Matteo Collina
|
|
42
|
-
dev,aws-sdk,Apache 2.0,Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
43
42
|
dev,axios,MIT,Copyright 2014-present Matt Zabriskie
|
|
44
43
|
dev,benchmark,MIT,Copyright 2010-2016 Mathias Bynens Robert Kieffer John-David Dalton
|
|
45
44
|
dev,body-parser,MIT,Copyright 2014 Jonathan Ong 2014-2015 Douglas Christopher Wilson
|
|
@@ -48,7 +47,6 @@ dev,chalk,MIT,Copyright Sindre Sorhus
|
|
|
48
47
|
dev,checksum,MIT,Copyright Daniel D. Shaw
|
|
49
48
|
dev,cli-table3,MIT,Copyright 2014 James Talmage
|
|
50
49
|
dev,dotenv,BSD-2-Clause,Copyright 2015 Scott Motte
|
|
51
|
-
dev,esbuild,MIT,Copyright (c) 2020 Evan Wallace
|
|
52
50
|
dev,eslint,MIT,Copyright JS Foundation and other contributors https://js.foundation
|
|
53
51
|
dev,eslint-config-standard,MIT,Copyright Feross Aboukhadijeh
|
|
54
52
|
dev,eslint-plugin-import,MIT,Copyright 2015 Ben Mosher
|
|
@@ -62,7 +60,6 @@ dev,glob,ISC,Copyright Isaac Z. Schlueter and Contributors
|
|
|
62
60
|
dev,globals,MIT,Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
63
61
|
dev,graphql,MIT,Copyright 2015 Facebook Inc.
|
|
64
62
|
dev,jszip,MIT,Copyright 2015-2016 Stuart Knightley and contributors
|
|
65
|
-
dev,knex,MIT,Copyright (c) 2013-present Tim Griesser
|
|
66
63
|
dev,mkdirp,MIT,Copyright 2010 James Halliday
|
|
67
64
|
dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation
|
|
68
65
|
dev,multer,MIT,Copyright 2014 Hage Yaapa
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
Most of the documentation for `dd-trace` is available on these webpages:
|
|
16
16
|
|
|
17
17
|
- [Tracing Node.js Applications](https://docs.datadoghq.com/tracing/languages/nodejs/) - most project documentation, including setup instructions
|
|
18
|
-
- [Configuring the
|
|
18
|
+
- [Configuring the Node.js Tracing Library](https://docs.datadoghq.com/tracing/trace_collection/library_config/nodejs) - environment variables and config options
|
|
19
19
|
- [API Documentation](https://datadog.github.io/dd-trace-js) - method signatures, plugin list, and some usage examples
|
|
20
20
|
- [APM Terms and Concepts](https://docs.datadoghq.com/tracing/visualization/) - a glossary of concepts applicable across all languages
|
|
21
21
|
|
|
@@ -59,7 +59,7 @@ When a new release line is introduced the previous release line then enters main
|
|
|
59
59
|
Once that year is up the release line enters End of Life and will not receive new updates.
|
|
60
60
|
The library also follows the Node.js LTS lifecycle wherein new release lines drop compatibility with Node.js versions that reach end-of-life (with the maintenance release line still receiving updates for a year).
|
|
61
61
|
|
|
62
|
-
For more information about library versioning and compatibility, see the [
|
|
62
|
+
For more information about library versioning and compatibility, see the [Node.js Compatibility Requirements](https://docs.datadoghq.com/tracing/trace_collection/compatibility/nodejs/#releases) page.
|
|
63
63
|
|
|
64
64
|
Changes associated with each individual release are documented on the [GitHub Releases](https://github.com/DataDog/dd-trace-js/releases) screen.
|
|
65
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.49.1",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"test:integration:cucumber": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"",
|
|
49
49
|
"test:integration:cypress": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"",
|
|
50
50
|
"test:integration:debugger": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/debugger/*.spec.js\"",
|
|
51
|
+
"test:integration:esbuild": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/esbuild/*.spec.js\"",
|
|
51
52
|
"test:integration:jest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"",
|
|
52
53
|
"test:integration:mocha": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/mocha/*.spec.js\"",
|
|
53
54
|
"test:integration:playwright": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/playwright/*.spec.js\"",
|
|
@@ -105,6 +106,7 @@
|
|
|
105
106
|
"lodash.sortby": "^4.7.0",
|
|
106
107
|
"lru-cache": "^7.14.0",
|
|
107
108
|
"module-details-from-path": "^1.0.3",
|
|
109
|
+
"mutexify": "^1.4.0",
|
|
108
110
|
"opentracing": ">=0.12.1",
|
|
109
111
|
"path-to-regexp": "^0.1.12",
|
|
110
112
|
"pprof-format": "^2.1.0",
|
|
@@ -118,7 +120,6 @@
|
|
|
118
120
|
"ttl-set": "^1.0.0"
|
|
119
121
|
},
|
|
120
122
|
"devDependencies": {
|
|
121
|
-
"@apollo/server": "^4.11.0",
|
|
122
123
|
"@babel/helpers": "^7.26.10",
|
|
123
124
|
"@eslint/eslintrc": "^3.2.0",
|
|
124
125
|
"@eslint/js": "^9.19.0",
|
|
@@ -126,7 +127,6 @@
|
|
|
126
127
|
"@stylistic/eslint-plugin-js": "^3.0.1",
|
|
127
128
|
"@types/node": "^16.0.0",
|
|
128
129
|
"autocannon": "^4.5.2",
|
|
129
|
-
"aws-sdk": "^2.1446.0",
|
|
130
130
|
"axios": "^1.8.2",
|
|
131
131
|
"benchmark": "^2.1.4",
|
|
132
132
|
"body-parser": "^1.20.3",
|
|
@@ -135,7 +135,6 @@
|
|
|
135
135
|
"checksum": "^1.0.0",
|
|
136
136
|
"cli-table3": "^0.6.3",
|
|
137
137
|
"dotenv": "16.3.1",
|
|
138
|
-
"esbuild": "^0.25.0",
|
|
139
138
|
"eslint": "^9.19.0",
|
|
140
139
|
"eslint-config-standard": "^17.1.0",
|
|
141
140
|
"eslint-plugin-import": "^2.31.0",
|
|
@@ -144,12 +143,11 @@
|
|
|
144
143
|
"eslint-plugin-promise": "^7.2.1",
|
|
145
144
|
"eslint-plugin-unicorn": "^57.0.0",
|
|
146
145
|
"express": "^4.21.2",
|
|
147
|
-
"get-port": "^
|
|
146
|
+
"get-port": "^5.1.1",
|
|
148
147
|
"glob": "^7.1.6",
|
|
149
148
|
"globals": "^15.10.0",
|
|
150
149
|
"graphql": "0.13.2",
|
|
151
150
|
"jszip": "^3.5.0",
|
|
152
|
-
"knex": "^2.4.2",
|
|
153
151
|
"mkdirp": "^3.0.1",
|
|
154
152
|
"mocha": "^10",
|
|
155
153
|
"multer": "^1.4.5-lts.1",
|
|
@@ -47,8 +47,7 @@ const DEBUG = !!process.env.DD_TRACE_DEBUG
|
|
|
47
47
|
// Those packages will still be handled via RITM
|
|
48
48
|
// Attempting to instrument them would fail as they have no package.json file
|
|
49
49
|
for (const pkg of INSTRUMENTED) {
|
|
50
|
-
if (builtins.has(pkg)) continue
|
|
51
|
-
if (pkg.startsWith('node:')) continue
|
|
50
|
+
if (builtins.has(pkg) || pkg.startsWith('node:')) continue
|
|
52
51
|
modulesOfInterest.add(pkg)
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -71,7 +70,9 @@ module.exports.setup = function (build) {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
// TODO: Should this also check for namespace === 'file'?
|
|
74
|
-
if (args.path
|
|
73
|
+
if (!modulesOfInterest.has(args.path) &&
|
|
74
|
+
args.path.startsWith('@') &&
|
|
75
|
+
!args.importer.includes('node_modules/')) {
|
|
75
76
|
// This is the Next.js convention for loading local files
|
|
76
77
|
if (DEBUG) console.log(`@LOCAL: ${args.path}`)
|
|
77
78
|
return
|
|
@@ -761,9 +761,17 @@ addHook({
|
|
|
761
761
|
return rootSuite
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
|
|
764
|
+
// We need to proxy the createRootSuite function because the function is not configurable
|
|
765
|
+
const proxy = new Proxy(loadUtilsPackage, {
|
|
766
|
+
get (target, prop) {
|
|
767
|
+
if (prop === 'createRootSuite') {
|
|
768
|
+
return newCreateRootSuite
|
|
769
|
+
}
|
|
770
|
+
return target[prop]
|
|
771
|
+
}
|
|
772
|
+
})
|
|
765
773
|
|
|
766
|
-
return
|
|
774
|
+
return proxy
|
|
767
775
|
})
|
|
768
776
|
|
|
769
777
|
// main process hook
|
|
@@ -805,19 +813,25 @@ addHook({
|
|
|
805
813
|
|
|
806
814
|
const page = this
|
|
807
815
|
|
|
808
|
-
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
816
|
+
try {
|
|
817
|
+
if (page) {
|
|
818
|
+
const isRumActive = await page.evaluate(() => {
|
|
819
|
+
if (window.DD_RUM && window.DD_RUM.getInternalContext) {
|
|
820
|
+
return !!window.DD_RUM.getInternalContext()
|
|
821
|
+
} else {
|
|
822
|
+
return false
|
|
823
|
+
}
|
|
824
|
+
})
|
|
815
825
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
826
|
+
if (isRumActive) {
|
|
827
|
+
testPageGotoCh.publish({
|
|
828
|
+
isRumActive,
|
|
829
|
+
page
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
} catch (e) {
|
|
834
|
+
// ignore errors such as redirects, context destroyed, etc
|
|
821
835
|
}
|
|
822
836
|
|
|
823
837
|
return response
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const log = require('../../dd-trace/src/log')
|
|
4
|
-
|
|
5
3
|
function copyProperties (original, wrapped) {
|
|
6
4
|
// TODO getPrototypeOf is not fast. Should we instead do this in specific
|
|
7
5
|
// instrumentations where needed?
|
|
@@ -24,9 +22,7 @@ function copyProperties (original, wrapped) {
|
|
|
24
22
|
function wrapFunction (original, wrapper) {
|
|
25
23
|
if (typeof original === 'function') assertNotClass(original)
|
|
26
24
|
|
|
27
|
-
const wrapped =
|
|
28
|
-
? safeWrapper(original, wrapper)
|
|
29
|
-
: wrapper(original)
|
|
25
|
+
const wrapped = wrapper(original)
|
|
30
26
|
|
|
31
27
|
if (typeof original === 'function') copyProperties(original, wrapped)
|
|
32
28
|
|
|
@@ -37,36 +33,6 @@ const wrapFn = function (original, delegate) {
|
|
|
37
33
|
throw new Error('calling `wrap()` with 2 args is deprecated. Use wrapFunction instead.')
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
// This is only used in safe mode. It's a simple state machine to track if the
|
|
41
|
-
// original method was called and if it returned. We need this to determine if
|
|
42
|
-
// an error was thrown by the original method, or by us. We'll use one of these
|
|
43
|
-
// per call to a wrapped method.
|
|
44
|
-
class CallState {
|
|
45
|
-
constructor () {
|
|
46
|
-
this.called = false
|
|
47
|
-
this.completed = false
|
|
48
|
-
this.retVal = undefined
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
startCall () {
|
|
52
|
-
this.called = true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
endCall (retVal) {
|
|
56
|
-
this.completed = true
|
|
57
|
-
this.retVal = retVal
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isPromise (obj) {
|
|
62
|
-
return obj && typeof obj === 'object' && typeof obj.then === 'function'
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let safeMode = !!process.env.DD_INEJCTION_ENABLED
|
|
66
|
-
function setSafe (value) {
|
|
67
|
-
safeMode = value
|
|
68
|
-
}
|
|
69
|
-
|
|
70
36
|
function wrapMethod (target, name, wrapper, noAssert) {
|
|
71
37
|
if (!noAssert) {
|
|
72
38
|
assertMethod(target, name)
|
|
@@ -74,9 +40,7 @@ function wrapMethod (target, name, wrapper, noAssert) {
|
|
|
74
40
|
}
|
|
75
41
|
|
|
76
42
|
const original = target[name]
|
|
77
|
-
const wrapped =
|
|
78
|
-
? safeWrapper(original, wrapper)
|
|
79
|
-
: wrapper(original)
|
|
43
|
+
const wrapped = wrapper(original)
|
|
80
44
|
|
|
81
45
|
const descriptor = Object.getOwnPropertyDescriptor(target, name)
|
|
82
46
|
|
|
@@ -110,94 +74,6 @@ function wrapMethod (target, name, wrapper, noAssert) {
|
|
|
110
74
|
return target
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
function safeWrapper (original, wrapper) {
|
|
114
|
-
// In this mode, we make a best-effort attempt to handle errors that are thrown
|
|
115
|
-
// by us, rather than wrapped code. With such errors, we log them, and then attempt
|
|
116
|
-
// to return the result as if no wrapping was done at all.
|
|
117
|
-
//
|
|
118
|
-
// Caveats:
|
|
119
|
-
// * If the original function is called in a later iteration of the event loop,
|
|
120
|
-
// and we throw _then_, then it won't be caught by this. In practice, we always call
|
|
121
|
-
// the original function synchronously, so this is not a problem.
|
|
122
|
-
// * While async errors are dealt with here, errors in callbacks are not. This
|
|
123
|
-
// is because we don't necessarily know _for sure_ that any function arguments
|
|
124
|
-
// are wrapped by us. We could wrap them all anyway and just make that assumption,
|
|
125
|
-
// or just assume that the last argument is always a callback set by us if it's a
|
|
126
|
-
// function, but those don't seem like things we can rely on. We could add a
|
|
127
|
-
// `shimmer.markCallbackAsWrapped()` function that's a no-op outside safe-mode,
|
|
128
|
-
// but that means modifying every instrumentation. Even then, the complexity of
|
|
129
|
-
// this code increases because then we'd need to effectively do the reverse of
|
|
130
|
-
// what we're doing for synchronous functions. This is a TODO.
|
|
131
|
-
|
|
132
|
-
// We're going to hold on to current callState in this variable in this scope,
|
|
133
|
-
// which is fine because any time we reference it, we're referencing it synchronously.
|
|
134
|
-
// We'll use it in the our wrapper (which, again, is called syncrhonously), and in the
|
|
135
|
-
// errorHandler, which will already have been bound to this callState.
|
|
136
|
-
let currentCallState
|
|
137
|
-
|
|
138
|
-
// Rather than calling the original function directly from the shim wrapper, we wrap
|
|
139
|
-
// it again so that we can track if it was called and if it returned. This is because
|
|
140
|
-
// we need to know if an error was thrown by the original function, or by us.
|
|
141
|
-
// We could do this inside the `wrapper` function defined below, which would simplify
|
|
142
|
-
// managing the callState, but then we'd be calling `wrapper` on each invocation, so
|
|
143
|
-
// instead we do it here, once.
|
|
144
|
-
const innerWrapped = wrapper(function (...args) {
|
|
145
|
-
// We need to stash the callState here because of recursion.
|
|
146
|
-
const callState = currentCallState
|
|
147
|
-
callState.startCall()
|
|
148
|
-
const retVal = original.apply(this, args)
|
|
149
|
-
if (isPromise(retVal)) {
|
|
150
|
-
retVal.then(callState.endCall.bind(callState))
|
|
151
|
-
} else {
|
|
152
|
-
callState.endCall(retVal)
|
|
153
|
-
}
|
|
154
|
-
return retVal
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// This is the crux of what we're doing in safe mode. It handles errors
|
|
158
|
-
// that _we_ cause, by logging them, and transparently providing results
|
|
159
|
-
// as if no wrapping was done at all. That means detecting (via callState)
|
|
160
|
-
// whether the function has already run or not, and if it has, returning
|
|
161
|
-
// the result, and otherwise calling the original function unwrapped.
|
|
162
|
-
const handleError = function (args, callState, e) {
|
|
163
|
-
if (callState.completed) {
|
|
164
|
-
// error was thrown after original function returned/resolved, so
|
|
165
|
-
// it was us. log it.
|
|
166
|
-
log.error('Shimmer error was thrown after original function returned/resolved', e)
|
|
167
|
-
// original ran and returned something. return it.
|
|
168
|
-
return callState.retVal
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!callState.called) {
|
|
172
|
-
// error was thrown before original function was called, so
|
|
173
|
-
// it was us. log it.
|
|
174
|
-
log.error('Shimmer error was thrown before original function was called', e)
|
|
175
|
-
// original never ran. call it unwrapped.
|
|
176
|
-
return original.apply(this, args)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// error was thrown during original function execution, so
|
|
180
|
-
// it was them. throw.
|
|
181
|
-
throw e
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// The wrapped function is the one that will be called by the user.
|
|
185
|
-
// It calls our version of the original function, which manages the
|
|
186
|
-
// callState. That way when we use the errorHandler, it can tell where
|
|
187
|
-
// the error originated.
|
|
188
|
-
return function (...args) {
|
|
189
|
-
currentCallState = new CallState()
|
|
190
|
-
const errorHandler = handleError.bind(this, args, currentCallState)
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const retVal = innerWrapped.apply(this, args)
|
|
194
|
-
return isPromise(retVal) ? retVal.catch(errorHandler) : retVal
|
|
195
|
-
} catch (e) {
|
|
196
|
-
return errorHandler(e)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
77
|
function wrap (target, name, wrapper) {
|
|
202
78
|
return typeof name === 'function'
|
|
203
79
|
? wrapFn(target, name)
|
|
@@ -256,6 +132,5 @@ function assertNotClass (target) {
|
|
|
256
132
|
module.exports = {
|
|
257
133
|
wrap,
|
|
258
134
|
wrapFunction,
|
|
259
|
-
massWrap
|
|
260
|
-
setSafe
|
|
135
|
+
massWrap
|
|
261
136
|
}
|
|
@@ -13,7 +13,7 @@ module.exports = function extractSensitiveRanges (evidence) {
|
|
|
13
13
|
let regexResult = pattern.exec(evidence.value)
|
|
14
14
|
while (regexResult != null) {
|
|
15
15
|
if (!regexResult.groups.LITERAL) continue
|
|
16
|
-
// Computing indices manually since
|
|
16
|
+
// Computing indices manually since Node.js 12 does not support d flag on regular expressions
|
|
17
17
|
// TODO Get indices from group by adding d flag in regular expression
|
|
18
18
|
const start = regexResult.index + (regexResult[0].length - regexResult.groups.LITERAL.length - 1)
|
|
19
19
|
const end = start + regexResult.groups.LITERAL.length
|
|
@@ -530,7 +530,7 @@ class Config {
|
|
|
530
530
|
this._setValue(defaults, 'isManualApiEnabled', false)
|
|
531
531
|
this._setValue(defaults, 'langchain.spanCharLimit', 128)
|
|
532
532
|
this._setValue(defaults, 'langchain.spanPromptCompletionSampleRate', 1.0)
|
|
533
|
-
this._setValue(defaults, 'llmobs.agentlessEnabled',
|
|
533
|
+
this._setValue(defaults, 'llmobs.agentlessEnabled', undefined)
|
|
534
534
|
this._setValue(defaults, 'llmobs.enabled', false)
|
|
535
535
|
this._setValue(defaults, 'llmobs.mlApp', undefined)
|
|
536
536
|
this._setValue(defaults, 'ciVisibilityTestSessionName', '')
|
|
@@ -594,10 +594,10 @@ class Config {
|
|
|
594
594
|
this._setValue(defaults, 'url', undefined)
|
|
595
595
|
this._setValue(defaults, 'version', pkg.version)
|
|
596
596
|
this._setValue(defaults, 'instrumentation_config_id', undefined)
|
|
597
|
-
this._setValue(defaults, 'aws.dynamoDb.tablePrimaryKeys', undefined)
|
|
598
597
|
this._setValue(defaults, 'vertexai.spanCharLimit', 128)
|
|
599
598
|
this._setValue(defaults, 'vertexai.spanPromptCompletionSampleRate', 1.0)
|
|
600
599
|
this._setValue(defaults, 'trace.aws.addSpanPointers', true)
|
|
600
|
+
this._setValue(defaults, 'trace.dynamoDb.tablePrimaryKeys', undefined)
|
|
601
601
|
this._setValue(defaults, 'trace.nativeSpanEvents', false)
|
|
602
602
|
}
|
|
603
603
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const lock = require('mutexify/promise')()
|
|
3
4
|
const { getGeneratedPosition } = require('./source-maps')
|
|
4
|
-
const lock = require('./lock')()
|
|
5
5
|
const session = require('./session')
|
|
6
6
|
const { compile: compileCondition, compileSegments, templateRequiresEvaluation } = require('./condition')
|
|
7
7
|
const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
|
|
@@ -64,14 +64,14 @@ async function addBreakpoint (probe) {
|
|
|
64
64
|
const release = await lock()
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
log.debug(
|
|
68
|
-
'[debugger:devtools_client] Adding breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
69
|
-
url, lineNumber, columnNumber, probe.id, probe.version
|
|
70
|
-
)
|
|
71
|
-
|
|
72
67
|
const locationKey = generateLocationKey(scriptId, lineNumber, columnNumber)
|
|
73
68
|
const breakpoint = locationToBreakpoint.get(locationKey)
|
|
74
69
|
|
|
70
|
+
log.debug(
|
|
71
|
+
'[debugger:devtools_client] %s breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
72
|
+
breakpoint ? 'Updating' : 'Adding', url, lineNumber, columnNumber, probe.id, probe.version
|
|
73
|
+
)
|
|
74
|
+
|
|
75
75
|
if (breakpoint) {
|
|
76
76
|
// A breakpoint already exists at this location, so we need to add the probe to the existing breakpoint
|
|
77
77
|
await updateBreakpoint(breakpoint, probe)
|
|
@@ -82,10 +82,15 @@ async function addBreakpoint (probe) {
|
|
|
82
82
|
lineNumber: lineNumber - 1, // Beware! lineNumber is zero-indexed
|
|
83
83
|
columnNumber
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
let result
|
|
86
|
+
try {
|
|
87
|
+
result = await session.post('Debugger.setBreakpoint', {
|
|
88
|
+
location,
|
|
89
|
+
condition: probe.condition
|
|
90
|
+
})
|
|
91
|
+
} catch (err) {
|
|
92
|
+
throw new Error(`Error setting breakpoint for probe ${probe.id}`, { cause: err })
|
|
93
|
+
}
|
|
89
94
|
probeToLocation.set(probe.id, locationKey)
|
|
90
95
|
locationToBreakpoint.set(locationKey, { id: result.breakpointId, location, locationKey })
|
|
91
96
|
breakpointToProbes.set(result.breakpointId, new Map([[probe.id, probe]]))
|
|
@@ -120,7 +125,11 @@ async function removeBreakpoint ({ id }) {
|
|
|
120
125
|
if (breakpointToProbes.size === 0) {
|
|
121
126
|
await stop() // TODO: Will this actually delete the breakpoint?
|
|
122
127
|
} else {
|
|
123
|
-
|
|
128
|
+
try {
|
|
129
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw new Error(`Error removing breakpoint for probe ${id}`, { cause: err })
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
} else {
|
|
126
135
|
await updateBreakpoint(breakpoint)
|
|
@@ -144,12 +153,21 @@ async function updateBreakpoint (breakpoint, probe) {
|
|
|
144
153
|
const condition = compileCompoundCondition(Array.from(probesAtLocation.values()))
|
|
145
154
|
|
|
146
155
|
if (condition || conditionBeforeNewProbe !== condition) {
|
|
147
|
-
|
|
156
|
+
try {
|
|
157
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
158
|
+
} catch (err) {
|
|
159
|
+
throw new Error(`Error removing breakpoint for probe ${probe.id}`, { cause: err })
|
|
160
|
+
}
|
|
148
161
|
breakpointToProbes.delete(breakpoint.id)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
let result
|
|
163
|
+
try {
|
|
164
|
+
result = await session.post('Debugger.setBreakpoint', {
|
|
165
|
+
location: breakpoint.location,
|
|
166
|
+
condition
|
|
167
|
+
})
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw new Error(`Error setting breakpoint for probe ${probe.id}`, { cause: err })
|
|
170
|
+
}
|
|
153
171
|
breakpoint.id = result.breakpointId
|
|
154
172
|
breakpointToProbes.set(result.breakpointId, probesAtLocation)
|
|
155
173
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { workerData: { rcPort } } = require('node:worker_threads')
|
|
4
|
-
const lock = require('
|
|
4
|
+
const lock = require('mutexify/promise')()
|
|
5
5
|
const { addBreakpoint, removeBreakpoint } = require('./breakpoints')
|
|
6
6
|
const { ackReceived, ackInstalled, ackError } = require('./status')
|
|
7
7
|
const log = require('../../log')
|
|
@@ -26,7 +26,7 @@ const ddtags = [
|
|
|
26
26
|
['host_name', hostname],
|
|
27
27
|
[GIT_COMMIT_SHA, config.commitSHA],
|
|
28
28
|
[GIT_REPOSITORY_URL, config.repositoryUrl]
|
|
29
|
-
].map((pair) => pair.join(':')).join(',')
|
|
29
|
+
].filter(([, value]) => value !== undefined).map((pair) => pair.join(':')).join(',')
|
|
30
30
|
|
|
31
31
|
const path = `/debugger/v1/input?${stringify({ ddtags })}`
|
|
32
32
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
-
EVP_PROXY_AGENT_BASE_PATH: 'evp_proxy/v2',
|
|
5
|
-
EVP_PROXY_AGENT_ENDPOINT: 'evp_proxy/v2/api/v2/llmobs',
|
|
4
|
+
EVP_PROXY_AGENT_BASE_PATH: '/evp_proxy/v2/',
|
|
6
5
|
EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
SPANS_EVENT_TYPE: 'span',
|
|
8
|
+
SPANS_INTAKE: 'llmobs-intake',
|
|
9
|
+
SPANS_ENDPOINT: '/api/v2/llmobs',
|
|
10
|
+
|
|
11
|
+
EVALUATIONS_INTAKE: 'api',
|
|
12
|
+
EVALUATIONS_EVENT_TYPE: 'evaluation_metric',
|
|
13
|
+
EVALUATIONS_ENDPOINT: '/api/intake/llm-obs/v1/eval-metric',
|
|
10
14
|
|
|
11
15
|
EVP_PAYLOAD_SIZE_LIMIT: 5 << 20, // 5MB (actual limit is 5.1MB)
|
|
12
16
|
EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024 // 999KB (actual limit is 1MB)
|
|
@@ -13,9 +13,9 @@ const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
|
13
13
|
const flushCh = channel('llmobs:writers:flush')
|
|
14
14
|
const injectCh = channel('dd-trace:span:inject')
|
|
15
15
|
|
|
16
|
-
const LLMObsAgentlessSpanWriter = require('./writers/spans/agentless')
|
|
17
|
-
const LLMObsAgentProxySpanWriter = require('./writers/spans/agentProxy')
|
|
18
16
|
const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
17
|
+
const LLMObsSpanWriter = require('./writers/spans')
|
|
18
|
+
const { setAgentStrategy } = require('./writers/util')
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Setting writers and processor globally when LLMObs is enabled
|
|
@@ -25,8 +25,14 @@ const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
|
25
25
|
* if the tracer is `init`ed. But, in those cases, we don't want to start writers or subscribe
|
|
26
26
|
* to channels.
|
|
27
27
|
*/
|
|
28
|
+
|
|
29
|
+
/** @type {LLMObsSpanProcessor | null} */
|
|
28
30
|
let spanProcessor
|
|
31
|
+
|
|
32
|
+
/** @type {LLMObsSpanWriter | null} */
|
|
29
33
|
let spanWriter
|
|
34
|
+
|
|
35
|
+
/** @type {LLMObsEvalMetricsWriter | null} */
|
|
30
36
|
let evalWriter
|
|
31
37
|
|
|
32
38
|
function enable (config) {
|
|
@@ -34,7 +40,7 @@ function enable (config) {
|
|
|
34
40
|
// create writers and eval writer append and flush channels
|
|
35
41
|
// span writer append is handled by the span processor
|
|
36
42
|
evalWriter = new LLMObsEvalMetricsWriter(config)
|
|
37
|
-
spanWriter =
|
|
43
|
+
spanWriter = new LLMObsSpanWriter(config)
|
|
38
44
|
|
|
39
45
|
evalMetricAppendCh.subscribe(handleEvalMetricAppend)
|
|
40
46
|
flushCh.subscribe(handleFlush)
|
|
@@ -46,7 +52,20 @@ function enable (config) {
|
|
|
46
52
|
|
|
47
53
|
// distributed tracing for llmobs
|
|
48
54
|
injectCh.subscribe(handleLLMObsParentIdInjection)
|
|
49
|
-
|
|
55
|
+
|
|
56
|
+
setAgentStrategy(config, useAgentless => {
|
|
57
|
+
if (useAgentless && !(config.apiKey && config.site)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Cannot send LLM Observability data without a running agent or without both a Datadog API key and site.\n' +
|
|
60
|
+
'Ensure these configurations are set before running your application.'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
evalWriter?.setAgentless(useAgentless)
|
|
65
|
+
spanWriter?.setAgentless(useAgentless)
|
|
66
|
+
|
|
67
|
+
telemetry.recordLLMObsEnabled(startTime, config)
|
|
68
|
+
})
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
function disable () {
|
|
@@ -74,11 +93,6 @@ function handleLLMObsParentIdInjection ({ carrier }) {
|
|
|
74
93
|
carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
75
94
|
}
|
|
76
95
|
|
|
77
|
-
function createSpanWriter (config) {
|
|
78
|
-
const SpanWriter = config.llmobs.agentlessEnabled ? LLMObsAgentlessSpanWriter : LLMObsAgentProxySpanWriter
|
|
79
|
-
return new SpanWriter(config)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
96
|
function handleFlush () {
|
|
83
97
|
try {
|
|
84
98
|
spanWriter.flush()
|
|
@@ -287,13 +287,6 @@ class LLMObs extends NoopLLMObs {
|
|
|
287
287
|
submitEvaluation (llmobsSpanContext, options = {}) {
|
|
288
288
|
if (!this.enabled) return
|
|
289
289
|
|
|
290
|
-
if (!this._config.apiKey) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
'DD_API_KEY is required for sending evaluation metrics. Evaluation metric data will not be sent.\n' +
|
|
293
|
-
'Ensure this configuration is set before running your application.'
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
290
|
const { traceId, spanId } = llmobsSpanContext
|
|
298
291
|
if (!traceId || !spanId) {
|
|
299
292
|
throw new Error(
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const request = require('../../exporters/common/request')
|
|
4
|
-
const { URL, format } = require('url')
|
|
4
|
+
const { URL, format } = require('node:url')
|
|
5
|
+
const path = require('node:path')
|
|
5
6
|
|
|
6
7
|
const logger = require('../../log')
|
|
7
8
|
|
|
8
9
|
const { encodeUnicode } = require('../util')
|
|
9
10
|
const telemetry = require('../telemetry')
|
|
10
11
|
const log = require('../../log')
|
|
12
|
+
const {
|
|
13
|
+
EVP_SUBDOMAIN_HEADER_NAME,
|
|
14
|
+
EVP_PROXY_AGENT_BASE_PATH
|
|
15
|
+
} = require('../constants/writers')
|
|
16
|
+
const { parseResponseAndLog } = require('./util')
|
|
11
17
|
|
|
12
18
|
class BaseLLMObsWriter {
|
|
13
|
-
constructor ({ interval, timeout,
|
|
19
|
+
constructor ({ interval, timeout, eventType, config, endpoint, intake }) {
|
|
14
20
|
this._interval = interval || 1000 // 1s
|
|
15
21
|
this._timeout = timeout || 5000 // 5s
|
|
16
22
|
this._eventType = eventType
|
|
@@ -19,28 +25,20 @@ class BaseLLMObsWriter {
|
|
|
19
25
|
this._bufferLimit = 1000
|
|
20
26
|
this._bufferSize = 0
|
|
21
27
|
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
port: port || 443,
|
|
26
|
-
pathname: endpoint
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
this._headers = {
|
|
30
|
-
'Content-Type': 'application/json'
|
|
31
|
-
}
|
|
28
|
+
this._config = config
|
|
29
|
+
this._endpoint = endpoint
|
|
30
|
+
this._intake = intake
|
|
32
31
|
|
|
33
32
|
this._periodic = setInterval(() => {
|
|
34
33
|
this.flush()
|
|
35
34
|
}, this._interval).unref()
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
this._beforeExitHandler = () => {
|
|
38
37
|
this.destroy()
|
|
39
|
-
}
|
|
38
|
+
}
|
|
39
|
+
process.once('beforeExit', this._beforeExitHandler)
|
|
40
40
|
|
|
41
41
|
this._destroyed = false
|
|
42
|
-
|
|
43
|
-
logger.debug(`Started ${this.constructor.name} to ${this._url}`)
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
append (event, byteLength) {
|
|
@@ -55,7 +53,9 @@ class BaseLLMObsWriter {
|
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
flush () {
|
|
58
|
-
|
|
56
|
+
const noAgentStrategy = this._agentless == null
|
|
57
|
+
|
|
58
|
+
if (this._buffer.length === 0 || noAgentStrategy) {
|
|
59
59
|
return
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -64,29 +64,12 @@ class BaseLLMObsWriter {
|
|
|
64
64
|
this._bufferSize = 0
|
|
65
65
|
const payload = this._encode(this.makePayload(events))
|
|
66
66
|
|
|
67
|
-
const options = {
|
|
68
|
-
headers: this._headers,
|
|
69
|
-
method: 'POST',
|
|
70
|
-
url: this._url,
|
|
71
|
-
timeout: this._timeout
|
|
72
|
-
}
|
|
73
|
-
|
|
74
67
|
log.debug(`Encoded LLMObs payload: ${payload}`)
|
|
75
68
|
|
|
69
|
+
const options = this._getOptions()
|
|
70
|
+
|
|
76
71
|
request(payload, options, (err, resp, code) => {
|
|
77
|
-
|
|
78
|
-
logger.error(
|
|
79
|
-
'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, err.message, err
|
|
80
|
-
)
|
|
81
|
-
telemetry.recordDroppedPayload(events.length, this._eventType, 'request_error')
|
|
82
|
-
} else if (code >= 300) {
|
|
83
|
-
logger.error(
|
|
84
|
-
'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, code
|
|
85
|
-
)
|
|
86
|
-
telemetry.recordDroppedPayload(events.length, this._eventType, 'http_error')
|
|
87
|
-
} else {
|
|
88
|
-
logger.debug(`Sent ${events.length} LLMObs ${this._eventType} events to ${this._url}`)
|
|
89
|
-
}
|
|
72
|
+
parseResponseAndLog(err, code, events.length, options.url.href, this._eventType)
|
|
90
73
|
})
|
|
91
74
|
}
|
|
92
75
|
|
|
@@ -96,12 +79,57 @@ class BaseLLMObsWriter {
|
|
|
96
79
|
if (!this._destroyed) {
|
|
97
80
|
logger.debug(`Stopping ${this.constructor.name}`)
|
|
98
81
|
clearInterval(this._periodic)
|
|
99
|
-
process.removeListener('beforeExit', this.
|
|
82
|
+
process.removeListener('beforeExit', this._beforeExitHandler)
|
|
100
83
|
this.flush()
|
|
101
84
|
this._destroyed = true
|
|
102
85
|
}
|
|
103
86
|
}
|
|
104
87
|
|
|
88
|
+
setAgentless (agentless) {
|
|
89
|
+
this._agentless = agentless
|
|
90
|
+
this._url = this._getUrl()
|
|
91
|
+
logger.debug(`Configuring ${this.constructor.name} to ${this._url.href}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_getUrl () {
|
|
95
|
+
if (this._agentless) {
|
|
96
|
+
return new URL(format({
|
|
97
|
+
protocol: 'https:',
|
|
98
|
+
hostname: `${this._intake}.${this._config.site}`,
|
|
99
|
+
pathname: this._endpoint
|
|
100
|
+
}))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { hostname, port } = this._config
|
|
104
|
+
const base = this._config.url || new URL(format({
|
|
105
|
+
protocol: 'http:',
|
|
106
|
+
hostname,
|
|
107
|
+
port
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
const proxyPath = path.join(EVP_PROXY_AGENT_BASE_PATH, this._endpoint)
|
|
111
|
+
return new URL(proxyPath, base)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_getOptions () {
|
|
115
|
+
const options = {
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json'
|
|
118
|
+
},
|
|
119
|
+
method: 'POST',
|
|
120
|
+
timeout: this._timeout,
|
|
121
|
+
url: this._url
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this._agentless) {
|
|
125
|
+
options.headers['DD-API-KEY'] = this._config.apiKey || ''
|
|
126
|
+
} else {
|
|
127
|
+
options.headers[EVP_SUBDOMAIN_HEADER_NAME] = this._intake
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return options
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
_encode (payload) {
|
|
106
134
|
return JSON.stringify(payload, (key, value) => {
|
|
107
135
|
if (typeof value === 'string') {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
EVALUATIONS_ENDPOINT,
|
|
5
|
+
EVALUATIONS_EVENT_TYPE,
|
|
6
|
+
EVALUATIONS_INTAKE
|
|
7
|
+
} = require('../constants/writers')
|
|
4
8
|
const BaseWriter = require('./base')
|
|
5
9
|
|
|
6
10
|
class LLMObsEvalMetricsWriter extends BaseWriter {
|
|
7
11
|
constructor (config) {
|
|
8
12
|
super({
|
|
9
|
-
|
|
10
|
-
intake:
|
|
11
|
-
eventType:
|
|
13
|
+
config,
|
|
14
|
+
intake: EVALUATIONS_INTAKE,
|
|
15
|
+
eventType: EVALUATIONS_EVENT_TYPE,
|
|
16
|
+
endpoint: EVALUATIONS_ENDPOINT
|
|
12
17
|
})
|
|
13
|
-
|
|
14
|
-
this._headers['DD-API-KEY'] = config.apiKey
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
makePayload (events) {
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const {
|
|
4
|
+
EVP_EVENT_SIZE_LIMIT,
|
|
5
|
+
EVP_PAYLOAD_SIZE_LIMIT,
|
|
6
|
+
SPANS_ENDPOINT,
|
|
7
|
+
SPANS_EVENT_TYPE,
|
|
8
|
+
SPANS_INTAKE
|
|
9
|
+
} = require('../constants/writers')
|
|
10
|
+
const { DROPPED_VALUE_TEXT } = require('../constants/text')
|
|
11
|
+
const { DROPPED_IO_COLLECTION_ERROR } = require('../constants/tags')
|
|
12
|
+
const BaseWriter = require('./base')
|
|
13
|
+
const telemetry = require('../telemetry')
|
|
14
|
+
const logger = require('../../log')
|
|
9
15
|
|
|
10
|
-
const tracerVersion = require('
|
|
16
|
+
const tracerVersion = require('../../../../../package.json').version
|
|
11
17
|
|
|
12
18
|
class LLMObsSpanWriter extends BaseWriter {
|
|
13
|
-
constructor (
|
|
19
|
+
constructor (config) {
|
|
14
20
|
super({
|
|
15
|
-
|
|
16
|
-
eventType:
|
|
21
|
+
config,
|
|
22
|
+
eventType: SPANS_EVENT_TYPE,
|
|
23
|
+
intake: SPANS_INTAKE,
|
|
24
|
+
endpoint: SPANS_ENDPOINT
|
|
17
25
|
})
|
|
18
26
|
}
|
|
19
27
|
|
|
@@ -33,11 +41,11 @@ class LLMObsSpanWriter extends BaseWriter {
|
|
|
33
41
|
telemetry.recordLLMObsSpanSize(event, processedEventSizeBytes, shouldTruncate)
|
|
34
42
|
|
|
35
43
|
if (this._bufferSize + eventSizeBytes > EVP_PAYLOAD_SIZE_LIMIT) {
|
|
36
|
-
logger.debug('
|
|
44
|
+
logger.debug('Flushing queue because queuing next event will exceed EvP payload limit')
|
|
37
45
|
this.flush()
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
super.append(event,
|
|
48
|
+
super.append(event, processedEventSizeBytes)
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
makePayload (events) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const logger = require('../../log')
|
|
4
|
+
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/writers')
|
|
5
|
+
const telemetry = require('../telemetry')
|
|
6
|
+
|
|
7
|
+
const AgentInfoExporter = require('../../exporters/common/agent-info-exporter')
|
|
8
|
+
/** @type {AgentInfoExporter} */
|
|
9
|
+
let agentInfoExporter
|
|
10
|
+
|
|
11
|
+
function setAgentStrategy (config, setWritersAgentlessValue) {
|
|
12
|
+
const agentlessEnabled = config.llmobs.agentlessEnabled
|
|
13
|
+
|
|
14
|
+
if (agentlessEnabled != null) {
|
|
15
|
+
setWritersAgentlessValue(agentlessEnabled)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!agentInfoExporter) {
|
|
20
|
+
agentInfoExporter = new AgentInfoExporter(config)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
agentInfoExporter.getAgentInfo((err, agentInfo) => {
|
|
24
|
+
if (err) {
|
|
25
|
+
setWritersAgentlessValue(true)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const endpoints = agentInfo.endpoints
|
|
30
|
+
const hasEndpoint = Array.isArray(endpoints) && endpoints.some(endpoint => endpoint === EVP_PROXY_AGENT_BASE_PATH)
|
|
31
|
+
setWritersAgentlessValue(!hasEndpoint)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseResponseAndLog (err, code, eventsLength, url, eventType) {
|
|
36
|
+
if (code === 403 && err.message.includes('API key is invalid')) {
|
|
37
|
+
logger.error(
|
|
38
|
+
'[LLMObs] The provided Datadog API key is invalid (likely due to an API key and DD_SITE mismatch). ' +
|
|
39
|
+
'Please verify your API key and DD_SITE are correct.'
|
|
40
|
+
)
|
|
41
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'request_error')
|
|
42
|
+
} else if (err) {
|
|
43
|
+
logger.error(
|
|
44
|
+
'Error sending %d LLMObs %s events to %s: %s', eventsLength, eventType, url, err.message, err
|
|
45
|
+
)
|
|
46
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'request_error')
|
|
47
|
+
} else if (code >= 300) {
|
|
48
|
+
logger.error(
|
|
49
|
+
'Error sending %d LLMObs %s events to %s: %s', eventsLength, eventType, url, code
|
|
50
|
+
)
|
|
51
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'http_error')
|
|
52
|
+
} else {
|
|
53
|
+
logger.debug(`Sent ${eventsLength} LLMObs ${eventType} events to ${url}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
setAgentStrategy,
|
|
59
|
+
parseResponseAndLog
|
|
60
|
+
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
EVP_SUBDOMAIN_HEADER_NAME,
|
|
5
|
-
EVP_SUBDOMAIN_HEADER_VALUE,
|
|
6
|
-
EVP_PROXY_AGENT_ENDPOINT
|
|
7
|
-
} = require('../../constants/writers')
|
|
8
|
-
const LLMObsBaseSpanWriter = require('./base')
|
|
9
|
-
|
|
10
|
-
class LLMObsAgentProxySpanWriter extends LLMObsBaseSpanWriter {
|
|
11
|
-
constructor (config) {
|
|
12
|
-
super({
|
|
13
|
-
intake: config.url?.hostname || config.hostname || 'localhost',
|
|
14
|
-
protocol: config.url?.protocol || 'http:',
|
|
15
|
-
endpoint: EVP_PROXY_AGENT_ENDPOINT,
|
|
16
|
-
port: config.url?.port || config.port
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
this._headers[EVP_SUBDOMAIN_HEADER_NAME] = EVP_SUBDOMAIN_HEADER_VALUE
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = LLMObsAgentProxySpanWriter
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { AGENTLESS_SPANS_ENDPOINT } = require('../../constants/writers')
|
|
4
|
-
const LLMObsBaseSpanWriter = require('./base')
|
|
5
|
-
|
|
6
|
-
class LLMObsAgentlessSpanWriter extends LLMObsBaseSpanWriter {
|
|
7
|
-
constructor (config) {
|
|
8
|
-
super({
|
|
9
|
-
intake: `llmobs-intake.${config.site}`,
|
|
10
|
-
endpoint: AGENTLESS_SPANS_ENDPOINT
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
this._headers['DD-API-KEY'] = config.apiKey
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
module.exports = LLMObsAgentlessSpanWriter
|