bazis 0.97.0 → 0.97.1
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.
package/core/http/HttpServer.ts
CHANGED
|
@@ -150,8 +150,15 @@ export class HttpServer implements HostedService {
|
|
|
150
150
|
if (this.options.securityHeaders !== false) {
|
|
151
151
|
serverChain.push(securityHeaders(this.options.securityHeaders ?? {}));
|
|
152
152
|
}
|
|
153
|
+
// Unexpected errors go to the application logger, like the access log.
|
|
154
|
+
const errorLogger = this.resolver.tryResolve(LOGGER);
|
|
153
155
|
serverChain.push(
|
|
154
|
-
errorHandler({
|
|
156
|
+
errorHandler({
|
|
157
|
+
exposeDetails,
|
|
158
|
+
...(errorLogger ? { logger: errorLogger } : {}),
|
|
159
|
+
...this.options.errorHandler,
|
|
160
|
+
onUnexpectedError: this.collectErrorHook(),
|
|
161
|
+
}),
|
|
155
162
|
);
|
|
156
163
|
this.shortCircuitChain = [...serverChain];
|
|
157
164
|
serverChain.push(...this.collectServerMiddleware());
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { REQUEST_ID_STATE_KEY, type Logger } from "../../kernel";
|
|
2
|
+
import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
|
|
2
3
|
import type { HttpContext } from "../HttpContext/HttpContext";
|
|
3
4
|
import { HttpError } from "../Errors/HttpError";
|
|
4
5
|
import type { HttpMiddleware } from "./types";
|
|
@@ -9,10 +10,20 @@ export interface ErrorHandlerOptions {
|
|
|
9
10
|
* never enable in production).
|
|
10
11
|
*/
|
|
11
12
|
exposeDetails?: boolean;
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Notified of every non-{@link HttpError} failure, for example to send it to
|
|
15
|
+
* an error tracker; `HTTP_ERROR_HOOK` registrations are merged in. It does not
|
|
16
|
+
* replace logging: the error is logged as well.
|
|
17
|
+
*/
|
|
13
18
|
onUnexpectedError?: (ctx: HttpContext, error: unknown) => void;
|
|
14
|
-
/**
|
|
19
|
+
/** Replaces the built-in logging of unexpected errors. */
|
|
15
20
|
logError?: (error: unknown) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Logger for unexpected errors when {@link logError} is absent: one `error`
|
|
23
|
+
* line with the method, path, request id and the redacted error. The server
|
|
24
|
+
* passes the application `LOGGER`; without either, `console.error` is used.
|
|
25
|
+
*/
|
|
26
|
+
logger?: Logger;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
function jsonResponse(status: number, body: unknown, extraHeaders?: readonly [string, string][]): Response {
|
|
@@ -33,9 +44,30 @@ function safeJsonResponse(status: number, body: unknown, extraHeaders?: readonly
|
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
47
|
+
type ErrorLog = (ctx: HttpContext, error: unknown) => void;
|
|
48
|
+
|
|
49
|
+
function resolveErrorLog(options: ErrorHandlerOptions): ErrorLog {
|
|
50
|
+
const { logError, logger } = options;
|
|
51
|
+
if (logError) {
|
|
52
|
+
return (_ctx, error) => logError(error);
|
|
53
|
+
}
|
|
54
|
+
if (logger) {
|
|
55
|
+
return (ctx, error) => {
|
|
56
|
+
const requestId = ctx.state.get(REQUEST_ID_STATE_KEY);
|
|
57
|
+
logger.error(`${ctx.method} ${ctx.path} failed`, {
|
|
58
|
+
method: ctx.method,
|
|
59
|
+
path: ctx.path,
|
|
60
|
+
...(typeof requestId === "string" ? { requestId } : {}),
|
|
61
|
+
error: redactSensitive(error),
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return (_ctx, error) => console.error("[http] unhandled error:", redactSensitive(error));
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
function safelyReportUnexpected(
|
|
37
69
|
options: ErrorHandlerOptions,
|
|
38
|
-
|
|
70
|
+
log: ErrorLog,
|
|
39
71
|
ctx: HttpContext,
|
|
40
72
|
error: unknown,
|
|
41
73
|
): void {
|
|
@@ -43,13 +75,13 @@ function safelyReportUnexpected(
|
|
|
43
75
|
options.onUnexpectedError?.(ctx, error);
|
|
44
76
|
} catch (hookError) {
|
|
45
77
|
try {
|
|
46
|
-
|
|
78
|
+
log(ctx, hookError);
|
|
47
79
|
} catch {
|
|
48
80
|
// Logging hooks are best-effort; response safety is primary.
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
83
|
try {
|
|
52
|
-
|
|
84
|
+
log(ctx, error);
|
|
53
85
|
} catch {
|
|
54
86
|
// A broken sink must not escape the HTTP error boundary.
|
|
55
87
|
}
|
|
@@ -61,7 +93,7 @@ function safelyReportUnexpected(
|
|
|
61
93
|
* else becomes 500 Internal Server Error.
|
|
62
94
|
*/
|
|
63
95
|
export function errorHandler(options: ErrorHandlerOptions = {}): HttpMiddleware {
|
|
64
|
-
const
|
|
96
|
+
const log = resolveErrorLog(options);
|
|
65
97
|
return async (ctx, next) => {
|
|
66
98
|
try {
|
|
67
99
|
await next();
|
|
@@ -87,12 +119,14 @@ export function errorHandler(options: ErrorHandlerOptions = {}): HttpMiddleware
|
|
|
87
119
|
);
|
|
88
120
|
return;
|
|
89
121
|
}
|
|
90
|
-
safelyReportUnexpected(options,
|
|
122
|
+
safelyReportUnexpected(options, log, ctx, error);
|
|
91
123
|
ctx.response = options.exposeDetails
|
|
92
124
|
? safeJsonResponse(500, {
|
|
93
125
|
error: "Internal Server Error",
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
// Development only, but redacted like the log: a secret in an error
|
|
127
|
+
// message must not reach a browser or a shared screenshot.
|
|
128
|
+
message: redactSensitiveText(error instanceof Error ? error.message : String(error)),
|
|
129
|
+
stack: error instanceof Error && error.stack !== undefined ? redactSensitiveText(error.stack) : undefined,
|
|
96
130
|
})
|
|
97
131
|
: safeJsonResponse(500, { error: "Internal Server Error" });
|
|
98
132
|
}
|
package/core/http/SPEC.md
CHANGED
|
@@ -405,9 +405,10 @@ Exported from `bazis/core/http`. They are connected through `@Middleware(...)`,
|
|
|
405
405
|
|
|
406
406
|
| `ErrorHandlerOptions` field | Type | Default | Description |
|
|
407
407
|
| --- | --- | --- | --- |
|
|
408
|
-
| `exposeDetails` | `boolean` | `httpModule({ exposeErrorDetails })` / environment | message/stack in 500 responses; never enable in production |
|
|
409
|
-
| `onUnexpectedError` | `(ctx, error) => void` | DI `HTTP_ERROR_HOOK` hooks |
|
|
410
|
-
| `logError` | `(error) => void` |
|
|
408
|
+
| `exposeDetails` | `boolean` | `httpModule({ exposeErrorDetails })` / environment | redacted message/stack in 500 responses; never enable in production |
|
|
409
|
+
| `onUnexpectedError` | `(ctx, error) => void` | DI `HTTP_ERROR_HOOK` hooks | notification for non-`HttpError` failures (e.g. an error tracker); the error is still logged |
|
|
410
|
+
| `logError` | `(error) => void` | — | replaces the built-in logging of unexpected errors |
|
|
411
|
+
| `logger` | `Logger` | the application `LOGGER` | one `error` line: `"<METHOD> <path> failed"` with `method`, `path`, `requestId` and the redacted `error`; `console.error` when there is no logger |
|
|
411
412
|
|
|
412
413
|
`HttpError` → JSON with its status; anything else → 500.
|
|
413
414
|
|
|
@@ -31,8 +31,8 @@ export const ROUTE_MIDDLEWARE_COMPOSER = createToken<RouteMiddlewareComposerRegi
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Hook invoked by the built-in error boundary on a non-{@link HttpError}
|
|
34
|
-
* failure
|
|
35
|
-
* error
|
|
34
|
+
* failure, for example to report it to an error tracker. It is a notification:
|
|
35
|
+
* the error is still logged and answered with 500. Collected via `resolveAll`.
|
|
36
36
|
*/
|
|
37
37
|
export type HttpErrorHook = (ctx: HttpContext, error: unknown) => void;
|
|
38
38
|
|
|
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
|
|
|
232
232
|
if (method === "account/updated") this.modelCache = undefined;
|
|
233
233
|
}, () => { this.clearLogin(); this.modelCache = undefined; });
|
|
234
234
|
const initialized = await client.request("initialize", {
|
|
235
|
-
clientInfo: { name: "bazis", title: "Bazis", version: "0.97.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.97.1" }, capabilities: { experimentalApi: true },
|
|
236
236
|
}, 15_000, signal);
|
|
237
237
|
if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
|
|
238
238
|
client.notify("initialized");
|
package/package.json
CHANGED