@luanpdd/kit-mcp 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -0
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -0
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +5 -2
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -0
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: structured-events
|
|
3
|
-
description: Use ao instrumentar — wide events de alta cardinalidade (1/request), campos canônicos com dot notation, evite logs unstructured e métricas pre-aggregated.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Observabilidade — Structured Events (Wide Events)
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando instrumentar código para emitir telemetria. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "structured logging", "wide events", "observability events"
|
|
13
|
-
- "instrumentar handler", "emitir telemetria", "log estruturado"
|
|
14
|
-
- "como salvar evento de request"
|
|
15
|
-
- "campos canônicos", "atributos de span"
|
|
16
|
-
- "alta cardinalidade", "debug por user_id"
|
|
17
|
-
|
|
18
|
-
## Regras absolutas
|
|
19
|
-
|
|
20
|
-
- **1 evento por request** — não múltiplos. Acumule contexto durante o request, emita 1 wide event no final (ou em erros).
|
|
21
|
-
- **Wide é melhor que narrow** — adicione campos liberalmente. Custo de 100 campos/evento ≈ 10 campos. Disco é barato; falta de campo no incidente é caro.
|
|
22
|
-
- **Alta cardinalidade é OBRIGATÓRIA** — `user.id`, `tenant_id`, `request.id`, `customer.email`. Sem isso, observabilidade não funciona (Cap 1).
|
|
23
|
-
- **Dot notation OTel** — `user.id` (não `userId` nem `user_id`). `error.type`, `http.status_code`, `db.query`. Snake_case apenas em colunas de DB.
|
|
24
|
-
- **NUNCA pre-aggregate** — não emita "p99 latency = 247ms"; emita o `duration_ms` cru de cada request. Aggregation no read time.
|
|
25
|
-
- **Estruturado, não texto livre** — JSON, OTel attributes, ou colunas tipadas. **Nunca** `console.log("user 123 did X at 12:34")`.
|
|
26
|
-
- **Errors são especiais** — sample 100% de eventos com `result.success = false`. Sucesso pode ser samplado (skill `telemetry-sampling`).
|
|
27
|
-
- **Capture context, não code** — emita atributos de business logic (`customer.tier`, `feature_flag.x`), não estado interno de código (`var_x_value_at_line_42`).
|
|
28
|
-
|
|
29
|
-
## Patterns canônicos
|
|
30
|
-
|
|
31
|
-
### Pattern: handler instrumentado (Node/TypeScript)
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
// PT-BR: 1 evento por request, alta cardinalidade, atributos canônicos
|
|
35
|
-
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
36
|
-
|
|
37
|
-
const tracer = trace.getTracer('orders-service')
|
|
38
|
-
|
|
39
|
-
export async function handlePlaceOrder(req: Request) {
|
|
40
|
-
return tracer.startActiveSpan('place_order', async (span) => {
|
|
41
|
-
// PT-BR: campos canônicos sempre — alta cardinalidade
|
|
42
|
-
span.setAttribute('user.id', req.user.id)
|
|
43
|
-
span.setAttribute('tenant_id', req.user.tenant)
|
|
44
|
-
span.setAttribute('customer.tier', req.user.tier)
|
|
45
|
-
span.setAttribute('request.id', req.headers['x-request-id'])
|
|
46
|
-
span.setAttribute('endpoint', '/api/v1/orders')
|
|
47
|
-
span.setAttribute('http.method', 'POST')
|
|
48
|
-
span.setAttribute('build_id', process.env.BUILD_ID ?? 'dev')
|
|
49
|
-
|
|
50
|
-
// PT-BR: feature flags como dimensões
|
|
51
|
-
span.setAttribute('feature_flag.new_pricing', req.flags.newPricing)
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const order = await createOrder(req.body)
|
|
55
|
-
|
|
56
|
-
// PT-BR: result e atributos de domínio
|
|
57
|
-
span.setAttribute('result.success', true)
|
|
58
|
-
span.setAttribute('order.id', order.id)
|
|
59
|
-
span.setAttribute('order.amount_cents', order.amount)
|
|
60
|
-
span.setAttribute('order.items_count', order.items.length)
|
|
61
|
-
span.setAttribute('http.status_code', 200)
|
|
62
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
63
|
-
return order
|
|
64
|
-
} catch (e) {
|
|
65
|
-
// PT-BR: erros — sample 100%, classificar por tipo
|
|
66
|
-
span.setAttribute('result.success', false)
|
|
67
|
-
span.setAttribute('error.type', classifyError(e))
|
|
68
|
-
span.setAttribute('error.message', e.message)
|
|
69
|
-
span.setAttribute('http.status_code', e.statusCode ?? 500)
|
|
70
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: e.message })
|
|
71
|
-
throw e
|
|
72
|
-
} finally {
|
|
73
|
-
span.end() // PT-BR: SEMPRE — duration_ms é calculado aqui
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function classifyError(e: any): string {
|
|
79
|
-
if (e.code === 'P2002') return 'db_conflict'
|
|
80
|
-
if (e.statusCode === 401) return 'auth'
|
|
81
|
-
if (e.statusCode === 403) return 'authz'
|
|
82
|
-
if (e.statusCode === 422) return 'validation'
|
|
83
|
-
if (e.statusCode === 429) return 'rate_limit'
|
|
84
|
-
if (e.code === 'ETIMEDOUT') return 'timeout'
|
|
85
|
-
return 'unknown'
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Pattern: Edge Function (Deno) com structured event
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
// PT-BR: Supabase Edge Function — 1 evento estruturado por invocação
|
|
93
|
-
import { trace } from 'npm:@opentelemetry/api@1.9.0'
|
|
94
|
-
|
|
95
|
-
const tracer = trace.getTracer('edge-process-emails')
|
|
96
|
-
|
|
97
|
-
Deno.serve(async (req) => {
|
|
98
|
-
return tracer.startActiveSpan('process_emails', async (span) => {
|
|
99
|
-
const requestId = crypto.randomUUID()
|
|
100
|
-
span.setAttribute('request.id', requestId)
|
|
101
|
-
span.setAttribute('build_id', Deno.env.get('SUPABASE_GIT_SHA') ?? 'local')
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const body = await req.json()
|
|
105
|
-
span.setAttribute('user.id', body.user_id)
|
|
106
|
-
span.setAttribute('tenant_id', body.tenant_id)
|
|
107
|
-
span.setAttribute('email.batch_size', body.emails?.length ?? 0)
|
|
108
|
-
|
|
109
|
-
const result = await processBatch(body.emails)
|
|
110
|
-
|
|
111
|
-
span.setAttribute('result.success', true)
|
|
112
|
-
span.setAttribute('email.sent_count', result.sent)
|
|
113
|
-
span.setAttribute('email.failed_count', result.failed)
|
|
114
|
-
span.setAttribute('duration_ms', result.duration)
|
|
115
|
-
|
|
116
|
-
return new Response(JSON.stringify(result), { status: 200 })
|
|
117
|
-
} catch (e) {
|
|
118
|
-
span.setAttribute('result.success', false)
|
|
119
|
-
span.setAttribute('error.type', classify(e))
|
|
120
|
-
span.setAttribute('error.message', String(e))
|
|
121
|
-
return new Response(JSON.stringify({ error: 'failed' }), { status: 500 })
|
|
122
|
-
} finally {
|
|
123
|
-
span.end()
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Pattern: campos canônicos por categoria
|
|
130
|
-
|
|
131
|
-
| Categoria | Campos | Exemplo |
|
|
132
|
-
|---|---|---|
|
|
133
|
-
| **Identidade** | `user.id`, `tenant_id`, `session.id` | `"550e8400-e29b-41d4-..."` |
|
|
134
|
-
| **Request** | `request.id`, `endpoint`, `http.method`, `http.status_code` | `"req_abc123"`, `"/api/v1/orders"`, `"POST"`, `200` |
|
|
135
|
-
| **Resultado** | `result.success`, `error.type`, `error.message` | `true`, `"validation"`, `"email already exists"` |
|
|
136
|
-
| **Performance** | `duration_ms`, `db.query_count`, `cache.hit` | `127`, `3`, `true` |
|
|
137
|
-
| **Build/Deploy** | `build_id`, `service.version`, `region` | `"abc123f"`, `"v1.9.0"`, `"us-east-1"` |
|
|
138
|
-
| **Business** | `customer.tier`, `order.amount_cents`, `feature_flag.<name>` | `"pro"`, `4990`, `true` |
|
|
139
|
-
| **Tracing** | `trace.id`, `span.id`, `span.parent_id` | (auto via OTel) |
|
|
140
|
-
|
|
141
|
-
### Pattern: query observability — encontrar pattern em wide events
|
|
142
|
-
|
|
143
|
-
```sql
|
|
144
|
-
-- PT-BR: alta cardinalidade permite group by ad hoc — sem schema rigido
|
|
145
|
-
-- Exemplo: qual tenant + endpoint + error_type domina os erros da última hora?
|
|
146
|
-
select
|
|
147
|
-
tenant_id,
|
|
148
|
-
endpoint,
|
|
149
|
-
error_type,
|
|
150
|
-
count(*) as error_count,
|
|
151
|
-
avg(duration_ms) as avg_duration
|
|
152
|
-
from observability.events
|
|
153
|
-
where
|
|
154
|
-
result_success = false
|
|
155
|
-
and timestamp > now() - interval '1 hour'
|
|
156
|
-
group by tenant_id, endpoint, error_type
|
|
157
|
-
order by error_count desc
|
|
158
|
-
limit 20;
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Anti-patterns
|
|
162
|
-
|
|
163
|
-
### ANTI: log unstructured
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
// PT-BR: BAD — não estruturado, não queryable, sem alta cardinalidade
|
|
167
|
-
console.log(`User ${userId} placed order ${orderId} for $${amount}`)
|
|
168
|
-
|
|
169
|
-
// PT-BR: GOOD — structured wide event
|
|
170
|
-
span.setAttribute('user.id', userId)
|
|
171
|
-
span.setAttribute('order.id', orderId)
|
|
172
|
-
span.setAttribute('order.amount_cents', amount * 100)
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### ANTI: pre-aggregate em métricas
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
// PT-BR: BAD — pre-aggregation perde alta cardinalidade
|
|
179
|
-
metrics.histogram('order_latency_ms').record(duration, { service: 'orders' })
|
|
180
|
-
|
|
181
|
-
// PT-BR: GOOD — emit raw event, agregue no read
|
|
182
|
-
span.setAttribute('duration_ms', duration)
|
|
183
|
-
// PT-BR: ao queryar: SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
|
|
184
|
-
// FROM events GROUP BY tenant_id, endpoint -- alta cardinalidade preservada!
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### ANTI: múltiplos eventos por request
|
|
188
|
-
|
|
189
|
-
```ts
|
|
190
|
-
// PT-BR: BAD — 5 eventos para 1 request, sem trace context
|
|
191
|
-
log('user_action_started', { user_id })
|
|
192
|
-
log('user_action_db_query', { user_id, query })
|
|
193
|
-
log('user_action_email_sent', { user_id, to })
|
|
194
|
-
log('user_action_completed', { user_id })
|
|
195
|
-
log('user_action_response_sent', { user_id, status })
|
|
196
|
-
|
|
197
|
-
// PT-BR: GOOD — 1 wide event acumulando contexto
|
|
198
|
-
const span = tracer.startSpan('user_action')
|
|
199
|
-
span.setAttribute('user.id', user_id)
|
|
200
|
-
// ... ao longo do handler, span.setAttribute('email.recipient', ...) etc.
|
|
201
|
-
span.end() // 1 evento emitido com todos os atributos
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### ANTI: cardinalidade baixa
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
// PT-BR: BAD — apenas service e endpoint, sem identidade
|
|
208
|
-
span.setAttribute('service', 'orders')
|
|
209
|
-
span.setAttribute('endpoint', '/place')
|
|
210
|
-
// PT-BR: durante incident você não consegue responder "afeta quem?"
|
|
211
|
-
|
|
212
|
-
// PT-BR: GOOD — adicione identidades de alta cardinalidade
|
|
213
|
-
span.setAttribute('user.id', '550e8400-...')
|
|
214
|
-
span.setAttribute('tenant_id', 'acme-corp')
|
|
215
|
-
span.setAttribute('customer.tier', 'pro')
|
|
216
|
-
// PT-BR: durante incident: "afeta quem?" → group by customer.tier, tenant_id
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### ANTI: capturar valores internos de código
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
// PT-BR: BAD — atributos sobre estado de variáveis, não sobre business
|
|
223
|
-
span.setAttribute('var_temp_array_length', tempArr.length)
|
|
224
|
-
span.setAttribute('loop_iteration', i)
|
|
225
|
-
|
|
226
|
-
// PT-BR: GOOD — atributos sobre business + identidade
|
|
227
|
-
span.setAttribute('order.items_count', items.length)
|
|
228
|
-
span.setAttribute('user.id', userId)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### ANTI: nomes inconsistentes de atributos
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
// PT-BR: BAD — mesmo conceito com nomes diferentes em handlers diferentes
|
|
235
|
-
span.setAttribute('userId', user.id) // handler A
|
|
236
|
-
span.setAttribute('user_id', user.id) // handler B
|
|
237
|
-
span.setAttribute('user', user.id) // handler C
|
|
238
|
-
// PT-BR: query `WHERE user_id = X` falha em handler A; agg cross-handler quebra
|
|
239
|
-
|
|
240
|
-
// PT-BR: GOOD — convenção única em todo o projeto
|
|
241
|
-
span.setAttribute('user.id', user.id) // sempre dot notation OTel
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Verificação
|
|
245
|
-
|
|
246
|
-
Antes de marcar instrumentação completa:
|
|
247
|
-
|
|
248
|
-
1. **1 evento por request** — em request de exemplo, contar eventos emitidos. Deve ser 1 (ou 2 se houver retry interno).
|
|
249
|
-
2. **Atributos canônicos presentes** — checar `user.id`, `tenant_id`, `request.id`, `result.success`, `endpoint`, `duration_ms` no evento emitido.
|
|
250
|
-
3. **Alta cardinalidade verificada** — `select count(distinct user_id)` deve crescer com tráfego real (não estagnar em N pequeno).
|
|
251
|
-
4. **`result.success` define SLI** — boolean confiável para alimentar SLO downstream (ver skill `event-based-slos`).
|
|
252
|
-
5. **Erros têm `error.type` enum** — não `error.message` cru. Permite group by por categoria.
|
|
253
|
-
6. **Build_id presente** — permite comparar versão antes vs depois de deploy.
|
|
254
|
-
7. **Smoke local** — emitir 100 eventos sintéticos, queryar via `select * from events where user_id = X` deve retornar todos.
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Ver também
|
|
259
|
-
|
|
260
|
-
- `kit/skills/_shared-observability/glossary.md` — termos canônicos, campos canônicos, anti-patterns
|
|
261
|
-
- `kit/skills/distributed-tracing/SKILL.md` — como spans se conectam em traces
|
|
262
|
-
- `kit/skills/opentelemetry-standard/SKILL.md` — SDK e exporters
|
|
263
|
-
- `kit/skills/core-analysis-loop/SKILL.md` — como queryar wide events para debug
|
|
264
|
-
|
|
265
|
-
*Material-fonte: Observability Engineering (O'Reilly, 2022) — Cap 5: "Structured Events Are the Building Blocks of Observability".*
|
|
1
|
+
---
|
|
2
|
+
name: structured-events
|
|
3
|
+
description: Use ao instrumentar — wide events de alta cardinalidade (1/request), campos canônicos com dot notation, evite logs unstructured e métricas pre-aggregated.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Observabilidade — Structured Events (Wide Events)
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando instrumentar código para emitir telemetria. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "structured logging", "wide events", "observability events"
|
|
13
|
+
- "instrumentar handler", "emitir telemetria", "log estruturado"
|
|
14
|
+
- "como salvar evento de request"
|
|
15
|
+
- "campos canônicos", "atributos de span"
|
|
16
|
+
- "alta cardinalidade", "debug por user_id"
|
|
17
|
+
|
|
18
|
+
## Regras absolutas
|
|
19
|
+
|
|
20
|
+
- **1 evento por request** — não múltiplos. Acumule contexto durante o request, emita 1 wide event no final (ou em erros).
|
|
21
|
+
- **Wide é melhor que narrow** — adicione campos liberalmente. Custo de 100 campos/evento ≈ 10 campos. Disco é barato; falta de campo no incidente é caro.
|
|
22
|
+
- **Alta cardinalidade é OBRIGATÓRIA** — `user.id`, `tenant_id`, `request.id`, `customer.email`. Sem isso, observabilidade não funciona (Cap 1).
|
|
23
|
+
- **Dot notation OTel** — `user.id` (não `userId` nem `user_id`). `error.type`, `http.status_code`, `db.query`. Snake_case apenas em colunas de DB.
|
|
24
|
+
- **NUNCA pre-aggregate** — não emita "p99 latency = 247ms"; emita o `duration_ms` cru de cada request. Aggregation no read time.
|
|
25
|
+
- **Estruturado, não texto livre** — JSON, OTel attributes, ou colunas tipadas. **Nunca** `console.log("user 123 did X at 12:34")`.
|
|
26
|
+
- **Errors são especiais** — sample 100% de eventos com `result.success = false`. Sucesso pode ser samplado (skill `telemetry-sampling`).
|
|
27
|
+
- **Capture context, não code** — emita atributos de business logic (`customer.tier`, `feature_flag.x`), não estado interno de código (`var_x_value_at_line_42`).
|
|
28
|
+
|
|
29
|
+
## Patterns canônicos
|
|
30
|
+
|
|
31
|
+
### Pattern: handler instrumentado (Node/TypeScript)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// PT-BR: 1 evento por request, alta cardinalidade, atributos canônicos
|
|
35
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
36
|
+
|
|
37
|
+
const tracer = trace.getTracer('orders-service')
|
|
38
|
+
|
|
39
|
+
export async function handlePlaceOrder(req: Request) {
|
|
40
|
+
return tracer.startActiveSpan('place_order', async (span) => {
|
|
41
|
+
// PT-BR: campos canônicos sempre — alta cardinalidade
|
|
42
|
+
span.setAttribute('user.id', req.user.id)
|
|
43
|
+
span.setAttribute('tenant_id', req.user.tenant)
|
|
44
|
+
span.setAttribute('customer.tier', req.user.tier)
|
|
45
|
+
span.setAttribute('request.id', req.headers['x-request-id'])
|
|
46
|
+
span.setAttribute('endpoint', '/api/v1/orders')
|
|
47
|
+
span.setAttribute('http.method', 'POST')
|
|
48
|
+
span.setAttribute('build_id', process.env.BUILD_ID ?? 'dev')
|
|
49
|
+
|
|
50
|
+
// PT-BR: feature flags como dimensões
|
|
51
|
+
span.setAttribute('feature_flag.new_pricing', req.flags.newPricing)
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const order = await createOrder(req.body)
|
|
55
|
+
|
|
56
|
+
// PT-BR: result e atributos de domínio
|
|
57
|
+
span.setAttribute('result.success', true)
|
|
58
|
+
span.setAttribute('order.id', order.id)
|
|
59
|
+
span.setAttribute('order.amount_cents', order.amount)
|
|
60
|
+
span.setAttribute('order.items_count', order.items.length)
|
|
61
|
+
span.setAttribute('http.status_code', 200)
|
|
62
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
63
|
+
return order
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// PT-BR: erros — sample 100%, classificar por tipo
|
|
66
|
+
span.setAttribute('result.success', false)
|
|
67
|
+
span.setAttribute('error.type', classifyError(e))
|
|
68
|
+
span.setAttribute('error.message', e.message)
|
|
69
|
+
span.setAttribute('http.status_code', e.statusCode ?? 500)
|
|
70
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: e.message })
|
|
71
|
+
throw e
|
|
72
|
+
} finally {
|
|
73
|
+
span.end() // PT-BR: SEMPRE — duration_ms é calculado aqui
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function classifyError(e: any): string {
|
|
79
|
+
if (e.code === 'P2002') return 'db_conflict'
|
|
80
|
+
if (e.statusCode === 401) return 'auth'
|
|
81
|
+
if (e.statusCode === 403) return 'authz'
|
|
82
|
+
if (e.statusCode === 422) return 'validation'
|
|
83
|
+
if (e.statusCode === 429) return 'rate_limit'
|
|
84
|
+
if (e.code === 'ETIMEDOUT') return 'timeout'
|
|
85
|
+
return 'unknown'
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Pattern: Edge Function (Deno) com structured event
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// PT-BR: Supabase Edge Function — 1 evento estruturado por invocação
|
|
93
|
+
import { trace } from 'npm:@opentelemetry/api@1.9.0'
|
|
94
|
+
|
|
95
|
+
const tracer = trace.getTracer('edge-process-emails')
|
|
96
|
+
|
|
97
|
+
Deno.serve(async (req) => {
|
|
98
|
+
return tracer.startActiveSpan('process_emails', async (span) => {
|
|
99
|
+
const requestId = crypto.randomUUID()
|
|
100
|
+
span.setAttribute('request.id', requestId)
|
|
101
|
+
span.setAttribute('build_id', Deno.env.get('SUPABASE_GIT_SHA') ?? 'local')
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const body = await req.json()
|
|
105
|
+
span.setAttribute('user.id', body.user_id)
|
|
106
|
+
span.setAttribute('tenant_id', body.tenant_id)
|
|
107
|
+
span.setAttribute('email.batch_size', body.emails?.length ?? 0)
|
|
108
|
+
|
|
109
|
+
const result = await processBatch(body.emails)
|
|
110
|
+
|
|
111
|
+
span.setAttribute('result.success', true)
|
|
112
|
+
span.setAttribute('email.sent_count', result.sent)
|
|
113
|
+
span.setAttribute('email.failed_count', result.failed)
|
|
114
|
+
span.setAttribute('duration_ms', result.duration)
|
|
115
|
+
|
|
116
|
+
return new Response(JSON.stringify(result), { status: 200 })
|
|
117
|
+
} catch (e) {
|
|
118
|
+
span.setAttribute('result.success', false)
|
|
119
|
+
span.setAttribute('error.type', classify(e))
|
|
120
|
+
span.setAttribute('error.message', String(e))
|
|
121
|
+
return new Response(JSON.stringify({ error: 'failed' }), { status: 500 })
|
|
122
|
+
} finally {
|
|
123
|
+
span.end()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Pattern: campos canônicos por categoria
|
|
130
|
+
|
|
131
|
+
| Categoria | Campos | Exemplo |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| **Identidade** | `user.id`, `tenant_id`, `session.id` | `"550e8400-e29b-41d4-..."` |
|
|
134
|
+
| **Request** | `request.id`, `endpoint`, `http.method`, `http.status_code` | `"req_abc123"`, `"/api/v1/orders"`, `"POST"`, `200` |
|
|
135
|
+
| **Resultado** | `result.success`, `error.type`, `error.message` | `true`, `"validation"`, `"email already exists"` |
|
|
136
|
+
| **Performance** | `duration_ms`, `db.query_count`, `cache.hit` | `127`, `3`, `true` |
|
|
137
|
+
| **Build/Deploy** | `build_id`, `service.version`, `region` | `"abc123f"`, `"v1.9.0"`, `"us-east-1"` |
|
|
138
|
+
| **Business** | `customer.tier`, `order.amount_cents`, `feature_flag.<name>` | `"pro"`, `4990`, `true` |
|
|
139
|
+
| **Tracing** | `trace.id`, `span.id`, `span.parent_id` | (auto via OTel) |
|
|
140
|
+
|
|
141
|
+
### Pattern: query observability — encontrar pattern em wide events
|
|
142
|
+
|
|
143
|
+
```sql
|
|
144
|
+
-- PT-BR: alta cardinalidade permite group by ad hoc — sem schema rigido
|
|
145
|
+
-- Exemplo: qual tenant + endpoint + error_type domina os erros da última hora?
|
|
146
|
+
select
|
|
147
|
+
tenant_id,
|
|
148
|
+
endpoint,
|
|
149
|
+
error_type,
|
|
150
|
+
count(*) as error_count,
|
|
151
|
+
avg(duration_ms) as avg_duration
|
|
152
|
+
from observability.events
|
|
153
|
+
where
|
|
154
|
+
result_success = false
|
|
155
|
+
and timestamp > now() - interval '1 hour'
|
|
156
|
+
group by tenant_id, endpoint, error_type
|
|
157
|
+
order by error_count desc
|
|
158
|
+
limit 20;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Anti-patterns
|
|
162
|
+
|
|
163
|
+
### ANTI: log unstructured
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// PT-BR: BAD — não estruturado, não queryable, sem alta cardinalidade
|
|
167
|
+
console.log(`User ${userId} placed order ${orderId} for $${amount}`)
|
|
168
|
+
|
|
169
|
+
// PT-BR: GOOD — structured wide event
|
|
170
|
+
span.setAttribute('user.id', userId)
|
|
171
|
+
span.setAttribute('order.id', orderId)
|
|
172
|
+
span.setAttribute('order.amount_cents', amount * 100)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### ANTI: pre-aggregate em métricas
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// PT-BR: BAD — pre-aggregation perde alta cardinalidade
|
|
179
|
+
metrics.histogram('order_latency_ms').record(duration, { service: 'orders' })
|
|
180
|
+
|
|
181
|
+
// PT-BR: GOOD — emit raw event, agregue no read
|
|
182
|
+
span.setAttribute('duration_ms', duration)
|
|
183
|
+
// PT-BR: ao queryar: SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms)
|
|
184
|
+
// FROM events GROUP BY tenant_id, endpoint -- alta cardinalidade preservada!
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### ANTI: múltiplos eventos por request
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// PT-BR: BAD — 5 eventos para 1 request, sem trace context
|
|
191
|
+
log('user_action_started', { user_id })
|
|
192
|
+
log('user_action_db_query', { user_id, query })
|
|
193
|
+
log('user_action_email_sent', { user_id, to })
|
|
194
|
+
log('user_action_completed', { user_id })
|
|
195
|
+
log('user_action_response_sent', { user_id, status })
|
|
196
|
+
|
|
197
|
+
// PT-BR: GOOD — 1 wide event acumulando contexto
|
|
198
|
+
const span = tracer.startSpan('user_action')
|
|
199
|
+
span.setAttribute('user.id', user_id)
|
|
200
|
+
// ... ao longo do handler, span.setAttribute('email.recipient', ...) etc.
|
|
201
|
+
span.end() // 1 evento emitido com todos os atributos
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### ANTI: cardinalidade baixa
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// PT-BR: BAD — apenas service e endpoint, sem identidade
|
|
208
|
+
span.setAttribute('service', 'orders')
|
|
209
|
+
span.setAttribute('endpoint', '/place')
|
|
210
|
+
// PT-BR: durante incident você não consegue responder "afeta quem?"
|
|
211
|
+
|
|
212
|
+
// PT-BR: GOOD — adicione identidades de alta cardinalidade
|
|
213
|
+
span.setAttribute('user.id', '550e8400-...')
|
|
214
|
+
span.setAttribute('tenant_id', 'acme-corp')
|
|
215
|
+
span.setAttribute('customer.tier', 'pro')
|
|
216
|
+
// PT-BR: durante incident: "afeta quem?" → group by customer.tier, tenant_id
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### ANTI: capturar valores internos de código
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// PT-BR: BAD — atributos sobre estado de variáveis, não sobre business
|
|
223
|
+
span.setAttribute('var_temp_array_length', tempArr.length)
|
|
224
|
+
span.setAttribute('loop_iteration', i)
|
|
225
|
+
|
|
226
|
+
// PT-BR: GOOD — atributos sobre business + identidade
|
|
227
|
+
span.setAttribute('order.items_count', items.length)
|
|
228
|
+
span.setAttribute('user.id', userId)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### ANTI: nomes inconsistentes de atributos
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// PT-BR: BAD — mesmo conceito com nomes diferentes em handlers diferentes
|
|
235
|
+
span.setAttribute('userId', user.id) // handler A
|
|
236
|
+
span.setAttribute('user_id', user.id) // handler B
|
|
237
|
+
span.setAttribute('user', user.id) // handler C
|
|
238
|
+
// PT-BR: query `WHERE user_id = X` falha em handler A; agg cross-handler quebra
|
|
239
|
+
|
|
240
|
+
// PT-BR: GOOD — convenção única em todo o projeto
|
|
241
|
+
span.setAttribute('user.id', user.id) // sempre dot notation OTel
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Verificação
|
|
245
|
+
|
|
246
|
+
Antes de marcar instrumentação completa:
|
|
247
|
+
|
|
248
|
+
1. **1 evento por request** — em request de exemplo, contar eventos emitidos. Deve ser 1 (ou 2 se houver retry interno).
|
|
249
|
+
2. **Atributos canônicos presentes** — checar `user.id`, `tenant_id`, `request.id`, `result.success`, `endpoint`, `duration_ms` no evento emitido.
|
|
250
|
+
3. **Alta cardinalidade verificada** — `select count(distinct user_id)` deve crescer com tráfego real (não estagnar em N pequeno).
|
|
251
|
+
4. **`result.success` define SLI** — boolean confiável para alimentar SLO downstream (ver skill `event-based-slos`).
|
|
252
|
+
5. **Erros têm `error.type` enum** — não `error.message` cru. Permite group by por categoria.
|
|
253
|
+
6. **Build_id presente** — permite comparar versão antes vs depois de deploy.
|
|
254
|
+
7. **Smoke local** — emitir 100 eventos sintéticos, queryar via `select * from events where user_id = X` deve retornar todos.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Ver também
|
|
259
|
+
|
|
260
|
+
- `kit/skills/_shared-observability/glossary.md` — termos canônicos, campos canônicos, anti-patterns
|
|
261
|
+
- `kit/skills/distributed-tracing/SKILL.md` — como spans se conectam em traces
|
|
262
|
+
- `kit/skills/opentelemetry-standard/SKILL.md` — SDK e exporters
|
|
263
|
+
- `kit/skills/core-analysis-loop/SKILL.md` — como queryar wide events para debug
|
|
264
|
+
|
|
265
|
+
*Material-fonte: Observability Engineering (O'Reilly, 2022) — Cap 5: "Structured Events Are the Building Blocks of Observability".*
|