ข้ามไปยังเนื้อหา

Worked review 1: Node checkout service

Prompt คือ “เพิ่ม Datadog APM และ log correlation ให้ Express checkout service ของเรา ใช้ pino, service ชื่อ checkout-api, environment production, ส่ง trace ไปที่ Datadog Agent” ด้านล่างคือ draft แรกที่ assistant ส่งกลับมา — มัน start ได้ รับ request ได้ พิมพ์ JSON log ได้ และไม่มีอะไร throw ตอน startup ทุกข้อค้นพบด้านล่างมาจากการรันจริง

พิสูจน์ด้วยอะไร:

  • dd-trace 6.17.0 (pin ของ harness ใน tools/probe/versions.json), express 5.2.1, pino 10.3.1, typescript 7.0.2, Node 24.13.0 บน Mac เครื่องนี้
  • fake trace-agent: Node HTTP server 20 บรรทัดที่รับ PUT /v0.4/traces ถอด msgpack แล้วพิมพ์ทีละ span service ที่รีวิวชี้มาที่ตัวนี้ จึง ไม่มีอะไรถูกส่งไป Datadog และไม่มี API key อยู่ที่ไหนเลย
  • โหมด --options ของ tools/verify-snippets.mjs ที่เช็กทุก key ของ tracer.init({...}) และทุกชื่อ DD_* กับ .d.ts ที่ pin และ reference env-var ที่รวมไว้
// @expect-error as generated -- every finding below comes from running this file
const express = require('express');
const pino = require('pino');
const tracer = require('dd-trace').init({
service: 'checkout-api',
env: 'Production',
version: '1.0',
log_injection: true,
analytics: true,
agentUrl: process.env.TRACE_URL || 'http://127.0.0.1:28126',
traceSampleRate: 0.5,
flushInterval: 200,
});
const logger = pino({ base: { service: 'checkout', env: 'prod' } });
const app = express();
app.get('/health', (req, res) => res.send('ok'));
app.post('/charge', async (req, res) => {
const span = tracer.scope().active();
try {
logger.info('charging card');
throw new Error('card declined');
} catch (err) {
span.setTag('error', true);
logger.error({ msg: 'charge failed' });
res.status(200).json({ ok: false });
}
});
app.listen(process.env.PORT || 3100, () => logger.info('listening'));

และ Deployment fragment ที่มากับมัน เพื่อให้ container “ส่ง trace ไปที่ Agent”:

# @expect-error DD_LOG_INJECTION is not a dd-trace variable; DD_AGENT_HOST=localhost is the pod itself, not the node Agent
env:
- name: DD_LOG_INJECTION
value: "true"
- name: DD_AGENT_HOST
value: localhost

สองบรรทัดแรก require framework กับ logger แล้วค่อยรัน init() รันตามที่ generate โดยมี fake agent รออยู่ และยิง 2 request (GET /health, POST /charge):

$ curl -s -XPOST localhost:3100/charge
TypeError: Cannot read properties of null (reading 'setTag')
at .../bad/server.js:26:10
at Layer.handleRequest (.../node_modules/router/lib/layer.js:152:17)
fake agent: (no spans received at all)
{"level":30,"time":1790736292381,"service":"checkout","env":"prod","msg":"charging card"}

สามความล้มเหลวจากสาเหตุเดียว express ถูกโหลดก่อนที่ tracer จะ hook require จึงไม่ถูก instrument: ไม่มี span express.request, tracer.scope().active() เป็น null (จึงเกิด TypeError ใน request จริง ครั้งแรก), fake agent ไม่ได้รับอะไรเลย และ log ของ pino ไม่มี object dd — ไม่มี trace id ก็เชื่อม log กับ trace ไม่ได้ โค้ดเดิมเป๊ะที่ย้ายแค่สองบรรทัด require ลงมาหลัง init() พิมพ์ผลสำหรับ 2 request เดิมว่า:

DATADOG TRACER INTEGRATIONS LOADED - ["fs","net","dns","http","router","express","pino"]
{"level":30,...,"msg":"charging card","dd":{"trace_id":"6abc77b500000000453cf38eaa9ba509","span_id":"2662234485403838737",...}}

docs บอกเรื่องเดียวกันในส่วนของ logger: ใน docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/nodejs/ snippet มี comment ว่า “This line must come before importing the logger.”

🐛 หา bug ในโค้ดนี้

const express = require('express');
const pino = require('pino');
const tracer = require('dd-trace').init({ ... });
เฉลย

dd-trace patch module ตอนที่ถูก require ดังนั้นต้อง initialise ก่อนทุกอย่างที่ต้องการให้ instrument — web framework, HTTP client, database driver และ logger AI ชอบวาง init() “หลัง import” เพราะปกติ config อยู่ตรงนั้น สอง finding ด้านล่างมองไม่เห็นในการรันนี้จนกว่าจะแก้ข้อนี้: ไม่มี active span ก็ไม่มีทางรัน tag หรือ log ได้เลย วิธีแก้: init() บรรทัดแรก หรือ preload ด้วย node --require dd-trace/init app.js

Block ถัดมาขอ log_injection: true tsc --strict กับ typings ที่ pin ทีละ key:

$ tsc --noEmit --strict opts.ts
opts.ts(6,3): error TS2561: Object literal may only specify known properties, but 'log_injection' does not exist in type 'TracerOptions'. Did you mean to write 'logInjection'?

JavaScript ล้วนรับ key ที่ไม่รู้จักแบบเงียบๆ ข้อนี้อันตรายเพราะ ดูเหมือนใช้ได้: default ของ tracer คือเปิด log จึงถูก inject อยู่ดี พิสูจน์ด้วยค่าตรงข้าม — log_injection: false ยังทิ้ง "log_injection_enabled":true ไว้ใน startup config dump (ผลเดียวกับ entry 9 ของ bug catalog) พอมีคนพยายาม ปิด หรือ default เปลี่ยน บรรทัดนี้ก็โกหก

🐛 หา bug ในโค้ดนี้

log_injection: true,
เฉลย

option จริงคือ logInjection (TracerOptions.logInjection, index.d.ts บรรทัด 551 ของ package ที่ติดตั้ง) ในไฟล์ JavaScript ไม่มีอะไรปฏิเสธการสะกดผิด แต่ TypeScript และโหมด --options ของ harness ปฏิเสธ วิธีแก้: logInjection: true หรือไม่ต้องใส่เพราะ default เป็น true อยู่แล้ว หรือตั้ง DD_LOGS_INJECTION

service ถูกสั่งให้ส่งไป http://127.0.0.1:28126 รัน fake agent สองตัวคู่กัน ตัวหนึ่งที่ 28126 อีกตัวที่ default 8126:

agent A (28126, the URL in code): 0 spans
agent B (default 8126): 6 spans

startup line ของ tracer เองก็ยืนยัน: "agent_url":"http://127.0.0.1:8126/" agentUrl ไม่ใช่ option ของ dd-trace (tsc: TS2353 ... 'agentUrl' does not exist in type 'TracerOptions') key จริงคือ url หรือ environment variable DD_TRACE_AGENT_URL บนเครื่อง dev มัน “ใช้ได้” โดยบังเอิญเพราะ Agent ของ developer ฟังที่ 8126 แต่ใน cluster span จะไปที่ localhost:8126 ของ pod เองแล้วหายไป

🐛 หา bug ในโค้ดนี้

agentUrl: process.env.TRACE_URL || 'http://127.0.0.1:28126',
เฉลย

option ที่แต่งขึ้นเป็น bug ใน config แต่ outage เกิดใน network: span ถูกส่งไปที่อื่นโดยไม่มี error วิธีแก้: ลบบรรทัดนี้ แล้วตั้ง DD_TRACE_AGENT_URL (หรือ DD_AGENT_HOST) ใน environment — ดู bug 11 ว่าใน Kubernetes ค่านั้นควรเป็นอะไร

ตระกูลเดียวกัน ผลต่างกัน tsc บอก TS2353 สำหรับ analytics และ TS2561 ... Did you mean to write 'sampleRate'? สำหรับ traceSampleRate โหมด --options ของ harness รายงานทั้งหมดในครั้งเดียว ซึ่ง tsc ไม่ทำ (มันหยุดที่ excess property แรก):

ERROR src/content/docs/en/x/bad.mdx:fence #2: tracer.init({...}) unknown key(s) [log_injection, agentUrl, traceSampleRate, analytics] ([email protected] TracerOptions)

หลักฐานเชิงพฤติกรรมคือการนับ 40 request ผ่าน tracer ที่ init ด้วย key ที่แต่งขึ้น เทียบกับ sampleRate: 0.5 จริง อ่าน _sampling_priority_v1 จาก payload ที่จับได้:

{traceSampleRate: 0.5, analytics: true} -> {priority 1: 40}
{sampleRate: 0.5} -> {priority 2: 15, priority -1: 25}

key ที่แต่งขึ้นเก็บ trace 100 เปอร์เซ็นต์ ส่วนของจริงให้ผสมระหว่าง “keep” (2) กับ “reject” (-1) สังเกตว่า trace ที่ reject ยังถูกส่ง ไป fake agent ในรอบนี้ด้วย priority -1 — tracer แค่ติดป้าย ส่วน Agent และ ingestion controls เป็นผู้ตัดสินว่าจะทิ้งอะไร (docs.datadoghq.com/tracing/trace_pipeline/ingestion_mechanisms/) การ head sample 50 เปอร์เซ็นต์เป็นความคิดที่ดีหรือไม่เป็นอีกคำถามหนึ่งที่ trace sampling pipeline ตอบ ประเด็นตรงนี้คือบรรทัดที่ผู้เขียนเชื่อว่าลด volume ไม่ได้ทำอะไรเลย

🐛 หา bug ในโค้ดนี้

analytics: true,
traceSampleRate: 0.5,
เฉลย

ไม่มีทั้งสอง key ใน TracerOptions analytics ไม่อยู่ใน typings ที่ pin เลย และ traceSampleRate เป็นชื่อใกล้เคียงของ sampleRate (compiler แนะนำตรงนั้นเอง) วิธีแก้: ลบ analytics ใช้ sampleRate หรือดีกว่านั้นคือ DD_TRACE_SAMPLE_RATE / sampling rules และทำเฉพาะเมื่อ requirement คือ head sampling จริง

service ถูก deploy ด้วย DD_ENV=prod และ DD_VERSION=2.4.1 (จาก label ของ pod ตามที่ unified service tagging กำหนด) แต่โค้ดเขียน env: 'Production', version: '1.0' เมื่อตั้ง environment variable เหล่านั้น ทั้ง config dump ของ tracer และ span ใช้ค่าจากโค้ด:

"env":"Production", "tags":{"service":"checkout-api","env":"Production","version":"1.0", ...}
express.request POST /charge env=Production version=1.0

option ที่ส่งให้ init() มี precedence เหนือตัวแปร DD_* — พิสูจน์แล้ว ไม่ได้สันนิษฐาน ดังนั้น DD_ENV=prod และ DD_VERSION=2.4.1 จาก deploy pipeline ถูกทิ้งแบบเงียบๆ ทุก deploy รายงาน version 1.0 และ Production เป็นค่า env คนละค่ากับ prod ที่ service อื่นทุกตัวใช้ (tracer ส่งต่อค่าตามที่ให้ ส่วนการ normalise tag ฝั่ง platform เป็นกฎของ Datadog — ดู docs.datadoghq.com/getting_started/tagging/unified_service_tagging/)

🐛 หา bug ในโค้ดนี้

service: 'checkout-api',
env: 'Production',
version: '1.0',
เฉลย

Unified service tagging ต้องการให้ตั้ง env, service และ version ครั้งเดียว นอกโค้ด เพื่อให้ image เดียวรันได้ทั้ง staging และ prod และให้ deploy tooling เป็นเจ้าของ version การใส่ใน init() ตรึงค่าไว้ใน image และ override environment วิธีแก้: require('dd-trace').init() โดยไม่ใส่ identity option แล้วให้ pod ส่ง DD_ENV, DD_SERVICE, DD_VERSION (bug 10)

pino({ base: { service: 'checkout', env: 'prod' } }) คือความพยายามของ assistant ที่จะทำ “unified tagging ใน log” พอ injection ทำงาน log บรรทัดเดียวมี สอง คำตอบ:

{"level":30,...,"service":"checkout","env":"prod","msg":"charging card",
"dd":{"trace_id":"6abc77b5...","service":"checkout-api","version":"1.0","env":"Production"}}

checkout กับ checkout-api, prod กับ Production Datadog ถือ service ระดับบนสุดของ JSON log เป็น service ของ log และยังรู้จัก dd.service (docs.datadoghq.com/logs/log_configuration/pipelines/ หัวข้อ “Service attribute” ระบุ service, syslog.appname, dd.service) docs ที่อ่านไม่ได้บอกว่าตัวไหนชนะเมื่อมีทั้งคู่ในบรรทัดเดียว ดังนั้นข้อความที่ซื่อสัตย์คือ: ผลขึ้นกับ pipeline ของ org และไม่มี offline tool ไหนบอกได้ ผู้รีวิวยังเห็นได้ว่า process เดียวปล่อยค่าสองค่าที่ต่างกัน

🐛 หา bug ในโค้ดนี้

const logger = pino({ base: { service: 'checkout', env: 'prod' } });
เฉลย

block dd ที่ inject มีทั้ง service, env และ version จาก tracer อยู่แล้ว และ DD_SERVICE/DD_ENV ขับทั้งสองฝั่ง การ hard-code ชุดที่สองใน logger รับประกันว่าจะ drift วิธีแก้: pino() โดยไม่มี identity field ใน base — แหล่งความจริงเดียว (environment) ที่ tracer อ่านและ stamp ลง log

เมื่อแก้ init แล้ว handler ก็ทำงาน สิ่งที่ fake agent ได้รับสำหรับ POST /charge:

express.request POST /charge error=0 http.status_code=200
router.middleware handle error=0
router.middleware <anonymous> error=1

tracer.scope().active() ใน route handler ไม่ใช่ root span ของ request แต่เป็น span ในสุด — ที่นี่คือ layer router.middleware ของ Express error:1 ไปตกที่นั่น ส่วน root span express.request ซึ่งเป็นตัวที่ service error rate ของ APM และหน้า service คำนวณจากมัน มี error=0 และเพราะค่าเป็น boolean true จึงไม่มี error.message, error.type หรือ error.stack บน span ใดเลย ในรอบที่แก้แล้ว ความล้มเหลวเดียวกันให้:

express.request POST /charge error=1 http.status_code=502
payment.charge payment.charge error=1 error.type=Error error.message=card declined

🐛 หา bug ในโค้ดนี้

const span = tracer.scope().active();
...
span.setTag('error', true);
เฉลย

สองความผิดในสามบรรทัด active() คืน span ที่เป็นปัจจุบัน ไม่ใช่ root และ setTag('error', true) แค่ธงว่า span error โดยไม่บันทึกสาเหตุ การส่ง object Error (setTag('error', err)) บันทึก error.message, error.type และ error.stack idiom ที่สะอาดคือห่องานด้วย span ของตัวเองผ่าน tracer.trace('payment.charge', fn) — exception ที่ throw (หรือ promise ที่ reject) ใน fn จะทำให้ span นั้น เป็น error พร้อม message โดยอัตโนมัติ

res.status(200).json({ ok: false }) span express.request ข้างบนแสดง http.status_code=200, error=0 HTTP server plugin ของ dd-trace ตัดสิน “error” จาก status: validateStatus มี default เป็น code => code < 500 (index.d.ts, HttpServer.validateStatus) บัตรที่ถูกปฏิเสธซึ่งตอบ 200 คือ success สำหรับทุก APM monitor, service error rate และ availability SLO ใดๆ เมื่อตอบ 502 การรันเดิมทำให้ root span เป็น error=1

🐛 หา bug ในโค้ดนี้

res.status(200).json({ ok: false });
เฉลย

นี่คือการตัดสินใจเชิง design ของแอปที่ทำให้ observability มองไม่เห็น ให้ตอบ status ที่ตรงกับผลลัพธ์ (ความล้มเหลวฝั่ง server หรือ upstream จริงคือ 5xx; การ decline ที่เกิดจากฝั่ง client อาจเป็น 4xx ได้อย่างสมเหตุสมผล ซึ่ง default ไม่ นับเป็น error — ตั้ง validateStatus ถ้านิยามของ error ของคุณต่างออกไป) อย่าเลือก status code เพื่อเอาใจ dashboard แต่ก็อย่าซ่อนความล้มเหลวจากมัน

รายการ fake-agent แรกข้างบนมี express.request GET /health — trace เต็มพร้อม span router.middleware สองตัว สำหรับทุก liveness และ readiness probe probe ทุก 10 วินาทีคือ 8,640 trace ต่อ pod ต่อวันที่เป็น noise ล้วนๆ และทำให้ latency กับ error statistics ของ service เจือจาง ด้วย tracer.use('http', { server: { blocklist: ['/health'] } }) (Http.blocklist ใน typings; ผ่าน tsc) สอง request เดิมให้ trace ของ /charge เท่านั้น: request /health ไม่ปรากฏใน fake agent เลย

🐛 หา bug ในโค้ดนี้

app.get('/health', (req, res) => res.send('ok'));
เฉลย

route ไม่ได้ผิดอะไร bug คือสิ่งที่ หายไป ข้างๆ มัน traffic ของ probe ควรถูกตัดออกจากการ trace ด้วย blocklist (หรือ filter) ของ http plugin วางหลัง init() และก่อน server เริ่ม ทางเลือกฝั่ง ingestion — sampling rule ที่ทิ้ง resource — ก็ใช้ได้แต่ tracer ยังต้องทำงานสร้าง span; docs ของทั้งสองทางอยู่ใต้ docs.datadoghq.com/tracing/

ใน Deployment มีการ “เปิด” injection ด้วย DD_LOG_INJECTION=true harness กับสำเนา fence นั้น:

ERROR src/content/docs/en/x/bad.mdx:fence #1: unknown env var DD_LOG_INJECTION

และหลักฐาน runtime โค้ดเดียวกัน ต่างแค่ชื่อตัวแปร อ่านจาก config dump และ log หนึ่งบรรทัด:

DD_LOG_INJECTION=false -> "log_injection_enabled":true {"msg":"hi","dd":{"service":"x","version":"1.0.0"}}
DD_LOGS_INJECTION=false -> "log_injection_enabled":false {"msg":"hi"}

ชื่อจริงคือ DD_LOGS_INJECTION (พหูพจน์) ตัวที่ไม่รู้จักถูกเพิกเฉย ซึ่งเป็นความล้มเหลวแบบ “รับเงียบๆ” เดียวกับ option snake_case แต่ห่างออกไปอีกชั้น

🐛 หา bug ในโค้ดนี้

env:
- name: DD_LOG_INJECTION
value: "true"
เฉลย

ชื่อ environment variable ถูก match แบบตรงตัว และ tracer ไม่รายงานตัวที่ไม่รู้จัก harness ที่ pin ไว้เช็กทุกชื่อ DD_* ใน fence ที่ถูกเก็บกับรายการ supported-configuration ของ dd-trace เอง วิธีแก้: DD_LOGS_INJECTION — หรือลบทิ้ง เพราะ default ของ Node tracer เปิดอยู่แล้ว

localhost ใน pod คือ pod นั้นเอง tracer ใช้ตามที่เขียนทุกตัวอักษร:

DD_AGENT_HOST=localhost -> "agent_url":"http://localhost:8126/"
DD_AGENT_HOST=10.20.30.40 -> "agent_url":"http://10.20.30.40:8126/"

ใน Kubernetes Datadog Agent มักรันเป็น DaemonSet ดังนั้น address คือ IP ของ node จาก docs (docs.datadoghq.com/containers/kubernetes/apm/): “The application container needs the DD_AGENT_HOST environment variable that points to status.hostIP” ด้วย fieldRef ที่ status.hostIP หรือใช้ Admission Controller ส่วนที่ node Agent รับ traffic นั้นหรือไม่เป็นการตั้งค่าฝั่ง Agent อีกเรื่อง (hostPort หรือ host-network / Unix socket) — เป็นหัวข้อของ worked review ถัดไป

🐛 หา bug ในโค้ดนี้

- name: DD_AGENT_HOST
value: localhost
เฉลย

localhost ใช้ได้บน laptop ที่มี Agent ในเครื่อง ซึ่งเป็นเหตุผลที่ assistant เขียนมัน ใน pod มันชี้ไปที่ port ที่ไม่มีอะไรฟัง และ trace ถูกทิ้งโดยไม่มี error ที่แอปมองเห็น วิธีแก้: valueFrom.fieldRef.fieldPath: status.hostIP

src/reading-datadog/worked-review-1/server.js
const tracer = require('dd-trace').init({ logInjection: true });
tracer.use('http', { server: { blocklist: ['/health'] } });
const express = require('express');
const pino = require('pino');
const logger = pino();
const app = express();
app.get('/health', (req, res) => res.send('ok'));
app.post('/charge', async (req, res) => {
try {
await tracer.trace('payment.charge', async () => {
logger.info('charging card');
throw new Error('card declined');
});
} catch (err) {
logger.error({ err }, 'charge failed');
res.status(502).json({ ok: false, error: 'payment_failed' }); // choose the status that matches what happened
}
});
app.listen(process.env.PORT || 3100, () => logger.info('listening'));
k8s/checkout-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
labels:
tags.datadoghq.com/env: prod
tags.datadoghq.com/service: checkout-api
tags.datadoghq.com/version: 2.4.1
spec:
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
tags.datadoghq.com/env: prod
tags.datadoghq.com/service: checkout-api
tags.datadoghq.com/version: 2.4.1
spec:
containers:
- name: checkout-api
image: registry.example.com/checkout-api:2.4.1
env:
- name: DD_ENV
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/env']
- name: DD_SERVICE
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/service']
- name: DD_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.labels['tags.datadoghq.com/version']
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: DD_LOGS_INJECTION
value: "true"

รันจริง (fake agent บน port default, DD_ENV=prod DD_SERVICE=checkout-api DD_VERSION=2.4.1, GET /health หนึ่งครั้งและ POST /charge หนึ่งครั้ง) fake agent พิมพ์ span สี่ตัวนี้เป๊ะ และ ไม่มี trace ของ /health:

express.request POST /charge error=1 env=prod version=2.4.1 http.status_code=502
router.middleware handle error=0 env=prod version=2.4.1
router.middleware <anonymous> error=0 env=prod version=2.4.1
payment.charge payment.charge error=1 env=prod version=2.4.1 error.type=Error error.message=card declined

และ log line ตอนนี้มี identity เดียวที่สอดคล้องกัน:

{"level":30,...,"msg":"charging card","dd":{"trace_id":"6abc77ea0000000033867be986a3a994","span_id":"598578184621079437","service":"checkout-api","version":"2.4.1","env":"prod"}}
{"level":50,...,"err":{"type":"Error","message":"card declined",...},"msg":"charge failed","dd":{...same trace_id...}}

YAML ไม่ได้ถูก apply กับ cluster (ไม่มี cluster kind สำหรับ harness ของคอร์ส; โหมด --k8s รายงานแล้วออกแบบ clean) สิ่งที่ ถูก เช็กคือทุกชื่อ DD_* ในนั้น ด้วยโหมด --options ของ harness

#Bugวิธีแก้พิสูจน์ด้วย
1init() หลังโหลด express/pinoinit() บรรทัด 1ไม่มี span, span เป็น null, ไม่มี dd ใน log เทียบกับมีครบทั้งสาม
2log_injection (snake_case)logInjection หรือไม่ใส่tsc TS2561; config dump
3agentUrl ที่แต่งขึ้นDD_TRACE_AGENT_URL / DD_AGENT_HOSTagent 28126 ได้ 0 span, 8126 ได้ 6
4analytics, traceSampleRate ที่แต่งขึ้นลบ; sampleRate จริงเฉพาะเมื่อตั้งใจ40 จาก 40 เป็น priority 1 เทียบกับ 15/25
5env/version hard-code ใน init()identity จาก DD_ENV/DD_SERVICE/DD_VERSIONspan แสดง Production/1.0 ทั้งที่ตั้ง DD_ENV=prod
6service/env hard-code ใน pino base ด้วยแหล่งเดียว; block dd ที่ injectสองค่าที่ต่างกันในบรรทัดเดียว
7setTag('error', true) บน scope().active()ห่างานด้วย tracer.trace(...)error=1 บน router.middleware, 0 บน root
8ตอบความล้มเหลวด้วย 200status ที่ตรงกับผลลัพธ์root span error=0 status 200 เทียบกับ error=1, 502
9/health ถูก tracetracer.use('http', { server: { blocklist } })trace ของ probe มีเทียบกับไม่มี
10DD_LOG_INJECTIONDD_LOGS_INJECTION (หรือไม่ใส่)harness “unknown env var”; config dump
11DD_AGENT_HOST=localhost ใน podfieldRef: status.hostIPconfig dump; docs URL

อนุมัติไม่ได้ตามนี้ — 11 เรื่อง เรียงตามลำดับที่กัดจริง:

  1. init() ต้องเป็นบรรทัดแรก ทุกอย่างที่ require ก่อนมันไม่ถูก instrument: ตอนนี้ไม่มี Express span, scope().active() เป็น null (/charge ครั้งแรก throw) และ log ไม่มี trace id
  2. Option ที่แต่งขึ้นสี่ตัว (log_injection, agentUrl, analytics, traceSampleRate) — ไม่มีใน TracerOptions รัน tsc หรือ npm run verify โดยเฉพาะ agentUrl แปลว่า trace ไป :8126 ไม่ใช่ที่โค้ดบอก
  3. อย่าใส่ env, version หรือ service ในโค้ด หรือใน base ของ pino มัน override ค่า DD_* ของ pod (พิสูจน์แล้ว) และขัดกับ block dd ที่ inject แหล่งเดียว: environment
  4. Error handling: setTag('error', true) ธง router layer ไม่ใช่ root และไม่มี message ให้ห่าด้วย tracer.trace(...) และ charge ที่ถูกปฏิเสธต้องไม่ใช่ 200
  5. Block /health ใน http plugin
  6. Deployment: DD_LOG_INJECTION ไม่ใช่ตัวแปร (DD_LOGS_INJECTION ต่างหาก และเป็น default อยู่แล้ว) และ DD_AGENT_HOST ต้องมาจาก status.hostIP ไม่ใช่ localhost
flowchart TD
  A["require order: express + pino first"] -->|"1: nothing instrumented"| B["tracer.init options"]
  B -->|"2, 3, 4: invented keys ignored"| C["tracer config"]
  B -->|"5: code beats DD_ENV / DD_VERSION"| C
  C -->|"3, 11: wrong agent address"| D["Agent"]
  E["pino base service + env"] -->|"6: contradicts dd block"| F["log line"]
  G["route handler"] -->|"7: error on inner span"| H["spans"]
  G -->|"8: 200 on failure"| H
  I["/health probe"] -->|"9: traced"| H
  J["pod env"] -->|"10: unknown variable"| C
แต่ละ bug อยู่ตรงไหนของ Node service

สี่จากสิบเอ็ด bug (2, 3, 4, 10) คือ ชื่อที่ไม่มีใครเช็ก: JavaScript และ tracer รับ key อะไรก็ได้ และ environment รับตัวแปรอะไรก็ได้ tsc และโหมด --options ของ harness ปิดช่องนี้ได้ฟรี Bug 1 เป็นข้อผิดพลาดเรื่องลำดับที่ option checker ไม่เห็น อีกหกข้อ (5, 6, 7, 8, 9, 11) ผ่าน type-check และ start ได้ มีแต่ payload ที่จับได้ config dump หรือ log line เท่านั้นที่เห็น นี่คือเหตุผลที่รีวิวนี้ใช้ fake agent ไม่ใช่แค่ linter — และทำไม bug 1 มาก่อน: มันซ่อนที่เหลือไว้

  • Config ในโค้ด vs environment init({ service, env, version }) ในโค้ดอ่านง่ายแต่ผิดสำหรับ image ที่รันหลาย environment; environment มองไม่เห็นในซอร์สแต่เป็นสิ่งเดียวที่ deploy tooling ควบคุมได้ เลือก environment สำหรับ identity; เก็บ option ในโค้ดไว้สำหรับพฤติกรรม (logInjection, ค่า plugin)
  • blocklist vs กฎฝั่ง ingestion การ block /health ใน tracer ตัดทั้งงานและข้อมูลที่ต้นทาง; sampling rule ฝั่ง Datadog แก้ได้โดยไม่ต้อง deploy แต่ span ยังถูกสร้างและส่ง
  • Fake agent vs Agent จริง ตัวรับในเครื่องแสดงเป๊ะว่า tracer ส่งอะไร และไม่บอกอะไรเลยว่า Datadog ทำอะไรกับมัน การ normalise, ingestion sampling และพฤติกรรม pipeline ยังคงเป็นข้อกล่าวอ้างที่มาจาก docs ในรีวิวนี้ และติดป้ายไว้เช่นนั้น
  • “ถ้า init() รับ options object ของฉัน แปลว่า option ถูกต้อง” พิสูจน์แล้วข้างบน: key ที่แต่งขึ้นทั้งสี่ตัวไม่เปลี่ยนอะไรเลย โดยไม่มี warning ที่ log level ใดๆ มีแต่ tsc, harness หรือ config dump ที่บอก
  • “tracer.scope().active() คือ request span” พิสูจน์แล้วข้างบน: ใน Express route มันคือ span router.middleware ในสุด; การ tag มันทิ้ง root ไว้ที่ error=0
  • “ตั้ง env ในโค้ดและ DD_ENV บน pod ด้วยคือเข็มขัดกับเชือกผูกกางเกง” พิสูจน์แล้วข้างบน: ค่าจากโค้ดชนะแบบเงียบๆ สองแหล่งแปลว่าตัวที่ผิดอาจชนะ
  • “HTTP 200 พร้อม ok: false ไม่เป็นไร ตราบที่ log เขียนว่า error” ทุก APM error-rate monitor คำนวณจาก span ไม่ใช่จากข้อความใน log
service ที่รีวิว require express และ pino ที่บรรทัด 1-2 แล้วเรียก init() ของ dd-trace ที่บรรทัด 3 การรันแสดงอะไรสำหรับ POST /charge?
tracer.init({ log_injection: true, agentUrl: 'http://127.0.0.1:28126' }) start ได้โดยไม่มี error อะไรจริง?
pod ตั้ง DD_ENV=prod และ DD_VERSION=2.4.1; โค้ดเรียก init({ env: 'Production', version: '1.0' }) ค่าไหนปรากฏบน span?
handler จับ payment error, รัน tracer.scope().active().setTag('error', true) และตอบ HTTP 200 span ที่จับได้แสดงอะไร?
manifest ตั้ง DD_LOG_INJECTION=false เพื่อปิด log injection เกิดอะไรขึ้น?
ข้อกล่าวอ้างใดในรีวิวนี้เป็นคำอ้างจาก documentation ไม่ใช่สิ่งที่พิสูจน์ด้วยการรันในเครื่อง?