diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 1b6a356..57eee9b 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,9 +1,29 @@
-
-
-
-
+
+
+
+
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index 728f2aa..5efda25 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,8 @@ Start logging with `console.error`, `console.warn`, `console.log`,
# App Server Support
-Preconfigured support for ExpressJS and Fresh V1 provides the following:
+Preconfigured support for ExpressJS, Fresh V1, Hono, and SolidStart provides the
+following:
- Metrics (Request and Logger)
- Log Levels
@@ -73,6 +74,33 @@ export default defineConfig({
});
```
+## Hono Configuration
+
+```typescript
+import { Hono } from "hono";
+import { honoLoggerMiddleware, initLogger } from "@daringway/logger";
+
+initLogger();
+
+const app = new Hono();
+app.use("*", honoLoggerMiddleware());
+```
+
+## SolidStart Configuration
+
+In a SolidStart middleware file:
+
+```typescript
+import { createMiddleware } from "@solidjs/start/middleware";
+import { initLogger, solidStartLoggerMiddleware } from "@daringway/logger";
+
+initLogger();
+
+export default createMiddleware({
+ onRequest: solidStartLoggerMiddleware(),
+});
+```
+
## Request Context Information
```typescript
@@ -110,7 +138,8 @@ WebStorm testing. Default: false
LOG_PRETTY: boolean Enable pretty printing of logs for development. Use for CLI
testing. Default: false
-LOG_WITH_CONSOLE: boolean Enable logging to console. Default: false, writes to STDOUT
+LOG_WITH_CONSOLE: boolean Enable logging to console. Default: false, writes to
+STDOUT
# Log Levels Explained
diff --git a/agents.md b/agents.md
new file mode 100644
index 0000000..623945a
--- /dev/null
+++ b/agents.md
@@ -0,0 +1,33 @@
+# Agents Reference Table
+
+Use this file as the quick index. Follow each `Details` link for full behavior,
+caveats, and source mapping.
+
+| Area | Setting | Default | Override Path | Details |
+| ------------- | ---------------------------------------------- | ----------------------: | ---------------------------------------------- | ---------------------------------------------------------------------------- |
+| Logger | `logLevel` | `"log"` | `initLogger`, `LOG_LEVEL` | [details](docs/settings.md#loglevel) |
+| Logger | `logSecondsBetweenMetrics` | `500` | `initLogger`, `LOG_SECONDS_BETWEEN_METRICS` | [details](docs/settings.md#logsecondsbetweenmetrics) |
+| Logger | `logPriorityThresholdBytes` | `1048576` | `initLogger`, `LOG_PRIORITY_THRESHOLD_BYTES` | [details](docs/settings.md#logprioritythresholdbytes) |
+| Logger | `logMeta` | `null` | `initLogger` | [details](docs/settings.md#logmeta) |
+| Logger | `logObjects` | `false` | `initLogger`, `LOG_OBJECTS` | [details](docs/settings.md#logobjects) |
+| Logger | `logPretty` | `false` | `initLogger`, `LOG_PRETTY` | [details](docs/settings.md#logpretty) |
+| Logger | `logWithConsole` | `false` | `initLogger`, `LOG_WITH_CONSOLE` | [details](docs/settings.md#logwithconsole) |
+| Logger | `silentInit` | `false` | `initLogger` | [details](docs/settings.md#silentinit) |
+| Env Var | `LOG_LEVEL` | n/a | environment | [details](docs/settings.md#log_level) |
+| Env Var | `LOG_SECONDS_BETWEEN_METRICS` | n/a | environment | [details](docs/settings.md#log_seconds_between_metrics) |
+| Env Var | `LOG_PRIORITY_THRESHOLD_BYTES` | n/a | environment | [details](docs/settings.md#log_priority_threshold_bytes) |
+| Env Var | `LOG_OBJECTS` | n/a | environment | [details](docs/settings.md#log_objects) |
+| Env Var | `LOG_PRETTY` | n/a | environment | [details](docs/settings.md#log_pretty) |
+| Env Var | `LOG_WITH_CONSOLE` | n/a | environment | [details](docs/settings.md#log_with_console) |
+| Express | `doNotLogURLs` | unset | `expressLoggerMiddleware({ doNotLogURLs })` | [details](docs/settings.md#donotlogurls-express) |
+| Fresh | `setCookies` | unset | `freshV1LoggerPlugin({ setCookies })` | [details](docs/settings.md#setcookies-fresh) |
+| Fresh | `doNotLogURLs` | unset | `freshV1LoggerPlugin({ doNotLogURLs })` | [details](docs/settings.md#donotlogurls-fresh) |
+| Hono | `doNotLogURLs` | unset | `honoLoggerMiddleware({ doNotLogURLs })` | [details](docs/settings.md#donotlogurls-hono) |
+| SolidStart | `doNotLogURLs` | unset | `solidStartLoggerMiddleware({ doNotLogURLs })` | [details](docs/settings.md#donotlogurls-solidstart) |
+| Context Input | `x-request-id` | generated | request header/cookie | [details](docs/settings.md#x-request-id) |
+| Context Input | `x-trace-path` | derived | request header | [details](docs/settings.md#x-trace-path) |
+| Context Input | `x-correlation-id` / `correlationId` | `"unknown"` | request header/cookie | [details](docs/settings.md#x-correlation-id--correlationid-cookie) |
+| Context Input | `authorization` / `session` | `"unknown"` | request header/cookie | [details](docs/settings.md#authorization--session-cookie) |
+| Context Input | `x-application-name` / `applicationName` | `"unknown"`/`"postman"` | request header/cookie | [details](docs/settings.md#x-application-name--applicationname-cookie) |
+| Context Input | `x-application-version` / `applicationVersion` | `"unknown"` | request header/cookie | [details](docs/settings.md#x-application-version--applicationversion-cookie) |
+| Context Input | `user-agent` | `"unknown"` | request header | [details](docs/settings.md#user-agent) |
diff --git a/deno.json b/deno.json
index a86a493..eb08be4 100644
--- a/deno.json
+++ b/deno.json
@@ -1,6 +1,6 @@
{
"name": "@daringway/logger",
- "version": "0.0.8",
+ "version": "0.0.9",
"license": "MIT",
"description": "A high-performance, drop-in replacement for console.log() that outputs structured JSON logs and metrics to STDOUT. Built for both Deno and Node.js, it delivers blazing-fast, non-blocking logging with optional request context, log levels, and ExpressJS/Fresh V1 integration—making it ideal for production apps that need speed, consistency, and easy observability.",
"exports": "./mod.ts",
diff --git a/docs/settings.md b/docs/settings.md
new file mode 100644
index 0000000..7107cac
--- /dev/null
+++ b/docs/settings.md
@@ -0,0 +1,229 @@
+# Settings Reference
+
+This document is the detailed companion for [`agents.md`](../agents.md).
+
+## Configuration Precedence
+
+For logger config (`initLogger`), values are resolved in this order:
+
+1. Environment variable (if available for that setting)
+2. `initLogger({...})` argument
+3. Existing in-memory `logConfig`
+4. Built-in defaults
+
+## Logger Settings (`initLogger`)
+
+### logLevel
+
+- Scope: logger
+- Type: `"metrics" | "error" | "warn" | "info" | "log" | "debug" | "trace"`
+- Default: `"log"`
+- Env override: `LOG_LEVEL`
+- Notes: controls which console methods are emitted (`error` and `metrics`
+ always emit)
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logSecondsBetweenMetrics
+
+- Scope: logger
+- Type: number (`>= 0`)
+- Default: `500`
+- Env override: `LOG_SECONDS_BETWEEN_METRICS`
+- Notes: `0` disables periodic metrics writes
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logPriorityThresholdBytes
+
+- Scope: logger
+- Type: number (`>= 10`)
+- Default: `1048576` (1 MiB)
+- Env override: `LOG_PRIORITY_THRESHOLD_BYTES`
+- Notes: when queue size exceeds threshold, flushing is prioritized immediately
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logMeta
+
+- Scope: logger
+- Type: `Record | null`
+- Default: `null`
+- Env override: none
+- Notes: merged into emitted `context.meta`; merged shallowly on updates
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logObjects
+
+- Scope: logger
+- Type: boolean (`true/false/yes/no` accepted by parser)
+- Default: `false`
+- Env override: `LOG_OBJECTS`
+- Notes: logs objects directly to console; forces `logSecondsBetweenMetrics=0`
+ when enabled
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logPretty
+
+- Scope: logger
+- Type: boolean (`true/false/yes/no`)
+- Default: `false`
+- Env override: `LOG_PRETTY`
+- Notes: pretty-prints JSON logs via `console.log`
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### logWithConsole
+
+- Scope: logger
+- Type: boolean (`true/false/yes/no`)
+- Default: `false`
+- Env override: `LOG_WITH_CONSOLE`
+- Notes: bypasses queue and writes via `console.log(JSON.stringify(...))`
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+### silentInit
+
+- Scope: logger
+- Type: boolean (`true/false/yes/no`)
+- Default: `false`
+- Env override: none
+- Notes: intended to suppress init/update debug logs. Current implementation
+ does not apply `initLogger({ silentInit })` into `newConfig`, so this setting
+ is effectively always defaulted during init.
+- Source: `src/zod.ts`, `src/dare-console-logger.ts`
+
+## Environment Variables
+
+### LOG_LEVEL
+
+- Maps to: `logLevel`
+- Example: `LOG_LEVEL=info`
+- Source: `src/dare-console-logger.ts`, `README.md`
+
+### LOG_SECONDS_BETWEEN_METRICS
+
+- Maps to: `logSecondsBetweenMetrics`
+- Example: `LOG_SECONDS_BETWEEN_METRICS=60`
+- Source: `src/dare-console-logger.ts`, `README.md`
+
+### LOG_PRIORITY_THRESHOLD_BYTES
+
+- Maps to: `logPriorityThresholdBytes`
+- Example: `LOG_PRIORITY_THRESHOLD_BYTES=262144`
+- Source: `src/dare-console-logger.ts`
+
+### LOG_OBJECTS
+
+- Maps to: `logObjects`
+- Example: `LOG_OBJECTS=true`
+- Source: `src/dare-console-logger.ts`, `README.md`
+
+### LOG_PRETTY
+
+- Maps to: `logPretty`
+- Example: `LOG_PRETTY=true`
+- Source: `src/dare-console-logger.ts`, `README.md`
+
+### LOG_WITH_CONSOLE
+
+- Maps to: `logWithConsole`
+- Example: `LOG_WITH_CONSOLE=true`
+- Source: `src/dare-console-logger.ts`, `README.md`
+
+## Express Middleware Settings (`expressLoggerMiddleware`)
+
+### doNotLogURLs (express)
+
+- Scope: Express middleware
+- Type: `RegExp`
+- Default: unset
+- Notes: skip logging for matching `req.originalUrl`
+- Source: `src/express.ts`
+
+## Fresh Middleware Settings (`freshV1LoggerPlugin`)
+
+### setCookies (fresh)
+
+- Scope: Fresh middleware
+- Type: `Cookie[]`
+- Default: unset
+- Notes: additional cookies appended to response
+- Source: `src/fresh.ts`
+
+### doNotLogURLs (fresh)
+
+- Scope: Fresh middleware
+- Type: `RegExp`
+- Default: unset
+- Notes: skip logging for matching route path
+- Source: `src/fresh.ts`
+
+## Hono Middleware Settings (`honoLoggerMiddleware`)
+
+### doNotLogURLs (hono)
+
+- Scope: Hono middleware
+- Type: `RegExp`
+- Default: unset
+- Notes: skip logging for matching request path
+- Source: `src/hono.ts`
+
+## SolidStart Middleware Settings (`solidStartLoggerMiddleware`)
+
+### doNotLogURLs (solidstart)
+
+- Scope: SolidStart middleware
+- Type: `RegExp`
+- Default: unset
+- Notes: skip logging for matching request path
+- Source: `src/solidstart.ts`
+
+## Request Context Inputs (Header/Cookie Driven)
+
+### x-request-id
+
+- Scope: request context
+- Default/fallback: generated `"--"`
+- Source: `src/utils.ts`
+
+### x-trace-path
+
+- Scope: request context
+- Type: comma-separated list
+- Default/fallback: `[requestId]` when request id is generated
+- Source: `src/utils.ts`
+
+### x-correlation-id / correlationId cookie
+
+- Scope: request context
+- Default/fallback: `"unknown"`
+- Source: `src/utils.ts`
+
+### authorization / session cookie
+
+- Scope: request context
+- Notes: extracts `sessionId` from token payload when present
+- Default/fallback: `"unknown"`
+- Source: `src/utils.ts`
+
+### x-application-name / applicationName cookie
+
+- Scope: request context
+- Default/fallback: `"postman"` for Postman user-agent; otherwise `"unknown"`
+- Source: `src/utils.ts`
+
+### x-application-version / applicationVersion cookie
+
+- Scope: request context
+- Default/fallback: `"unknown"`
+- Source: `src/utils.ts`
+
+### user-agent
+
+- Scope: request context
+- Default/fallback: `"unknown"`
+- Source: `src/utils.ts`
+
+## Implementation Notes
+
+- `LoggingConfig` type in `src/zod.ts` does not currently include
+ `logWithConsole`, but runtime schema/config uses it.
+- `silentInit` exists in schema/type, but is not wired into `initLogger`'s
+ `newConfig` object.
diff --git a/mod.ts b/mod.ts
index 2ab0de4..34e2b40 100644
--- a/mod.ts
+++ b/mod.ts
@@ -6,4 +6,9 @@ export {
export { MetricsTracker } from "./src/dare-metrics.ts";
export { expressLoggerMiddleware, type ExpressOptions } from "./src/express.ts";
export { freshV1LoggerPlugin, type FreshV1Options } from "./src/fresh.ts";
+export { honoLoggerMiddleware, type HonoOptions } from "./src/hono.ts";
+export {
+ solidStartLoggerMiddleware,
+ type SolidStartOptions,
+} from "./src/solidstart.ts";
export { type LoggingConfig } from "./src/zod.ts";
diff --git a/src/dare-console-logger.ts b/src/dare-console-logger.ts
index 04354e9..3917151 100644
--- a/src/dare-console-logger.ts
+++ b/src/dare-console-logger.ts
@@ -39,14 +39,14 @@ interface LogObject {
// set defaults
const defaultValues = {
- logLevel:"log",
+ logLevel: "log",
logSecondsBetweenMetrics: 500,
logPriorityThresholdBytes: 1024 * 1024, // 1 MB
logMeta: null,
logObjects: false,
logPretty: false,
logWithConsole: false,
-}
+};
export let logConfig = baseZodLogConfig.parse(defaultValues);
let logLevelValue = logLevels[logConfig.logLevel];
@@ -195,7 +195,7 @@ const logFormatter = (
};
} else if (typeof part === "string") {
if (message) {
- message += ' ' + part;
+ message += " " + part;
} else {
message = part;
}
@@ -233,7 +233,7 @@ const logFormatter = (
};
function queueLog(logObject: LogObject) {
- if (logConfig.logPretty ) {
+ if (logConfig.logPretty) {
origLog(JSON.stringify(logObject, undefined, 2));
} else if (logConfig.logWithConsole) {
origLog(JSON.stringify(logObject));
@@ -274,12 +274,17 @@ export function initLogger(
const updates = baseZodLogConfig.partial().parse(configuration);
const newConfig = {
logLevel: process.env.LOG_LEVEL ?? updates.logLevel ?? logConfig.logLevel,
- logSecondsBetweenMetrics: process.env.LOG_SECONDS_BETWEEN_METRICS ?? updates.logSecondsBetweenMetrics ?? logConfig.logSecondsBetweenMetrics,
- logPriorityThresholdBytes: process.env.LOG_PRIORITY_THRESHOLD_BYTES ?? updates.logPriorityThresholdBytes ?? logConfig.logPriorityThresholdBytes,
- logMeta: {...logConfig.logMeta, ...updates.logMeta},
- logObjects: process.env.LOG_OBJECTS ?? updates.logObjects ?? logConfig.logObjects,
- logPretty: process.env.LOG_PRETTY ?? updates.logPretty ?? logConfig.logPretty,
- logWithConsole: process.env.LOG_WITH_CONSOLE ?? updates.logWithConsole ?? logConfig.logWithConsole,
+ logSecondsBetweenMetrics: process.env.LOG_SECONDS_BETWEEN_METRICS ??
+ updates.logSecondsBetweenMetrics ?? logConfig.logSecondsBetweenMetrics,
+ logPriorityThresholdBytes: process.env.LOG_PRIORITY_THRESHOLD_BYTES ??
+ updates.logPriorityThresholdBytes ?? logConfig.logPriorityThresholdBytes,
+ logMeta: { ...logConfig.logMeta, ...updates.logMeta },
+ logObjects: process.env.LOG_OBJECTS ?? updates.logObjects ??
+ logConfig.logObjects,
+ logPretty: process.env.LOG_PRETTY ?? updates.logPretty ??
+ logConfig.logPretty,
+ logWithConsole: process.env.LOG_WITH_CONSOLE ?? updates.logWithConsole ??
+ logConfig.logWithConsole,
};
logConfig = baseZodLogConfig.parse(newConfig);
diff --git a/src/hono.ts b/src/hono.ts
new file mode 100644
index 0000000..91231cc
--- /dev/null
+++ b/src/hono.ts
@@ -0,0 +1,115 @@
+import { asyncLocalStorage, storeItemFromRequest } from "./utils.ts";
+
+/**
+ * Options for the Hono logger
+ * @param doNotLogURLs - a regex to match URLs that should not be logged
+ */
+export type HonoOptions = {
+ doNotLogURLs?: RegExp;
+};
+
+type HJson =
+ | string
+ | number
+ | boolean
+ | Date
+ | null
+ | { [key: string]: HJson }
+ | HJson[];
+
+type HonoLikeContext = {
+ req: {
+ raw: Request;
+ path?: string;
+ method?: string;
+ };
+ res: Response;
+ header(name: string, value: string): void;
+};
+
+type HonoLikeNext = () => Promise;
+
+function resLogData(
+ req: Request,
+ response: Response | null,
+ path: string,
+ status: string,
+): Record {
+ const url = new URL(req.url);
+ return {
+ type: "api_call",
+ status,
+ request: {
+ method: req.method,
+ path: path,
+ search: url.search,
+ },
+ response: {
+ statusMessage: response?.statusText || "unknown",
+ statusCode: response?.status || "unknown",
+ },
+ };
+}
+
+/**
+ * Add a request logger to a Hono app
+ * @param options
+ */
+export function honoLoggerMiddleware(
+ options?: HonoOptions,
+): (ctx: HonoLikeContext, next: HonoLikeNext) => Promise {
+ return async (ctx, next): Promise => {
+ const req = ctx.req.raw;
+ const url = new URL(req.url);
+ const path = ctx.req.path || url.pathname;
+
+ if (options?.doNotLogURLs?.test(path)) {
+ await next();
+ return;
+ }
+
+ const storeItem = storeItemFromRequest(
+ req.headers,
+ { method: req.method, path },
+ );
+ ctx.header("x-request-id", storeItem.trace.requestId);
+
+ await asyncLocalStorage.run(storeItem, async () => {
+ console.trace(() => {
+ return [
+ `api request start ${path}`,
+ {
+ type: "request",
+ request: {
+ method: req.method,
+ url: req.url,
+ },
+ },
+ ];
+ });
+
+ try {
+ await next();
+ console.info(
+ `request end ${req.method} ${path}`,
+ { metrics: storeItem.metrics?.getMetrics() || {} },
+ resLogData(req, ctx.res, path, "success"),
+ );
+ } catch (error) {
+ console.error(
+ `request end error ${path}`,
+ resLogData(req, ctx.res, path, "error"),
+ { metrics: storeItem.metrics?.getMetrics() || {} },
+ {
+ javascriptError: {
+ message: error instanceof Error ? error.message : String(error),
+ data: JSON.parse(JSON.stringify(error)),
+ stack: error instanceof Error ? error.stack : "no stack trace",
+ },
+ },
+ );
+ throw error;
+ }
+ });
+ };
+}
diff --git a/src/solidstart.ts b/src/solidstart.ts
new file mode 100644
index 0000000..bca283c
--- /dev/null
+++ b/src/solidstart.ts
@@ -0,0 +1,135 @@
+import { asyncLocalStorage, storeItemFromRequest } from "./utils.ts";
+
+/**
+ * Options for the SolidStart logger
+ * @param doNotLogURLs - a regex to match URLs that should not be logged
+ */
+export type SolidStartOptions = {
+ doNotLogURLs?: RegExp;
+};
+
+type HJson =
+ | string
+ | number
+ | boolean
+ | Date
+ | null
+ | { [key: string]: HJson }
+ | HJson[];
+
+type SolidStartLikeEvent = {
+ request: Request;
+ url?: URL;
+};
+
+function withHeader(response: Response, key: string, value: string): Response {
+ try {
+ response.headers.set(key, value);
+ return response;
+ } catch {
+ const headers = new Headers(response.headers);
+ headers.set(key, value);
+ return new Response(response.body, {
+ status: response.status,
+ statusText: response.statusText,
+ headers,
+ });
+ }
+}
+
+function resLogData(
+ req: Request,
+ response: Response | null,
+ path: string,
+ status: string,
+): Record {
+ const url = new URL(req.url);
+ return {
+ type: "api_call",
+ status,
+ request: {
+ method: req.method,
+ path: path,
+ search: url.search,
+ },
+ response: {
+ statusMessage: response?.statusText || "unknown",
+ statusCode: response?.status || "unknown",
+ },
+ };
+}
+
+/**
+ * Add a request logger middleware for SolidStart server middleware pipelines.
+ * @param options
+ */
+export function solidStartLoggerMiddleware(
+ options?: SolidStartOptions,
+): (
+ event: SolidStartLikeEvent,
+ next: () => Promise,
+) => Promise {
+ return async (
+ event: SolidStartLikeEvent,
+ next: () => Promise,
+ ): Promise => {
+ const req = event.request;
+ const url = event.url ?? new URL(req.url);
+
+ if (options?.doNotLogURLs?.test(url.pathname)) {
+ return await next();
+ }
+
+ const storeItem = storeItemFromRequest(
+ req.headers,
+ { method: req.method, path: url.pathname },
+ );
+
+ let response: Response | null = null;
+
+ await asyncLocalStorage.run(storeItem, async () => {
+ console.trace(() => {
+ return [
+ `api request start ${url.pathname}`,
+ {
+ type: "request",
+ request: {
+ method: req.method,
+ url: req.url,
+ },
+ },
+ ];
+ });
+
+ try {
+ response = await next();
+ response = withHeader(
+ response,
+ "x-request-id",
+ storeItem.trace.requestId,
+ );
+
+ console.info(
+ `request end ${req.method} ${url.pathname}`,
+ { metrics: storeItem.metrics?.getMetrics() || {} },
+ resLogData(req, response, url.pathname, "success"),
+ );
+ } catch (error) {
+ console.error(
+ `request end error ${url.pathname}`,
+ resLogData(req, response, url.pathname, "error"),
+ { metrics: storeItem.metrics?.getMetrics() || {} },
+ {
+ javascriptError: {
+ message: error instanceof Error ? error.message : String(error),
+ data: JSON.parse(JSON.stringify(error)),
+ stack: error instanceof Error ? error.stack : "no stack trace",
+ },
+ },
+ );
+ throw error;
+ }
+ });
+ return response!;
+ };
+}