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

Worked review 3: monitor, SLO และ dashboard as code

Prompt คือ “เขียน observability ของ checkout service เป็น Terraform: monitor p95 latency, SLO availability 99.9 เปอร์เซ็นต์ใน 30 วัน, burn-rate alert บน SLO นั้น และ dashboard ที่มี p95 latency ขอ JSON ของ object เดียวกันด้วย เผื่อเราใช้ API ตรงๆ” ได้สอง artifact กลับมา: ไฟล์ .tf หนึ่งไฟล์และเอกสาร JSON สามฉบับ ไม่มีอันไหนมี syntax error ในความหมายที่ text editor จะแสดง

ใช้อะไร และแต่ละเครื่องมือไปถึงไหน:

  • terraform validate กับ provider DataDog/datadog 4.22.0 ที่ pin ไว้บน Terraform 1.16.4 ใน directory ชั่วคราวที่มีสำเนา plugin cache ส่วนตัว provider block ใช้ key ปลอมและ validate = false; ไม่เคยรัน terraform plan และ apply และตัวแปร AWS_* ที่มีอยู่ในเครื่องนี้ถูกเอาออกจาก environment ของทุก process terraform
  • โมเดลของ datadog-api-client 2.61.0 (Monitor, ServiceLevelObjective, Dashboard) สองทาง: constructor กับ _check_type=True (สิ่งที่โหมด --json ของ harness ในคอร์สรัน) และ deserializer ของ client เอง ApiClient(Configuration()).deserialize(json, (Model,), True) ซึ่งตั้ง _unparsed = True เมื่อค่า enum หรือ oneOf ไม่ match ไม่มีอะไรถูกส่งไปที่ไหน
  • Docs พร้อม URL ทุกที่ที่ bug เกินกว่าทั้งสองเครื่องมือ — และนั่นคือหัวใจที่ซื่อสัตย์ของรีวิวนี้: หลาย bug ด้านล่างผ่านทุกการเช็กแบบ offline
# @expect-error as generated -- five terraform validate errors at once, more behind them
resource "datadog_monitor" "checkout_latency" {
name = "Checkout p95 latency"
type = "metric alert"
message = "p95 too high @pagerduty-checkout"
query = "avg(last_5m):p95:trace.http.request{service:checkout,env:prod} > 2"
tags = "team:payments"
thresholds {
critical = 2
warning = 1.5
}
}
resource "datadog_service_level_objective" "checkout_availability" {
name = "Checkout availability"
type = "metric"
query {
numerator = "sum:checkout.requests{env:prod,outcome:error}.as_count()"
denominator = "sum:checkout.requests{env:prod}.as_count()"
}
thresholds {
timeframe = "30days"
target = 99.9
warning = 99.95
}
}
resource "datadog_monitor" "checkout_burn_rate" {
name = "Checkout SLO burn rate"
type = "slo alert"
message = "Error budget is burning @pagerduty-checkout"
query = "burn_rate(\"${datadog_service_level_objective.checkout_availability.slo_id}\").over(\"1h\").long_window(\"6h\") > 10"
}
resource "datadog_dashboard" "checkout" {
title = "Checkout overview"
template_variables {
name = "env"
prefix = "env"
}
widget {
timeseries_definition {
title = "p95 latency"
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "p95:trace.http.request{service:checkout,env:$env}"
}
}
formula {
formula_expression = "query2"
}
display_type = "line"
}
}
}
}

และ JSON แบบ API ที่มากับมัน:

// @expect-error monitor JSON as generated -- notify_no_data is a string; thresholds sits at the top level
{
"name": "Checkout p95 latency",
"type": "metric alert",
"query": "avg(last_5m):p95:trace.http.request{service:checkout,env:prod} > 2",
"message": "p95 too high @pagerduty-checkout",
"thresholds": { "critical": 2, "warning": 1.5 },
"options": { "notify_no_data": "true", "renotify_interval": 60 }
}
// @expect-error SLO JSON as generated -- type, timeframe and target are all wrong; the constructor accepts every one
{
"name": "Checkout availability",
"type": "metrics",
"query": {
"numerator": "sum:checkout.requests{env:prod,outcome:error}.as_count()",
"denominator": "sum:checkout.requests{env:prod}.as_count()"
},
"thresholds": [{ "timeframe": "30days", "target": "99.9" }]
}
// @expect-error dashboard JSON as generated -- layout_type "grid" and display_type "lines" are not enum values
{
"title": "Checkout overview",
"layout_type": "grid",
"widgets": [
{
"definition": {
"type": "timeseries",
"title": "p95 latency",
"requests": [{ "q": "p95:trace.http.request{service:checkout}", "display_type": "lines" }]
}
}
]
}

reviewer ไม่รัน terraform validate ครั้งเดียว Terraform รายงาน structural error ก่อน แล้วค่อยประเมิน reference ดังนั้นไฟล์ถูกรีวิวเป็นขั้น: รันแรกแสดงห้า error, แก้แล้วเจออันที่หก, แก้อันนั้นแล้วผลเขียว — โดยยังมีสาม bug เหลือในไฟล์

$ terraform validate
Error: Incorrect attribute value type
on main.tf line 6, in resource "datadog_monitor" "checkout_latency":
6: tags = "team:payments"
Inappropriate value for attribute "tags": set of string required, but have string.

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

tags = "team:payments"
เฉลย

datadog_monitor.tags เป็น set ของ string AI เขียนแบบ string เดี่ยวเพราะ tag สะกดแบบนั้นใน UI และใน DD_TAGS เป็น hard error จับได้แบบ offline (string เดียวกันนี้ถูกโมเดลของ API client ปฏิเสธด้วย — ดูหัวข้อ JSON) วิธีแก้: tags = ["service:checkout", "team:payments"]

Error: Unsupported block type
on main.tf line 8, in resource "datadog_monitor" "checkout_latency":
8: thresholds {
Blocks of type "thresholds" are not expected here.

block ที่ถูกคือ monitor_thresholds ส่วน thresholds มีบน datadog_service_level_objective — assistant เอาการสะกดของ SLO มาใช้กับ monitor เปรียบเทียบกับ bug 10: โมเดล JSON ยอมรับ field ที่วางผิดที่แบบเงียบๆ

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

thresholds {
critical = 2
warning = 1.5
}
เฉลย

สอง resource สองการสะกด: monitor_thresholds บน datadog_monitor, thresholds บน datadog_service_level_objective hard error แบบ offline วิธีแก้: monitor_thresholds { critical = 2 warning = 1.5 } warning และ recovery threshold ก็สำคัญเชิงปฏิบัติการ — docs: “If no recovery threshold is set, the monitor recovers whenever the alert conditions are no longer met” (docs.datadoghq.com/monitors/guide/recovery-thresholds/)

Error: Invalid enum value
with datadog_service_level_objective.checkout_availability,
on main.tf line 24, in resource "datadog_service_level_objective" "checkout_availability":
24: timeframe = "30days"
`invalid value '30days' for SLOTimeframe: valid values are [7d 30d 90d custom]`

ข้อนี้ควรจำไว้ใช้ทีหลัง: provider บังคับ SLOTimeframe ตอน validate ส่วนโมเดล Python client ใน bug 11 ไม่บังคับ

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

thresholds {
timeframe = "30days"
target = 99.9
warning = 99.95
}
เฉลย

คำศัพท์ของ API คือ 7d, 30d, 90d (และ custom) “30 days” ใน prompt กลายเป็น 30days ในไฟล์ hard error แบบ offline กับ provider; วิธีแก้ timeframe = "30d"

Error: Missing required argument
on main.tf line 37, in resource "datadog_dashboard" "checkout":
37: resource "datadog_dashboard" "checkout" {
The argument "layout_type" is required, but no definition was found.
Error: Unsupported block type
on main.tf line 40, in resource "datadog_dashboard" "checkout":
40: template_variables {
Blocks of type "template_variables" are not expected here. Did you mean "template_variable"?

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

resource "datadog_dashboard" "checkout" {
title = "Checkout overview"
template_variables {
name = "env"
prefix = "env"
}
เฉลย

สอง hard error แยกกันจาก 6 บรรทัดเดียวกัน: layout_type (ordered หรือ free) จำเป็นบน datadog_dashboard และ block ที่ซ้ำได้คือ template_variable เอกพจน์ ทั้งสองเป็นรูปแบบ error ที่ terraform validate มีไว้เพื่อสิ่งนี้ วิธีแก้: เพิ่ม layout_type = "ordered" และเปลี่ยนชื่อ block (entry 20 ของ bug catalog บันทึกความผิดพหูพจน์แบบเดียวกัน)

เมื่อแก้หกข้อข้างบนแล้วรันอีก จะได้ error ใหม่ ที่การรันแรกแสดงไม่ได้:

Error: Unsupported attribute
on main.tf line 34, in resource "datadog_monitor" "checkout_burn_rate":
34: query = "burn_rate(\"${datadog_service_level_objective.checkout_availability.slo_id}\").over(...
This object has no argument, nested block, or exported attribute named "slo_id".

Terraform หยุดประเมิน reference ตราบที่ยังมี structural error identifier ของ resource คือ .id มาตรฐาน (หน้า docs ของ burn-rate alert เรียก placeholder ว่า slo_id; assistant เปลี่ยนชื่อ placeholder เป็น attribute)

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

query = "burn_rate(\"${datadog_service_level_objective.checkout_availability.slo_id}\")...
เฉลย

literal "slo_id" ใน template ของ docs หมายถึง “แทนที่ด้วย alphanumeric ID ของ SLO” (docs.datadoghq.com/service_management/service_level_objectives/burn_rate/) ใน Terraform ID นั้นคือ datadog_service_level_objective.checkout_availability.id นิสัยการรีวิวที่มีประโยชน์: ผลเขียวหลังผลแดงยังไม่ใช่การรีวิวที่สมบูรณ์ เพราะรันที่แดงอาจซ่อน error ทั้งประเภทไว้

หลังแก้หกข้อนี้ การรันได้:

$ terraform validate
Success! The configuration is valid.

มันยังผิดอยู่สามที่ ทุกอย่างจากนี้ terraform validate มองไม่เห็น

# @expect-error validates; the burn_rate grammar and the missing monitor_thresholds are only enforced by the Datadog API
resource "datadog_monitor" "checkout_burn_rate" {
name = "Checkout SLO burn rate"
type = "slo alert"
message = "Error budget is burning @pagerduty-checkout"
query = "burn_rate(\"${datadog_service_level_objective.checkout_availability.id}\").over(\"1h\").long_window(\"6h\") > 10"
}

query เป็น string ทึบสำหรับทั้ง provider และโมเดลของ client ไวยากรณ์ที่มีเอกสาร (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("1h"): docs บอก “replace time_window with one of 7d, 30d or 90d - depending on which target is used to configure your SLO” ดังนั้นค่าของ SLO นี้คือ 30d ไม่มี .short_window(): docs จับคู่ long และ short window และแนะนำ “keeping the short window as 1/12 of the long window” (1h กับ 5m ในตัวอย่างของ docs) และไม่มี monitor_thresholds หน้า docs ของ provider เองแสดง threshold ข้าง query — monitor_thresholds { critical = 14.4 } สำหรับ query > 14.4 query เดียวกันในรูป monitor JSON construct ได้ OK และ deserialize ได้ _unparsed = False (เครื่องมือของ bug 10): โมเดลเช็กแค่ว่า query เป็น str ไม่ได้เรียก API ผมจึงไม่แสดงข้อความ rejection ที่แน่นอน; ข้อกล่าวอ้างอิงตาม docs

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

query = "burn_rate(\"${...checkout_availability.id}\").over(\"1h\").long_window(\"6h\") > 10"
เฉลย

เขียวใน terraform validate เขียวในโมเดล เขียวทุกอย่างแบบ offline .over ระบุ window ของเป้าหมาย SLO (7d, 30d หรือ 90d), burn-rate alert ต้องมีทั้ง long และ short window (docs แนะนำให้ short เป็น 1/12 ของ long) และ threshold ใน query ควรซ้ำใน monitor_thresholds.critical วิธีแก้: .over("30d").long_window("1h").short_window("5m") > 14.4 พร้อม monitor_thresholds { critical = 14.4 } — ตัวอย่างที่ทำงานได้ใน docs

# @expect-error validates; numerator must be GOOD events - the SLI is numerator / denominator
query {
numerator = "sum:checkout.requests{env:prod,outcome:error}.as_count()"
denominator = "sum:checkout.requests{env:prod}.as_count()"
}

ชื่อ metric checkout.requests และ tag outcome เป็นตัวแทนของ count metric ที่ service ปล่อยจริง ความผิดเป็นเชิงโครงสร้าง ตัวอย่างของ provider เองใช้ numerator = "sum:my.custom.count.metric{type:good_events}.as_count()" เหนือ denominator = "sum:my.custom.count.metric{*}.as_count()" และ docs ของ SLO: “A metric query uses the sum of the good events divided by the sum of total events over time to calculate a Service Level Indicator (or SLI)” (docs.datadoghq.com/service_management/service_level_objectives/metric/) เมื่อใส่ error ใน numerator “availability” คือ error rate — service ที่สุขภาพดีได้ 0.1 เปอร์เซ็นต์เทียบกับ target 99.9 และ SLO แดงถาวร ส่วนที่พังจะได้ค่าสูงกว่าและดูดีกว่า

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

query {
numerator = "sum:checkout.requests{env:prod,outcome:error}.as_count()"
denominator = "sum:checkout.requests{env:prod}.as_count()"
}
เฉลย

นี่คือความผิดของ metric SLO ที่ AI ทำบ่อยที่สุด และไม่มี offline tool ไหนเห็น: ทั้งสอง string เป็น metric query ที่ถูกต้อง อาร์กิวเมนต์เป็น string ทั้งคู่ และ validate พิมพ์ Success! มีแต่ผู้อ่านที่รู้ว่า SLI คือ good หารด้วย total เท่านั้นที่จับได้ วิธีแก้: outcome:success ใน numerator (หรือ filter good-event ที่เทียบเท่า) และยืนยันใน preview ของ SLO editor ของ Datadog ว่าตัวเลขขยับถูกทาง

# @expect-error validates; formula_expression "query2" refers to a name no query defines
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "p95:trace.http.request{service:checkout,env:$env}"
}
}
formula {
formula_expression = "query2"
}
display_type = "line"
}

formula_expression เป็น string อิสระที่ถูกประเมินกับ name ของ query ใน request เดียวกัน Terraform resolve ไม่ได้ และโมเดล Python สำหรับ JSON ที่เทียบเท่า ("formulas": [{"formula": "query2"}]) construct และ deserialize สะอาด (_unparsed = False); ทั้งสองถูกรันแล้ว UI จะแสดง widget ที่ error; API เป็นฝ่ายเดียวที่ปฏิเสธได้ และผมไม่ได้เรียกมัน

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

formula {
formula_expression = "query2"
}
เฉลย

reference ที่ค้างอยู่ใน string query1 คือชื่อเดียวที่นิยาม วิธีแก้: formula_expression = "query1" ในการรีวิว ทุก formula_expression ควรได้รับการไล่นิ้วกลับไปหา name

JSON monitor: field ที่วางผิดที่ซึ่งโมเดลยอมรับ และ string ในที่ที่ต้องเป็น bool

หัวข้อที่มีชื่อว่า “JSON monitor: field ที่วางผิดที่ซึ่งโมเดลยอมรับ และ string ในที่ที่ต้องเป็น bool”
$ python monitor-bad.json # constructor with _check_type=True
ApiTypeError: Invalid type for variable 'notify_no_data'. Required value type is bool and passed type was str at ['options']['notify_no_data']

นั่นคือโมเดลทำหน้าที่: "notify_no_data": "true" ผิด และตะโกนดัง ทีนี้เอา error นั้นออกแล้วรันโมเดลอีก — object thresholds ระดับบนสุดยังอยู่:

monitor thresholds at top level constructor: OK (constructs) deserialize: _unparsed = False
monitor, critical="2" (string) constructor: ApiTypeError: Invalid type for variable 'critical'. Required value type is float and passed type was str at ['options']['thresholds']['critical']

Terraform ปฏิเสธความคิดเดียวกัน (bug 2) เป็น unsupported block; โมเดล JSON ยอมรับ field ระดับบนสุดที่ไม่รู้จักโดยไม่บ่นทั้งสองทาง ตำแหน่งจริงคือ options.thresholds เมื่อ additionalProperties เปิดอยู่ นี่คือ field ที่วางผิดที่ซึ่งโมเดลไม่มีความเห็น; API ทำอะไรกับ property ระดับบนสุดที่ไม่รู้จักไม่ได้แสดงที่นี่ (ไม่ได้ทดสอบ ไม่มี API) แต่ warning threshold ที่ตั้งใจไว้แน่นอนไม่ได้อยู่ในที่ที่ API อ่าน

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

{
"type": "metric alert",
"thresholds": { "critical": 2, "warning": 1.5 },
"options": { "notify_no_data": "true" }
}
เฉลย

สอง bug ที่มองเห็นได้ต่างกันสุดขั้วใน object เดียว: bool ที่เป็น string เป็น ApiTypeError แข็งๆ ส่วน thresholds ที่วางผิดที่มองไม่เห็นทั้งจาก constructor และ deserializer วิธีแก้: "options": { "thresholds": { "critical": 2, "warning": 1.5 }, "notify_no_data": false }

JSON SLO และ dashboard: constructor บอกว่าไม่เป็นไร _unparsed บอกว่าไม่จริง

หัวข้อที่มีชื่อว่า “JSON SLO และ dashboard: constructor บอกว่าไม่เป็นไร _unparsed บอกว่าไม่จริง”
slo type="metrics" constructor: OK (constructs) deserialize: _unparsed = True
slo timeframe="30days" constructor: OK (constructs) deserialize: _unparsed = True
slo target="99.9" (string) constructor: OK (constructs) deserialize: _unparsed = True
slo numerator=123 constructor: ApiTypeError: Invalid type for variable 'numerator'. Required value type is str and passed type was int
slo no thresholds constructor: TypeError: ServiceLevelObjective.__init__() missing 1 required positional argument: 'thresholds'
dash layout_type="grid" constructor: OK (constructs) deserialize: _unparsed = True
dash display_type="lines" constructor: OK (constructs) deserialize: _unparsed = True
dash widget definition, no type constructor: OK (constructs) deserialize: _unparsed = True
dash no layout_type constructor: TypeError: Dashboard.__init__() missing 1 required positional argument: 'layout_type'

อ่านสามบรรทัดแรกด้วยกัน constructor — สิ่งที่โหมด --json ของ harness ในคอร์สใช้ — ยอมรับ enum ที่ผิด (type), enum ที่ผิด (timeframe) และ string ในที่ที่ต้องเป็น float ภายใน list item ที่ซ้อนอยู่ (target) เพราะ — ตามที่วัดได้ที่นี่สำหรับ thresholds[] — item ของ list ที่ซ้อนไม่ถูกเช็ก type ตอน construct มีแต่ deserializer ที่สังเกตเห็น และเป็นแค่ boolean flag ที่ไม่มีข้อความ การเช็กที่ปิดช่องนี้มีหกบรรทัด และควรเพิ่มใน CI job สำหรับ JSON-as-code:

app/unparsed_check.py
import json
import sys
from datadog_api_client import ApiClient, Configuration
from datadog_api_client.v1.model.dashboard import Dashboard
from datadog_api_client.v1.model.monitor import Monitor
from datadog_api_client.v1.model.service_level_objective import ServiceLevelObjective
MODELS = {"monitor": Monitor, "dashboard": Dashboard, "slo": ServiceLevelObjective}
def check(kind: str, path: str) -> bool:
"""Deserialize like the real client does; _unparsed is set on enum/oneOf mismatches."""
with open(path) as f:
raw = f.read()
obj = ApiClient(Configuration()).deserialize(raw, (MODELS[kind],), True)
print(f"{path}: _unparsed = {obj._unparsed}")
return not obj._unparsed
if __name__ == "__main__":
ok = check(sys.argv[1], sys.argv[2])
sys.exit(0 if ok else 1)

รันกับสามเอกสารที่ผิดพิมพ์ _unparsed = True สำหรับ SLO และ dashboard; รันกับสามเอกสารที่แก้แล้วด้านล่างพิมพ์ _unparsed = False ทุกครั้ง (_unparsed ไม่บอกว่า field ไหน: แบ่งครึ่งหาด้วยมือ เหมือนที่ตารางข้างบนทำ) มันยังมองไม่เห็น bug เชิงความหมาย — numerator ที่นับ error, formula ที่ค้างอยู่ — ซึ่งยังเป็นงานของ reviewer และท้ายที่สุดคือ API

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

{
"type": "metrics",
"thresholds": [{ "timeframe": "30days", "target": "99.9" }]
}
เฉลย

สามความผิดเรื่อง enum/type ในสองบรรทัด ซึ่งแต่ละอันผ่าน ServiceLevelObjective(**data, _check_type=True) นี่คือด้านตรงข้ามของ bug 3: Terraform provider ปฏิเสธ 30days ตอน validate แต่โมเดล Python client ไม่ วิธีแก้: "type": "metric", "timeframe": "30d", "target": 99.9 อย่าถือว่าการรัน --json ของ harness ที่เขียวเป็นหลักฐานสำหรับ enum หรือ field ที่ซ้อน

terraform/checkout-observability.tf
resource "datadog_monitor" "checkout_latency" {
name = "Checkout p95 latency"
type = "metric alert"
message = "p95 too high @pagerduty-checkout"
query = "avg(last_5m):p95:trace.http.request{service:checkout,env:prod} > 2"
tags = ["service:checkout", "team:payments"]
monitor_thresholds {
critical = 2
warning = 1.5
}
}
resource "datadog_service_level_objective" "checkout_availability" {
name = "Checkout availability"
type = "metric"
tags = ["service:checkout", "team:payments"]
query {
numerator = "sum:checkout.requests{env:prod,outcome:success}.as_count()"
denominator = "sum:checkout.requests{env:prod}.as_count()"
}
thresholds {
timeframe = "30d"
target = 99.9
warning = 99.95
}
}
resource "datadog_monitor" "checkout_burn_rate" {
name = "Checkout SLO burn rate"
type = "slo alert"
message = "Error budget is burning @pagerduty-checkout"
query = "burn_rate(\"${datadog_service_level_objective.checkout_availability.id}\").over(\"30d\").long_window(\"1h\").short_window(\"5m\") > 14.4"
tags = ["service:checkout", "team:payments"]
monitor_thresholds {
critical = 14.4
}
}
resource "datadog_dashboard" "checkout" {
title = "Checkout overview"
layout_type = "ordered"
template_variable {
name = "env"
prefix = "env"
}
widget {
timeseries_definition {
title = "p95 latency"
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "p95:trace.http.request{service:checkout,env:$env}"
}
}
formula {
formula_expression = "query1"
}
display_type = "line"
}
}
}
}
$ terraform validate # provider datadog 4.22.0, no plan, no apply
Success! The configuration is valid.

JSON คู่แฝด ทั้งสี่ถูกเก็บและรันผ่านโหมด --json ของ harness ในคอร์ส (และผ่านการเช็ก _unparsed ข้างบน แต่ละอันเป็น False):

monitors/wr3-checkout-latency.json
{
"name": "Checkout p95 latency",
"type": "metric alert",
"query": "avg(last_5m):p95:trace.http.request{service:checkout,env:prod} > 2",
"message": "p95 too high @pagerduty-checkout",
"tags": ["service:checkout", "team:payments"],
"options": {
"thresholds": { "critical": 2, "warning": 1.5 },
"notify_no_data": false,
"renotify_interval": 60
}
}
monitors/wr3-checkout-burn-rate.json
{
"name": "Checkout SLO burn rate",
"type": "slo alert",
"query": "burn_rate(\"<slo_id>\").over(\"30d\").long_window(\"1h\").short_window(\"5m\") > 14.4",
"message": "Error budget is burning @pagerduty-checkout",
"tags": ["service:checkout", "team:payments"],
"options": { "thresholds": { "critical": 14.4 } }
}
slos/wr3-checkout-availability.json
{
"name": "Checkout availability",
"type": "metric",
"query": {
"numerator": "sum:checkout.requests{env:prod,outcome:success}.as_count()",
"denominator": "sum:checkout.requests{env:prod}.as_count()"
},
"thresholds": [{ "timeframe": "30d", "target": 99.9, "warning": 99.95 }],
"tags": ["service:checkout", "team:payments"]
}
dashboards/wr3-checkout-overview.json
{
"title": "Checkout overview",
"layout_type": "ordered",
"widgets": [
{
"definition": {
"type": "timeseries",
"title": "p95 latency",
"requests": [
{
"response_format": "timeseries",
"queries": [
{ "data_source": "metrics", "name": "query1", "query": "p95:trace.http.request{service:checkout,env:prod}" }
],
"formulas": [{ "formula": "query1" }],
"display_type": "line"
}
]
}
}
]
}

"<slo_id>" ใน JSON ของ burn-rate เป็น placeholder โดยตั้งใจสำหรับ alphanumeric ID จริง; โมเดล validate รูปร่าง ไม่ใช่ ID

#Bugวิธีแก้จับได้ด้วย
1tags = "..." (string)list ของ stringterraform validate
2block thresholds บน monitormonitor_thresholdsterraform validate
3timeframe = "30days""30d"terraform validate (provider บังคับ enum)
4Dashboard: ไม่มี layout_typelayout_type = "ordered"terraform validate
5Dashboard: template_variablestemplate_variableterraform validate (“Did you mean”)
6attribute .slo_id.idterraform validate — รันครั้งที่สองเท่านั้น
7query burn-rate: .over("1h"), ไม่มี .short_window(), ไม่มี threshold.over("30d")...short_window("5m") > 14.4 + monitor_thresholdsdocs เท่านั้น; API จริง
8numerator ของ SLO นับ errorนับ good eventdocs เท่านั้น; คน; preview ของ SLO
9formula ของ dashboard ชื่อ query2query1API จริง / UI เท่านั้น
10JSON monitor: notify_no_data: "true", thresholds ระดับบนสุดbool; options.thresholdsโมเดลจับ bool ได้; ไม่มีอะไร offline จับ field ที่วางผิดที่ได้
11enum ของ JSON SLO/dashboard และ target ที่เป็น string ซ้อนenum ที่ถูก, target ตัวเลข_unparsed ตอน deserialize — ไม่ใช่ constructor / --json

สิบเอ็ดเรื่อง และมีแค่หกเรื่องที่ terraform validate จะบอกคุณ:

  1. รูปร่าง Terraform (หก แบบเป็นขั้น) tags เป็น list; monitor ใช้ monitor_thresholds; timeframe ของ SLO คือ 30d; dashboard ต้องมี layout_type และ template_variable (เอกพจน์); และ query burn-rate อ้าง .id ไม่ใช่ .slo_id สังเกตว่าอันสุดท้ายโผล่หลังแก้อันอื่นแล้วเท่านั้น — รันซ้ำหลังแก้แต่ละครั้ง
  2. query ของ burn-rate validate ผ่านและผิด: .over("30d"), .long_window("1h"), .short_window("5m") > 14.4 และใส่ 14.4 ใน monitor_thresholds.critical ไวยากรณ์ตาม docs burn-rate ของ SLO
  3. numerator ของ SLO ต้องเป็น good event ตามที่เขียน SLI คือ error rate เช็กตัวเลขใน SLO editor ก่อน merge
  4. formula_expression = "query2" ชี้ไปที่ความว่างเปล่า query1
  5. JSON: options.thresholds ไม่ใช่ thresholds ระดับบนสุด (โมเดลของ client ยอมรับแบบเงียบๆ); notify_no_data เป็น bool; type ของ SLO คือ metric, timeframe คือ 30d, target เป็นตัวเลข; layout_type ของ dashboard คือ ordered, display_type คือ line การเช็ก --json ของ harness ผ่านหลายข้อเหล่านี้ — ช่วยรันการเช็ก _unparsed ด้วย
  6. ไม่ได้ยืนยัน: ไม่มีอะไรที่นี่ถูกส่งไป Datadog ข้อความ rejection ของ API สำหรับข้อ 2, 3 และ 4 ผมไม่ทราบ; โปรดยืนยันบน sandbox org ก่อน apply
flowchart TD
  A["AI-written .tf and JSON"] --> B["terraform validate"]
  A --> C["client model: constructor"]
  A --> D["client model: deserializer"]
  A --> E["Datadog API and docs"]
  B -->|"1, 2, 3, 4, 5, 6: shape, enums, references"| F["caught"]
  C -->|"10: string bool"| F
  D -->|"11: _unparsed enums and nested types"| F
  E -->|"7, 8, 9: grammar, meaning, dangling names"| G["needs the API or a person"]
  C -->|"10: misplaced field passes"| G
ชั้นไหนเห็น bug ไหน
ชั้นเห็นมองไม่เห็น
terraform validate (provider 4.22.0)ชื่อและ type ของ argument และ block, enum บางตัว (SLOTimeframe), reference — เมื่อ error ก่อนหน้าหมดไปเนื้อของ string query, ชื่อใน formula_expression, metric หมายถึงอะไร
constructor ของโมเดล (โหมด --json ของ harness)field ที่จำเป็น, type ระดับบนสุดและระดับแรกค่า enum, item ของ list ที่ซ้อน, field ที่ไม่รู้จัก, ไวยากรณ์ของ query
deserializer ของโมเดล (_unparsed)enum และ oneOf ที่ไม่ match, type ที่ซ้อนfield ไหน; field ที่ไม่รู้จัก; ไวยากรณ์ของ query
Datadog API จริง / reviewer ที่มี docsไวยากรณ์, การมีอยู่ของ ID และชื่อ, ตัวเลข SLI ขยับถูกทางหรือไม่— (และไม่มีอะไรถูกรันที่นี่)

ความต่างระหว่าง bug 3 กับ bug 11 คือบทเรียนที่คมที่สุด: ค่าผิดตัวเดียวกัน 30days เป็น hard error ใน Terraform และเป็นไฟเขียวใน constructor ของ Python กระบวนการรีวิวที่เชื่อ “validation ผ่าน” ครั้งเดียวสำหรับทั้งสองจึงผิดสำหรับครึ่งหนึ่ง

  • Terraform vs JSON/API ดิบ Terraform ให้ schema ที่มี type, validate, reference ระหว่าง object (.id) และ workflow plan/apply; JSON ดิบให้รูปร่างของ API เป๊ะและไม่มีการเช็กของ Terraform ถ้าเก็บ JSON ใน git ให้เพิ่มการเช็ก _unparsed ใน CI เพราะการเช็ก constructor ของ harness อ่อนกว่า
  • ความเข้มงวดของ provider vs ความจริงของ API provider ปฏิเสธ 30days แต่เพราะผู้เขียนคัดลอก enum ลง schema เท่านั้น; ไวยากรณ์ burn-rate เป็น string ดังนั้น provider กับ API รู้ไม่เท่ากัน เลือก provider สำหรับรูปร่าง และ apply บน sandbox (นอกรีวิวนี้) สำหรับความหมาย
  • burn-rate alert หลาย window vs budget alert ธรรมดา คู่ long-และ-short window (1h กับ 5m ในตัวอย่างของ docs) แจ้งเตือนและ resolve เร็วกว่า window เดียว แลกกับ threshold สองตัวที่ต้องคิด หน้า docs ข้างบนอธิบาย trade-off
  • “terraform validate ผ่าน แปลว่า monitor ถูก” พิสูจน์แล้วข้างบน: ไฟล์ stage 3 พิมพ์ Success! ขณะที่ SLO นับ error, burn rate ไม่มี short window และ formula ชี้ไปที่ความว่างเปล่า
  • “ถ้า validate ครั้งแรกแสดงห้า error ก็มีห้า error” มีอันที่หกอยู่ข้างหลัง: Terraform รายงาน reference หลังจากโครงสร้างถูกต้องเท่านั้น
  • “การรัน --json ของ harness เขียว แปลว่า JSON ของ SLO ถูก” constructor ยอมรับ type: "metrics", timeframe: "30days" และ target: "99.9"; มีแต่ _unparsed = True ของ deserializer ที่ไม่เห็นด้วย
  • “โมเดลปฏิเสธ field ที่ไม่รู้จัก” มันยอมรับ thresholds ระดับบนสุดบน monitor โดย _unparsed = False ทั้งสองทาง
  • “JSON แบบ API ปลอดภัยกว่า Terraform เพราะเป็นสิ่งที่ API รับ” มันคือ API เดียวกัน แต่มีการเช็กแบบ offline น้อยกว่า
terraform validate ครั้งแรกกับไฟล์ของ AI แสดงห้า error หลังแก้ครบทั้งห้า error ที่หก (This object has no argument, nested block, or exported attribute named "slo_id") ก็โผล่ ทำไมมันไม่อยู่ในการรันแรก?
timeframe = "30days" บน datadog_service_level_objective เทียบกับ "timeframe": "30days" ใน SLO JSON provider 4.22.0 และ constructor ของ Python client ทำอะไร?
metric SLO มี numerator = "sum:checkout.requests{env:prod,outcome:error}.as_count()" เหนือ denominator ที่เป็น total requests terraform validate บอกอะไร และอะไรผิด?
query ของ burn-rate monitor คือ burn_rate("<id>").over("1h").long_window("6h") > 10 ข้อใดถูกต้อง?
monitor JSON มี "thresholds": { "critical": 2 } ระดับบนสุดแทนที่จะอยู่ใต้ options เส้นทางของโมเดล client รายงานอะไร?
"thresholds": [{ "timeframe": "30d", "target": "99.9" }] (target เป็น string) ใน SLO JSON constructor ที่มี _check_type=True ทำอะไร?