Skip to content

Commit 4c87e42

Browse files
committed
docs(observability): clarify event channel and request error rules
- Add built-in audit trail section mapping events to compliance controls - Note emit is a no-op when no listener is registered - Note missing request context marks an event internal - State request:error carries error only on framework faults
1 parent 69bab41 commit 4c87e42

4 files changed

Lines changed: 66 additions & 10 deletions

File tree

docs/id/middleware/observability/events.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ Event view datang dari [mesin rendering DVE](/id/rendering/).
6666
| Kind | Metadata |
6767
| ------------------- | --------------------------------------------------- |
6868
| `request:complete` | `method`, `statusCode`, `url`, `durationMs`, metrik |
69-
| `request:error` | sama dengan `request:complete`, plus `error` |
69+
| `request:error` | sama dengan `request:complete`, plus `error` opsional |
7070

71-
`request:complete` menyala untuk setiap request yang selesai. `request:error` menyala tambahan setiap kali status `400` atau lebih tinggi. Keduanya membawa metrik selaras-OpenTelemetry opsional saat diketahui: `route`, `serverAddress`, `serverPort`, `userAgent`, `requestSize`, `responseSize`, dan `ip`.
71+
`request:complete` menyala untuk setiap request yang selesai. `request:error` menyala tambahan setiap kali status `400` atau lebih tinggi, dan membawa `error` hanya saat kegagalan dipicu oleh error framework. Keduanya membawa metrik selaras-OpenTelemetry opsional saat diketahui: `route`, `serverAddress`, `serverPort`, `userAgent`, `requestSize`, `responseSize`, dan `ip`.
7272

7373
Ubah ini menjadi log di [Request Logging](/id/middleware/observability/logging).
7474

@@ -78,4 +78,4 @@ Ubah ini menjadi log di [Request Logging](/id/middleware/observability/logging).
7878
| --------------- | -------------------------------------------------------------------- |
7979
| `process:error` | `error`, `origin` (`unhandledrejection`, `uncaughterror`, `process:exit`) |
8080

81-
Router yang sedang melayani menjebak unhandled rejection, uncaught error, dan upaya menghentikan proses. Setiap kesalahan menjadi event `process:error` alih-alih membuat server crash, jadi satu kegagalan tidak pernah menjatuhkan proses. Panggilan terminasi yang diblokir membawa `origin: 'process:exit'` dan menyebut panggilannya, contohnya `Blocked Deno.exit(0) - process termination is not permitted from application code`. Lihat [Proteksi Proses](/id/getting-started/server-configuration#proteksi-proses) untuk alasannya, dan tangkap ini di [Pelaporan Error](/id/middleware/observability/errors).
81+
Router yang sedang melayani menjebak unhandled rejection, uncaught error, dan upaya menghentikan proses. Setiap kesalahan menjadi event `process:error` alih-alih membuat server crash, jadi satu kegagalan tidak pernah menjatuhkan proses. Panggilan terminasi yang diblokir membawa `origin: 'process:exit'` dan menyebut panggilannya, contohnya `Blocked Deno.exit(0) process termination is not permitted from application code`. Lihat [Proteksi Proses](/id/getting-started/server-configuration#proteksi-proses) untuk alasannya, dan tangkap ini di [Pelaporan Error](/id/middleware/observability/errors).

docs/id/middleware/observability/overview.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ await router.serve(8000)
3232
off()
3333
```
3434

35-
Listener menyala untuk semua jenis event, jadi penyaringan terjadi di dalam callback.
35+
Listener menyala untuk semua jenis event, jadi penyaringan terjadi di dalam callback. Tanpa listener terdaftar, emit jadi no-op dan tidak berbiaya, jadi bus tetap bebas sampai ada yang berlangganan.
3636

3737
## Bentuk Event
3838

@@ -47,7 +47,7 @@ Setiap event berbagi amplop yang sama:
4747
}
4848
```
4949

50-
- **`type`** - `external` untuk lalu lintas klien normal, `internal` untuk kesalahan framework dan timeout. Sebuah event request bernilai `internal` ketika kesalahan framework atau timeout 503 sintetis yang memicunya, selain itu `external`. Setiap jenis lain selalu `internal`.
50+
- **`type`** - `external` untuk lalu lintas klien normal, `internal` untuk kesalahan framework. Sebuah event request bernilai `internal` ketika kesalahan framework, timeout 503 sintetis, atau context request yang hilang yang memicunya, selain itu `external`. Setiap jenis lain selalu `internal`.
5151
- **`kind`** - diskriminan yang dipakai untuk membedakan event.
5252
- **`metadata`** - field readonly yang bergantung pada jenis.
5353
- **`timestamp`** - kapan event dibuat.
@@ -58,6 +58,34 @@ Daftar lengkap jenis dan metadata-nya ada di [Referensi Event](/id/middleware/ob
5858

5959
Observability bus melaporkan aktivitas framework seperti request, rute, view, dan kesalahan. Domain event bus membawa fakta aplikasi seperti `user:created`. Keduanya melayani tugas berbeda dan sering berjalan berdampingan. Lihat [pola domain event bus](/id/core-concepts/multi-service#event-bus) untuk berbagi event aplikasi antar service.
6060

61+
## Jejak Audit Bawaan
62+
63+
Setiap subsistem melapor di bus yang sama, dari sinyal [server](/id/middleware/observability/events#server) dan [rute](/id/middleware/observability/events#rute) sampai kesalahan [worker](/id/middleware/observability/events#worker), [middleware](/id/middleware/observability/events#middleware), dan [proses](/id/middleware/observability/events#process). Masing-masing tiba sebagai amplop `{ type, kind, metadata, timestamp }` yang sama, terstruktur dan bercap waktu pada saat ia menyala. Sebuah listener sederhana berubah menjadi jejak audit yang mencatat dirinya sendiri selama server berjalan, tanpa kabel tambahan.
64+
65+
Itu mencakup hal yang biasanya diminta oleh kerja kepatuhan dan keamanan:
66+
67+
- **[SOC 2](https://www.aicpa-cima.com/topic/audit-assurance/audit-and-assurance-greater-than-soc-2)**, **[ISO/IEC 27001](https://www.iso.org/standard/27001)**, dan **[PCI DSS](https://www.pcisecuritystandards.org/document_library/)** meminta event relevan-keamanan tercatat. Cookie yang dirusak (`session:invalid`), panggilan terminasi yang diblokir (`process:error`), dan aturan CSRF yang gagal (`csrf:rule-error`) semua menyala sendiri.
68+
- **Garis waktu** dapat direkonstruksi karena aliran event terurut dan setiap event membawa `timestamp` dalam milidetik epoch.
69+
- **Setiap request** dapat dilacak asalnya lewat `method`, `url`, `statusCode`, `durationMs`, dan `ip` opsional pada `request:complete`.
70+
- **[SIEM](https://csrc.nist.gov/glossary/term/security_information_and_event_management)** dapat menyerap aliran ini karena satu `router.on()` meneruskan seluruh permukaan ke kolektor mana pun.
71+
72+
Field `type` menjaga kanal kesalahan tetap bersih. Lalu lintas klien normal bernilai `external`, sementara kesalahan framework, timeout 503 sintetis, atau context request yang hilang menandai event `internal`. Pipeline alert kesalahan menyaring `internal` dan tidak pernah tenggelam dalam request rutin.
73+
74+
```typescript twoslash
75+
import { Router } from '@neabyte/deserve'
76+
77+
const router = new Router({
78+
routesDir: './routes'
79+
})
80+
// ---cut---
81+
router.on((event) => {
82+
// Teruskan hanya kesalahan framework
83+
if (event.type === 'internal') {
84+
console.log(JSON.stringify({ at: event.timestamp, ...event }))
85+
}
86+
})
87+
```
88+
6189
## Langkah Berikutnya
6290

6391
- [Referensi Event](/id/middleware/observability/events) - setiap jenis event dan metadata-nya.

docs/middleware/observability/events.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ View events come from the [DVE rendering engine](/rendering/).
6666
| Kind | Metadata |
6767
| ------------------- | --------------------------------------------------- |
6868
| `request:complete` | `method`, `statusCode`, `url`, `durationMs`, metrics |
69-
| `request:error` | same as `request:complete`, plus `error` |
69+
| `request:error` | same as `request:complete`, plus an optional `error` |
7070

71-
`request:complete` fires for every finished request. `request:error` fires in addition whenever the status is `400` or higher. Both carry optional OpenTelemetry-aligned metrics when known: `route`, `serverAddress`, `serverPort`, `userAgent`, `requestSize`, `responseSize`, and `ip`.
71+
`request:complete` fires for every finished request. `request:error` fires in addition whenever the status is `400` or higher, and carries `error` only when a framework error produced the failure. Both carry optional OpenTelemetry-aligned metrics when known: `route`, `serverAddress`, `serverPort`, `userAgent`, `requestSize`, `responseSize`, and `ip`.
7272

7373
Turn these into a log in [Request Logging](/middleware/observability/logging).
7474

@@ -78,4 +78,4 @@ Turn these into a log in [Request Logging](/middleware/observability/logging).
7878
| --------------- | -------------------------------------------------------------------- |
7979
| `process:error` | `error`, `origin` (`unhandledrejection`, `uncaughterror`, `process:exit`) |
8080

81-
A serving router traps unhandled rejections, uncaught errors, and attempts to terminate the process. Each fault becomes a `process:error` event rather than crashing the server, so a single failure never takes the process down. A blocked termination call carries `origin: 'process:exit'` and names the call, for example `Blocked Deno.exit(0) - process termination is not permitted from application code`. See [Process Protection](/getting-started/server-configuration#process-protection) for the reasoning, and capture these in [Error Reporting](/middleware/observability/errors).
81+
A serving router traps unhandled rejections, uncaught errors, and attempts to terminate the process. Each fault becomes a `process:error` event rather than crashing the server, so a single failure never takes the process down. A blocked termination call carries `origin: 'process:exit'` and names the call, for example `Blocked Deno.exit(0) process termination is not permitted from application code`. See [Process Protection](/getting-started/server-configuration#process-protection) for the reasoning, and capture these in [Error Reporting](/middleware/observability/errors).

docs/middleware/observability/overview.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ await router.serve(8000)
3232
off()
3333
```
3434

35-
The listener fires for all event kinds, so filtering happens inside the callback.
35+
The listener fires for all event kinds, so filtering happens inside the callback. With no listener registered, emitting is a no-op and costs nothing, so the bus stays free until something subscribes.
3636

3737
## Event Shape
3838

@@ -47,7 +47,7 @@ Every event shares the same envelope:
4747
}
4848
```
4949

50-
- **`type`** - `external` for normal client traffic, `internal` for framework faults and timeouts. A request event is `internal` when a framework error or the synthetic 503 timeout produced it, otherwise `external`. Every other kind is always `internal`.
50+
- **`type`** - `external` for normal client traffic, `internal` for framework faults. A request event is `internal` when a framework error, the synthetic 503 timeout, or a missing request context produced it, otherwise `external`. Every other kind is always `internal`.
5151
- **`kind`** - the discriminant used to tell events apart.
5252
- **`metadata`** - readonly fields that depend on the kind.
5353
- **`timestamp`** - when the event was created.
@@ -58,6 +58,34 @@ The full list of kinds and their metadata lives in [Event Reference](/middleware
5858

5959
The observability bus reports framework activity such as requests, routes, views, and faults. A domain event bus carries application facts like `user:created`. They serve different jobs and often run side by side. See the [domain event bus pattern](/core-concepts/multi-service#event-bus) for sharing application events across services.
6060

61+
## A Built-In Audit Trail
62+
63+
Every subsystem reports on the same bus, from [server](/middleware/observability/events#server) and [route](/middleware/observability/events#routes) signals to [worker](/middleware/observability/events#workers), [middleware](/middleware/observability/events#middleware), and [process](/middleware/observability/events#process) faults. Each one arrives as the same `{ type, kind, metadata, timestamp }` envelope, structured and stamped at the instant it fires. A plain listener becomes an audit trail that records itself as the server runs, with no extra wiring.
64+
65+
That covers what compliance and security work usually ask for, and each control maps to a behaviour the bus already provides:
66+
67+
- **[SOC 2](https://www.aicpa-cima.com/topic/audit-assurance/audit-and-assurance-greater-than-soc-2) (CC7 monitoring)** wants security-relevant events captured. Tampered cookies (`session:invalid`), blocked termination calls (`process:error`), and failed CSRF rules (`csrf:rule-error`) all emit on their own.
68+
- **[ISO/IEC 27001](https://www.iso.org/standard/27001) (A.8.15 logging)** wants an event log that holds up over time. Every event carries a `timestamp` in epoch milliseconds and arrives in order, so a timeline reconstructs cleanly.
69+
- **[PCI DSS](https://www.pcisecuritystandards.org/document_library/) (Requirement 10 audit trails)** wants each action tied to its source. `request:complete` reports `method`, `url`, `statusCode`, `durationMs`, and an optional `ip` when the address is known.
70+
- **[SIEM](https://csrc.nist.gov/glossary/term/security_information_and_event_management) and real-time alerting** want a feed to ingest. A single `router.on()` forwards the whole surface to wherever logs or alerts go.
71+
72+
The `type` field keeps the fault channel clean. Normal client traffic is `external`, while a framework error, the synthetic 503 timeout, or a missing request context marks the event `internal`. A fault alert pipeline filters on `internal` and never drowns in routine requests.
73+
74+
```typescript twoslash
75+
import { Router } from '@neabyte/deserve'
76+
77+
const router = new Router({
78+
routesDir: './routes'
79+
})
80+
// ---cut---
81+
router.on((event) => {
82+
// Forward framework faults only
83+
if (event.type === 'internal') {
84+
console.log(JSON.stringify({ at: event.timestamp, ...event }))
85+
}
86+
})
87+
```
88+
6189
## Where to Go Next
6290

6391
- [Event Reference](/middleware/observability/events) - every event kind and its metadata.

0 commit comments

Comments
 (0)