Skip to content
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

v5.28.0 proposal #4940

Merged
merged 8 commits into from
Nov 26, 2024
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
11 changes: 10 additions & 1 deletion .github/workflows/plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,11 @@ jobs:
# TODO: Figure out why nyc stopped working with EACCESS errors.
oracledb:
runs-on: ubuntu-latest
container: bengl/node-12-with-oracle-client
container:
image: bengl/node-12-with-oracle-client
volumes:
- /node20217:/node20217:rw,rshared
- /node20217:/__e/node20:ro,rshared
services:
oracledb:
image: gvenzl/oracle-xe:18-slim
Expand All @@ -827,6 +831,11 @@ jobs:
# Needed to fix issue with `actions/checkout@v3: https://github.com/actions/checkout/issues/1590
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
steps:
# https://github.com/actions/runner/issues/2906#issuecomment-2109514798
- name: Install Node for runner (with glibc 2.17 compatibility)
run: |
curl -LO https://unofficial-builds.nodejs.org/download/release/v20.9.0/node-v20.9.0-linux-x64-glibc-217.tar.xz
tar -xf node-v20.9.0-linux-x64-glibc-217.tar.xz --strip-components 1 -C /node20217
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# setting fail-fast to false in an attempt to prevent this from happening
fail-fast: false
matrix:
version: [18, 20, latest]
version: [18, 20, 22, latest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -34,7 +34,7 @@ jobs:
integration-guardrails:
strategy:
matrix:
version: [12, 14, 16]
version: [12.0.0, 12, 14.0.0, 14, 16.0.0, 16, 18.0.0, 18.1.0, 20.0.0, 22.0.0]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 4 additions & 2 deletions init.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

/* eslint-disable no-var */

var NODE_MAJOR = require('./version').NODE_MAJOR
var nodeVersion = require('./version')
var NODE_MAJOR = nodeVersion.NODE_MAJOR
var NODE_MINOR = nodeVersion.NODE_MINOR

// We use several things that are not supported by older versions of Node:
// - AsyncLocalStorage
Expand All @@ -11,7 +13,7 @@ var NODE_MAJOR = require('./version').NODE_MAJOR
// - Mocha (for testing)
// and probably others.
// TODO: Remove all these dependencies so that we can report telemetry.
if (NODE_MAJOR >= 12) {
if ((NODE_MAJOR === 12 && NODE_MINOR >= 17) || NODE_MAJOR > 12) {
var path = require('path')
var Module = require('module')
var semver = require('semver')
Expand Down
23 changes: 13 additions & 10 deletions initialize.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import { isMainThread } from 'worker_threads'

import * as Module from 'node:module'
import { fileURLToPath } from 'node:url'
import {
load as origLoad,
Expand All @@ -31,11 +32,16 @@ ${result.source}`
return result
}

const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(x => +x)

const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0

export async function load (...args) {
return insertInit(await origLoad(...args))
const loadHook = brokenLoaders ? args[args.length - 1] : origLoad
return insertInit(await loadHook(...args))
}

export const resolve = origResolve
export const resolve = brokenLoaders ? undefined : origResolve

export const getFormat = origGetFormat

Expand All @@ -44,12 +50,9 @@ export async function getSource (...args) {
}

if (isMainThread) {
// Need this IIFE for versions of Node.js without top-level await.
(async () => {
await import('./init.js')
const { register } = await import('node:module')
if (register) {
register('./loader-hook.mjs', import.meta.url)
}
})()
const require = Module.createRequire(import.meta.url)
require('./init.js')
if (Module.register) {
Module.register('./loader-hook.mjs', import.meta.url)
}
}
39 changes: 34 additions & 5 deletions integration-tests/init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const telemetryGood = ['complete', 'injection_forced:false']
const { engines } = require('../package.json')
const supportedRange = engines.node
const currentVersionIsSupported = semver.satisfies(process.versions.node, supportedRange)
const currentVersionCanLog = semver.satisfies(process.versions.node, '>=12.17.0')

// These are on by default in release tests, so we'll turn them off for
// more fine-grained control of these variables in these tests.
Expand Down Expand Up @@ -83,7 +84,30 @@ function testRuntimeVersionChecks (arg, filename) {
}
}

if (!currentVersionIsSupported) {
if (!currentVersionCanLog) {
context('when node version is too low for AsyncLocalStorage', () => {
useEnv({ NODE_OPTIONS })

it('should initialize the tracer, if no DD_INJECTION_ENABLED', () =>
doTest('false\n'))
context('with DD_INJECTION_ENABLED', () => {
useEnv({ DD_INJECTION_ENABLED })

context('without debug', () => {
it('should not initialize the tracer', () => doTest('false\n'))
it('should not, if DD_INJECT_FORCE', () => doTestForced('false\n'))
})
context('with debug', () => {
useEnv({ DD_TRACE_DEBUG })

it('should not initialize the tracer', () =>
doTest('false\n'))
it('should initialize the tracer, if DD_INJECT_FORCE', () =>
doTestForced('false\n'))
})
})
})
} else if (!currentVersionIsSupported) {
context('when node version is less than engines field', () => {
useEnv({ NODE_OPTIONS })

Expand Down Expand Up @@ -165,17 +189,22 @@ describe('init.js', () => {
testRuntimeVersionChecks('require', 'init.js')
})

// ESM is not supportable prior to Node.js 12
if (semver.satisfies(process.versions.node, '>=12')) {
// ESM is not supportable prior to Node.js 12.17.0, 14.13.1 on the 14.x line,
// or on 18.0.0 in particular.
if (
semver.satisfies(process.versions.node, '>=12.17.0') &&
semver.satisfies(process.versions.node, '>=14.13.1')
) {
describe('initialize.mjs', () => {
useSandbox()
stubTracerIfNeeded()

context('as --loader', () => {
testInjectionScenarios('loader', 'initialize.mjs', true)
testInjectionScenarios('loader', 'initialize.mjs',
process.versions.node !== '18.0.0')
testRuntimeVersionChecks('loader', 'initialize.mjs')
})
if (Number(process.versions.node.split('.')[0]) >= 18) {
if (semver.satisfies(process.versions.node, '>=20.6.0')) {
context('as --import', () => {
testInjectionScenarios('import', 'initialize.mjs', true)
testRuntimeVersionChecks('loader', 'initialize.mjs')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dd-trace",
"version": "5.27.1",
"version": "5.28.0",
"description": "Datadog APM tracing client for JavaScript",
"main": "index.js",
"typings": "index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-instrumentations/src/azure-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
const shimmer = require('../../datadog-shimmer')
const dc = require('dc-polyfill')

const azureFunctionsChannel = dc.tracingChannel('datadog:azure-functions:invoke')
const azureFunctionsChannel = dc.tracingChannel('datadog:azure:functions:invoke')

addHook({ name: '@azure/functions', versions: ['>=4'] }, azureFunction => {
const { app } = azureFunction
Expand Down
2 changes: 1 addition & 1 deletion packages/datadog-plugin-azure-functions/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
static get kind () { return 'server' }
static get type () { return 'serverless' }

static get prefix () { return 'tracing:datadog:azure-functions:invoke' }
static get prefix () { return 'tracing:datadog:azure:functions:invoke' }

bindStart (ctx) {
const { functionName, methodName } = ctx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('esm', () => {
assert.strictEqual(payload.length, 1)
assert.isArray(payload[0])
assert.strictEqual(payload[0].length, 1)
assert.propertyVal(payload[0][0], 'name', 'azure-functions.invoke')
assert.propertyVal(payload[0][0], 'name', 'azure.functions.invoke')
})
}).timeout(50000)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class LangChainChatModelHandler extends LangChainLanguageModelHandler {

this.extractTokenMetrics(ctx.currentStore?.span, result)

for (const messageSetIdx in result.generations) {
for (const messageSetIdx in result?.generations) {
const messageSet = result.generations[messageSetIdx]

for (const chatCompletionIdx in messageSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class LangChainLLMHandler extends LangChainLanguageModelHandler {

this.extractTokenMetrics(ctx.currentStore?.span, result)

for (const completionIdx in result.generations) {
for (const completionIdx in result?.generations) {
const completion = result.generations[completionIdx]
if (this.isPromptCompletionSampled()) {
tags[`langchain.response.completions.${completionIdx}.text`] = this.normalize(completion[0].text) || ''
Expand Down
108 changes: 108 additions & 0 deletions packages/datadog-plugin-langchain/test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,33 @@ describe('Plugin', () => {
})

describe('llm', () => {
it('does not tag output on error', async () => {
nock('https://api.openai.com').post('/v1/completions').reply(403)

const checkTraces = agent
.use(traces => {
expect(traces[0].length).to.equal(1)

const span = traces[0][0]

const langchainResponseRegex = /^langchain\.response\.completions\./
const hasMatching = Object.keys(span.meta).some(key => langchainResponseRegex.test(key))

expect(hasMatching).to.be.false

expect(span.meta).to.have.property('error.message')
expect(span.meta).to.have.property('error.type')
expect(span.meta).to.have.property('error.stack')
})

try {
const llm = new langchainOpenai.OpenAI({ model: 'gpt-3.5-turbo-instruct', maxRetries: 0 })
await llm.generate(['what is 2 + 2?'])
} catch {}

await checkTraces
})

it('instruments a langchain llm call for a single prompt', async () => {
stubCall({
...openAiBaseCompletionInfo,
Expand Down Expand Up @@ -270,6 +297,32 @@ describe('Plugin', () => {
})

describe('chat model', () => {
it('does not tag output on error', async () => {
nock('https://api.openai.com').post('/v1/chat/completions').reply(403)

const checkTraces = agent
.use(traces => {
expect(traces[0].length).to.equal(1)

const span = traces[0][0]

const langchainResponseRegex = /^langchain\.response\.completions\./
const hasMatching = Object.keys(span.meta).some(key => langchainResponseRegex.test(key))
expect(hasMatching).to.be.false

expect(span.meta).to.have.property('error.message')
expect(span.meta).to.have.property('error.type')
expect(span.meta).to.have.property('error.stack')
})

try {
const chatModel = new langchainOpenai.ChatOpenAI({ model: 'gpt-4', maxRetries: 0 })
await chatModel.invoke('Hello!')
} catch {}

await checkTraces
})

it('instruments a langchain openai chat model call for a single string prompt', async () => {
stubCall({
...openAiBaseChatInfo,
Expand Down Expand Up @@ -546,6 +599,37 @@ describe('Plugin', () => {
})

describe('chain', () => {
it('does not tag output on error', async () => {
nock('https://api.openai.com').post('/v1/chat/completions').reply(403)

const checkTraces = agent
.use(traces => {
expect(traces[0].length).to.equal(2)

const chainSpan = traces[0][0]

const langchainResponseRegex = /^langchain\.response\.outputs\./

const hasMatching = Object.keys(chainSpan.meta).some(key => langchainResponseRegex.test(key))
expect(hasMatching).to.be.false

expect(chainSpan.meta).to.have.property('error.message')
expect(chainSpan.meta).to.have.property('error.type')
expect(chainSpan.meta).to.have.property('error.stack')
})

try {
const model = new langchainOpenai.ChatOpenAI({ model: 'gpt-4', maxRetries: 0 })
const parser = new langchainOutputParsers.StringOutputParser()

const chain = model.pipe(parser)

await chain.invoke('Hello!')
} catch {}

await checkTraces
})

it('instruments a langchain chain with a single openai chat model call', async () => {
stubCall({
...openAiBaseChatInfo,
Expand Down Expand Up @@ -790,6 +874,30 @@ describe('Plugin', () => {

describe('embeddings', () => {
describe('@langchain/openai', () => {
it('does not tag output on error', async () => {
nock('https://api.openai.com').post('/v1/embeddings').reply(403)

const checkTraces = agent
.use(traces => {
expect(traces[0].length).to.equal(1)

const span = traces[0][0]

expect(span.meta).to.not.have.property('langchain.response.outputs.embedding_length')

expect(span.meta).to.have.property('error.message')
expect(span.meta).to.have.property('error.type')
expect(span.meta).to.have.property('error.stack')
})

try {
const embeddings = new langchainOpenai.OpenAIEmbeddings()
await embeddings.embedQuery('Hello, world!')
} catch {}

await checkTraces
})

it('instruments a langchain openai embedQuery call', async () => {
stubCall({
...openAiBaseEmbeddingInfo,
Expand Down
2 changes: 1 addition & 1 deletion packages/dd-trace/src/appsec/api_security_sampler.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function computeKey (req, res) {
const status = res.statusCode

if (!method || !status) {
log.warn('Unsupported groupkey for API security')
log.warn('[ASM] Unsupported groupkey for API security')
return null
}
return method + route + status
Expand Down
2 changes: 1 addition & 1 deletion packages/dd-trace/src/appsec/blocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function getBlockingData (req, specificType, actionParameters) {

function block (req, res, rootSpan, abortController, actionParameters = defaultBlockingActionParameters) {
if (res.headersSent) {
log.warn('Cannot send blocking response when headers have already been sent')
log.warn('[ASM] Cannot send blocking response when headers have already been sent')
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const Analyzer = require('./vulnerability-analyzer')
const { getNodeModulesPaths } = require('../path-line')
const iastLog = require('../iast-log')
const log = require('../../../log')

const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')

Expand All @@ -16,7 +16,7 @@ class CookieAnalyzer extends Analyzer {
try {
this.cookieFilterRegExp = new RegExp(config.iast.cookieFilterPattern)
} catch {
iastLog.error('Invalid regex in cookieFilterPattern')
log.error('[ASM] Invalid regex in cookieFilterPattern')
this.cookieFilterRegExp = /.{32,}/
}

Expand Down
Loading
Loading