|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +## Prerequisites |
| 4 | + |
| 5 | +- Node.js >= 18 |
| 6 | +- yarn 1.x |
| 7 | +- Docker + docker-compose (for running service dependencies in tests) |
| 8 | + |
| 9 | +## Setup |
| 10 | + |
| 11 | +- Install dependencies: `yarn install` |
| 12 | + |
| 13 | +**Note:** This project uses yarn, not npm. Always use `yarn` commands instead of `npm` commands. |
| 14 | + |
| 15 | +## Project Overview |
| 16 | + |
| 17 | +dd-trace is the Datadog client library for Node.js. |
| 18 | + |
| 19 | +**Key Directories:** |
| 20 | +- `packages/dd-trace/` - Main library (APM, profiling, debugger, appsec, llmobs, CI visibility) |
| 21 | +- `packages/datadog-core/` - Async context storage, shared utilities |
| 22 | +- `packages/datadog-instrumentations/` - Instrumentation implementations |
| 23 | +- `packages/datadog-plugin-*/` - 100+ plugins for third-party integrations |
| 24 | +- `integration-tests/` - E2E integration tests |
| 25 | +- `benchmark/` - Performance benchmarks |
| 26 | + |
| 27 | +## Testing Instructions |
| 28 | + |
| 29 | +### Testing Workflow |
| 30 | + |
| 31 | +When developing a feature or fixing a bug: |
| 32 | + |
| 33 | +1. Start with individual test files to verify things work |
| 34 | +2. Run component tests: `yarn test:<component>` (e.g., `yarn test:debugger`, `yarn test:appsec`) |
| 35 | +3. Run integration tests: `yarn test:integration:<component>` (e.g., `yarn test:integration:debugger`) |
| 36 | + |
| 37 | +### Running Individual Tests |
| 38 | + |
| 39 | +**IMPORTANT**: Never run `yarn test` directly. Use `mocha` or `tap` directly on test files. |
| 40 | + |
| 41 | +**Mocha unit tests:** |
| 42 | +```bash |
| 43 | +./node_modules/.bin/mocha -r "packages/dd-trace/test/setup/mocha.js" path/to/test.spec.js |
| 44 | +``` |
| 45 | + |
| 46 | +**Tap unit tests:** |
| 47 | +```bash |
| 48 | +./node_modules/.bin/tap path/to/test.spec.js |
| 49 | +``` |
| 50 | + |
| 51 | +**Integration tests:** |
| 52 | +```bash |
| 53 | +./node_modules/.bin/mocha --timeout 60000 -r "packages/dd-trace/test/setup/core.js" path/to/test.spec.js |
| 54 | +``` |
| 55 | + |
| 56 | +**Target specific tests:** |
| 57 | +- Add `--grep "test name pattern"` flag |
| 58 | + |
| 59 | +**Enable debug logging:** |
| 60 | +- Prefix with `DD_TRACE_DEBUG=true` |
| 61 | + |
| 62 | +**Note**: New tests should be written using mocha, not tap. Existing tap tests use mocha-style `describe` and `it` blocks. |
| 63 | + |
| 64 | +### Plugin Tests |
| 65 | + |
| 66 | +**Use `PLUGINS` env var:** |
| 67 | +```bash |
| 68 | +PLUGINS="amqplib" yarn test:plugins |
| 69 | +PLUGINS="amqplib|bluebird" yarn test:plugins # pipe-delimited for multiple |
| 70 | +./node_modules/.bin/mocha -r "packages/dd-trace/test/setup/mocha.js" packages/datadog-plugin-amqplib/test/index.spec.js |
| 71 | +``` |
| 72 | + |
| 73 | +**With external services** (check `.github/workflows/apm-integrations.yml` for `SERVICES`): |
| 74 | +```bash |
| 75 | +export SERVICES="rabbitmq" PLUGINS="amqplib" |
| 76 | +docker compose up -d $SERVICES |
| 77 | +yarn services && yarn test:plugins |
| 78 | +``` |
| 79 | + |
| 80 | +**ARM64 incompatible:** `aerospike`, `couchbase`, `grpc`, `oracledb` |
| 81 | + |
| 82 | +### Test Coverage |
| 83 | + |
| 84 | +```bash |
| 85 | +./node_modules/.bin/nyc --include "packages/dd-trace/src/debugger/**/*.js" \ |
| 86 | + ./node_modules/.bin/mocha -r "packages/dd-trace/test/setup/mocha.js" \ |
| 87 | + "packages/dd-trace/test/debugger/**/*.spec.js" |
| 88 | +``` |
| 89 | + |
| 90 | +**Philosophy:** |
| 91 | +- Integration tests (running in sandboxes) don't count towards nyc coverage metrics |
| 92 | +- Don't add redundant unit tests solely to improve coverage numbers |
| 93 | +- Focus on covering important production code paths with whichever test type makes sense |
| 94 | + |
| 95 | +### Test Assertions |
| 96 | + |
| 97 | +Use `node:assert/strict` for standard assertions. For partial deep object checks, use `assertObjectContains` from `integration-tests/helpers/index.js`: |
| 98 | + |
| 99 | +```js |
| 100 | +const assert = require('node:assert/strict') |
| 101 | +const { assertObjectContains } = require('../helpers') |
| 102 | + |
| 103 | +assert.equal(actual, expected) |
| 104 | +assertObjectContains(response, { status: 200, body: { user: { name: 'Alice' } } }) |
| 105 | +``` |
| 106 | + |
| 107 | +### Time-Based Testing |
| 108 | + |
| 109 | +**Never rely on actual time passing in unit tests.** Use sinon's fake timers to mock time and make tests deterministic and fast. |
| 110 | + |
| 111 | +## Code Style & Linting |
| 112 | + |
| 113 | +### Linting & Naming |
| 114 | +- Lint: `yarn lint` / `yarn lint:fix` |
| 115 | +- Files: kebab-case |
| 116 | +- JSDoc: TypeScript-compatible syntax (`@param {string}`, `@returns {Promise<void>}`, `@typedef`) |
| 117 | + |
| 118 | +### Import Ordering |
| 119 | + |
| 120 | +Separate groups with empty line, sort alphabetically within each: |
| 121 | +1. Node.js core modules (with `node:` prefix) |
| 122 | +2. Third-party modules |
| 123 | +3. Internal imports (by path proximity, then alpha) |
| 124 | + |
| 125 | +Use destructuring for utility modules when appropriate. |
| 126 | + |
| 127 | +```js |
| 128 | +const fs = require('node:fs') |
| 129 | +const path = require('node:path') |
| 130 | + |
| 131 | +const express = require('express') |
| 132 | + |
| 133 | +const { myConf } = require('./config') |
| 134 | +const log = require('../log') |
| 135 | +``` |
| 136 | + |
| 137 | +### ECMAScript and Node.js API Standards |
| 138 | + |
| 139 | +**Target Node.js 18.0.0 compatibility:** |
| 140 | +- Use modern JS features supported by Node.js (e.g., optional chaining `?.`, nullish coalescing `??`) |
| 141 | +- Guard newer APIs with version checks using [`version.js`](./version.js): |
| 142 | + ```js |
| 143 | + const { NODE_MAJOR } = require('./version') |
| 144 | + if (NODE_MAJOR >= 20) { /* Use Node.js 20+ API */ } |
| 145 | + ``` |
| 146 | +- **Prefix Node.js core modules with `node:`** (e.g., `require('node:assert')`) |
| 147 | + |
| 148 | +### Performance and Memory |
| 149 | + |
| 150 | +**CRITICAL: Tracer runs in application hot paths - every operation counts.** |
| 151 | + |
| 152 | +**Async/Await:** |
| 153 | +- Do NOT use `async/await` or promises in production code (npm package) |
| 154 | +- Allowed ONLY in: test files, worker threads (e.g., `packages/dd-trace/src/debugger/devtools_client/`) |
| 155 | +- Use callbacks or synchronous patterns instead |
| 156 | + |
| 157 | +**Memory:** |
| 158 | +- Minimize allocations in frequently-called paths |
| 159 | +- Avoid unnecessary objects, closures, arrays |
| 160 | +- Reuse objects and buffers |
| 161 | +- Minimize GC pressure |
| 162 | + |
| 163 | +#### Array Iteration |
| 164 | + |
| 165 | +**Prefer `for-of`, `for`, `while` loops over functional methods (`map()`, `forEach()`, `filter()`):** |
| 166 | +- Avoid `items.forEach(item => process(item))` → use `for (const item of items) { process(item) }` |
| 167 | +- Avoid chaining `items.filter(...).map(...)` → use single loop with conditional push |
| 168 | +- Functional methods create closures and intermediate arrays |
| 169 | + |
| 170 | +**Functional methods acceptable in:** |
| 171 | +- Test files |
| 172 | +- Non-hot-path code where readability benefits |
| 173 | +- One-time initialization code |
| 174 | + |
| 175 | +**Loop selection:** |
| 176 | +- `for-of` - Simple iteration |
| 177 | +- `for` with index - Need index or better performance in hot paths |
| 178 | +- `while` - Custom iteration logic |
| 179 | + |
| 180 | +### Debugging and Logging |
| 181 | + |
| 182 | +Use `log` module (`packages/dd-trace/src/log/index.js`) with printf-style formatting (not template strings): |
| 183 | + |
| 184 | +```js |
| 185 | +const log = require('../log') |
| 186 | +log.debug('Value: %s', someValue) // printf-style |
| 187 | +log.debug(() => `Expensive: ${expensive()}`) // callback for expensive ops |
| 188 | +log.error('Error: %s', msg, err) // error as last arg |
| 189 | +``` |
| 190 | + |
| 191 | +Enable: `DD_TRACE_DEBUG=true DD_TRACE_LOG_LEVEL=info node app.js` |
| 192 | +Levels: `trace`, `debug`, `info`, `warn`, `error` |
| 193 | + |
| 194 | +### Error Handling |
| 195 | + |
| 196 | +**Never crash user apps:** Catch/log errors (`log.error()`/`log.warn()`), resume or disable plugin/subsystem |
| 197 | +Avoid try/catch in hot paths - validate inputs early |
| 198 | + |
| 199 | +## Development Workflow |
| 200 | + |
| 201 | +### Core Principles |
| 202 | +- **Search first**: Check for existing utilities/patterns before creating new code |
| 203 | +- **Small PRs**: Break large efforts into incremental, reviewable changes |
| 204 | +- **Descriptive code**: Self-documenting with verbs in function names; comment when needed |
| 205 | +- **Readable formatting**: Empty lines for grouping, split complex objects, extract variables |
| 206 | +- **Avoid large refactors**: Iterative changes, gradual pattern introduction |
| 207 | +- **Test changes**: Test logic (not mocks), failure cases, edge cases - always update tests |
| 208 | + |
| 209 | +### Always Consider Backportability |
| 210 | + |
| 211 | +**We always backport `master` to older versions.** |
| 212 | +- Keep breaking changes to a minimum |
| 213 | +- Don't use language/runtime features that are too new |
| 214 | +- **Guard breaking changes with version checks** using [`version.js`](./version.js): |
| 215 | + ```js |
| 216 | + const { DD_MAJOR } = require('./version') |
| 217 | + if (DD_MAJOR >= 6) { |
| 218 | + // New behavior for v6+ |
| 219 | + } else { |
| 220 | + // Old behavior for v5 and earlier |
| 221 | + } |
| 222 | + ``` |
| 223 | + |
| 224 | +## Adding New Configuration Options |
| 225 | + |
| 226 | +1. **Add default value** in `packages/dd-trace/src/config_defaults.js` |
| 227 | +2. **Map environment variable** in `packages/dd-trace/src/config.js` (`#applyEnvironment()` method) |
| 228 | +3. **Add TypeScript definitions** in `index.d.ts` |
| 229 | +4. **Add to telemetry name mapping** (if applicable) in `packages/dd-trace/src/telemetry/telemetry.js` |
| 230 | +5. **Update** `packages/dd-trace/src/supported-configurations.json` |
| 231 | +6. **Document** in `docs/API.md` (non-internal/experimental options only) |
| 232 | +7. **Add tests** in `packages/dd-trace/test/config.spec.js` |
| 233 | + |
| 234 | +**Naming Convention:** Size/time-based config options should have unit suffixes (e.g., `timeoutMs`, `maxBytes`, `intervalSeconds`). |
| 235 | + |
| 236 | +## Adding New Instrumentation |
| 237 | + |
| 238 | +**New instrumentations go in `packages/datadog-instrumentations/`.** The instrumentation system uses diagnostic channels for communication. |
| 239 | + |
| 240 | +Many integrations have corresponding plugins in `packages/datadog-plugin-*/` that work with the instrumentation layer. |
| 241 | + |
| 242 | +### What Are Plugins? |
| 243 | + |
| 244 | +Plugins are modular code components in `packages/datadog-plugin-*/` directories that: |
| 245 | +- Subscribe to diagnostic channels to receive instrumentation events |
| 246 | +- Handle APM tracing logic (spans, metadata, error tracking) |
| 247 | +- Manage feature-specific logic (e.g., code origin tracking, LLM observability) |
| 248 | + |
| 249 | +**Plugin Base Classes:** |
| 250 | +- **`Plugin`** - Base class with diagnostic channel subscription, storage binding, enable/disable lifecycle. Use for non-tracing functionality. |
| 251 | +- **`TracingPlugin`** - Extends `Plugin` with APM tracing helpers (`startSpan()`, automatic trace events, `activeSpan` getter). Use for plugins creating trace spans. |
| 252 | +- **`CompositePlugin`** - Extends `Plugin` to compose multiple sub-plugins. Use when one integration needs multiple feature plugins (e.g., `express` combines tracing and code origin plugins). |
| 253 | + |
| 254 | +**Plugin Loading:** |
| 255 | +- Plugins load lazily when application `require()`s the corresponding library |
| 256 | +- Disable with `DD_TRACE_DISABLED_PLUGINS` or `DD_TRACE_<PLUGIN>_ENABLED=false` |
| 257 | +- Test framework plugins only load when Test Optimization mode (`isCiVisibility`) is enabled |
| 258 | + |
| 259 | +**When to Create a New Plugin:** |
| 260 | +1. Adding support for a new third-party library/framework |
| 261 | +2. Adding a new product feature that integrates with existing libraries (use `CompositePlugin`) |
| 262 | + |
| 263 | +### Creating a New Plugin |
| 264 | + |
| 265 | +```bash |
| 266 | +mkdir -p packages/datadog-plugin-<name>/{src,test} |
| 267 | +cp packages/datadog-plugin-kafkajs/src/index.js packages/datadog-plugin-<name>/src/ |
| 268 | +``` |
| 269 | + |
| 270 | +Edit `src/index.js`, create `test/index.spec.js`, then register in: |
| 271 | +`packages/dd-trace/src/plugins/index.js`, `index.d.ts`, `docs/test.ts`, `docs/API.md`, `.github/workflows/apm-integrations.yml` |
| 272 | + |
| 273 | +## Pull Requests and CI |
| 274 | + |
| 275 | +### Commit Messages |
| 276 | + |
| 277 | +Conventional format: `type(scope): description` |
| 278 | +Types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci` |
| 279 | +Example: `feat(appsec): add new WAF rule` |
| 280 | + |
| 281 | +### PR Requirements |
| 282 | + |
| 283 | +- Use template from `.github/pull_request_template.md` |
| 284 | +- Label: `semver-patch` (fixes only), `semver-minor` (new features), `semver-major` (breaking) |
| 285 | +- **All tests must pass - all-green policy, no exceptions** |
| 286 | + |
| 287 | +## Vendoring Dependencies |
| 288 | + |
| 289 | +Using rspack: Run `yarn` in `vendor/` to install/bundle dependencies → `packages/node_modules/` |
| 290 | +(Some deps excluded, e.g., `@opentelemetry/api`) |
0 commit comments