Worked review 1: Node checkout service
Feature ที่ขอ
หัวข้อที่มีชื่อว่า “Feature ที่ขอ”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-trace6.17.0 (pin ของ harness ในtools/probe/versions.json),express5.2.1,pino10.3.1,typescript7.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 ที่รวมไว้
โค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “โค้ดที่ AI สร้าง”// @expect-error as generated -- every finding below comes from running this fileconst 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 Agentenv: - name: DD_LOG_INJECTION value: "true" - name: DD_AGENT_HOST value: localhostเดินรีวิวทีละข้อ
หัวข้อที่มีชื่อว่า “เดินรีวิวทีละข้อ”dd-trace ถูก initialise หลังจากโหลด express และ pino ไปแล้ว
หัวข้อที่มีชื่อว่า “dd-trace ถูก initialise หลังจากโหลด express และ pino ไปแล้ว”สองบรรทัดแรก require framework กับ logger แล้วค่อยรัน init() รันตามที่ generate โดยมี fake agent รออยู่ และยิง 2 request (GET /health, POST /charge):
$ curl -s -XPOST localhost:3100/chargeTypeError: 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
log_injection — option แบบ snake_case ที่ไม่ทำอะไรเลย
หัวข้อที่มีชื่อว่า “log_injection — option แบบ snake_case ที่ไม่ทำอะไรเลย”Block ถัดมาขอ log_injection: true tsc --strict กับ typings ที่ pin ทีละ key:
$ tsc --noEmit --strict opts.tsopts.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
agentUrl — trace ไป 127.0.0.1:8126 ไม่ใช่ที่โค้ดบอก
หัวข้อที่มีชื่อว่า “agentUrl — trace ไป 127.0.0.1:8126 ไม่ใช่ที่โค้ดบอก”service ถูกสั่งให้ส่งไป http://127.0.0.1:28126 รัน fake agent สองตัวคู่กัน ตัวหนึ่งที่ 28126 อีกตัวที่ default 8126:
agent A (28126, the URL in code): 0 spansagent B (default 8126): 6 spansstartup 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 ค่านั้นควรเป็นอะไร
analytics และ traceSampleRate — option ที่ไม่เคยมีอยู่
หัวข้อที่มีชื่อว่า “analytics และ traceSampleRate — option ที่ไม่เคยมีอยู่”ตระกูลเดียวกัน ผลต่างกัน 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 จริง
env และ version ถูก hard-code — env var ของ Kubernetes แพ้
หัวข้อที่มีชื่อว่า “env และ version ถูก hard-code — env var ของ Kubernetes แพ้”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.0option ที่ส่งให้ 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)
service และ env ของ logger เองขัดกับค่าที่ inject
หัวข้อที่มีชื่อว่า “service และ env ของ logger เองขัดกับค่าที่ inject”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
setTag('error', true) — บน span ผิดตัว และไม่มี message
หัวข้อที่มีชื่อว่า “setTag('error', true) — บน span ผิดตัว และไม่มี message”เมื่อแก้ init แล้ว handler ก็ทำงาน สิ่งที่ fake agent ได้รับสำหรับ POST /charge:
express.request POST /charge error=0 http.status_code=200router.middleware handle error=0router.middleware <anonymous> error=1tracer.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=502payment.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 โดยอัตโนมัติ
ตอบ charge ที่ล้มเหลวด้วย 200 OK
หัวข้อที่มีชื่อว่า “ตอบ charge ที่ล้มเหลวด้วย 200 OK”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 แต่ก็อย่าซ่อนความล้มเหลวจากมัน
/health ถูก trace ทุกครั้งที่ probe
หัวข้อที่มีชื่อว่า “/health ถูก trace ทุกครั้งที่ probe”รายการ 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/
DD_LOG_INJECTION — ตัวแปรที่ไม่มีอยู่จริง
หัวข้อที่มีชื่อว่า “DD_LOG_INJECTION — ตัวแปรที่ไม่มีอยู่จริง”ใน 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 เปิดอยู่แล้ว
DD_AGENT_HOST=localhost — คือ pod เอง ไม่ใช่ node Agent
หัวข้อที่มีชื่อว่า “DD_AGENT_HOST=localhost — คือ pod เอง ไม่ใช่ node Agent”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_HOSTvalue: localhostเฉลย
localhost ใช้ได้บน laptop ที่มี Agent ในเครื่อง ซึ่งเป็นเหตุผลที่ assistant เขียนมัน ใน pod มันชี้ไปที่ port ที่ไม่มีอะไรฟัง และ trace ถูกทิ้งโดยไม่มี error ที่แอปมองเห็น วิธีแก้: valueFrom.fieldRef.fieldPath: status.hostIP
โค้ดที่แก้แล้ว
หัวข้อที่มีชื่อว่า “โค้ดที่แก้แล้ว”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'));apiVersion: apps/v1kind: Deploymentmetadata: name: checkout-api labels: tags.datadoghq.com/env: prod tags.datadoghq.com/service: checkout-api tags.datadoghq.com/version: 2.4.1spec: 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=502router.middleware handle error=0 env=prod version=2.4.1router.middleware <anonymous> error=0 env=prod version=2.4.1payment.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 | วิธีแก้ | พิสูจน์ด้วย |
|---|---|---|---|
| 1 | init() หลังโหลด express/pino | init() บรรทัด 1 | ไม่มี span, span เป็น null, ไม่มี dd ใน log เทียบกับมีครบทั้งสาม |
| 2 | log_injection (snake_case) | logInjection หรือไม่ใส่ | tsc TS2561; config dump |
| 3 | agentUrl ที่แต่งขึ้น | DD_TRACE_AGENT_URL / DD_AGENT_HOST | agent 28126 ได้ 0 span, 8126 ได้ 6 |
| 4 | analytics, traceSampleRate ที่แต่งขึ้น | ลบ; sampleRate จริงเฉพาะเมื่อตั้งใจ | 40 จาก 40 เป็น priority 1 เทียบกับ 15/25 |
| 5 | env/version hard-code ใน init() | identity จาก DD_ENV/DD_SERVICE/DD_VERSION | span แสดง Production/1.0 ทั้งที่ตั้ง DD_ENV=prod |
| 6 | service/env hard-code ใน pino base ด้วย | แหล่งเดียว; block dd ที่ inject | สองค่าที่ต่างกันในบรรทัดเดียว |
| 7 | setTag('error', true) บน scope().active() | ห่างานด้วย tracer.trace(...) | error=1 บน router.middleware, 0 บน root |
| 8 | ตอบความล้มเหลวด้วย 200 | status ที่ตรงกับผลลัพธ์ | root span error=0 status 200 เทียบกับ error=1, 502 |
| 9 | /health ถูก trace | tracer.use('http', { server: { blocklist } }) | trace ของ probe มีเทียบกับไม่มี |
| 10 | DD_LOG_INJECTION | DD_LOGS_INJECTION (หรือไม่ใส่) | harness “unknown env var”; config dump |
| 11 | DD_AGENT_HOST=localhost ใน pod | fieldRef: status.hostIP | config dump; docs URL |
สิ่งที่ reviewer เขียนใน PR
หัวข้อที่มีชื่อว่า “สิ่งที่ reviewer เขียนใน PR”อนุมัติไม่ได้ตามนี้ — 11 เรื่อง เรียงตามลำดับที่กัดจริง:
init()ต้องเป็นบรรทัดแรก ทุกอย่างที่requireก่อนมันไม่ถูก instrument: ตอนนี้ไม่มี Express span,scope().active()เป็นnull(/chargeครั้งแรก throw) และ log ไม่มี trace id- Option ที่แต่งขึ้นสี่ตัว (
log_injection,agentUrl,analytics,traceSampleRate) — ไม่มีในTracerOptionsรันtscหรือnpm run verifyโดยเฉพาะagentUrlแปลว่า trace ไป:8126ไม่ใช่ที่โค้ดบอก- อย่าใส่
env,versionหรือserviceในโค้ด หรือในbaseของ pino มัน override ค่าDD_*ของ pod (พิสูจน์แล้ว) และขัดกับ blockddที่ inject แหล่งเดียว: environment- Error handling:
setTag('error', true)ธง router layer ไม่ใช่ root และไม่มี message ให้ห่าด้วยtracer.trace(...)และ charge ที่ถูกปฏิเสธต้องไม่ใช่200- Block
/healthในhttpplugin- Deployment:
DD_LOG_INJECTIONไม่ใช่ตัวแปร (DD_LOGS_INJECTIONต่างหาก และเป็น default อยู่แล้ว) และDD_AGENT_HOSTต้องมาจากstatus.hostIPไม่ใช่localhost
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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 (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 มาก่อน: มันซ่อนที่เหลือไว้
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- Config ในโค้ด vs environment
init({ service, env, version })ในโค้ดอ่านง่ายแต่ผิดสำหรับ image ที่รันหลาย environment; environment มองไม่เห็นในซอร์สแต่เป็นสิ่งเดียวที่ deploy tooling ควบคุมได้ เลือก environment สำหรับ identity; เก็บ option ในโค้ดไว้สำหรับพฤติกรรม (logInjection, ค่า plugin) blocklistvs กฎฝั่ง ingestion การ block/healthใน tracer ตัดทั้งงานและข้อมูลที่ต้นทาง; sampling rule ฝั่ง Datadog แก้ได้โดยไม่ต้อง deploy แต่ span ยังถูกสร้างและส่ง- Fake agent vs Agent จริง ตัวรับในเครื่องแสดงเป๊ะว่า tracer ส่งอะไร และไม่บอกอะไรเลยว่า Datadog ทำอะไรกับมัน การ normalise, ingestion sampling และพฤติกรรม pipeline ยังคงเป็นข้อกล่าวอ้างที่มาจาก docs ในรีวิวนี้ และติดป้ายไว้เช่นนั้น
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “ถ้า
init()รับ options object ของฉัน แปลว่า option ถูกต้อง” พิสูจน์แล้วข้างบน: key ที่แต่งขึ้นทั้งสี่ตัวไม่เปลี่ยนอะไรเลย โดยไม่มี warning ที่ log level ใดๆ มีแต่tsc, harness หรือ config dump ที่บอก - “
tracer.scope().active()คือ request span” พิสูจน์แล้วข้างบน: ใน Express route มันคือ spanrouter.middlewareในสุด; การ tag มันทิ้ง root ไว้ที่error=0 - “ตั้ง
envในโค้ดและDD_ENVบน pod ด้วยคือเข็มขัดกับเชือกผูกกางเกง” พิสูจน์แล้วข้างบน: ค่าจากโค้ดชนะแบบเงียบๆ สองแหล่งแปลว่าตัวที่ผิดอาจชนะ - “HTTP 200 พร้อม
ok: falseไม่เป็นไร ตราบที่ log เขียนว่าerror” ทุก APM error-rate monitor คำนวณจาก span ไม่ใช่จากข้อความใน log