@nlite/logger-hapi 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +372 -23
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,48 +1,397 @@
1
- # NLite Logger Hapi SDK
1
+ # @nlite/logger-hapi
2
2
 
3
- Hapi plugin for integrating NLite Logger into your Hapi application.
3
+ > Drop-in Hapi.js plugin that turns your server into a fully observable NLite logger client. Captures every request, response, error, 404, validation failure, and auth challenge with zero boilerplate.
4
4
 
5
- ## Features
5
+ Built on top of [`@nlite/logger-core`](../sdk-core/README.md) and intended to be used with the [`@nlite/logger-server`](../server/README.md) ingestion API.
6
6
 
7
- - Seamless integration with Hapi.js
8
- - Automatic request logging
9
- - Structured log output
10
- - Real-time log streaming support
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Highlights](#highlights)
12
+ 2. [Installation](#installation)
13
+ 3. [Quick Start](#quick-start)
14
+ 4. [Plugin Options](#plugin-options)
15
+ 5. [Programmatic API](#programmatic-api)
16
+ 6. [Workflow & Request Lifecycle](#workflow--request-lifecycle)
17
+ 7. [Architecture Diagrams](#architecture-diagrams)
18
+ 8. [Advanced Examples](#advanced-examples)
19
+ 9. [Environment Variables](#environment-variables)
20
+ 10. [Scripts](#scripts)
21
+ 11. [Compatibility](#compatibility)
22
+ 12. [License & Author](#license--author)
23
+
24
+ ---
25
+
26
+ ## Highlights
27
+
28
+ - **Zero-config request/response/error capture** via Hapi lifecycle extensions.
29
+ - **Trace IDs** auto-generated or pulled from `x-trace-id` / `trace-id` headers.
30
+ - **Header sanitization** — `Authorization`, `Cookie`, `x-api-key`, etc. are redacted automatically.
31
+ - **Hooks for everything** — pass `getUserId`, `getSessionId`, `getTraceId` to integrate with your auth/session middleware.
32
+ - **Configurable ignore paths** so `/health`, `/ready`, `/metrics` don't flood your dashboard.
33
+ - **Graceful shutdown** — final flush on `server.events.on('stop')`.
34
+
35
+ ---
11
36
 
12
37
  ## Installation
13
38
 
14
39
  ```bash
40
+ # npm
15
41
  npm install @nlite/logger-hapi
42
+
43
+ # pnpm
44
+ pnpm add @nlite/logger-hapi
45
+
46
+ # yarn
47
+ yarn add @nlite/logger-hapi
16
48
  ```
17
49
 
18
- ## Requirements
50
+ **Requirements**
51
+
52
+ | Tool | Version |
53
+ |------|---------|
54
+ | `@hapi/hapi` | `>=20.0.0` (peer) |
55
+ | `@nlite/logger-core` | installed automatically (workspace dep) |
56
+ | Node.js | `>=18.0.0` |
19
57
 
20
- - @hapi/hapi >= 20.0.0
58
+ ---
21
59
 
22
- ## Usage
60
+ ## Quick Start
23
61
 
24
- ```typescript
62
+ ```ts
25
63
  import Hapi from '@hapi/hapi';
26
- import NliteLogger from '@nlite/logger-hapi';
64
+ import { createHapiMiddleware, getLogger } from '@nlite/logger-hapi';
65
+
66
+ async function bootstrap() {
67
+ const server = Hapi.server({ port: 3000 });
68
+
69
+ await server.register({
70
+ plugin: createHapiMiddleware({
71
+ loggerConfig: {
72
+ apiKey: process.env.NLITE_API_KEY!,
73
+ appName: 'orders-api',
74
+ platform: 'backend',
75
+ environment: (process.env.NODE_ENV as any) ?? 'development',
76
+ endpoint: process.env.NLITE_ENDPOINT ?? 'http://localhost:3000',
77
+ },
78
+ // Capture only what you need (all default to true)
79
+ captureRequest: true,
80
+ captureResponse: true,
81
+ captureError: true,
82
+ ignorePaths: ['/health', '/ready', '/metrics', '/favicon.ico'],
83
+ // Optional context extractors
84
+ getUserId: (req) => (req.auth.credentials?.user?.id as string | undefined),
85
+ getSessionId: (req) => req.headers['x-session-id'],
86
+ getTraceId: (req) => req.headers['x-trace-id'],
87
+ }),
88
+ });
89
+
90
+ server.route({
91
+ method: 'GET',
92
+ path: '/orders/{id}',
93
+ handler: async (request, h) => {
94
+ // Access the logger from inside any handler
95
+ const logger = getLogger(request);
96
+ logger?.info('Loading order', { orderId: request.params.id });
97
+ return { id: request.params.id };
98
+ },
99
+ });
100
+
101
+ await server.start();
102
+ }
103
+
104
+ bootstrap();
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Plugin Options
110
+
111
+ `createHapiMiddleware(options: HapiMiddlewareOptions)` accepts:
112
+
113
+ | Option | Type | Default | Description |
114
+ |--------|------|---------|-------------|
115
+ | `loggerConfig` | `SdkConfig` | — | **Required.** Forwarded to `@nlite/logger-core`. |
116
+ | `captureRequest` | `boolean` | `true` | Log incoming requests + add navigation breadcrumbs. |
117
+ | `captureResponse` | `boolean` | `true` | Log outgoing responses with status, duration, headers. |
118
+ | `captureError` | `boolean` | `true` | Log internal errors (5xx, thrown exceptions, request errors). |
119
+ | `ignorePaths` | `string[]` | `['/health', '/ready', '/metrics', '/favicon.ico']` | Prefix-matched paths that skip request/response logging. |
120
+ | `customTags` | `Record<string,string>` | `{}` | Tags merged into every log emitted by this plugin. |
121
+ | `getUserId` | `(req) => string \| undefined` | — | Extract the authenticated user id. |
122
+ | `getSessionId` | `(req) => string \| undefined` | — | Extract a session id. |
123
+ | `getTraceId` | `(req) => string \| undefined` | — | Extract / generate a trace id. |
124
+
125
+ A ready-made helper is also exported:
126
+
127
+ ```ts
128
+ import { createDefaultHapiConfig } from '@nlite/logger-hapi';
27
129
 
28
- const server = Hapi.server({
29
- port: 3000,
130
+ const options = createDefaultHapiConfig('API_KEY', 'orders-api', {
131
+ captureResponse: false,
132
+ ignorePaths: ['/_internal/*'],
30
133
  });
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Programmatic API
139
+
140
+ ### `createHapiMiddleware(options)`
141
+
142
+ Returns a Hapi plugin object. Register it with `server.register({ plugin })`.
143
+
144
+ ### `getLogger(request)`
145
+
146
+ Inside any route handler:
147
+
148
+ ```ts
149
+ server.route({
150
+ method: 'POST',
151
+ path: '/checkout',
152
+ handler: async (request) => {
153
+ const logger = getLogger(request)!;
154
+ logger.setUser('user_42', { plan: 'pro' });
155
+ logger.info('Checkout started');
156
+ // ...
157
+ },
158
+ });
159
+ ```
160
+
161
+ The plugin decorates each request with:
162
+
163
+ ```ts
164
+ request.nLiteLogger // LoggerSdk
165
+ request.nLiteLogContext // { startTime, traceId, spanId }
166
+ ```
167
+
168
+ ### `hapiMiddlewareOptionsSchema`
169
+
170
+ Static schema description (useful for Hapi's `server.register({ options, plugin })` validation pattern). Note: this is a documentation object, not a Joi schema — wire it through your own validator if you need strict runtime checks.
171
+
172
+ ---
173
+
174
+ ## Workflow & Request Lifecycle
175
+
176
+ Every request that is **not** on an ignore path flows through these stages:
31
177
 
32
- await server.register(NliteLogger);
33
178
  ```
179
+ ┌────────────────────────────────────────────────────┐
180
+ │ Hapi request arrives │
181
+ └─────────────────────┬──────────────────────────────┘
182
+
183
+
184
+ ┌──────────────────────────────────────────────────────────┐
185
+ │ onRequest │
186
+ │ - record startTime, traceId, spanId │
187
+ │ - store context on request.nLiteLogContext │
188
+ │ - build sanitized LogRequest (headers redacted) │
189
+ │ - add 'http' breadcrumb │
190
+ │ - call getUserId / getSessionId, attach user/session │
191
+ │ - if payload > 10KB → log 'large payload' debug │
192
+ └──────────────────────────┬───────────────────────────────┘
193
+
194
+
195
+ route handler
196
+
197
+
198
+ ┌──────────────────────────────────────────────────────────┐
199
+ │ onPreResponse (response branch) │
200
+ │ - compute durationMs │
201
+ │ - build LogResponse │
202
+ │ - emit single log with level by status (5xx=error, │
203
+ │ 4xx=warn, else=info) │
204
+ └──────────────────────────┬───────────────────────────────┘
205
+
206
+
207
+ ┌──────────────────────────────────────────────────────────┐
208
+ │ onPreResponse (error branch — captureError: true) │
209
+ │ - 404 → warn 'Route not found' │
210
+ │ - 400 → warn 'Validation error' │
211
+ │ - 401/403 → warn 'Auth error' │
212
+ │ - 5xx → error with thrown Boom │
213
+ └──────────────────────────┬───────────────────────────────┘
214
+
215
+
216
+ ┌──────────────────────────────────────────────────────────┐
217
+ │ request event tagged error │
218
+ │ - emits 'Request error' with thrown error context │
219
+ └──────────────────────────┬───────────────────────────────┘
220
+
221
+
222
+ ┌─────────────────────────────────────┐
223
+ │ Enqueue → @nlite/logger-core │
224
+ │ (batched, retried, persisted) │
225
+ └─────────────────────────────────────┘
226
+ ```
227
+
228
+ ### Level mapping
229
+
230
+ | HTTP status | Log level |
231
+ |-------------|-----------|
232
+ | `5xx` | `error` |
233
+ | `4xx` | `warn` |
234
+ | `2xx/3xx` | `info` |
235
+
236
+ ---
237
+
238
+ ## Architecture Diagrams
239
+
240
+ ### Component view
241
+
242
+ ```
243
+ ┌──────────────────────────┐
244
+ │ Hapi Server │
245
+ └────────────┬─────────────┘
246
+
247
+
248
+ ┌─────────────────────────────────────────────────────────┐
249
+ │ createHapiMiddleware() │
250
+ │ │
251
+ │ onRequest ─► build LogRequest (sanitize headers) │
252
+ │ store request.nLiteLogContext │
253
+ │ add 'http' breadcrumb │
254
+ │ │
255
+ │ onPreResponse ─► build LogResponse │
256
+ │ emit level-mapped log │
257
+ │ handle 4xx/5xx branches │
258
+ │ │
259
+ │ request 'error' event ─► logger.error() │
260
+ │ │
261
+ │ server 'stop' ─► logger.destroy() (flush) │
262
+ └────────────────────────────┬──────────────────────────┘
263
+
264
+
265
+ ┌────────────────────────────────────────┐
266
+ │ @nlite/logger-core │
267
+ │ queue → batch → retry → transport │
268
+ └────────────────────┬───────────────────┘
269
+
270
+
271
+ POST {endpoint}/api/logs/batch
272
+ @nlite/logger-server → SQLite / Redis
273
+ ```
274
+
275
+ ### Sequence diagram
276
+
277
+ ```
278
+ Client Hapi HapiLoggerPlugin CoreLogger Transport Server
279
+ | | | | | |
280
+ | POST / | | | | |
281
+ |-------->| | | | |
282
+ | | onRequest| | | |
283
+ | |--------->| | | |
284
+ | | | breadcrumb | | |
285
+ | | |----------------->| | |
286
+ | | handler | | | |
287
+ | |--------->| | | |
288
+ | | | info('hi') | | |
289
+ | | |----------------->| | |
290
+ | | onPreResponse | | |
291
+ | |<---------| log('POST / 200')| | |
292
+ | | |----------------->| | |
293
+ | | | | batch POST | |
294
+ | | | |------------>| |
295
+ | | | | | 200 OK |
296
+ | | | | |<-------->|
297
+ | 200 | | | | |
298
+ |<--------| | | | |
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Advanced Examples
304
+
305
+ ### Selective capture (ignore noisy paths)
306
+
307
+ ```ts
308
+ createHapiMiddleware({
309
+ loggerConfig: { /* ... */ },
310
+ captureResponse: true,
311
+ captureError: true,
312
+ captureRequest: true,
313
+ ignorePaths: ['/health', '/ready', '/metrics', '/static', '/_next'],
314
+ });
315
+ ```
316
+
317
+ ### Per-route user binding
318
+
319
+ ```ts
320
+ const server = Hapi.server({ port: 3000 });
321
+
322
+ await server.register({
323
+ plugin: createHapiMiddleware({
324
+ loggerConfig: { /* ... */ },
325
+ getUserId: (req) => (req.auth.isAuthenticated ? req.auth.credentials.user.id : undefined),
326
+ }),
327
+ });
328
+
329
+ server.route({
330
+ method: 'GET',
331
+ path: '/me',
332
+ options: { auth: 'jwt' },
333
+ handler: (request) => {
334
+ const logger = getLogger(request)!;
335
+ logger.setUser(request.auth.credentials.user.id, request.auth.credentials.user);
336
+ logger.info('Profile viewed');
337
+ return request.auth.credentials.user;
338
+ },
339
+ });
340
+ ```
341
+
342
+ ### Custom tags via `setTags`
343
+
344
+ ```ts
345
+ createHapiMiddleware({
346
+ loggerConfig: { /* ... */ },
347
+ customTags: { service: 'orders', team: 'checkout' },
348
+ });
349
+ ```
350
+
351
+ ### Tracing integration
352
+
353
+ ```ts
354
+ createHapiMiddleware({
355
+ loggerConfig: { /* ... */ },
356
+ getTraceId: (req) => req.headers['x-b3-traceid'] as string | undefined,
357
+ });
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Environment Variables
363
+
364
+ | Variable | Description |
365
+ |----------|-------------|
366
+ | `NLITE_ENDPOINT` | Override the ingestion endpoint. Defaults to `http://localhost:3000`. |
367
+ | `NLITE_API_KEY` | API key used by `createDefaultHapiConfig`. |
368
+ | `NODE_ENV` | Mapped to `environment` when using the default helper. |
369
+ | `npm_package_version` | Used as `appVersion` by the default helper. |
370
+
371
+ ---
34
372
 
35
373
  ## Scripts
36
374
 
37
375
  | Script | Description |
38
376
  |--------|-------------|
39
- | `build` | Compile TypeScript |
40
- | `dev` | Watch mode for development |
41
- | `test` | Run tests with Vitest |
42
- | `test:watch` | Run tests in watch mode |
43
- | `lint` | Lint source files |
44
- | `typecheck` | Type check with TypeScript |
377
+ | `npm run build` | `tsc` + copy `dist/index.js` to `dist/index.cjs`. |
378
+ | `npm run dev` | Watch-mode build. |
379
+ | `npm test` | Run Vitest. |
380
+ | `npm run test:watch` | Vitest watch. |
381
+ | `npm run lint` | ESLint over `src`. |
382
+ | `npm run typecheck` | `tsc --noEmit`. |
383
+
384
+ ---
385
+
386
+ ## Compatibility
387
+
388
+ - **Hapi** 20.x and 21.x.
389
+ - **Node.js** 18, 20, 22.
390
+ - TypeScript 5.3+.
391
+ - Works alongside other Hapi plugins (e.g. `@hapi/auth-jwt2`, `@hapi/inert`).
392
+
393
+ ---
45
394
 
46
- ## Author
395
+ ## License & Author
47
396
 
48
- Debanjan Dasgupta
397
+ MIT — © Debanjan Dasgupta. See the [root README](../../README.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nlite/logger-hapi",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "logging",
@@ -32,7 +32,7 @@
32
32
  "@hapi/hapi": ">=20.0.0"
33
33
  },
34
34
  "dependencies": {
35
- "@nlite/logger-core": "file:../sdk-core"
35
+ "@nlite/logger-core": "^1.0.2"
36
36
  },
37
37
  "author": "Debanjan Dasgupta",
38
38
  "devDependencies": {