cipher-logger 0.1.1 → 0.1.3
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/.agents/README.md +103 -0
- package/.agents/skills/fusion-api-reference/SKILL.md +61 -0
- package/.agents/skills/fusion-architecture/SKILL.md +64 -0
- package/.agents/skills/fusion-express-integration/SKILL.md +61 -0
- package/.agents/skills/fusion-log-schema/SKILL.md +34 -0
- package/.agents/skills/fusion-nextjs-integration/SKILL.md +81 -0
- package/.agents/skills/fusion-references/SKILL.md +51 -0
- package/README.md +85 -10
- package/dist/index.d.mts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +156 -2
- package/dist/index.mjs +153 -2
- package/package.json +26 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cipher-logger
|
|
3
|
+
description: >
|
|
4
|
+
Guidance for correct usage of the `cipher-logger` package (npm: cipher-logger,
|
|
5
|
+
organization: cipherunits) — an HTTP logging library for Node.js used with Express
|
|
6
|
+
and Next.js. Activate this skill whenever a user wants request logging, error
|
|
7
|
+
logging, middleware logs, or basic observability added to an Express or Next.js
|
|
8
|
+
project (especially cipherunits projects like rayyan-backend, Mananegar, Setad
|
|
9
|
+
Mahalle, Fusion GUI), or when imports of "cipher-logger" or `createCipherLogger`
|
|
10
|
+
are detected in code. Also use this skill to review/debug code that uses
|
|
11
|
+
cipher-logger (for example, incorrect status/duration in Next.js middleware or
|
|
12
|
+
optional fields not being logged) — even if the user simply asks "set up a
|
|
13
|
+
logger" or "add request logging" without naming the package.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Cipher Logger — Agent Skill
|
|
17
|
+
|
|
18
|
+
This skill is the official reference for installing, configuring, and correctly
|
|
19
|
+
using **cipher-logger** — a lightweight, production-ready HTTP logging library
|
|
20
|
+
for Node.js (organization: `cipherunits`, repository: `github.com/cipherunits/CipherLogger`).
|
|
21
|
+
Important: do not assume the API based on similarity to winston/pino/morgan — use
|
|
22
|
+
the exact API documented here.
|
|
23
|
+
|
|
24
|
+
## Golden Rule
|
|
25
|
+
|
|
26
|
+
cipher-logger has three distinct parts that must not be mixed:
|
|
27
|
+
|
|
28
|
+
1. **`Logger`** — the base application logger class (independent of HTTP). Use for
|
|
29
|
+
`logger.info/warn/error/debug`.
|
|
30
|
+
2. **`createCipherLogger(config)`** — factory that creates the main instance,
|
|
31
|
+
configures optional fields and level, and exposes `.express()`, `.next()`, and
|
|
32
|
+
`.logRequest()`.
|
|
33
|
+
3. **Framework adapters** (`cipher.express()` and `cipher.next()`) — ready-made
|
|
34
|
+
middleware; mount them rather than writing manual request logging.
|
|
35
|
+
|
|
36
|
+
If the user only asks to "log requests", they usually need steps 2 and 3, not 1.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add cipher-logger
|
|
42
|
+
# Framework packages are peer dependencies:
|
|
43
|
+
pnpm add express # if using Express
|
|
44
|
+
pnpm add next # if using Next.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Node.js 18+ is required. Install only the peer dependency actually used by the
|
|
48
|
+
project (e.g., `express` or `next`).
|
|
49
|
+
|
|
50
|
+
## Quick Decision Map
|
|
51
|
+
|
|
52
|
+
| Project type | What to mount/call |
|
|
53
|
+
|---|---|
|
|
54
|
+
| Express API (e.g. rayyan-backend if the backend is Node) | `cipher.express()` mounted before routes |
|
|
55
|
+
| Next.js (e.g. Setad Mahalle, Mananegar) | `middleware.ts` using `cipher.next()` |
|
|
56
|
+
| Manual non-HTTP event logging | `new Logger({...})` and `.info/.warn/.error/.debug` |
|
|
57
|
+
| Manual request logging (e.g. inside a job/cron) | `cipher.logRequest(input)` |
|
|
58
|
+
|
|
59
|
+
## Usage Steps (Summary)
|
|
60
|
+
|
|
61
|
+
1. Create a logger with `createCipherLogger({ fields: {...}, level: "info", prefix: "..." })` —
|
|
62
|
+
enable only the fields the user actually needs; all optional fields default to `false`.
|
|
63
|
+
2. For Express: mount `app.use(cipher.express())` **before** route definitions, otherwise
|
|
64
|
+
status/duration will be incorrect because the middleware must observe the request.
|
|
65
|
+
3. For Next.js: use `cipher.next()` in `middleware.ts` (or `src/middleware.ts`) and
|
|
66
|
+
always restrict `config.matcher` to exclude static/image routes to avoid noisy logs.
|
|
67
|
+
4. For more detailed guidance, read the related skill files below — only open the
|
|
68
|
+
section relevant to the user's request:
|
|
69
|
+
- `skills/fusion-references/SKILL.md` — configuration options and optional fields
|
|
70
|
+
- `skills/fusion-express-integration/SKILL.md` — Express integration and sample output
|
|
71
|
+
- `skills/fusion-nextjs-integration/SKILL.md` — Next.js middleware and the important
|
|
72
|
+
status/duration limitation
|
|
73
|
+
- `skills/fusion-log-schema/SKILL.md` — full log schema (required/optional fields)
|
|
74
|
+
- `skills/fusion-api-reference/SKILL.md` — function/class signatures and TypeScript exports
|
|
75
|
+
- `skills/fusion-architecture/SKILL.md` — internal package structure (for debugging/contrib)
|
|
76
|
+
|
|
77
|
+
## Common Pitfalls (Follow these)
|
|
78
|
+
|
|
79
|
+
- **Check peer dependencies**: If the project is Express but `express` is not installed
|
|
80
|
+
(or vice versa), a runtime error will occur. Check `package.json` before suggesting code.
|
|
81
|
+
- **Default `level` is `"debug"`**, not `"info"` — in production `"info"` is usually
|
|
82
|
+
more appropriate; set it explicitly in config instead of relying on the package default.
|
|
83
|
+
- **In Next.js middleware, `status` and `duration` reflect the middleware's execution, not
|
|
84
|
+
the final route response** (because middleware runs before route handlers). Remind the
|
|
85
|
+
user of this when they expect the actual response status — route-handler wrappers are
|
|
86
|
+
planned for future versions but are not available now.
|
|
87
|
+
- **In Express the opposite is true**: `status` and `duration` are recorded after `res.finish`
|
|
88
|
+
and therefore reflect the real response — explain this difference when comparing adapters.
|
|
89
|
+
- **Optional fields are off by default**; the user must explicitly enable `ip`, `userAgent`,
|
|
90
|
+
`query`, etc.
|
|
91
|
+
- **This package is lightweight** — don't recommend adding heavy log frameworks like
|
|
92
|
+
winston/pino unless the user specifically asks; the package is intended to be minimal.
|
|
93
|
+
- **Exclude static routes in Next.js matcher** (example: `/((?!_next/static|_next/image|favicon.ico).*)`) to
|
|
94
|
+
avoid excessive noisy logs.
|
|
95
|
+
|
|
96
|
+
## Before Suggesting Code
|
|
97
|
+
|
|
98
|
+
1. Check whether the project is Express or Next.js (via `package.json` or folder structure).
|
|
99
|
+
2. If `middleware.ts` already exists, merge the new code instead of replacing it (it may
|
|
100
|
+
contain other middleware like auth or i18n).
|
|
101
|
+
3. If the project is a monorepo (e.g. Mananegar), determine which workspace/app the
|
|
102
|
+
package should be installed into, not necessarily the monorepo root.
|
|
103
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## `createCipherLogger(config?)`
|
|
4
|
+
|
|
5
|
+
instance اصلی cipher-logger را میسازد.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const cipher = createCipherLogger(config);
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Instance methods
|
|
12
|
+
|
|
13
|
+
| Method | Description |
|
|
14
|
+
| ----------------------------- | ---------------------------------------- |
|
|
15
|
+
| `cipher.logRequest(input)` | Manually log an HTTP request |
|
|
16
|
+
| `cipher.express()` | Returns Express middleware |
|
|
17
|
+
| `cipher.next()` | Returns Next.js middleware |
|
|
18
|
+
|
|
19
|
+
Use `cipher.logRequest(input)` when the request cannot be captured by standard
|
|
20
|
+
middleware — for example inside a route handler when you need to log the actual
|
|
21
|
+
response status (not the middleware's status), subject to the limitation described
|
|
22
|
+
in `skills/fusion-nextjs-integration/SKILL.md`.
|
|
23
|
+
|
|
24
|
+
## `Logger` — base application logger (non-HTTP)
|
|
25
|
+
|
|
26
|
+
For regular application logs (server startup, non-HTTP errors, deprecation warnings)
|
|
27
|
+
use `Logger` directly rather than `createCipherLogger`:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Logger } from "cipher-logger";
|
|
31
|
+
|
|
32
|
+
const logger = new Logger({ level: "info", prefix: "app" });
|
|
33
|
+
|
|
34
|
+
logger.info("Server started");
|
|
35
|
+
logger.warn("Deprecated API used", { route: "/old" });
|
|
36
|
+
logger.error("Unhandled error", { err: "..." });
|
|
37
|
+
logger.debug("Debug info");
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each method (`info` / `warn` / `error` / `debug`) accepts a text message and an
|
|
41
|
+
optional metadata object.
|
|
42
|
+
|
|
43
|
+
## TypeScript Exports
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import type {
|
|
47
|
+
CipherLogger,
|
|
48
|
+
CipherLoggerConfig,
|
|
49
|
+
RequestLog,
|
|
50
|
+
RequestLogInput,
|
|
51
|
+
OptionalRequestField,
|
|
52
|
+
LogLevel,
|
|
53
|
+
LoggerOptions,
|
|
54
|
+
ExpressMiddleware,
|
|
55
|
+
NextMiddleware,
|
|
56
|
+
} from "cipher-logger";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the user wants to write a type-safe wrapper or factory over this package
|
|
60
|
+
(for example for Fusion GUI or a shared package in the Mananegar monorepo), use
|
|
61
|
+
the provided types rather than redefining them manually.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Architecture Reference
|
|
2
|
+
|
|
3
|
+
Only when the user needs to modify the CipherLogger package itself (not a project
|
|
4
|
+
that uses it) — for example, bug fixes, adding a new optional field, or writing an
|
|
5
|
+
adapter for another framework (like Fastify or Hono).
|
|
6
|
+
|
|
7
|
+
## Folder structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
cipher-logger/
|
|
11
|
+
├── src/
|
|
12
|
+
│ ├── core/ # core — field configuration and log construction
|
|
13
|
+
│ │ ├── logger.ts
|
|
14
|
+
│ │ ├── types.ts
|
|
15
|
+
│ │ ├── build-request-log.ts
|
|
16
|
+
│ │ └── create-cipher-logger.ts
|
|
17
|
+
│ ├── express/ # Express adapter
|
|
18
|
+
│ │ └── middleware.ts
|
|
19
|
+
│ ├── next/ # Next.js adapter
|
|
20
|
+
│ │ └── middleware.ts
|
|
21
|
+
│ └── index.ts # public entry point
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Data flow
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Core
|
|
28
|
+
fields config → buildRequestLog
|
|
29
|
+
│
|
|
30
|
+
┌─────────┴─────────┐
|
|
31
|
+
▼ ▼
|
|
32
|
+
Express Next.js
|
|
33
|
+
middleware middleware
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Both adapters (`express/middleware.ts` and `next/middleware.ts`) rely on the
|
|
37
|
+
same `buildRequestLog` core — only the extraction of request/response fields from
|
|
38
|
+
the framework differs. The difference in `status`/`duration` behavior between the
|
|
39
|
+
two adapters stems from how each adapter calls the core, not from `buildRequestLog`
|
|
40
|
+
itself.
|
|
41
|
+
|
|
42
|
+
## Local development
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/cipherunits/CipherLogger.git
|
|
46
|
+
cd CipherLogger
|
|
47
|
+
pnpm install
|
|
48
|
+
pnpm run build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Adding a new adapter (e.g. Fastify)
|
|
52
|
+
|
|
53
|
+
Create a new file such as `src/fastify/middleware.ts` that calls the same
|
|
54
|
+
`buildRequestLog` from `core/build-request-log.ts` and only differs in how fields
|
|
55
|
+
are extracted from the framework — do not reimplement the log-construction logic
|
|
56
|
+
inside the new adapter.
|
|
57
|
+
|
|
58
|
+
## Contribution rules (from README)
|
|
59
|
+
|
|
60
|
+
- Commits must follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
61
|
+
- Run `pnpm run build` before opening a PR
|
|
62
|
+
- Keep changes focused and small
|
|
63
|
+
- Update README for any API changes
|
|
64
|
+
- License: MIT © Cipher Unit
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Express Integration Reference
|
|
2
|
+
|
|
3
|
+
## Installation and mount
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import express from "express";
|
|
7
|
+
import { createCipherLogger } from "cipher-logger";
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
|
|
11
|
+
const cipher = createCipherLogger({
|
|
12
|
+
fields: {
|
|
13
|
+
ip: true,
|
|
14
|
+
userAgent: true,
|
|
15
|
+
query: true,
|
|
16
|
+
requestId: true,
|
|
17
|
+
},
|
|
18
|
+
level: "info",
|
|
19
|
+
prefix: "express",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Mount before defining routes
|
|
23
|
+
app.use(cipher.express());
|
|
24
|
+
|
|
25
|
+
app.get("/users", (req, res) => {
|
|
26
|
+
res.json({ users: [] });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.listen(3000, () => {
|
|
30
|
+
console.log("Server running on http://localhost:3000");
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Critical note: mount order
|
|
35
|
+
|
|
36
|
+
`app.use(cipher.express())` must be called **before any route handlers** (it is
|
|
37
|
+
usually fine to mount it after body-parser/CORS at the start of the chain) so the
|
|
38
|
+
middleware can observe `res.finish`.
|
|
39
|
+
|
|
40
|
+
## Sample real output
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[2026-09-01T20:00:00.000Z] [INFO] [express] HTTP Request {
|
|
44
|
+
id: 'a1b2c3d4-...',
|
|
45
|
+
type: 'http',
|
|
46
|
+
timestamp: '2026-09-01T20:00:00.000Z',
|
|
47
|
+
method: 'GET',
|
|
48
|
+
path: '/users?page=1',
|
|
49
|
+
status: 200,
|
|
50
|
+
duration: 12,
|
|
51
|
+
ip: '::1',
|
|
52
|
+
userAgent: 'Mozilla/5.0 ...',
|
|
53
|
+
query: { page: '1' }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Why Express is more accurate than Next.js
|
|
58
|
+
|
|
59
|
+
In Express, `status` and `duration` are recorded **after the `res.finish` event** —
|
|
60
|
+
so they reflect the actual response sent to the client. Mention this when explaining
|
|
61
|
+
the behavioral difference between adapters (see `skills/fusion-nextjs-integration/SKILL.md`).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Log Schema Reference
|
|
2
|
+
|
|
3
|
+
## Required fields (`required`) — always present in every HTTP log
|
|
4
|
+
|
|
5
|
+
| Field | Type | Description |
|
|
6
|
+
| ----------- | -------- | --------------------------- |
|
|
7
|
+
| `id` | `string` | Unique identifier (UUID) |
|
|
8
|
+
| `type` | `"http"` | Log type |
|
|
9
|
+
| `timestamp` | `string` | ISO 8601 timestamp |
|
|
10
|
+
| `method` | `string` | HTTP method |
|
|
11
|
+
| `path` | `string` | Request path |
|
|
12
|
+
| `status` | `number` | HTTP status code |
|
|
13
|
+
| `duration` | `number` | Duration in milliseconds |
|
|
14
|
+
|
|
15
|
+
## Optional fields (`optional`) — enabled via `fields` in config
|
|
16
|
+
|
|
17
|
+
| Field | Type | Description |
|
|
18
|
+
| ----------- | ---------------------------- | ---------------------------- |
|
|
19
|
+
| `ip` | `string` | Client IP address |
|
|
20
|
+
| `userAgent` | `string` | User-Agent header |
|
|
21
|
+
| `referer` | `string` | Referer header |
|
|
22
|
+
| `protocol` | `string` | `http` / `https` |
|
|
23
|
+
| `host` | `string` | Host header |
|
|
24
|
+
| `query` | `Record<string, string>` | Query string parameters |
|
|
25
|
+
| `requestId` | `string` | From the `x-request-id` header |
|
|
26
|
+
| `metadata` | `Record<string, unknown>` | Arbitrary metadata |
|
|
27
|
+
|
|
28
|
+
See `skills/fusion-references/SKILL.md` for the full configuration details of these fields.
|
|
29
|
+
|
|
30
|
+
## Note for log processing/ingestion (for example, when sending to a log pipeline)
|
|
31
|
+
|
|
32
|
+
Each HTTP log is a flat object with `type: "http"` — if you need to distinguish
|
|
33
|
+
between HTTP logs and manual logs (calls to `logger.info(...)` from the `Logger`
|
|
34
|
+
class), check the `type` field; manual logs do not include `type: "http"`.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Next.js Integration Reference
|
|
2
|
+
|
|
3
|
+
## Full approach — `middleware.ts` at project root (or `src/middleware.ts`)
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createCipherLogger } from "cipher-logger";
|
|
7
|
+
import type { NextRequest } from "next/server";
|
|
8
|
+
|
|
9
|
+
const cipher = createCipherLogger({
|
|
10
|
+
fields: {
|
|
11
|
+
ip: true,
|
|
12
|
+
userAgent: true,
|
|
13
|
+
host: true,
|
|
14
|
+
query: true,
|
|
15
|
+
},
|
|
16
|
+
level: "info",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export function middleware(request: NextRequest) {
|
|
20
|
+
return cipher.next()(request);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const config = {
|
|
24
|
+
matcher: [
|
|
25
|
+
// all paths except static and image files
|
|
26
|
+
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Short approach (when no other middleware exists in the project)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createCipherLogger } from "cipher-logger";
|
|
35
|
+
|
|
36
|
+
const cipher = createCipherLogger({
|
|
37
|
+
fields: { ip: true, userAgent: true },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default cipher.next();
|
|
41
|
+
|
|
42
|
+
export const config = {
|
|
43
|
+
matcher: ["/api/:path*", "/dashboard/:path*"],
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
⚠️ If the project already has `middleware.ts` (for example auth or i18n — like
|
|
48
|
+
Mananegar/Setad Mahalle which are multilingual), do not use the short approach; instead
|
|
49
|
+
merge the export function with the existing logic using the full method above, because
|
|
50
|
+
only one default middleware export is allowed per Next.js project.
|
|
51
|
+
|
|
52
|
+
## ⚠️ Important limitation — read before explaining status/duration to the user
|
|
53
|
+
|
|
54
|
+
Next.js middleware runs **before** the route handler. Therefore:
|
|
55
|
+
|
|
56
|
+
- `status` and `duration` recorded in the logs refer to the **middleware's execution**,
|
|
57
|
+
not the final response returned by the route handler.
|
|
58
|
+
- If the user expects the actual API route status code (e.g. 404 or 500 from inside a
|
|
59
|
+
handler) to appear in this log, **that is not possible** with the current package.
|
|
60
|
+
- According to the official package docs, a "route handler wrapper for more accurate
|
|
61
|
+
logging" is planned for future versions but is not available now — do not claim
|
|
62
|
+
such an API exists at present.
|
|
63
|
+
- If the user needs accurate status/duration for a route, the current solution is to
|
|
64
|
+
manually call `cipher.logRequest(input)` inside the route handler or use the `Logger`
|
|
65
|
+
class (see `skills/fusion-api-reference/SKILL.md`), not the middleware.
|
|
66
|
+
|
|
67
|
+
## Always restrict the matcher
|
|
68
|
+
|
|
69
|
+
Without a restricted `matcher`, static requests (`_next/static`, `_next/image`,
|
|
70
|
+
`favicon.ico`) will also be logged and produce a lot of noise. Official suggested
|
|
71
|
+
pattern:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"];
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or restrict to specific paths:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
matcher: ["/api/:path*", "/dashboard/:path*"];
|
|
81
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
## `createCipherLogger(config?)`
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createCipherLogger } from "cipher-logger";
|
|
7
|
+
|
|
8
|
+
const cipher = createCipherLogger({
|
|
9
|
+
fields: {
|
|
10
|
+
ip: true,
|
|
11
|
+
userAgent: true,
|
|
12
|
+
referer: false,
|
|
13
|
+
protocol: true,
|
|
14
|
+
host: true,
|
|
15
|
+
query: true,
|
|
16
|
+
requestId: true,
|
|
17
|
+
metadata: false,
|
|
18
|
+
},
|
|
19
|
+
level: "info",
|
|
20
|
+
prefix: "api",
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Options Table
|
|
25
|
+
|
|
26
|
+
| Option | Type | Default | Description |
|
|
27
|
+
| -------- | -------------------------------------------------- | ------------ | ------------------------------------ |
|
|
28
|
+
| `fields` | `Partial<Record<OptionalRequestField, boolean>>` | all `false` | Which optional fields to include in logs |
|
|
29
|
+
| `level` | `"debug" \| "info" \| "warn" \| "error"` | `"debug"` | Minimum log level |
|
|
30
|
+
| `prefix` | `string` | — | Prefix used in console output |
|
|
31
|
+
> **Note:** The package default `level` is `"debug"`, which is often too verbose for
|
|
32
|
+
production. If the user doesn't specify a level, explicitly set `"info"` in the
|
|
33
|
+
config and document the reason rather than relying on the package default.
|
|
34
|
+
|
|
35
|
+
## Optional fields (`fields`) — all default to `false`
|
|
36
|
+
|
|
37
|
+
| Field | Type | Description |
|
|
38
|
+
| ----------- | -------------------------- | ------------------------------ |
|
|
39
|
+
| `ip` | `string` | Client IP address |
|
|
40
|
+
| `userAgent` | `string` | User-Agent header |
|
|
41
|
+
| `referer` | `string` | Referer header |
|
|
42
|
+
| `protocol` | `string` | `http` or `https` |
|
|
43
|
+
| `host` | `string` | Host header |
|
|
44
|
+
| `query` | `Record<string, string>` | Query string parameters |
|
|
45
|
+
| `requestId` | `string` | From the `x-request-id` header |
|
|
46
|
+
| `metadata` | `Record<string, unknown>` | Arbitrary metadata |
|
|
47
|
+
|
|
48
|
+
Enable only the fields the user truly needs; turning on everything can produce
|
|
49
|
+
large logs and risk leaking sensitive data (for example IPs or query params that
|
|
50
|
+
contain tokens). If query params may contain secrets (passwords, tokens), warn the
|
|
51
|
+
user before enabling `query: true`.
|
package/README.md
CHANGED
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
- **Express middleware** — records real `status` and `duration` after the response finishes
|
|
40
40
|
- **Next.js middleware** — drop-in support for `middleware.ts`
|
|
41
41
|
- **Zero heavy dependencies** — only `express` or `next` as optional peer dependencies
|
|
42
|
+
- **Fastify middleware** — lightweight hook-compatible middleware for Fastify
|
|
43
|
+
- **NestJS middleware** — Express-compatible middleware for Nest apps
|
|
44
|
+
- **Hono middleware** — edge-friendly middleware for Hono apps
|
|
45
|
+
- **Zero heavy dependencies** — only framework peers are optional
|
|
42
46
|
- **Node.js 18+** compatible
|
|
43
47
|
|
|
44
48
|
---
|
|
@@ -63,6 +67,15 @@ npm install express
|
|
|
63
67
|
|
|
64
68
|
# Next.js
|
|
65
69
|
npm install next
|
|
70
|
+
|
|
71
|
+
# Fastify
|
|
72
|
+
npm install fastify
|
|
73
|
+
|
|
74
|
+
# NestJS (core packages)
|
|
75
|
+
npm install @nestjs/core @nestjs/common
|
|
76
|
+
|
|
77
|
+
# Hono
|
|
78
|
+
npm install hono
|
|
66
79
|
```
|
|
67
80
|
|
|
68
81
|
---
|
|
@@ -222,6 +235,73 @@ export const config = {
|
|
|
222
235
|
|
|
223
236
|
---
|
|
224
237
|
|
|
238
|
+
## Fastify
|
|
239
|
+
|
|
240
|
+
Register the Fastify-compatible middleware returned by `cipher.fastify()` using `addHook`:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import fastify from "fastify";
|
|
244
|
+
import { createCipherLogger } from "cipher-logger";
|
|
245
|
+
|
|
246
|
+
const app = fastify();
|
|
247
|
+
|
|
248
|
+
const cipher = createCipherLogger({
|
|
249
|
+
fields: { ip: true, userAgent: true, query: true },
|
|
250
|
+
level: "info",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Register before your routes
|
|
254
|
+
app.addHook("onRequest", cipher.fastify());
|
|
255
|
+
|
|
256
|
+
app.get("/", async () => ({ hello: "world" }));
|
|
257
|
+
|
|
258
|
+
app.listen({ port: 3000 });
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## NestJS
|
|
262
|
+
|
|
263
|
+
Use the Express-compatible middleware in Nest's runtime (works when Nest is using the Express platform):
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { NestFactory } from "@nestjs/core";
|
|
267
|
+
import { AppModule } from "./app.module";
|
|
268
|
+
import { createCipherLogger } from "cipher-logger";
|
|
269
|
+
|
|
270
|
+
async function bootstrap() {
|
|
271
|
+
const app = await NestFactory.create(AppModule);
|
|
272
|
+
|
|
273
|
+
const cipher = createCipherLogger({ fields: { ip: true, userAgent: true } });
|
|
274
|
+
|
|
275
|
+
// Mount as global middleware
|
|
276
|
+
app.use(cipher.nest());
|
|
277
|
+
|
|
278
|
+
await app.listen(3000);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
bootstrap();
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Hono
|
|
285
|
+
|
|
286
|
+
Mount the Hono middleware using `app.use` (works on edge and Node runtimes):
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import { Hono } from "hono";
|
|
290
|
+
import { createCipherLogger } from "cipher-logger";
|
|
291
|
+
|
|
292
|
+
const app = new Hono();
|
|
293
|
+
|
|
294
|
+
const cipher = createCipherLogger({ fields: { ip: true, userAgent: true } });
|
|
295
|
+
|
|
296
|
+
// Mount for all routes
|
|
297
|
+
app.use("*", cipher.hono());
|
|
298
|
+
|
|
299
|
+
app.get("/", (c) => c.text("ok"));
|
|
300
|
+
|
|
301
|
+
app.listen({ port: 3000 });
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
|
|
225
305
|
## Log Schema
|
|
226
306
|
|
|
227
307
|
### Required Fields
|
|
@@ -301,6 +381,9 @@ import type {
|
|
|
301
381
|
LoggerOptions,
|
|
302
382
|
ExpressMiddleware,
|
|
303
383
|
NextMiddleware,
|
|
384
|
+
FastifyMiddleware,
|
|
385
|
+
NestMiddleware,
|
|
386
|
+
HonoMiddleware,
|
|
304
387
|
} from "cipher-logger";
|
|
305
388
|
```
|
|
306
389
|
|
|
@@ -383,16 +466,8 @@ Contributions are welcome and appreciated.
|
|
|
383
466
|
|
|
384
467
|
## License
|
|
385
468
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
Copyright (c) 2026, Cipher Logger
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Free to use, modify, and distribute — provided the copyright notice and license terms are preserved. See [LICENSE](LICENSE) for full details.
|
|
393
|
-
|
|
394
|
-
---
|
|
469
|
+
[MIT](./LICENSE) © Cipher Unit
|
|
395
470
|
|
|
396
471
|
<p align="center">
|
|
397
|
-
Built
|
|
472
|
+
<i>Built by <a href="https://cipherunit.xyz">Cipher Unit</a></i>
|
|
398
473
|
</p>
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
1
|
+
import { Request, Response as Response$1, NextFunction } from 'express';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
4
|
+
import { Context } from 'hono';
|
|
3
5
|
|
|
4
6
|
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
5
7
|
type LogMeta = Record<string, unknown>;
|
|
@@ -59,19 +61,31 @@ type CipherLoggerConfig = {
|
|
|
59
61
|
prefix?: string;
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
64
|
+
type ExpressMiddleware = (req: Request, res: Response$1, next: NextFunction) => void;
|
|
63
65
|
declare function createExpressMiddleware(cipher: CipherLogger): ExpressMiddleware;
|
|
64
66
|
|
|
65
67
|
type NextMiddleware = (request: NextRequest) => NextResponse | Promise<NextResponse>;
|
|
66
68
|
declare function createNextMiddleware(cipher: CipherLogger): NextMiddleware;
|
|
67
69
|
|
|
70
|
+
type FastifyMiddleware = (request: FastifyRequest, reply: FastifyReply) => void;
|
|
71
|
+
declare function createFastifyMiddleware(cipher: CipherLogger): FastifyMiddleware;
|
|
72
|
+
|
|
73
|
+
type NestMiddleware = (req: Request, res: Response$1, next: NextFunction) => void;
|
|
74
|
+
declare function createNestMiddleware(cipher: CipherLogger): NestMiddleware;
|
|
75
|
+
|
|
76
|
+
type HonoMiddleware = (c: Context, next: () => Promise<void>) => Response | Promise<Response>;
|
|
77
|
+
declare function createHonoMiddleware(cipher: CipherLogger): HonoMiddleware;
|
|
78
|
+
|
|
68
79
|
interface CipherLogger {
|
|
69
80
|
logRequest(input: RequestLogInput): RequestLog;
|
|
70
81
|
express(): ExpressMiddleware;
|
|
71
82
|
next(): NextMiddleware;
|
|
83
|
+
fastify(): FastifyMiddleware;
|
|
84
|
+
nest(): NestMiddleware;
|
|
85
|
+
hono(): HonoMiddleware;
|
|
72
86
|
}
|
|
73
87
|
declare function createCipherLogger(config?: CipherLoggerConfig): CipherLogger;
|
|
74
88
|
|
|
75
89
|
declare const logger: Logger;
|
|
76
90
|
|
|
77
|
-
export { type CipherLogger, type CipherLoggerConfig, type ExpressMiddleware, type LogLevel, type LogMeta, Logger, type LoggerOptions, type NextMiddleware, type OptionalRequestField, type RequestLog, type RequestLogInput, createCipherLogger, createExpressMiddleware, createNextMiddleware, logger, type RequestLog as request };
|
|
91
|
+
export { type CipherLogger, type CipherLoggerConfig, type ExpressMiddleware, type FastifyMiddleware, type HonoMiddleware, type LogLevel, type LogMeta, Logger, type LoggerOptions, type NestMiddleware, type NextMiddleware, type OptionalRequestField, type RequestLog, type RequestLogInput, createCipherLogger, createExpressMiddleware, createFastifyMiddleware, createHonoMiddleware, createNestMiddleware, createNextMiddleware, logger, type RequestLog as request };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
|
1
|
+
import { Request, Response as Response$1, NextFunction } from 'express';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
4
|
+
import { Context } from 'hono';
|
|
3
5
|
|
|
4
6
|
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
5
7
|
type LogMeta = Record<string, unknown>;
|
|
@@ -59,19 +61,31 @@ type CipherLoggerConfig = {
|
|
|
59
61
|
prefix?: string;
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
64
|
+
type ExpressMiddleware = (req: Request, res: Response$1, next: NextFunction) => void;
|
|
63
65
|
declare function createExpressMiddleware(cipher: CipherLogger): ExpressMiddleware;
|
|
64
66
|
|
|
65
67
|
type NextMiddleware = (request: NextRequest) => NextResponse | Promise<NextResponse>;
|
|
66
68
|
declare function createNextMiddleware(cipher: CipherLogger): NextMiddleware;
|
|
67
69
|
|
|
70
|
+
type FastifyMiddleware = (request: FastifyRequest, reply: FastifyReply) => void;
|
|
71
|
+
declare function createFastifyMiddleware(cipher: CipherLogger): FastifyMiddleware;
|
|
72
|
+
|
|
73
|
+
type NestMiddleware = (req: Request, res: Response$1, next: NextFunction) => void;
|
|
74
|
+
declare function createNestMiddleware(cipher: CipherLogger): NestMiddleware;
|
|
75
|
+
|
|
76
|
+
type HonoMiddleware = (c: Context, next: () => Promise<void>) => Response | Promise<Response>;
|
|
77
|
+
declare function createHonoMiddleware(cipher: CipherLogger): HonoMiddleware;
|
|
78
|
+
|
|
68
79
|
interface CipherLogger {
|
|
69
80
|
logRequest(input: RequestLogInput): RequestLog;
|
|
70
81
|
express(): ExpressMiddleware;
|
|
71
82
|
next(): NextMiddleware;
|
|
83
|
+
fastify(): FastifyMiddleware;
|
|
84
|
+
nest(): NestMiddleware;
|
|
85
|
+
hono(): HonoMiddleware;
|
|
72
86
|
}
|
|
73
87
|
declare function createCipherLogger(config?: CipherLoggerConfig): CipherLogger;
|
|
74
88
|
|
|
75
89
|
declare const logger: Logger;
|
|
76
90
|
|
|
77
|
-
export { type CipherLogger, type CipherLoggerConfig, type ExpressMiddleware, type LogLevel, type LogMeta, Logger, type LoggerOptions, type NextMiddleware, type OptionalRequestField, type RequestLog, type RequestLogInput, createCipherLogger, createExpressMiddleware, createNextMiddleware, logger, type RequestLog as request };
|
|
91
|
+
export { type CipherLogger, type CipherLoggerConfig, type ExpressMiddleware, type FastifyMiddleware, type HonoMiddleware, type LogLevel, type LogMeta, Logger, type LoggerOptions, type NestMiddleware, type NextMiddleware, type OptionalRequestField, type RequestLog, type RequestLogInput, createCipherLogger, createExpressMiddleware, createFastifyMiddleware, createHonoMiddleware, createNestMiddleware, createNextMiddleware, logger, type RequestLog as request };
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,9 @@ __export(src_exports, {
|
|
|
23
23
|
Logger: () => Logger,
|
|
24
24
|
createCipherLogger: () => createCipherLogger,
|
|
25
25
|
createExpressMiddleware: () => createExpressMiddleware,
|
|
26
|
+
createFastifyMiddleware: () => createFastifyMiddleware,
|
|
27
|
+
createHonoMiddleware: () => createHonoMiddleware,
|
|
28
|
+
createNestMiddleware: () => createNestMiddleware,
|
|
26
29
|
createNextMiddleware: () => createNextMiddleware,
|
|
27
30
|
logger: () => logger
|
|
28
31
|
});
|
|
@@ -151,7 +154,7 @@ function buildRequestLog(fields, input) {
|
|
|
151
154
|
return log;
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
// src/express/middleware.ts
|
|
157
|
+
// src/adapters/express/middleware.ts
|
|
155
158
|
function parseQuery(query) {
|
|
156
159
|
const result = {};
|
|
157
160
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -191,7 +194,7 @@ function createExpressMiddleware(cipher) {
|
|
|
191
194
|
};
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
// src/next/middleware.ts
|
|
197
|
+
// src/adapters/next/middleware.ts
|
|
195
198
|
var import_server = require("next/server");
|
|
196
199
|
function getHeader2(request, name) {
|
|
197
200
|
return request.headers.get(name) ?? void 0;
|
|
@@ -224,6 +227,145 @@ function createNextMiddleware(cipher) {
|
|
|
224
227
|
};
|
|
225
228
|
}
|
|
226
229
|
|
|
230
|
+
// src/adapters/fastify/middleware.ts
|
|
231
|
+
function getHeader3(req, name) {
|
|
232
|
+
const value = req.headers[name];
|
|
233
|
+
if (value === void 0) {
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
return Array.isArray(value) ? String(value[0]) : String(value);
|
|
237
|
+
}
|
|
238
|
+
function parseQuery3(req) {
|
|
239
|
+
try {
|
|
240
|
+
const raw = req.raw.url ?? req.url ?? "";
|
|
241
|
+
const url = new URL(raw, "http://localhost");
|
|
242
|
+
const result = {};
|
|
243
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
244
|
+
result[key] = value;
|
|
245
|
+
}
|
|
246
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
247
|
+
} catch {
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function createFastifyMiddleware(cipher) {
|
|
252
|
+
return (request, reply) => {
|
|
253
|
+
const start = Date.now();
|
|
254
|
+
reply.raw.once("finish", () => {
|
|
255
|
+
const protocol = request.headers["x-forwarded-proto"] || (request.raw.socket && request.raw.socket.encrypted ? "https" : "http");
|
|
256
|
+
cipher.logRequest({
|
|
257
|
+
method: request.method,
|
|
258
|
+
path: request.raw.url ?? request.url,
|
|
259
|
+
status: reply.statusCode,
|
|
260
|
+
duration: Date.now() - start,
|
|
261
|
+
ip: request.headers["x-forwarded-for"] ? request.headers["x-forwarded-for"].split(",")[0].trim() : request.ip || request.socket?.remoteAddress || request.raw.socket?.remoteAddress,
|
|
262
|
+
userAgent: getHeader3(request, "user-agent"),
|
|
263
|
+
referer: getHeader3(request, "referer"),
|
|
264
|
+
protocol,
|
|
265
|
+
host: getHeader3(request, "host"),
|
|
266
|
+
query: parseQuery3(request),
|
|
267
|
+
requestId: getHeader3(request, "x-request-id")
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/adapters/nest/middleware.ts
|
|
274
|
+
function parseQuery4(query) {
|
|
275
|
+
const result = {};
|
|
276
|
+
for (const [key, value] of Object.entries(query)) {
|
|
277
|
+
if (value === void 0) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
result[key] = typeof value === "string" ? value : Array.isArray(value) ? String(value[0] ?? "") : String(value);
|
|
281
|
+
}
|
|
282
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
283
|
+
}
|
|
284
|
+
function getHeader4(req, name) {
|
|
285
|
+
const value = req.headers[name];
|
|
286
|
+
if (value === void 0) {
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
return Array.isArray(value) ? value[0] : value;
|
|
290
|
+
}
|
|
291
|
+
function createNestMiddleware(cipher) {
|
|
292
|
+
return (req, res, next) => {
|
|
293
|
+
const start = Date.now();
|
|
294
|
+
res.on("finish", () => {
|
|
295
|
+
cipher.logRequest({
|
|
296
|
+
method: req.method,
|
|
297
|
+
path: req.originalUrl || req.url,
|
|
298
|
+
status: res.statusCode,
|
|
299
|
+
duration: Date.now() - start,
|
|
300
|
+
ip: req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.ip || req.socket.remoteAddress,
|
|
301
|
+
userAgent: getHeader4(req, "user-agent"),
|
|
302
|
+
referer: getHeader4(req, "referer"),
|
|
303
|
+
protocol: req.protocol,
|
|
304
|
+
host: getHeader4(req, "host"),
|
|
305
|
+
query: parseQuery4(req.query),
|
|
306
|
+
requestId: getHeader4(req, "x-request-id")
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
next();
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/adapters/hono/middleware.ts
|
|
314
|
+
function getHeader5(request, name) {
|
|
315
|
+
if (!request) return void 0;
|
|
316
|
+
if (typeof request.header === "function") {
|
|
317
|
+
return request.header(name) ?? void 0;
|
|
318
|
+
}
|
|
319
|
+
if (request.headers && typeof request.headers.get === "function") {
|
|
320
|
+
return request.headers.get(name) ?? void 0;
|
|
321
|
+
}
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
function parseQuery5(urlString) {
|
|
325
|
+
try {
|
|
326
|
+
const url = new URL(urlString);
|
|
327
|
+
const result = {};
|
|
328
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
329
|
+
result[key] = value;
|
|
330
|
+
}
|
|
331
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
332
|
+
} catch {
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function createHonoMiddleware(cipher) {
|
|
337
|
+
return async (c, next) => {
|
|
338
|
+
const start = Date.now();
|
|
339
|
+
await next();
|
|
340
|
+
const req = c.req ?? c.request;
|
|
341
|
+
const url = typeof req?.url === "string" ? req.url : String(req?.url ?? "");
|
|
342
|
+
const parsed = new URL(url, "http://localhost");
|
|
343
|
+
let status = 200;
|
|
344
|
+
const cres = c.res ?? c.response ?? void 0;
|
|
345
|
+
if (cres && typeof cres.status === "number") {
|
|
346
|
+
status = cres.status;
|
|
347
|
+
} else if (cres && typeof cres.statusCode === "number") {
|
|
348
|
+
status = cres.statusCode;
|
|
349
|
+
}
|
|
350
|
+
const xfwd = getHeader5(req, "x-forwarded-for");
|
|
351
|
+
const ip = xfwd ? String(xfwd).split(",")[0].trim() : getHeader5(req, "x-real-ip") ?? void 0;
|
|
352
|
+
cipher.logRequest({
|
|
353
|
+
method: req?.method,
|
|
354
|
+
path: parsed.pathname + parsed.search,
|
|
355
|
+
status,
|
|
356
|
+
duration: Date.now() - start,
|
|
357
|
+
ip,
|
|
358
|
+
userAgent: getHeader5(req, "user-agent"),
|
|
359
|
+
referer: getHeader5(req, "referer"),
|
|
360
|
+
protocol: parsed.protocol.replace(":", ""),
|
|
361
|
+
host: getHeader5(req, "host"),
|
|
362
|
+
query: parseQuery5(url),
|
|
363
|
+
requestId: getHeader5(req, "x-request-id")
|
|
364
|
+
});
|
|
365
|
+
return cres ?? new Response(null, { status });
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
227
369
|
// src/core/create-cipher-logger.ts
|
|
228
370
|
function createCipherLogger(config = {}) {
|
|
229
371
|
const fields = resolveFieldConfig(config.fields);
|
|
@@ -242,6 +384,15 @@ function createCipherLogger(config = {}) {
|
|
|
242
384
|
},
|
|
243
385
|
next() {
|
|
244
386
|
return createNextMiddleware(cipherLogger);
|
|
387
|
+
},
|
|
388
|
+
fastify() {
|
|
389
|
+
return createFastifyMiddleware(cipherLogger);
|
|
390
|
+
},
|
|
391
|
+
nest() {
|
|
392
|
+
return createNestMiddleware(cipherLogger);
|
|
393
|
+
},
|
|
394
|
+
hono() {
|
|
395
|
+
return createHonoMiddleware(cipherLogger);
|
|
245
396
|
}
|
|
246
397
|
};
|
|
247
398
|
return cipherLogger;
|
|
@@ -254,6 +405,9 @@ var logger = new Logger();
|
|
|
254
405
|
Logger,
|
|
255
406
|
createCipherLogger,
|
|
256
407
|
createExpressMiddleware,
|
|
408
|
+
createFastifyMiddleware,
|
|
409
|
+
createHonoMiddleware,
|
|
410
|
+
createNestMiddleware,
|
|
257
411
|
createNextMiddleware,
|
|
258
412
|
logger
|
|
259
413
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -121,7 +121,7 @@ function buildRequestLog(fields, input) {
|
|
|
121
121
|
return log;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// src/express/middleware.ts
|
|
124
|
+
// src/adapters/express/middleware.ts
|
|
125
125
|
function parseQuery(query) {
|
|
126
126
|
const result = {};
|
|
127
127
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -161,7 +161,7 @@ function createExpressMiddleware(cipher) {
|
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// src/next/middleware.ts
|
|
164
|
+
// src/adapters/next/middleware.ts
|
|
165
165
|
import { NextResponse } from "next/server";
|
|
166
166
|
function getHeader2(request, name) {
|
|
167
167
|
return request.headers.get(name) ?? void 0;
|
|
@@ -194,6 +194,145 @@ function createNextMiddleware(cipher) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// src/adapters/fastify/middleware.ts
|
|
198
|
+
function getHeader3(req, name) {
|
|
199
|
+
const value = req.headers[name];
|
|
200
|
+
if (value === void 0) {
|
|
201
|
+
return void 0;
|
|
202
|
+
}
|
|
203
|
+
return Array.isArray(value) ? String(value[0]) : String(value);
|
|
204
|
+
}
|
|
205
|
+
function parseQuery3(req) {
|
|
206
|
+
try {
|
|
207
|
+
const raw = req.raw.url ?? req.url ?? "";
|
|
208
|
+
const url = new URL(raw, "http://localhost");
|
|
209
|
+
const result = {};
|
|
210
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
211
|
+
result[key] = value;
|
|
212
|
+
}
|
|
213
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
214
|
+
} catch {
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function createFastifyMiddleware(cipher) {
|
|
219
|
+
return (request, reply) => {
|
|
220
|
+
const start = Date.now();
|
|
221
|
+
reply.raw.once("finish", () => {
|
|
222
|
+
const protocol = request.headers["x-forwarded-proto"] || (request.raw.socket && request.raw.socket.encrypted ? "https" : "http");
|
|
223
|
+
cipher.logRequest({
|
|
224
|
+
method: request.method,
|
|
225
|
+
path: request.raw.url ?? request.url,
|
|
226
|
+
status: reply.statusCode,
|
|
227
|
+
duration: Date.now() - start,
|
|
228
|
+
ip: request.headers["x-forwarded-for"] ? request.headers["x-forwarded-for"].split(",")[0].trim() : request.ip || request.socket?.remoteAddress || request.raw.socket?.remoteAddress,
|
|
229
|
+
userAgent: getHeader3(request, "user-agent"),
|
|
230
|
+
referer: getHeader3(request, "referer"),
|
|
231
|
+
protocol,
|
|
232
|
+
host: getHeader3(request, "host"),
|
|
233
|
+
query: parseQuery3(request),
|
|
234
|
+
requestId: getHeader3(request, "x-request-id")
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/adapters/nest/middleware.ts
|
|
241
|
+
function parseQuery4(query) {
|
|
242
|
+
const result = {};
|
|
243
|
+
for (const [key, value] of Object.entries(query)) {
|
|
244
|
+
if (value === void 0) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
result[key] = typeof value === "string" ? value : Array.isArray(value) ? String(value[0] ?? "") : String(value);
|
|
248
|
+
}
|
|
249
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
250
|
+
}
|
|
251
|
+
function getHeader4(req, name) {
|
|
252
|
+
const value = req.headers[name];
|
|
253
|
+
if (value === void 0) {
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
return Array.isArray(value) ? value[0] : value;
|
|
257
|
+
}
|
|
258
|
+
function createNestMiddleware(cipher) {
|
|
259
|
+
return (req, res, next) => {
|
|
260
|
+
const start = Date.now();
|
|
261
|
+
res.on("finish", () => {
|
|
262
|
+
cipher.logRequest({
|
|
263
|
+
method: req.method,
|
|
264
|
+
path: req.originalUrl || req.url,
|
|
265
|
+
status: res.statusCode,
|
|
266
|
+
duration: Date.now() - start,
|
|
267
|
+
ip: req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.ip || req.socket.remoteAddress,
|
|
268
|
+
userAgent: getHeader4(req, "user-agent"),
|
|
269
|
+
referer: getHeader4(req, "referer"),
|
|
270
|
+
protocol: req.protocol,
|
|
271
|
+
host: getHeader4(req, "host"),
|
|
272
|
+
query: parseQuery4(req.query),
|
|
273
|
+
requestId: getHeader4(req, "x-request-id")
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
next();
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/adapters/hono/middleware.ts
|
|
281
|
+
function getHeader5(request, name) {
|
|
282
|
+
if (!request) return void 0;
|
|
283
|
+
if (typeof request.header === "function") {
|
|
284
|
+
return request.header(name) ?? void 0;
|
|
285
|
+
}
|
|
286
|
+
if (request.headers && typeof request.headers.get === "function") {
|
|
287
|
+
return request.headers.get(name) ?? void 0;
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
function parseQuery5(urlString) {
|
|
292
|
+
try {
|
|
293
|
+
const url = new URL(urlString);
|
|
294
|
+
const result = {};
|
|
295
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
296
|
+
result[key] = value;
|
|
297
|
+
}
|
|
298
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
299
|
+
} catch {
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function createHonoMiddleware(cipher) {
|
|
304
|
+
return async (c, next) => {
|
|
305
|
+
const start = Date.now();
|
|
306
|
+
await next();
|
|
307
|
+
const req = c.req ?? c.request;
|
|
308
|
+
const url = typeof req?.url === "string" ? req.url : String(req?.url ?? "");
|
|
309
|
+
const parsed = new URL(url, "http://localhost");
|
|
310
|
+
let status = 200;
|
|
311
|
+
const cres = c.res ?? c.response ?? void 0;
|
|
312
|
+
if (cres && typeof cres.status === "number") {
|
|
313
|
+
status = cres.status;
|
|
314
|
+
} else if (cres && typeof cres.statusCode === "number") {
|
|
315
|
+
status = cres.statusCode;
|
|
316
|
+
}
|
|
317
|
+
const xfwd = getHeader5(req, "x-forwarded-for");
|
|
318
|
+
const ip = xfwd ? String(xfwd).split(",")[0].trim() : getHeader5(req, "x-real-ip") ?? void 0;
|
|
319
|
+
cipher.logRequest({
|
|
320
|
+
method: req?.method,
|
|
321
|
+
path: parsed.pathname + parsed.search,
|
|
322
|
+
status,
|
|
323
|
+
duration: Date.now() - start,
|
|
324
|
+
ip,
|
|
325
|
+
userAgent: getHeader5(req, "user-agent"),
|
|
326
|
+
referer: getHeader5(req, "referer"),
|
|
327
|
+
protocol: parsed.protocol.replace(":", ""),
|
|
328
|
+
host: getHeader5(req, "host"),
|
|
329
|
+
query: parseQuery5(url),
|
|
330
|
+
requestId: getHeader5(req, "x-request-id")
|
|
331
|
+
});
|
|
332
|
+
return cres ?? new Response(null, { status });
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
197
336
|
// src/core/create-cipher-logger.ts
|
|
198
337
|
function createCipherLogger(config = {}) {
|
|
199
338
|
const fields = resolveFieldConfig(config.fields);
|
|
@@ -212,6 +351,15 @@ function createCipherLogger(config = {}) {
|
|
|
212
351
|
},
|
|
213
352
|
next() {
|
|
214
353
|
return createNextMiddleware(cipherLogger);
|
|
354
|
+
},
|
|
355
|
+
fastify() {
|
|
356
|
+
return createFastifyMiddleware(cipherLogger);
|
|
357
|
+
},
|
|
358
|
+
nest() {
|
|
359
|
+
return createNestMiddleware(cipherLogger);
|
|
360
|
+
},
|
|
361
|
+
hono() {
|
|
362
|
+
return createHonoMiddleware(cipherLogger);
|
|
215
363
|
}
|
|
216
364
|
};
|
|
217
365
|
return cipherLogger;
|
|
@@ -223,6 +371,9 @@ export {
|
|
|
223
371
|
Logger,
|
|
224
372
|
createCipherLogger,
|
|
225
373
|
createExpressMiddleware,
|
|
374
|
+
createFastifyMiddleware,
|
|
375
|
+
createHonoMiddleware,
|
|
376
|
+
createNestMiddleware,
|
|
226
377
|
createNextMiddleware,
|
|
227
378
|
logger
|
|
228
379
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cipher-logger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "See every request. Capture every error. Understand your application.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
"nextjs-middleware",
|
|
44
44
|
"express",
|
|
45
45
|
"express-logger",
|
|
46
|
+
"fastify",
|
|
47
|
+
"fastify-logger",
|
|
48
|
+
"nest",
|
|
49
|
+
"nestjs",
|
|
50
|
+
"nestjs-logger",
|
|
51
|
+
"hono",
|
|
52
|
+
"hono-logger",
|
|
46
53
|
"cli",
|
|
47
54
|
"dashboard",
|
|
48
55
|
"cipherlogger",
|
|
@@ -54,7 +61,11 @@
|
|
|
54
61
|
"type": "commonjs",
|
|
55
62
|
"peerDependencies": {
|
|
56
63
|
"express": ">=4",
|
|
57
|
-
"next": ">=13"
|
|
64
|
+
"next": ">=13",
|
|
65
|
+
"fastify": ">=4",
|
|
66
|
+
"hono": ">=1",
|
|
67
|
+
"@nestjs/common": ">=10",
|
|
68
|
+
"@nestjs/core": ">=10"
|
|
58
69
|
},
|
|
59
70
|
"peerDependenciesMeta": {
|
|
60
71
|
"express": {
|
|
@@ -63,6 +74,19 @@
|
|
|
63
74
|
"next": {
|
|
64
75
|
"optional": true
|
|
65
76
|
}
|
|
77
|
+
,
|
|
78
|
+
"fastify": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"hono": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@nestjs/common": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"@nestjs/core": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
66
90
|
},
|
|
67
91
|
"devDependencies": {
|
|
68
92
|
"@types/express": "^5.0.3",
|