Worked review 3: monitor, SLO และ dashboard as code
Feature ที่ขอ
หัวข้อที่มีชื่อว่า “Feature ที่ขอ”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กับ providerDataDog/datadog4.22.0 ที่ pin ไว้บน Terraform 1.16.4 ใน directory ชั่วคราวที่มีสำเนา plugin cache ส่วนตัว provider block ใช้ key ปลอมและvalidate = false; ไม่เคยรันterraform planและapplyและตัวแปรAWS_*ที่มีอยู่ในเครื่องนี้ถูกเอาออกจาก environment ของทุก processterraform- โมเดลของ
datadog-api-client2.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
โค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “โค้ดที่ AI สร้าง”# @expect-error as generated -- five terraform validate errors at once, more behind themresource "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 เหลือในไฟล์
tags = "team:payments" — string ในที่ที่ต้องเป็น set
หัวข้อที่มีชื่อว่า “tags = "team:payments" — string ในที่ที่ต้องเป็น set”$ terraform validateError: 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"]
thresholds { } — block ที่ไม่มีอยู่จริง
หัวข้อที่มีชื่อว่า “thresholds { } — block ที่ไม่มีอยู่จริง”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/)
timeframe = "30days" — provider เช็ก enum นี้จริง
หัวข้อที่มีชื่อว่า “timeframe = "30days" — provider เช็ก enum นี้จริง”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"
dashboard ไม่มี layout_type และ template_variables เป็นพหูพจน์
หัวข้อที่มีชื่อว่า “dashboard ไม่มี layout_type และ template_variables เป็นพหูพจน์”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 บันทึกความผิดพหูพจน์แบบเดียวกัน)
.slo_id — reference ที่ fail เฉพาะในการรันครั้งที่สอง
หัวข้อที่มีชื่อว่า “.slo_id — reference ที่ fail เฉพาะในการรันครั้งที่สอง”เมื่อแก้หกข้อข้างบนแล้วรันอีก จะได้ 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 validateSuccess! The configuration is valid.มันยังผิดอยู่สามที่ ทุกอย่างจากนี้ terraform validate มองไม่เห็น
query ของ burn-rate: .over() ผิด ไม่มี .short_window() และไม่มี block threshold
หัวข้อที่มีชื่อว่า “query ของ burn-rate: .over() ผิด ไม่มี .short_window() และไม่มี block threshold”# @expect-error validates; the burn_rate grammar and the missing monitor_thresholds are only enforced by the Datadog APIresource "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
numerator ของ SLO นับ bad event
หัวข้อที่มีชื่อว่า “numerator ของ SLO นับ bad event”# @expect-error validates; numerator must be GOOD events - the SLI is numerator / denominatorquery { 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 ว่าตัวเลขขยับถูกทาง
formula ของ dashboard อ้าง query ที่ไม่มีอยู่
หัวข้อที่มีชื่อว่า “formula ของ dashboard อ้าง query ที่ไม่มีอยู่”# @expect-error validates; formula_expression "query2" refers to a name no query definesrequest { 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=TrueApiTypeError: 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 = Falsemonitor, 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 = Trueslo timeframe="30days" constructor: OK (constructs) deserialize: _unparsed = Trueslo target="99.9" (string) constructor: OK (constructs) deserialize: _unparsed = Trueslo numerator=123 constructor: ApiTypeError: Invalid type for variable 'numerator'. Required value type is str and passed type was intslo no thresholds constructor: TypeError: ServiceLevelObjective.__init__() missing 1 required positional argument: 'thresholds'dash layout_type="grid" constructor: OK (constructs) deserialize: _unparsed = Truedash display_type="lines" constructor: OK (constructs) deserialize: _unparsed = Truedash widget definition, no type constructor: OK (constructs) deserialize: _unparsed = Truedash 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:
import jsonimport sys
from datadog_api_client import ApiClient, Configurationfrom datadog_api_client.v1.model.dashboard import Dashboardfrom datadog_api_client.v1.model.monitor import Monitorfrom 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 ที่ซ้อน
โค้ดที่แก้แล้ว
หัวข้อที่มีชื่อว่า “โค้ดที่แก้แล้ว”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 applySuccess! The configuration is valid.JSON คู่แฝด ทั้งสี่ถูกเก็บและรันผ่านโหมด --json ของ harness ในคอร์ส (และผ่านการเช็ก _unparsed ข้างบน แต่ละอันเป็น False):
{ "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 }}{ "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 } }}{ "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"]}{ "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 | วิธีแก้ | จับได้ด้วย |
|---|---|---|---|
| 1 | tags = "..." (string) | list ของ string | terraform validate |
| 2 | block thresholds บน monitor | monitor_thresholds | terraform validate |
| 3 | timeframe = "30days" | "30d" | terraform validate (provider บังคับ enum) |
| 4 | Dashboard: ไม่มี layout_type | layout_type = "ordered" | terraform validate |
| 5 | Dashboard: template_variables | template_variable | terraform validate (“Did you mean”) |
| 6 | attribute .slo_id | .id | terraform validate — รันครั้งที่สองเท่านั้น |
| 7 | query burn-rate: .over("1h"), ไม่มี .short_window(), ไม่มี threshold | .over("30d")...short_window("5m") > 14.4 + monitor_thresholds | docs เท่านั้น; API จริง |
| 8 | numerator ของ SLO นับ error | นับ good event | docs เท่านั้น; คน; preview ของ SLO |
| 9 | formula ของ dashboard ชื่อ query2 | query1 | API จริง / UI เท่านั้น |
| 10 | JSON monitor: notify_no_data: "true", thresholds ระดับบนสุด | bool; options.thresholds | โมเดลจับ bool ได้; ไม่มีอะไร offline จับ field ที่วางผิดที่ได้ |
| 11 | enum ของ JSON SLO/dashboard และ target ที่เป็น string ซ้อน | enum ที่ถูก, target ตัวเลข | _unparsed ตอน deserialize — ไม่ใช่ constructor / --json |
สิ่งที่ reviewer เขียนใน PR
หัวข้อที่มีชื่อว่า “สิ่งที่ reviewer เขียนใน PR”สิบเอ็ดเรื่อง และมีแค่หกเรื่องที่
terraform validateจะบอกคุณ:
- รูปร่าง Terraform (หก แบบเป็นขั้น)
tagsเป็น list; monitor ใช้monitor_thresholds;timeframeของ SLO คือ30d; dashboard ต้องมีlayout_typeและtemplate_variable(เอกพจน์); และ query burn-rate อ้าง.idไม่ใช่.slo_idสังเกตว่าอันสุดท้ายโผล่หลังแก้อันอื่นแล้วเท่านั้น — รันซ้ำหลังแก้แต่ละครั้ง- query ของ burn-rate validate ผ่านและผิด:
.over("30d"),.long_window("1h"),.short_window("5m") > 14.4และใส่ 14.4 ในmonitor_thresholds.criticalไวยากรณ์ตาม docs burn-rate ของ SLO- numerator ของ SLO ต้องเป็น good event ตามที่เขียน SLI คือ error rate เช็กตัวเลขใน SLO editor ก่อน merge
formula_expression = "query2"ชี้ไปที่ความว่างเปล่าquery1- 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ด้วย- ไม่ได้ยืนยัน: ไม่มีอะไรที่นี่ถูกส่งไป Datadog ข้อความ rejection ของ API สำหรับข้อ 2, 3 และ 4 ผมไม่ทราบ; โปรดยืนยันบน sandbox org ก่อน apply
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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
| ชั้น | เห็น | มองไม่เห็น |
|---|---|---|
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 ผ่าน” ครั้งเดียวสำหรับทั้งสองจึงผิดสำหรับครึ่งหนึ่ง
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- 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
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
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 น้อยกว่า