@udibo/oauth2 0.40.1 → 0.41.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.
@@ -1,8 +1,3 @@
1
- /**
2
- * Request logging that removes query values, including OAuth callback credentials.
3
- *
4
- * @module hono/log
5
- */
6
1
  import type { MiddlewareHandler } from "hono";
7
2
  /**
8
3
  * Produces a request target with every query value replaced by `[redacted]`.
@@ -26,14 +21,24 @@ import type { MiddlewareHandler } from "hono";
26
21
  */
27
22
  export declare function redactedRequestTarget(url: string | URL): string;
28
23
  /**
29
- * Logs Hono requests and responses without any query values. Mount before
30
- * authentication routes so OAuth callback codes and state stay out of logs.
31
- * Logs method, redacted target, response status, and elapsed milliseconds;
32
- * does not log headers or bodies. Paths and parameter names remain visible.
24
+ * Logs each Hono request as one line, written when the response is ready,
25
+ * without any query values. Mount before authentication routes so OAuth
26
+ * callback codes and state stay out of logs. Logs method, redacted target,
27
+ * response status and elapsed milliseconds; does not log headers or bodies.
28
+ * Paths and parameter names remain visible. A request whose handler throws
29
+ * something Hono does not answer (a non-`Error`) is still logged, as `500`.
30
+ *
31
+ * A request this middleware sees while it is handling another one (the app
32
+ * calling its own `fetch` from a handler or a server-rendered page's loader) is
33
+ * folded into the outer request's line, in brackets, in completion order,
34
+ * instead of writing its own. One that completes after the outer response was
35
+ * ready gets its own line, marked `(in-process)`; that includes a request
36
+ * started later from a timer or promise the outer request created. Each
37
+ * `requestLogger()` call folds only the requests it sees itself, so a sub-app
38
+ * with its own logger writes to its own sink.
33
39
  *
34
40
  * @param print Receives each log line; defaults to `console.log`, resolved per line.
35
- * @returns Middleware emitting a request line and, after downstream completion,
36
- * a response line.
41
+ * @returns Middleware emitting one line per request after downstream completion.
37
42
  *
38
43
  * @example
39
44
  * ```ts
@@ -43,6 +48,7 @@ export declare function redactedRequestTarget(url: string | URL): string;
43
48
  * const app = new Hono();
44
49
  * app.use(requestLogger());
45
50
  * app.get("/", (c) => c.text("Hello"));
51
+ * // --> GET /?code=[redacted] 200 3ms
46
52
  * ```
47
53
  */
48
54
  export declare function requestLogger(print?: (line: string) => void): MiddlewareHandler;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Request logging that removes query values, including OAuth callback credentials.
3
+ *
4
+ * @module hono/log
5
+ */
6
+ import { AsyncLocalStorage } from "node:async_hooks";
1
7
  /**
2
8
  * Produces a request target with every query value replaced by `[redacted]`.
3
9
  * Keeps the pathname and percent-encoded parameter names, including repeats,
@@ -28,15 +34,28 @@ export function redactedRequestTarget(url) {
28
34
  .join("&");
29
35
  return `${pathname}?${query}`;
30
36
  }
37
+ function summary(method, url, status, started) {
38
+ return `${method} ${redactedRequestTarget(url)} ${status} ${Date.now() - started}ms`;
39
+ }
31
40
  /**
32
- * Logs Hono requests and responses without any query values. Mount before
33
- * authentication routes so OAuth callback codes and state stay out of logs.
34
- * Logs method, redacted target, response status, and elapsed milliseconds;
35
- * does not log headers or bodies. Paths and parameter names remain visible.
41
+ * Logs each Hono request as one line, written when the response is ready,
42
+ * without any query values. Mount before authentication routes so OAuth
43
+ * callback codes and state stay out of logs. Logs method, redacted target,
44
+ * response status and elapsed milliseconds; does not log headers or bodies.
45
+ * Paths and parameter names remain visible. A request whose handler throws
46
+ * something Hono does not answer (a non-`Error`) is still logged, as `500`.
47
+ *
48
+ * A request this middleware sees while it is handling another one (the app
49
+ * calling its own `fetch` from a handler or a server-rendered page's loader) is
50
+ * folded into the outer request's line, in brackets, in completion order,
51
+ * instead of writing its own. One that completes after the outer response was
52
+ * ready gets its own line, marked `(in-process)`; that includes a request
53
+ * started later from a timer or promise the outer request created. Each
54
+ * `requestLogger()` call folds only the requests it sees itself, so a sub-app
55
+ * with its own logger writes to its own sink.
36
56
  *
37
57
  * @param print Receives each log line; defaults to `console.log`, resolved per line.
38
- * @returns Middleware emitting a request line and, after downstream completion,
39
- * a response line.
58
+ * @returns Middleware emitting one line per request after downstream completion.
40
59
  *
41
60
  * @example
42
61
  * ```ts
@@ -46,15 +65,39 @@ export function redactedRequestTarget(url) {
46
65
  * const app = new Hono();
47
66
  * app.use(requestLogger());
48
67
  * app.get("/", (c) => c.text("Hello"));
68
+ * // --> GET /?code=[redacted] 200 3ms
49
69
  * ```
50
70
  */
51
71
  export function requestLogger(print) {
52
72
  const emit = print ?? ((line) => console.log(line));
73
+ const outerRequest = new AsyncLocalStorage();
53
74
  return async (c, next) => {
54
- const target = redactedRequestTarget(c.req.url);
55
- emit(`<-- ${c.req.method} ${target}`);
56
- const start = Date.now();
57
- await next();
58
- emit(`--> ${c.req.method} ${target} ${c.res.status} ${Date.now() - start}ms`);
75
+ const started = Date.now();
76
+ const outer = outerRequest.getStore();
77
+ let status = 500;
78
+ if (outer) {
79
+ try {
80
+ await next();
81
+ status = c.res.status;
82
+ }
83
+ finally {
84
+ const call = summary(c.req.method, c.req.url, status, started);
85
+ if (outer.answered)
86
+ emit(`--> (in-process) ${call}`);
87
+ else
88
+ outer.calls.push(call);
89
+ }
90
+ return;
91
+ }
92
+ const request = { calls: [], answered: false };
93
+ try {
94
+ await outerRequest.run(request, next);
95
+ status = c.res.status;
96
+ }
97
+ finally {
98
+ request.answered = true;
99
+ const calls = request.calls.length === 0 ? "" : ` [${request.calls.join(", ")}]`;
100
+ emit(`--> ${summary(c.req.method, c.req.url, status, started)}${calls}`);
101
+ }
59
102
  };
60
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udibo/oauth2",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "description": "A flexible OAuth2 toolkit: framework-agnostic authorization & resource servers, a typed client, Hono adapters (including a backend-for-frontend), and React bindings.",
5
5
  "keywords": [
6
6
  "authorization-server",