AI bug catalog
ทุก snippet ด้านล่างเขียนเหมือนที่ AI assistant เขียนจริง แล้วรันกับเครื่องมือจริง: datadog/agent:7 (Agent 7.83.3, image digest sha256:c5bf5ec9…, container dd-reading-a ที่ลบทิ้งหลังใช้) ด้วย API key ปลอมและ DD_DD_URL ชี้ไป 127.0.0.1 หรือ listener ในเครื่อง, dd-trace 6.17.0 และ @datadog/browser-rum 7.15.0 กับ TypeScript compiler, ddtrace 4.15.2, terraform validate กับ datadog provider 4.22.0 (Terraform v1.16.4, ไม่มี plan ไม่มี apply, ไม่ตั้ง cloud credential) และ Python model ของ datadog-api-client 2.61.0 ไม่มีอะไรถูกส่งไป Datadog เลย — โมดูลนี้ไม่มี account และไม่มี key จริงที่ไหนทั้งสิ้น ตัวเลขเวลาและ port มาจาก Mac เครื่องเดียว
แต่ละ entry จบด้วยบรรทัด Caught by และคุณจะเห็น pattern: เครื่องมือที่จับ bug ต่างกันมากในแต่ละ entry บางอันเป็น error แข็ง (agent configcheck, terraform validate, tsc) บางอัน ทุกเครื่องมือ offline ยอมรับเงียบ ๆ และปรากฏที่อื่นเท่านั้น (agent status, payload ที่ดักได้) และบางอันมีแต่ Datadog API หรือการอ่าน docs ที่จับได้ — entry เหล่านั้นบอกตรง ๆ และอ้าง URL ของ docs แทนที่จะแกล้งว่ามีเครื่องมือพิสูจน์ให้
Agent configuration และ deployment
หัวข้อที่มีชื่อว่า “Agent configuration และ deployment”1. instances: เขียนเป็น mapping ไม่ใช่ list
หัวข้อที่มีชื่อว่า “1. instances: เขียนเป็น mapping ไม่ใช่ list”AI มัก flatten config ของ check เพราะใน README หลายที่ตัวอย่าง instances เขียนเป็นบล็อกเดียว แต่ Agent ต้องการ list ของ instance
# @expect-error mysql.d instances is a map, not a list -> "cannot unmarshal !!map into []integration.RawMap"init_config:
instances: host: mysql username: datadog port: 3306อาการ — output จริงจาก agent configcheck (Agent 7.83.3):
=== Configuration errors ===
mysql: yaml: unmarshal errors: line 4: cannot unmarshal !!map into []integration.RawMapagent status แสดงซ้ำใต้ Config Errors และ check ไม่รันเลย — ไม่มี metric ไม่มีบรรทัด instance ที่เป็น [ERROR]
วิธีแก้:
init_config:
instances: - host: mysql username: datadog port: 3306Caught by: agent configcheck (error แข็ง) ดู The Datadog Agent and integrations
2. tags: env:prod — scalar ในที่ที่ check ต้องการ list
หัวข้อที่มีชื่อว่า “2. tags: env:prod — scalar ในที่ที่ check ต้องการ list”YAML ถูกต้อง configcheck พิมพ์ config เหมือนไม่มีอะไรผิด — แต่ check ก็ load ไม่ขึ้น
# @expect-error tags must be a list: "cannot unmarshal !!str `env:prod` into []string" (only visible in agent status, not configcheck)init_config:
instances: - host: postgres port: 5432 username: datadog tags: env:prodอาการ — output จริง agent configcheck แสดง instance postgres พร้อม tags: env:prod และ ไม่มี error เลย ส่วน agent status บอกความจริง:
Loading Errors ============== postgres -------- Core Check Loader: Check postgres not found in Catalog
Python Check Loader: could not configure check instance for python check postgres: yaml: unmarshal errors: line 3: cannot unmarshal !!str `env:prod` into []stringวิธีแก้:
init_config:
instances: - host: postgres port: 5432 username: datadog tags: - env:prodCaught by: agent status (Loading Errors) — ไม่ใช่ agent configcheck ผล configcheck ที่สะอาดเป็นเงื่อนไขจำเป็น ไม่เคยเป็นเงื่อนไขเพียงพอ
3. conf.d/redis.d/ — integration ชื่อ redisdb
หัวข้อที่มีชื่อว่า “3. conf.d/redis.d/ — integration ชื่อ redisdb”AI ตั้งชื่อ directory ตามชื่อผลิตภัณฑ์ แต่ Agent ตั้งชื่อตาม check
# @expect-error the check is named redisdb, there is no redis check: "Check redis not found in Catalog"init_config:
instances: - host: redis port: 6379ไฟล์นี้อยู่ที่ conf.d/redis.d/conf.yaml อาการ — output จริง: agent configcheck พิมพ์บล็อก === redis check === ที่ดูปกติ (ไม่มี error) และ agent status บอกว่า:
redis ----- Core Check Loader: Check redis not found in Catalog
Python Check Loader: unable to load check redis: unable to load python module datadog_checks.redis: unable to import module 'datadog_checks.redis': No module named 'datadog_checks.redis', unable to load python module redis: unable to find a subclass of the base check in module 'redis': cannot find a subclassวิธีแก้: ย้ายไปใช้ชื่อ check จริง
init_config:
instances: - host: redis port: 6379Caught by: agent status (Loading Errors) บทเรียนเดียวกับ entry 2: ชื่อ directory คือข้อมูล และ configcheck ไม่รู้ว่ามี check ชื่อนั้นอยู่จริงหรือไม่
4. instance ที่ shape ถูกแต่ค่าผิด type
หัวข้อที่มีชื่อว่า “4. instance ที่ shape ถูกแต่ค่าผิด type”# @expect-error timeout must be a number: "Input should be a valid number, unable to parse string as a number" (agent status only)init_config:
instances: - name: checkout url: http://checkout:8080/health timeout: "five"อาการ — output จริง agent configcheck พิมพ์ timeout: five โดยไม่มี error ส่วน agent status:
http_check (13.1.0) ------------------- Instance ID: http_check:checkout:85dc6c39d0e92c1b [ERROR] ... Error: Detected 1 error while loading configuration model `InstanceConfig`:timeout Input should be a valid number, unable to parse string as a numberวิธีแก้:
init_config:
instances: - name: checkout url: http://checkout:8080/health timeout: 5Caught by: agent status — เป็น pydantic model ของ check เอง ไม่ใช่ตัว load config ของ Agent
5. bug ผี: “ไม่มี init_config: แล้ว check จะพัง”
หัวข้อที่มีชื่อว่า “5. bug ผี: “ไม่มี init_config: แล้ว check จะพัง””reviewer (คนหรือ AI) ที่จำมาว่า “conf.yaml ทุกไฟล์ต้องมี init_config:” จะ flag ไฟล์นี้
# @expect-error demonstrates a NON-bug: this loads fine without init_config; the only error is that host "nginx" does not resolveinstances: - nginx_status_url: http://nginx:81/nginx_status/Output จริง agent configcheck พิมพ์บล็อก === nginx check === โดย ไม่มี configuration error; agent status แสดง instance ที่รันและล้มเหลวเพราะ DNS เท่านั้น: Failed to resolve 'nginx' ([Errno -2] Name or service not known) ดังนั้นกฎ “ไม่มี init_config คือ error” เป็นแค่ความเชื่อสำหรับ check นี้บน Agent 7.83.3 — ไฟล์ถูก load
บทเรียนอยู่ที่ตัว review ไม่ใช่ไฟล์ การเติม init_config: ไม่มีโทษและตรงกับตัวอย่างอ้างอิงทุกตัว จึงควรใส่ แต่ห้ามเขียนว่า “ไม่มี init_config” เป็น bug ถ้ายังไม่ได้รันเครื่องมือ
init_config:
instances: - nginx_status_url: http://nginx:81/nginx_status/Caught by: ไม่มีอะไร — มันไม่เคยผิด entry นี้คือเหตุผลที่โมดูลทั้งโมดูลมีอยู่: พิสูจน์ อย่าจับ pattern
6. DD_SITE ที่เป็น URL
หัวข้อที่มีชื่อว่า “6. DD_SITE ที่เป็น URL”AI มักให้ DD_SITE เป็น API base URL เพราะเครื่องมืออื่นขอแบบนั้น แต่ DD_SITE คือ domain ของ site
# @expect-error DD_SITE takes a site domain (datadoghq.com), not a URLdocker run -d -e DD_API_KEY=<your-api-key> -e DD_SITE=https://api.datadoghq.com datadog/agent:7อาการ — output จริง (agent status, endpoints, ใช้ DD_DD_URL ในเครื่องเพื่อไม่ให้อะไรออกนอกเครื่อง):
# DD_SITE=https://api.datadoghq.com https://process.https://api.datadoghq.com. - API Key ending with:
# DD_SITE=datadoghq.eu https://process.datadoghq.eu. - API Key ending with:Agent ไม่ปฏิเสธค่านี้ แต่เอาไปต่อหน้าชื่อ intake ทุกตัว ได้ address ที่ resolve ไม่ได้
วิธีแก้:
site: datadoghq.comCaught by: รายการ endpoint ใน agent status (และ DNS error ของ forwarder ใน log) ไม่มี linter offline ที่รู้รายชื่อ site รายชื่อ site: docs.datadoghq.com/getting_started/site/
7. datadog/agent:latest ใน manifest
หัวข้อที่มีชื่อว่า “7. datadog/agent:latest ใน manifest”# @expect-error not a syntax error: an unpinned, moving tag (see the digest output below)image: datadog/agent:latestOutput จริง — ทั้งสาม tag ชี้ image เดียวกันในวันนี้:
$ for t in 7 7.83.3 latest; do docker image inspect datadog/agent:$t --format '{{.Id}}'; donesha256:da43d3ecfad79906df4d32c21c3dcee8c681ba8f0afa9a59f6663d8f5489d70csha256:da43d3ecfad79906df4d32c21c3dcee8c681ba8f0afa9a59f6663d8f5489d70csha256:da43d3ecfad79906df4d32c21c3dcee8c681ba8f0afa9a59f6663d8f5489d70cนี่คือผลลัพธ์ที่ซื่อสัตย์: วันนี้ไม่มีอะไรพัง bug คือ reproducibility — latest และ 7 จะชี้ไป Agent ตัวอื่นในสัปดาห์หน้า (และ 7.83.3 คือ release ณ วันที่จับข้อมูลนี้) postmortem ของ incident จึงบอกไม่ได้ว่า Agent ตัวไหนรันอยู่ ให้ pin tag เต็มหรือ digest แล้วอัปเดตอย่างตั้งใจ
apiVersion: v1kind: Podmetadata: name: agent-image-examplespec: containers: - name: agent image: datadog/agent:7.83.3Caught by: review เท่านั้น; docker image inspect แสดงว่า tag ตรงกันในตอนนี้ ซึ่งเป็นเหตุผลที่ไม่มีใครสังเกต
8. Autodiscovery annotation บน Deployment (และในรูปแบบ v1)
หัวข้อที่มีชื่อว่า “8. Autodiscovery annotation บน Deployment (และในรูปแบบ v1)”ความผิดสองอย่างที่ AI ทำในลมหายใจเดียว: ใส่ annotation บน workload object และใช้รูปแบบสาม key ก่อน 7.36
# @expect-error Autodiscovery reads POD annotations (spec.template.metadata), not Deployment metadata; v1 keys check_names/init_configs/instances are the pre-7.36 formatapiVersion: apps/v1kind: Deploymentmetadata: name: cache annotations: ad.datadoghq.com/cache.check_names: '["redisdb"]' ad.datadoghq.com/cache.init_configs: '[{}]' ad.datadoghq.com/cache.instances: '[{"host":"%%host%%","port":"6379"}]'spec: selector: matchLabels: { app: cache } template: metadata: labels: { app: cache } spec: containers: - name: cache image: redis:7ไม่ได้พิสูจน์ด้วยการรันที่นี่ — docs เท่านั้น ต้องมี cluster และ mode --k8s ของ harness ต้องมี kind cluster ซึ่งไม่มี สิ่งที่ docs บอก (docs.datadoghq.com/containers/kubernetes/integrations/): สำหรับ pod ที่สร้างโดย Deployment ให้ “Add annotations under spec.template.metadata”; identifier ของ container ใน ad.datadoghq.com/<CONTAINER_NAME>.checks ตรงกับ spec.containers[i].name; รูปแบบ .checks (v2) ที่รวมเป็นอันเดียวต้องใช้ Agent 7.36+ ส่วนแบบสาม key เป็นรูปแบบก่อนหน้า kubectl ยอมรับทั้งสองตำแหน่งโดยไม่บ่น ดังนั้นไม่มีอะไร offline จับได้
วิธีแก้:
apiVersion: apps/v1kind: Deploymentmetadata: name: cachespec: selector: matchLabels: { app: cache } template: metadata: labels: { app: cache } annotations: ad.datadoghq.com/cache.checks: | { "redisdb": { "init_config": {}, "instances": [{ "host": "%%host%%", "port": "6379" }] } } spec: containers: - name: cache image: redis:7Caught by: agent configcheck ของ Agent บน node ใน cluster จริง (AD config ของ pod หายไป) — ไม่เคยเป็น kubectl apply
dd-trace (Node.js)
หัวข้อที่มีชื่อว่า “dd-trace (Node.js)”9. log_injection — option แบบ snake_case ที่ไม่ทำอะไรเลย
หัวข้อที่มีชื่อว่า “9. log_injection — option แบบ snake_case ที่ไม่ทำอะไรเลย”นี่คือ bug จริงที่ Phase 3 review ของคอร์สนี้เจอเอง JavaScript ไม่ปฏิเสธ key ที่ไม่รู้จัก
// @expect-error 'log_injection' does not exist in TracerOptions: TS2561, and at runtime it is silently ignoredimport tracer from 'dd-trace';tracer.init({ service: 'checkout', log_injection: false });อาการ — output จริง tsc (typings ของ dd-trace 6.17.0): error TS2561: Object literal may only specify known properties, but 'log_injection' does not exist in type 'TracerOptions'. Did you mean to write 'logInjection'? — แต่ใน JS ล้วนมันรันได้ และ startup dump ของ tracer บอกว่า:
logInjection:false -> "log_injection_enabled":falselog_injection:false -> "log_injection_enabled":trueคนที่พยายาม ปิด injection ได้ injection ที่เปิดอยู่ (default คือ true ซึ่งเป็นเหตุผลที่ log_injection: true เดิมของคอร์ส “ใช้ได้” โดยบังเอิญ)
วิธีแก้:
import tracer from 'dd-trace';tracer.init({ service: 'checkout', logInjection: false });Caught by: tsc --noEmit สำหรับ TypeScript; harness ของคอร์ส --options กับทุก fence (ตรวจทุก key ของ tracer.init({...}) กับ .d.ts); startup config dump สำหรับ JavaScript ดู Connecting logs and traces
10. dd-trace ถูก initialise หลัง library ที่ควรถูก instrument
หัวข้อที่มีชื่อว่า “10. dd-trace ถูก initialise หลัง library ที่ควรถูก instrument”// @expect-error init after require('express'): express is not instrumented (no express.request span)const express = require('express');require('dd-trace').init({ flushInterval: 200 });const app = express();app.get('/', (req, res) => res.send('ok'));app.listen(3000);อาการ — output จริง trace-agent ปลอมที่ 127.0.0.1:28126 ได้รับ PUT /v0.4/traces หนึ่งครั้งจากแต่ละแบบ ชื่อ span ที่พบใน payload:
init first (good.js) => express.request, http.requestinit second (bad.js) => http.request, web.requestspan express.request — ตัวที่มี route — หายไป เหลือแค่ layer http ทั่วไป (ที่ load ก่อนโค้ดผู้ใช้) ที่ยังรายงาน
วิธีแก้: initialise ก่อน หรือ preload
require('dd-trace').init({ flushInterval: 200 });const express = require('express');const app = express();app.get('/', (req, res) => res.send('ok'));app.listen(3000);สำหรับ ESM statement import ถูก hoist ขึ้นเหนือการเรียก init() ใด ๆ จึงต้อง preload ด้วย node --import dd-trace/initialize.mjs app.mjs (package มี initialize.mjs และ init.js; ที่รันจริงที่นี่มีแค่ลำดับของ CommonJS ข้างบน) Docs: “The Node.js SDK needs to be imported and initialized before any other module” — docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/nodejs/
Caught by: payload ที่ดักได้เท่านั้น ไม่มี option checker ไหนเห็นลำดับ import
11. option ของ tracer ที่ AI คิดขึ้นเอง
หัวข้อที่มีชื่อว่า “11. option ของ tracer ที่ AI คิดขึ้นเอง”AI คิด option ที่ดูสมเหตุสมผลใกล้เคียงของจริง tsc ตรวจทีละ key กับ typings 6.17.0:
analytics -> TS2353: ... 'analytics' does not exist in type 'TracerOptions'traceSampleRate -> TS2561: ... Did you mean to write 'sampleRate'?runtimeMetric -> TS2561: ... Did you mean to write 'runtimeMetrics'?enabled -> TS2353debug -> TS2353agentUrl -> TS2353 (the real key is 'url')logInjection / sampleRate / runtimeMetrics / logLevel / env / url -> OK// @expect-error analytics, traceSampleRate, enabled, agentUrl are not TracerOptions keysimport tracer from 'dd-trace';tracer.init({ service: 'checkout', analytics: true, traceSampleRate: 0.5, agentUrl: 'http://dd-agent:8126' });สังเกตว่า compiler รายงาน excess property ตัว แรก ตัวเดียวต่อ literal ดังนั้นการแก้แล้วรันซ้ำเป็นเรื่องปกติ
import tracer from 'dd-trace';tracer.init({ service: 'checkout', sampleRate: 0.5, url: 'http://dd-agent:8126' });Caught by: tsc (key แรกเท่านั้น) และ harness --options (ทุก key) ไฟล์ .js ล้วนไม่ได้ทั้งสองอย่าง
12. DD_ENV=Prod ใน service หนึ่ง และ prod ในอีก service
หัวข้อที่มีชื่อว่า “12. DD_ENV=Prod ใน service หนึ่ง และ prod ในอีก service”Unified service tagging จะ join ได้ก็ต่อเมื่อค่าตรงกัน สมมติทีมหนึ่งตั้งตัวใหญ่:
DD_ENV=Prod DD_SERVICE=Checkout DD_VERSION=1.0.0 -> "env":"Prod","service":"Checkout" (tracer tags, verbatim)DD_ENV=prod DD_SERVICE=checkout -> "env":"prod","service":"checkout"tracer ไม่ normalise — มันส่งสิ่งที่คุณให้ สิ่งที่เกิดต่อไปเป็นกฎของ platform: “Span tags and metric tags are normalized to lowercase, so avoid using camel case in tag keys” (docs.datadoghq.com/getting_started/tagging/) ขณะที่ log attribute แยกตัวพิมพ์เล็กใหญ่ — ดังนั้น log attribute env:Prod กับ metric tag env:prod อาจ join ไม่ได้อีก การแก้คือ convention ที่บังคับใน deploy template:
apiVersion: v1kind: Podmetadata: name: checkout labels: tags.datadoghq.com/env: prod tags.datadoghq.com/service: checkout tags.datadoghq.com/version: 1.0.0spec: containers: - name: checkout image: checkout:1.0.0 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']Caught by: config dump ของ tracer แสดงค่าดิบ; ส่วนเรื่อง normalisation อ้างจาก docs อย่างเดียว (ไม่มีอะไรถูกส่งไป Datadog) ดู Unified service tagging in practice
13. “แก้” DD_TRACE_SAMPLE_RATE เพราะ ‘deprecated’ (ไม่ใช่ — แต่เป็น no-op ใน Python)
หัวข้อที่มีชื่อว่า “13. “แก้” DD_TRACE_SAMPLE_RATE เพราะ ‘deprecated’ (ไม่ใช่ — แต่เป็น no-op ใน Python)”reviewer ที่จำ deprecation notice เก่าได้จะเขียน DD_TRACE_SAMPLE_RATE=0.1 ใหม่เป็นอย่างอื่น บน Node ผลการรันบอกตรงข้าม:
$ DD_TRACE_SAMPLE_RATE=0.1 node cfg.js # dd-trace 6.17.0{"sampleRate":0.1} (and sampling_rules: [{ _sampler: { _rate: 0.1 } }])$ node cfg.js '{"sampleRate":0.25}'{"sampleRate":0.25}DD_TRACE_SAMPLE_RATE ไม่ได้ deprecated: หน้า config ของ Node และ Python ไม่มีข้อความ deprecation (“Controls the ingestion sample rate (between 0.0 and 1.0) between the Agent and the backend”, docs.datadoghq.com/tracing/trace_collection/library_config/nodejs/, .../python/) และ dd-trace (Node) ทำตามมัน ดังข้างบน แต่ ddtrace (Python) 4.15.2 ไม่ทำ — ตัวแปรนี้ปรากฏใน package เฉพาะใน list ของ supported-configurations (telemetry) และไม่มีผลต่อ sampling:
$ DD_TRACE_SAMPLE_RATE=0.1 python # ddtrace 4.15.2>>> ddtrace.tracer._sampler.rules[]>>> # 400 root spans created in a loop: sampling_priority > 0 on 400 / 400ดังนั้นบันทึกรีวิวที่ถูกต้องไม่ใช่ทั้ง “deprecated ให้เขียนใหม่” และ “รองรับทุกภาษา”: มันใช้ได้ใน Node, เป็น no-op เงียบ ๆ ใน Python 4.15.2 และ DD_TRACE_SAMPLING_RULES คือ setting ที่ทำงานเหมือนกันทั้งสองภาษา (harness โหมด --options ยอมรับชื่อนี้เพราะอยู่ใน list ที่รองรับ และไม่ flag มัน การอยู่ใน list นั้นไม่ใช่หลักฐานว่า setting ทำอะไรจริง) ดู Ingestion sampling vs. retention filters
import tracer from 'dd-trace';tracer.init({ service: 'checkout', sampleRate: 0.1 });Caught by: config dump ของ Node และสำหรับ Python คือ loop ของ span จริงที่ตรวจ sampling_priority ครึ่งหนึ่งของ entry นี้คือ bug ของ reviewer ดู entry 5
ddtrace (Python)
หัวข้อที่มีชื่อว่า “ddtrace (Python)”14. ddtrace.config.logs_injection = … — เขียนได้ แต่อ่านแล้ว raise
หัวข้อที่มีชื่อว่า “14. ddtrace.config.logs_injection = … — เขียนได้ แต่อ่านแล้ว raise”# @expect-error config.logs_injection is not an attribute (AttributeError on read); the write is silently acceptedfrom ddtrace import configconfig.logs_injection = Falseอาการ — output จริง (ddtrace 4.15.2):
>>> config.logs_injectionAttributeError: ... object has no attribute logs_injection, logs_injection is not a valid configuration>>> config.logs_injection = False; config.logs_injectionFalse # the setattr just created a new plain attribute>>> config._logs_injection # the real, env-driven settingTrue$ DD_LOGS_INJECTION=false python -c "from ddtrace import config; print(config._logs_injection)"Falseการเขียนลง attribute ที่แต่งขึ้นสำเร็จ จึงไม่มีอะไรล้มที่บรรทัดที่ผิด — และ setting จริงไม่ถูกแตะเลย ตัวสวิตช์ที่รองรับคือ environment variable
วิธีแก้:
import os# set before ddtrace is imported / ddtrace-run startsos.environ["DD_LOGS_INJECTION"] = "false"Caught by: harness --options (แก้ทุก reference ของ ddtrace.config.<x> ด้วย getattr จริง) — เป็นการตรวจแบบ อ่าน จึง flag การเขียนด้วยเพราะมัน look up ชื่อ
15. tracer.configure(hostname=…, port=…) — API ยุค 0.x/1.x
หัวข้อที่มีชื่อว่า “15. tracer.configure(hostname=…, port=…) — API ยุค 0.x/1.x”# @expect-error Tracer.configure() got an unexpected keyword argument 'hostname'from ddtrace import tracertracer.configure(hostname="dd-agent", port=8126)อาการ — output จริง:
TypeError: Tracer.configure() got an unexpected keyword argument 'hostname'>>> sorted(inspect.signature(tracer.configure).parameters)['apm_tracing_disabled', 'appsec_enabled', 'appsec_enabled_origin', 'compute_stats_enabled', 'context_provider', 'iast_enabled', 'trace_processors']วิธีแก้: address ของ agent เป็น environment configuration
import osos.environ["DD_TRACE_AGENT_URL"] = "http://dd-agent:8126"Caught by: การรันจริง (TypeError ตอน import) และ harness --options (ตรวจ tracer.configure(<kw>=))
RUM ในเบราว์เซอร์
หัวข้อที่มีชื่อว่า “RUM ในเบราว์เซอร์”16. allowedTracingOrigins และ RUM init ที่ไม่มี allowedTracingUrls
หัวข้อที่มีชื่อว่า “16. allowedTracingOrigins และ RUM init ที่ไม่มี allowedTracingUrls”// @expect-error allowedTracingOrigins does not exist in RumInitConfiguration (TS2561: did you mean allowedTrackingOrigins?)import { datadogRum } from '@datadog/browser-rum';datadogRum.init({ applicationId: '<your-application-id>', clientToken: '<your-client-token>', allowedTracingOrigins: ['https://api.example.com'],});อาการ — output จริง (@datadog/browser-rum 7.15.0, tsc):
allowedTracingUrls -> OK (exists)allowedTracingOrigins -> TS2561: ... does not exist in type 'RumInitConfiguration'.trackInteractions -> TS2561: ... Did you mean to write 'trackUserInteractions'?sampleRate -> TS2353sessionSampleRate / traceSampleRate / defaultPrivacyLevel -> OKและแบบ เงียบ: การละ allowedTracingUrls compile ผ่าน (เป็น optional) init ได้ และ RUM ทำงาน — แต่ request ของเบราว์เซอร์ไม่ถูกเชื่อมกับ backend trace Docs: “Configure the allowedTracingUrls initialization parameter with the list of internal, first-party origins called by your browser application” (docs.datadoghq.com/real_user_monitoring/correlate_with_other_telemetry/apm/); docs ไม่ได้ระบุโหมดล้มเหลวตรง ๆ ดังนั้น ผลลัพธ์ (ไม่มี link) เป็นการอนุมาน ไม่ได้สังเกตจริงที่นี่
วิธีแก้:
import { datadogRum } from '@datadog/browser-rum';datadogRum.init({ applicationId: '<your-application-id>', clientToken: '<your-client-token>', allowedTracingUrls: ['https://api.example.com'], traceSampleRate: 100,});Caught by: tsc สำหรับชื่อที่ผิด; มีแต่ review (หรือ trace ใน UI) สำหรับตัวที่หายไป harness ของคอร์สไม่ตรวจ option ของ RUM
Custom metrics
หัวข้อที่มีชื่อว่า “Custom metrics”17. histogram ทั้งที่ requirement บอก “global percentile”
หัวข้อที่มีชื่อว่า “17. histogram ทั้งที่ requirement บอก “global percentile””# @expect-error semantic: HISTOGRAM aggregates per Agent, so its percentile is per host, not globalstatsd.histogram("checkout.latency", latency_ms, tags=["env:prod"])อาการ — output จริง Agent 7.83.3 จริงได้รับค่าชุดเดียวกัน (100, 120, 300, 900) เป็น |h และ |d แล้ว flush ไปยัง receiver ในเครื่อง:
histogram -> POST /api/v1/series : checkout.latency_hist.avg (gauge, 355) .median (120) .95percentile (900) .max (900) .count (rate 0.4) [tags: env:prod]distribution -> POST /api/beta/sketches (protobuf, raw values, no per-host percentile series)Docs: HISTOGRAM “aggregates on the Agent during a given time interval”; DISTRIBUTION “sends all the raw data during a time interval to Datadog. Aggregations occur on the server-side” (docs.datadoghq.com/metrics/types/) สังเกตว่า .95percentile ข้างบนเป็นตัวเลขของ Agent ตัวเดียว — การเอามาเฉลี่ยข้าม host ไม่ใช่ p95 ของทั้ง fleet
วิธีแก้:
from datadog import statsd
statsd.distribution("checkout.latency", 120.0, tags=["env:prod"])Caught by: flush ที่ดักได้ (intake path ต่างกัน); การไม่ตรงกันเชิงความหมายเป็นเรื่องของ review ดู Custom metrics with DogStatsD
18. user_id เป็น metric tag
หัวข้อที่มีชื่อว่า “18. user_id เป็น metric tag”# @expect-error semantic: every distinct user_id value is a separate metric context (custom metric)statsd.increment("orders.count", tags=["env:prod", f"user_id:{user_id}"])อาการ — output จริง ส่ง 50 ครั้งด้วย user_id 50 ค่า เทียบกับส่ง 50 ครั้งบน plan 2 ค่า แล้ว flush ผ่าน Agent จริง:
orders.count 50 series (env:prod,user_id:u0 ... u49) each rate 0.1orders.bounded 2 series (env:prod,plan:pro / plan:free) each rate 2.5agent dogstatsd-stats: 50 rows for orders.count, one per user_idDocs: “A custom metric is identified by a unique combination of a metric’s name and tag values (including the host tag)” (docs.datadoghq.com/metrics/custom_metrics/) — เรื่อง billing ตามมาจากประโยคนี้ ที่นี่ไม่ได้อ้างราคา
วิธีแก้: ใช้ tag ที่มีขอบเขตเท่านั้น ใส่ id ใน log หรือ span attribute
from datadog import statsd
statsd.increment("orders.count", tags=["env:prod", "plan:pro"])Caught by: agent dogstatsd-stats (ต้องเปิด dogstatsd_metrics_stats_enable) หรือจำนวน series ที่ดักได้ ดู Metrics without limits and tag cardinality
Terraform และ monitor as code
หัวข้อที่มีชื่อว่า “Terraform และ monitor as code”19. query ของ request ใน datadog_dashboard เขียนแบบแบน
หัวข้อที่มีชื่อว่า “19. query ของ request ใน datadog_dashboard เขียนแบบแบน”bug จริงตัวที่สองจาก Phase 3 review
# @expect-error Unsupported argument: name / data_source / query are not expected directly inside request.queryresource "datadog_dashboard" "checkout" { title = "Checkout overview" layout_type = "ordered"
widget { timeseries_definition { title = "CPU by host" request { query { name = "query1" data_source = "metrics" query = "avg:system.cpu.user{service:checkout} by {host}" } display_type = "line" } } }}อาการ — terraform validate จริง (provider 4.22.0):
Error: Unsupported argument on main.tf line 10, in resource "datadog_dashboard" "checkout": 10: name = "query1"An argument named "name" is not expected here.(ซ้ำสำหรับ data_source และ query)
วิธีแก้: query { metric_query { … } } พร้อม block formula
resource "datadog_dashboard" "checkout" { title = "Checkout overview" layout_type = "ordered"
widget { timeseries_definition { title = "CPU by host" request { query { metric_query { name = "query1" data_source = "metrics" query = "avg:system.cpu.user{service:checkout} by {host}" } } formula { formula_expression = "query1" } display_type = "line" } } }}Caught by: terraform validate (error แข็ง) ดู Datadog as code with Terraform
20. argument และ block ที่แต่งขึ้นบน datadog_monitor / datadog_dashboard
หัวข้อที่มีชื่อว่า “20. argument และ block ที่แต่งขึ้นบน datadog_monitor / datadog_dashboard”ความล้มเหลวจริงของ terraform validate สี่แบบในตระกูลเดียวกัน:
critical_recovery = 1.5 at monitor top level -> Error: Unsupported argument ... An argument named "critical_recovery" is not expected here.tags = "env:prod" -> Error: Incorrect attribute value type ... set of string required, but have string.thresholds { critical = 2 } -> Error: Unsupported block type ... Blocks of type "thresholds" are not expected here.template_variables { name = "env" } (dashboard) -> Error: Unsupported block type ... Did you mean "template_variable"?(monitor without query) -> Error: Missing required argument ... The argument "query" is required# @expect-error critical_recovery is only valid inside monitor_thresholds; tags is a set of stringresource "datadog_monitor" "latency" { name = "Checkout p95 latency" type = "metric alert" message = "p95 too high @slack-oncall" query = "avg(last_5m):p95:trace.http.request{service:checkout} > 2" tags = "env:prod" critical_recovery = 1.5}recovery threshold ไม่ใช่ของประดับ Docs: “If no recovery threshold is set, the monitor recovers whenever the alert conditions are no longer met” และ “Recovery thresholds reduce noise of a flapping monitor” (docs.datadoghq.com/monitors/guide/recovery-thresholds/)
resource "datadog_monitor" "latency" { name = "Checkout p95 latency" type = "metric alert" message = "p95 too high @slack-oncall" query = "avg(last_5m):p95:trace.http.request{service:checkout} > 2" tags = ["env:prod", "service:checkout"]
monitor_thresholds { critical = 2 critical_recovery = 1.5 }}Caught by: terraform validate model ของ datadog-api-client จับ tags ที่เป็น string ได้ด้วย (ApiTypeError: Invalid type for variable 'tags'. Required value type is list and passed type was str)
21. notify_no_data บน metric ที่เบาบาง
หัวข้อที่มีชื่อว่า “21. notify_no_data บน metric ที่เบาบาง”“การเสริมความแกร่ง” ที่ AI ทำบ่อยคือ notify_no_data = true บน service ที่ traffic น้อย หรือปล่อย require_full_window เป็น default บน metric ที่เบาบาง terraform validate ผ่าน ข้อความใน schema ของ provider เอง (terraform providers schema -json, 4.22.0) บอกว่า default คืออะไร:
notify_no_data "... Defaults to `false`."no_data_timeframe "... We recommend at least 2x the monitor timeframe for metric alerts ... Defaults to `10`."require_full_window "Datadog strongly recommends you set this to `false` for sparse metrics, otherwise some evaluations may be skipped ... Defaults to `true`."แก้ข้อสรุปหนึ่งข้อในรีวิววางแผน: notify_no_data มี default เป็น false ใน provider ไม่ใช่ true bug คือการเปิดมัน (หรือไม่สนใจ require_full_window) โดยไม่คิดถึง traffic และมีแค่ API กับเวลาที่แสดงมัน
resource "datadog_monitor" "batch_errors" { name = "Nightly batch errors" type = "metric alert" message = "batch errors @slack-oncall" query = "sum(last_1h):sum:batch.errors{env:prod}.as_count() > 0"
require_full_window = false notify_no_data = false}Caught by: ไม่มีอะไร offline — terraform validate เขียว ข้อความใน provider schema คือหลักฐาน ส่วนพฤติกรรมเป็นของ API
22. permission ของ role ด้วยชื่อ ไม่ใช่ ID
หัวข้อที่มีชื่อว่า “22. permission ของ role ด้วยชื่อ ไม่ใช่ ID”# @expect-error validates cleanly but is semantic: permission.id is an ID, not the permission name; and this grants index modificationresource "datadog_role" "viewer" { name = "log-viewer"
permission { id = "logs_modify_indexes" }}terraform validate พิมพ์ Success! The configuration is valid. คำอธิบายของ permission.id ใน schema มีแค่ “ID of the permission to assign” โดยมี name เป็น attribute แยก — ชื่อในช่อง id จึงเป็น string ที่ type ถูกและมีแต่ API ที่ปฏิเสธได้ แย่กว่านั้น ถ้ามัน ถูกรับ ก็เป็น permission ผิดตัวสำหรับ viewer: logs_modify_indexes คือ “Read and modify all indexes in your account” (docs.datadoghq.com/account_management/rbac/permissions/)
data "datadog_permissions" "all" {}
resource "datadog_role" "viewer" { name = "log-viewer"
permission { id = data.datadog_permissions.all.permissions["logs_read_index_data"] }}Caught by: review (และ API) terraform validate resolve ค่าจาก data source ไม่ได้
23. exclusion filter ที่เขียนกว้างกว่าเจตนา
หัวข้อที่มีชื่อว่า “23. exclusion filter ที่เขียนกว้างกว่าเจตนา”# @expect-error validates; semantic: status:info at sample_rate 1.0 drops ALL info logs, including ones you meant to keepresource "datadog_logs_index" "main" { name = "main" retention_days = 15
filter { query = "*" }
exclusion_filter { name = "drop-noise" is_enabled = true filter { query = "status:info" sample_rate = 1.0 } }}terraform validate ยอมรับ Docs: log ที่ถูก exclude “are discarded from indexes, but still flow through the Live Tail and can be used to generate metrics and archived” และ “Index filters for logs are only processed with the first active exclusion filter matched” (docs.datadoghq.com/logs/log_configuration/indexes/) — ลำดับและความกว้างจึงสำคัญ และที่นี่ไม่มีอะไรตรวจ
resource "datadog_logs_index" "main" { name = "main" retention_days = 15
filter { query = "*" }
exclusion_filter { name = "drop-health-checks" is_enabled = true filter { query = "service:checkout @http.url_details.path:/healthz" sample_rate = 1.0 } }}Caught by: review ดู Indexes, exclusion filters, and retention
JSON ของ monitor, SLO และ catalog
หัวข้อที่มีชื่อว่า “JSON ของ monitor, SLO และ catalog”24. burn-rate alert ที่ .over() ผิดและไม่มี .short_window()
หัวข้อที่มีชื่อว่า “24. burn-rate alert ที่ .over() ผิดและไม่มี .short_window()”bug จริงตัวที่สามจาก review
// @expect-error query string is accepted by the model; the API grammar needs .over(<SLO window>) and .short_window(){ "name": "Checkout SLO burn rate", "type": "slo alert", "query": "burn_rate(\"<slo_id>\").over(\"1h\").long_window(\"6h\") > 10", "message": "@slack-oncall budget is burning"}Output จริง: Monitor(**data, _check_type=True) กับ JSON นี้และกับตัวที่ถูก พิมพ์ OK (constructs) ทั้งคู่ — client ตรวจแค่ว่า query เป็น str ไวยากรณ์มาจาก docs (docs.datadoghq.com/service_management/service_level_objectives/burn_rate/): burn_rate("slo_id").over("time_window").long_window("1h").short_window("5m") > 14.4 โดย .over คือระยะ target ของ SLO (7d/30d/90d) และ .short_window “ensures quick alert recovery”; คำแนะนำของ Google ที่ยกไว้ในหน้าเดียวกันคือ short window เป็น 1/12 ของ long window
{ "name": "Checkout SLO burn rate", "type": "slo alert", "query": "burn_rate(\"<slo_id>\").over(\"30d\").long_window(\"6h\").short_window(\"30m\") > 10", "message": "@slack-oncall budget is burning"}Caught by: Datadog API เท่านั้น (หรือคนที่อ่านไวยากรณ์) ดู SLOs, error budgets, and burn rate alerts
25. composite monitor ที่อ้าง ID ซึ่งไม่ใช่ ID
หัวข้อที่มีชื่อว่า “25. composite monitor ที่อ้าง ID ซึ่งไม่ใช่ ID”// @expect-error model accepts any string; composite queries are monitor IDs joined by &&, ||, !{ "name": "Checkout degraded", "type": "composite", "query": "12345 && not-an-id", "message": "@slack-oncall both signals are red"}Output จริง: composite bad ids: OK (constructs) Docs: query ของ composite หน้าตาเป็น "1234 && 5678", "!1234 || 5678"; และ “Creating a downtime on an individual monitor A, which is used within a composite monitor, does not mute the composite monitor” (docs.datadoghq.com/monitors/types/composite/) docs ไม่ได้บอกว่าเกิดอะไรกับ ID ที่ไม่มีอยู่ ผลนั้นจึงไม่ได้ทดสอบที่นี่
{ "name": "Checkout degraded", "type": "composite", "query": "12345 && 67890", "message": "@slack-oncall both signals are red"}Caught by: API ตอนสร้าง (และการเทียบ ID กับ monitor จริงตอน review)
26. enum ที่พิมพ์ผิดซึ่ง constructor ยอมรับและ _unparsed จับได้
หัวข้อที่มีชื่อว่า “26. enum ที่พิมพ์ผิดซึ่ง constructor ยอมรับและ _unparsed จับได้”// @expect-error type "metrics alert" and timeframe "30days" are not valid enum values, yet the model constructor accepts them{ "name": "Checkout p95", "type": "metrics alert", "query": "avg(last_5m):p95:trace.http.request{service:checkout} > 2", "message": "@slack-oncall"}Output จริง (datadog-api-client 2.61.0):
Monitor(**data, _check_type=True) with type="metrics alert" -> OK (constructs)ServiceLevelObjective(...) with timeframe="30days" -> OK (constructs)ServiceLevelObjective(...) with type="metrics" -> OK (constructs)LogsPipeline(...) with processor type "attribute_remapper" -> OK (constructs)Dashboard(...) with layout_type="grid", or a widget with no "type" -> OK (constructs)
ApiClient(Configuration()).deserialize(json, (Monitor,), True) monitor typo type / slo timeframe / pipeline underscore / layout grid / widget without type -> _unparsed = Truepath ของ constructor (ซึ่ง node tools/verify-snippets.mjs --json ใช้) ไม่บังคับ allowed_values; deserializer ตั้ง _unparsed = True เมื่อค่า enum หรือ oneOf ไม่ตรง ซึ่งเป็น signal ที่แข็งกว่ามากและ harness ไม่ได้ใช้ type error ที่มัน raise จริง:
options.thresholds.critical="2" -> ApiTypeError: Invalid type for variable 'critical'. Required value type is float and passed type was stroptions.notify_no_data="true" -> ApiTypeError: Invalid type for variable 'notify_no_data'. Required value type is bool and passed type was strtags="env:prod" -> ApiTypeError: Invalid type for variable 'tags'. Required value type is list and passed type was strmissing query / missing thresholds -> TypeError: __init__() missing 1 required positional argument{ "name": "Checkout p95", "type": "metric alert", "query": "avg(last_5m):p95:trace.http.request{service:checkout} > 2", "message": "@slack-oncall"}Caught by: _unparsed บน object ที่ deserialize แล้ว (ต้องเขียนการตรวจนี้เอง); --json ของ harness พลาด enum และบอกว่า “ok” API ปฏิเสธ type ที่ผิด
27. Service Catalog apiVersion: v2 กับ field ของ v3
หัวข้อที่มีชื่อว่า “27. Service Catalog apiVersion: v2 กับ field ของ v3”// @expect-error apiVersion must be "v3" for the entity schema: 'v2' is not one of ['v3']{ "apiVersion": "v2", "kind": "service", "metadata": { "name": "checkout", "owner": "payments-team" }, "spec": { "lifecycle": "production", "tier": "1", "dependsOn": ["service:payments-api"] }}Output จริง (JSON Schema จาก github.com/DataDog/schema validate ด้วย jsonschema ซึ่งเป็นการเรียกเดียวกับที่ harness ทำ):
apiVersion "v2" -> ['apiVersion']: 'v2' is not one of ['v3']apiVersion "v3", dependsOn -> okapiVersion "v3", depends_on (typo) -> ok # the schema does not reject unknown spec keyskind "microservice" -> ok # ...nor an unknown kindschema จึงจับ version ได้ และ ไม่จับ อีกสองความผิดที่ AI ทำกับมัน — schema เป็นเพดานล่าง ไม่ใช่การรับประกัน
{ "apiVersion": "v3", "kind": "service", "metadata": { "name": "checkout", "owner": "payments-team" }, "spec": { "lifecycle": "production", "tier": "1", "dependsOn": ["service:payments-api"] }}Caught by: harness --json (fence ใน service-catalog/) เฉพาะ version ดู The Service Catalog and ownership
Spot the bug
หัวข้อที่มีชื่อว่า “Spot the bug”🐛 หา bug ในโค้ดนี้
resource "datadog_monitor" "checkout_latency" {name = "Checkout p95 latency"type = "metric alert"message = "p95 above 2s @pagerduty-checkout"query = "avg(last_5m):p95:trace.http.request{service:checkout} > 2"tags = "team:payments"critical_recovery = 1.5}เฉลย
สอง error แข็ง ทั้งคู่จาก terraform validate กับ provider 4.22.0: tags เป็น set ของ string (set of string required, but have string) และ critical_recovery ไม่ใช่ argument ระดับบนสุด (An argument named "critical_recovery" is not expected here) — มันอยู่ใน monitor_thresholds ฉบับแก้ที่ validate ผ่าน:
resource "datadog_monitor" "checkout_latency" { name = "Checkout p95 latency" type = "metric alert" message = "p95 above 2s @pagerduty-checkout" query = "avg(last_5m):p95:trace.http.request{service:checkout} > 2" tags = ["team:payments"]
monitor_thresholds { critical = 2 critical_recovery = 1.5 }}🐛 หา bug ในโค้ดนี้
const express = require('express');const tracer = require('dd-trace').init({service: 'checkout',env: process.env.DD_ENV,log_injection: true,agentUrl: 'http://dd-agent:8126',});const app = express();เฉลย
สามปัญหา ซึ่งไม่มีอันไหน throw ตอนรันใน JavaScript ล้วน (1) dd-trace ถูก initialise หลัง require('express') จึงไม่มี span express.request (payload ที่ดักได้: มีแค่ http.request, web.request) (2) log_injection ไม่ใช่ option (key จริงคือ logInjection; ที่นี่เป็น no-op และ default เป็น true อยู่แล้ว) (3) agentUrl ไม่มีอยู่; key คือ url ฉบับแก้ที่ผ่าน type-check และ harness:
import tracer from 'dd-trace';
tracer.init({ service: 'checkout', env: process.env.DD_ENV, logInjection: true, url: 'http://dd-agent:8126',});สำหรับ ESM ให้ preload ด้วย node --import dd-trace/initialize.mjs แทนการพึ่งลำดับ statement
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”- Accepted ไม่เท่ากับ correct ทุกเครื่องมือที่นี่ยอมรับบางอย่างที่ไม่ควร:
configcheckยอมรับtagsแบบ scalar, model ยอมรับ"metrics alert",terraform validateยอมรับ permission ของ role ด้วยชื่อ, JS object ยอมรับlog_injectionbug อยู่ที่ layer ถัดไปจากเครื่องมือที่เงียบเสมอ - ถามว่าแต่ละ layer validate อะไร ตัว load config ของ Agent (shape) → model ของ check เอง (
agent status) → tracer/.d.ts(ชื่อ option) → provider schema (ชื่อ block/attribute) → API (ความหมาย) entry ส่วนใหญ่อยู่ในช่องว่างระหว่างสอง layer นี้ - ความเงียบมีสองรส no-op (
log_injection) และ ผิดแต่ทำงานอยู่ (DD_SITEเป็น URL, histogram แทน distribution) แบบหลังแพงกว่า: มันสร้างข้อมูลที่ดูปกติ - อย่าแก้เกิน entry 5 และครึ่งหนึ่งของ 13 คือสัญญาณเตือนเท็จ review ที่ flag ความเชื่อ (“ไม่มี
init_config”, “DD_TRACE_SAMPLE_RATEdeprecated”) มีต้นทุนเท่ากับพลาด bug
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”agent configcheckเร็ว;agent statusคือที่ที่ความจริงอยู่configcheckต้องมี Agent ที่รันอยู่ (ราว 20–35 วินาทีให้พร้อมบน Mac เครื่องนี้);statusมีความหมายเมื่อ check รันอย่างน้อยหนึ่งครั้งแล้วtscฟรีสำหรับ TypeScript แต่ไม่มีสำหรับ JavaScript harness--optionsอุดช่องว่างสำหรับโค้ดใน fence แต่ไม่ใช่สำหรับ repo ของคุณ เว้นแต่คุณรันการตรวจคล้ายกันใน CI_unparsedเข้มกว่า constructor แต่เป็นเป้าที่เคลื่อนที่ มันมีไว้ให้ client รอดจากค่า enum ใหม่ฝั่ง server; การใช้เป็น linter หมายความว่า client เก่าจะ flag ค่าใหม่ที่ ถูกต้อง ให้ pin เวอร์ชัน client ที่ใช้ตรวจ- entry ที่ใช้ docs อย่างเดียว (7, 8, 12, 24, 25) เขียนถูกและอาจเน่า อ้าง URL และตรวจใหม่ตอน upgrade
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
agent configcheckสะอาดแปลว่า check จะรัน” entry 2, 3 และ 4 พิมพ์ config ที่ดูปกติแล้วล้มในagent status - “model validation ใน harness พิสูจน์ว่า monitor JSON ของฉันถูก” มันพิสูจน์ type และ field ที่ required ไม่ใช่ค่า enum (26) ไม่ใช่ไวยากรณ์ query (24, 25)
- “key ของ option ที่ไม่รู้จักจะ throw” ใน JavaScript และการเขียนบน
Configobject ของ Python ไม่ throw (9, 14) - “
latestพัง” มันไม่ได้พัง — แค่ไม่ได้ pin (7) ความต่างนี้ตัดสินว่าคุณเขียน bug report หรือ policy - “ถ้า comment ของ AI อธิบายไว้ก็ถูก” comment ใน entry 12 และข้อความเดิมของคอร์สสำหรับ entry 9 อธิบายพฤติกรรมที่โค้ดไม่ได้มี