milestone 27: diagnostics — operational failures land in one curated log, resolved history purgeable
Gates / frontend (push) Successful in 1m33s
Gates / test (push) Successful in 1m48s
Gates / test-aarch64 (push) Successful in 7m10s
Gates / package (push) Successful in 5m31s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 14m51s
Gates / frontend (push) Successful in 1m33s
Gates / test (push) Successful in 1m48s
Gates / test-aarch64 (push) Successful in 7m10s
Gates / package (push) Successful in 5m31s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 14m51s
This commit is contained in:
+222
-1
@@ -256,6 +256,131 @@ paths:
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/diagnostics:
|
||||
get:
|
||||
summary: Operational event log
|
||||
description: |
|
||||
Failure episodes, newest first. One event is one subject failing
|
||||
continuously: it opens on the first failure, counts repeats in
|
||||
`occurrences`, and gets a `resolved_at` when the subject recovers. A
|
||||
subject that fails again opens a new event rather than reopening the
|
||||
old one. Keyset pagination — follow `next_before` until it is null.
|
||||
parameters:
|
||||
- name: state
|
||||
in: query
|
||||
schema: { type: string, enum: [active, resolved, all], default: all }
|
||||
- name: severity
|
||||
in: query
|
||||
schema: { type: string, enum: [warning, error] }
|
||||
- name: component
|
||||
in: query
|
||||
description: Matches the part of `code` before the dot, exactly.
|
||||
schema: { type: string, maxLength: 64 }
|
||||
- name: since
|
||||
in: query
|
||||
description: |
|
||||
Unix seconds. With `until`, selects episodes overlapping the
|
||||
window; an episode resolved exactly at `since` does not overlap.
|
||||
schema: { type: integer }
|
||||
- name: until
|
||||
in: query
|
||||
description: Unix seconds, exclusive.
|
||||
schema: { type: integer }
|
||||
- name: limit
|
||||
in: query
|
||||
schema: { type: integer, minimum: 1, maximum: 1000, default: 100 }
|
||||
- name: before
|
||||
in: query
|
||||
description: Return events with id strictly below this cursor.
|
||||
schema: { type: integer, minimum: 1 }
|
||||
responses:
|
||||
"200":
|
||||
description: One page.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DiagnosticsPage"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
delete:
|
||||
summary: Purge every resolved event
|
||||
description: |
|
||||
Deletes the resolved history and answers with how many rows went.
|
||||
Active events are never touched, so clearing the page cannot lose an
|
||||
episode that is still failing. Resolution stays automatic; this only
|
||||
decides when the history disappears. A runtime action, served in file
|
||||
mode too — the event log is not configuration.
|
||||
responses:
|
||||
"200":
|
||||
description: How many resolved events were removed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DiagnosticsPurge"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/diagnostics/{id}:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/RowId"
|
||||
get:
|
||||
summary: One operational event
|
||||
description: |
|
||||
404 for an id that never existed and for one retention has removed —
|
||||
the API does not distinguish them.
|
||||
responses:
|
||||
"200":
|
||||
description: The event.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DiagnosticEvent"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
delete:
|
||||
summary: Purge one resolved event
|
||||
description: |
|
||||
Deletes a resolved event. An event that is still active answers 409 —
|
||||
an open episode is the current state of the box, not history — and an
|
||||
id no row holds answers 404. A runtime action, served in file mode too.
|
||||
responses:
|
||||
"204":
|
||||
description: Purged.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"409":
|
||||
$ref: "#/components/responses/Conflict"
|
||||
"429":
|
||||
$ref: "#/components/responses/RateLimited"
|
||||
"500":
|
||||
$ref: "#/components/responses/Internal"
|
||||
"503":
|
||||
$ref: "#/components/responses/Unavailable"
|
||||
|
||||
/api/stats:
|
||||
get:
|
||||
summary: Totals for a period
|
||||
@@ -1664,11 +1789,21 @@ components:
|
||||
|
||||
Health:
|
||||
type: object
|
||||
required: [status, disk, upstreams, queries_dropped, writer_failed, refreshes_gated, snapshot_generation]
|
||||
required: [status, disk, upstreams, diagnostics, queries_dropped, writer_failed, refreshes_gated, snapshot_generation]
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: [ok, degraded]
|
||||
diagnostics:
|
||||
type: object
|
||||
required: [state, active_warnings, active_errors]
|
||||
properties:
|
||||
state:
|
||||
type: string
|
||||
enum: [recording, unavailable]
|
||||
description: unavailable when the event store failed to open or its writes are failing; either state degrades health.
|
||||
active_warnings: { type: integer }
|
||||
active_errors: { type: integer }
|
||||
disk:
|
||||
type: object
|
||||
required: [state, free_bytes, db_bytes, log_bytes, sample_failures]
|
||||
@@ -1760,6 +1895,92 @@ components:
|
||||
nullable: true
|
||||
description: Cursor for the next page; null on the last page.
|
||||
|
||||
DiagnosticEvent:
|
||||
type: object
|
||||
required: [id, code, component, subject, severity, first_seen, last_seen, occurrences, resolved_at, detail]
|
||||
properties:
|
||||
id: { type: integer }
|
||||
code:
|
||||
type: string
|
||||
description: |
|
||||
The failure kind, as `component.name`. One of a fixed set of
|
||||
fifteen; new codes are added with new releases.
|
||||
enum:
|
||||
- disk.space
|
||||
- disk.probe
|
||||
- blocklist.refresh
|
||||
- blocklist.snapshot
|
||||
- blocklist.storage
|
||||
- certificate.reload
|
||||
- query_log.write
|
||||
- query_log.maintenance
|
||||
- query_log.recreated
|
||||
- upstream_history.write
|
||||
- upstream.exchange
|
||||
- client_names.storage
|
||||
- clients.storage
|
||||
- listener.start
|
||||
- configuration.load
|
||||
component:
|
||||
type: string
|
||||
description: The part of `code` before the dot, repeated for filtering.
|
||||
subject:
|
||||
type: string
|
||||
description: |
|
||||
What failed, as a display name: a blocklist source name, an
|
||||
endpoint, an operation. Redacted where it derives from a url; the
|
||||
store's internal identity for the subject is never exposed.
|
||||
severity:
|
||||
type: string
|
||||
enum: [warning, error]
|
||||
first_seen:
|
||||
type: integer
|
||||
description: When this episode opened, unix seconds.
|
||||
last_seen:
|
||||
type: integer
|
||||
description: The most recent failure of this episode, unix seconds.
|
||||
occurrences:
|
||||
type: integer
|
||||
description: How many failures this episode has held; at least 1.
|
||||
resolved_at:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: |
|
||||
When the subject recovered, unix seconds. Null while the episode is
|
||||
still open. A subject that fails again opens a new event rather than
|
||||
reopening this one.
|
||||
detail:
|
||||
type: string
|
||||
description: The last error of this episode, truncated to 512 bytes.
|
||||
|
||||
DiagnosticsPage:
|
||||
type: object
|
||||
required: [events, next_before, active]
|
||||
properties:
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/DiagnosticEvent"
|
||||
next_before:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Cursor for the next page; null on the last page.
|
||||
active:
|
||||
type: object
|
||||
required: [warnings, errors]
|
||||
description: Episodes open right now, whatever this page filtered to.
|
||||
properties:
|
||||
warnings: { type: integer }
|
||||
errors: { type: integer }
|
||||
|
||||
DiagnosticsPurge:
|
||||
type: object
|
||||
required: [purged]
|
||||
properties:
|
||||
purged:
|
||||
type: integer
|
||||
description: How many resolved events the purge removed; zero when there were none.
|
||||
|
||||
StatsTotals:
|
||||
type: object
|
||||
required: [period, since, until, queries, blocked, cached, clients, avg_response_time_us]
|
||||
|
||||
Reference in New Issue
Block a user