Skip to content

Commit 9502de2

Browse files
authored
Merge pull request #115 from bufferings/work
Refactor logging system and add plugin logger support
2 parents 74d08d3 + 00df8e8 commit 9502de2

36 files changed

+2265
-555
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
'@korix/kori': patch
3+
'@korix/body-limit-plugin': patch
4+
'@korix/cors-plugin': patch
5+
'@korix/file-plugin-nodejs': patch
6+
'@korix/security-headers-plugin': patch
7+
'@korix/nodejs-adapter': patch
8+
---
9+
10+
Refactor logging system and add plugin logger support
11+
12+
This release significantly improves the logging infrastructure with better plugin integration and simplified API design.
13+
14+
**Breaking Changes in @korix/kori:**
15+
16+
The following context methods have been removed and replaced with standalone functions:
17+
18+
- `ctx.createSysLogger()``createSystemLogger({ baseLogger: ctx.log() })`
19+
- `ctx.createPluginLogger(name)``createPluginLogger({ baseLogger: ctx.log(), pluginName: name })`
20+
21+
**New Features:**
22+
23+
- Add `createPluginLogger()` function for better plugin log organization
24+
- Add `createSystemLogger()` function for framework internal logging
25+
- Plugin loggers automatically namespace logs under `plugin.{pluginName}` channels
26+
- Simplified logging system with removal of complex lazy initialization
27+
28+
**Improvements:**
29+
30+
- All official plugins now use dedicated plugin loggers for better debugging
31+
- Enhanced plugin logging documentation with comprehensive examples
32+
- Streamlined context logger implementation and test coverage
33+
- Better error handling and serialization in logging infrastructure
34+
35+
**Migration Guide:**
36+
37+
```typescript
38+
// Before
39+
const sysLog = ctx.createSysLogger();
40+
const pluginLog = ctx.createPluginLogger('my-plugin');
41+
42+
// After
43+
import { createSystemLogger, createPluginLogger } from '@korix/kori';
44+
45+
const sysLog = createSystemLogger({ baseLogger: ctx.log() });
46+
const pluginLog = createPluginLogger({
47+
baseLogger: ctx.log(),
48+
pluginName: 'my-plugin',
49+
});
50+
```
51+
52+
All official plugins and adapters have been updated to use the new logging API internally, but their public APIs remain unchanged.

.cursor/rules/tsdoc.mdc

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ Guidelines for writing comments and JSDoc in the Kori project.
1616

1717
## Public API
1818

19-
### ✅ Required
20-
21-
- Type parameter descriptions (`@template`)
22-
- Parameter descriptions (`@param`)
23-
- Return value descriptions (`@returns`)
24-
- Usage examples (`@example`)
25-
2619
````typescript
2720
/**
2821
* Handler context provides access to environment, request, response, and utilities
@@ -54,6 +47,29 @@ export type KoriHandlerContext<Env, Req, Res> = {
5447
};
5548
````
5649

50+
### ✅ Required
51+
52+
- Type parameter descriptions (`@template`)
53+
- Parameter descriptions (`@param`)
54+
- Return value descriptions (`@returns`)
55+
56+
### 📝 Recommended When Helpful
57+
58+
- Usage examples (`@example`) - when the usage is not obvious from the function signature
59+
60+
#### When to Include Examples
61+
62+
- Complex APIs with multiple options or chaining methods
63+
- Non-obvious usage patterns or parameter combinations
64+
- Functions where type information alone doesn't clarify intended usage
65+
- APIs that have common misconceptions or edge cases
66+
67+
#### When Examples Can Be Omitted
68+
69+
- Simple, self-explanatory functions (e.g., `getUserId()`, `setName(name: string)`)
70+
- Standard getters/setters with obvious behavior
71+
- Functions where usage is clear from type definitions and parameter names
72+
5773
### 📝 Highlight Important Details
5874

5975
- **Performance reasons** (mutation, caching, etc.)
@@ -80,8 +96,8 @@ const KoriRequestBrand = Symbol('kori-request');
8096

8197
```typescript
8298
/** @internal */
83-
export function createSysLogger(ctx: HandlerCtxState) {
84-
return KoriLoggerUtils.createSysLogger({
99+
export function createSystemLogger(ctx: HandlerCtxState) {
100+
return loggerHelpers.createSystemLogger({
85101
logger: getLoggerInternal(ctx),
86102
});
87103
}
@@ -92,7 +108,7 @@ export function createSysLogger(ctx: HandlerCtxState) {
92108
```typescript
93109
/** @internal Caches logger per request for performance */
94110
export function getLoggerInternal(ctx: HandlerCtxState): KoriLogger {
95-
ctx.loggerCache ??= KoriLoggerUtils.createRequestLogger(ctx.loggerFactory);
111+
ctx.loggerCache ??= loggerHelpers.createRequestLogger(ctx.loggerFactory);
96112
return ctx.loggerCache;
97113
}
98114
```

docs/en/guide/logging.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,37 @@ app.get('/profile', (ctx) => {
161161
```
162162

163163
The metadata function is executed lazily - only when the log level is enabled, avoiding unnecessary computation.
164+
165+
## Plugin Development
166+
167+
When developing plugins, use `createPluginLogger()` for better log organization:
168+
169+
```typescript
170+
export function myPlugin<
171+
Env extends KoriEnvironment,
172+
Req extends KoriRequest,
173+
Res extends KoriResponse,
174+
>(): KoriPlugin<Env, Req, Res> {
175+
return defineKoriPlugin({
176+
name: 'my-plugin',
177+
version: '0.0.0',
178+
apply(kori) {
179+
const log = createPluginLogger({
180+
baseLogger: kori.log(),
181+
pluginName: 'my-plugin',
182+
});
183+
log.info('Plugin initialized');
184+
185+
return kori.onRequest((ctx) => {
186+
const requestLog = createPluginLogger({
187+
baseLogger: ctx.log(),
188+
pluginName: 'my-plugin',
189+
});
190+
requestLog.info('Processing request');
191+
});
192+
},
193+
});
194+
}
195+
```
196+
197+
Plugin loggers automatically namespace your logs under `plugin.{pluginName}` channel for better organization and debugging.

docs/en/guide/plugins.md

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,39 +34,53 @@ For example, a logging plugin might use multiple hooks:
3434
```typescript
3535
import {
3636
defineKoriPlugin,
37+
createPluginLogger,
3738
type KoriEnvironment,
3839
type KoriRequest,
3940
type KoriResponse,
4041
type KoriPlugin,
4142
} from '@korix/kori';
4243

43-
// Simple logging plugin structure
44+
// Better logging plugin with dedicated logger
4445
export function loggingPlugin<
4546
Env extends KoriEnvironment,
4647
Req extends KoriRequest,
4748
Res extends KoriResponse,
4849
>(): KoriPlugin<Env, Req, Res, unknown, { startTime: number }, unknown> {
4950
return defineKoriPlugin({
50-
name: 'simple-logging',
51-
apply: (kori) =>
52-
kori.onRequest((ctx) => {
51+
name: 'request-logging',
52+
apply: (kori) => {
53+
const log = createPluginLogger({
54+
baseLogger: kori.log(),
55+
pluginName: 'request-logging',
56+
});
57+
58+
log.info('Request logging plugin initialized');
59+
60+
return kori.onRequest((ctx) => {
61+
const requestLog = createPluginLogger({
62+
baseLogger: ctx.log(),
63+
pluginName: 'request-logging',
64+
});
65+
5366
// Log when request starts
54-
ctx.log().info('Request started', {
67+
requestLog.info('Request started', {
5568
method: ctx.req.method(),
5669
path: ctx.req.url().pathname,
5770
});
5871

5972
// Defer response logging
6073
ctx.defer(() => {
6174
const duration = Date.now() - ctx.req.startTime;
62-
ctx.log().info('Request completed', {
75+
requestLog.info('Request completed', {
6376
status: ctx.res.getStatus(),
6477
duration: `${duration}ms`,
6578
});
6679
});
6780

6881
return ctx.withReq({ startTime: Date.now() });
69-
}),
82+
});
83+
},
7084
});
7185
}
7286
```
@@ -237,6 +251,95 @@ const requestIdPlugin = <
237251
});
238252
```
239253

254+
## Plugin Logging
255+
256+
Plugins should use dedicated loggers to provide better organization and debugging capabilities. Use `createPluginLogger()` to create plugin-specific loggers that are automatically namespaced.
257+
258+
### Plugin-Specific Loggers
259+
260+
```typescript
261+
import {
262+
defineKoriPlugin,
263+
createPluginLogger,
264+
type KoriEnvironment,
265+
type KoriRequest,
266+
type KoriResponse,
267+
type KoriPlugin,
268+
} from '@korix/kori';
269+
270+
export function authPlugin<
271+
Env extends KoriEnvironment,
272+
Req extends KoriRequest,
273+
Res extends KoriResponse,
274+
>(): KoriPlugin<Env, Req, Res> {
275+
return defineKoriPlugin({
276+
name: 'auth',
277+
apply: (kori) => {
278+
// Instance-level plugin logger
279+
const log = createPluginLogger({
280+
baseLogger: kori.log(),
281+
pluginName: 'auth',
282+
});
283+
284+
log.info('Auth plugin initialized');
285+
286+
return kori.onRequest((ctx) => {
287+
// Request-level plugin logger
288+
const requestLog = createPluginLogger({
289+
baseLogger: ctx.log(),
290+
pluginName: 'auth',
291+
});
292+
293+
requestLog.info('Checking authentication', {
294+
path: ctx.req.url().pathname,
295+
});
296+
297+
// Your authentication logic here...
298+
});
299+
},
300+
});
301+
}
302+
```
303+
304+
### Benefits of Plugin Loggers
305+
306+
Plugin-specific loggers provide several advantages:
307+
308+
- **Namespace isolation**: Logs are automatically prefixed with `plugin.{pluginName}`
309+
- **Better debugging**: Easy to filter logs by specific plugins
310+
- **Consistent formatting**: Inherits all bindings from the base logger
311+
- **Channel separation**: Plugin logs use dedicated channels for organization
312+
313+
### Logger Output
314+
315+
Plugin loggers automatically namespace their output:
316+
317+
```json
318+
{
319+
"time": 1754201824386,
320+
"level": "info",
321+
"channel": "plugin.auth",
322+
"name": "request",
323+
"message": "Checking authentication",
324+
"meta": {
325+
"path": "/api/users"
326+
}
327+
}
328+
```
329+
330+
Compare this with regular context logging:
331+
332+
```json
333+
{
334+
"time": 1754201824386,
335+
"level": "info",
336+
"channel": "app",
337+
"name": "request",
338+
"message": "Processing request",
339+
"meta": {}
340+
}
341+
```
342+
240343
## Official Plugins
241344

242345
Kori provides official plugins for common use cases. See the Extensions section for detailed documentation.

packages/body-limit-plugin/src/body-limit-plugin.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type KoriHandlerContext,
88
HttpStatus,
99
type KoriLogger,
10+
createPluginLogger,
1011
} from '@korix/kori';
1112

1213
import { PLUGIN_VERSION } from './version.js';
@@ -79,11 +80,9 @@ function isValidContentLength(value: string): boolean {
7980
return false;
8081
}
8182

82-
// Ensure parsed value exactly matches original string (catches leading zeros issues)
83-
if (parsed.toString() !== value) {
84-
return false;
85-
}
86-
83+
// Allow leading zeros as per RFC 7230/9110 ABNF (1*DIGIT)
84+
// While RFC 9110 5.6 recommends minimal decimal form,
85+
// rejecting syntactically valid values may cause interoperability issues
8786
return true;
8887
}
8988

@@ -190,14 +189,14 @@ export function bodyLimitPlugin<Env extends KoriEnvironment, Req extends KoriReq
190189
version: PLUGIN_VERSION,
191190
apply: (kori) => {
192191
// Instance-level logger for plugin initialization
193-
const log = kori.log().channel(PLUGIN_NAME);
192+
const log = createPluginLogger({ baseLogger: kori.log(), pluginName: PLUGIN_NAME });
194193
log.info(`Plugin initialized with max size: ${maxSize} bytes`);
195194

196195
// Setup request monitoring for chunked transfer encoding and error handling
197196
return kori
198197
.onRequest((ctx) => {
199198
const { req, res } = ctx;
200-
const requestLog = ctx.createPluginLogger(PLUGIN_NAME);
199+
const requestLog = createPluginLogger({ baseLogger: ctx.log(), pluginName: PLUGIN_NAME });
201200

202201
// Skip methods that don't typically have bodies
203202
if (!METHODS_WITH_BODY.has(req.method())) {
@@ -295,7 +294,7 @@ export function bodyLimitPlugin<Env extends KoriEnvironment, Req extends KoriReq
295294
.onError((ctx, error) => {
296295
if (error instanceof BodySizeLimitError) {
297296
const { req } = ctx;
298-
const requestLog = ctx.log().channel(PLUGIN_NAME);
297+
const requestLog = createPluginLogger({ baseLogger: ctx.log(), pluginName: PLUGIN_NAME });
299298
const xForwardedFor = req.headers()['x-forwarded-for']?.trim();
300299

301300
requestLog.warn('Request body size exceeds limit', {

packages/cors-plugin/src/cors-plugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
HttpStatus,
88
type KoriHandlerContext,
99
type KoriLogger,
10+
createPluginLogger,
1011
} from '@korix/kori';
1112

1213
import { type CorsPluginOptions } from './cors-plugin-options.js';
@@ -155,7 +156,7 @@ export function corsPlugin<Env extends KoriEnvironment, Req extends KoriRequest,
155156
name: PLUGIN_NAME,
156157
version: PLUGIN_VERSION,
157158
apply: (kori) => {
158-
const log = kori.createPluginLogger(PLUGIN_NAME);
159+
const log = createPluginLogger({ baseLogger: kori.log(), pluginName: PLUGIN_NAME });
159160
validateCorsOptions(log, options);
160161

161162
log.info('CORS plugin initialized', {

packages/file-plugin-nodejs/src/send-file-plugin/send-file-plugin.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineKoriPlugin } from '@korix/kori';
1+
import { defineKoriPlugin, createPluginLogger } from '@korix/kori';
22
import { type KoriEnvironment, type KoriPlugin, type KoriRequest, type KoriResponse } from '@korix/kori';
33

44
import { PLUGIN_VERSION } from '../version/index.js';
@@ -25,11 +25,11 @@ export function sendFilePlugin<Env extends KoriEnvironment, Req extends KoriRequ
2525
name: PLUGIN_NAME,
2626
version: PLUGIN_VERSION,
2727
apply: (kori) => {
28-
const log = kori.createPluginLogger(PLUGIN_NAME);
28+
const log = createPluginLogger({ baseLogger: kori.log(), pluginName: PLUGIN_NAME });
2929
log.info('Send file plugin initialized', { root });
3030

3131
return kori.onRequest((ctx) => {
32-
const requestLog = ctx.createPluginLogger(PLUGIN_NAME);
32+
const requestLog = createPluginLogger({ baseLogger: ctx.log(), pluginName: PLUGIN_NAME });
3333

3434
const sendFile = (filePath: string, sendFileOptions?: SendFileOptions) =>
3535
handleFileResponse({

0 commit comments

Comments
 (0)