cipher-logger 0.0.1 → 0.1.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/.github/workflows/publish.yml +60 -0
- package/LICENSE +28 -0
- package/README.md +398 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +259 -0
- package/dist/index.mjs +228 -0
- package/package.json +71 -8
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup pnpm
|
|
21
|
+
uses: pnpm/action-setup@v4
|
|
22
|
+
with:
|
|
23
|
+
version: 10
|
|
24
|
+
|
|
25
|
+
- name: Setup Node.js
|
|
26
|
+
uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 24
|
|
29
|
+
registry-url: https://registry.npmjs.org
|
|
30
|
+
cache: pnpm
|
|
31
|
+
|
|
32
|
+
- name: Update npm
|
|
33
|
+
run: npm install -g npm@latest
|
|
34
|
+
|
|
35
|
+
- name: Check versions
|
|
36
|
+
run: |
|
|
37
|
+
echo "Node: $(node --version)"
|
|
38
|
+
echo "npm: $(npm --version)"
|
|
39
|
+
echo "pnpm: $(pnpm --version)"
|
|
40
|
+
|
|
41
|
+
- name: Install dependencies
|
|
42
|
+
run: pnpm install --frozen-lockfile
|
|
43
|
+
|
|
44
|
+
- name: Build
|
|
45
|
+
run: pnpm run build
|
|
46
|
+
|
|
47
|
+
- name: Publish
|
|
48
|
+
run: |
|
|
49
|
+
NAME=$(node -p "require('./package.json').name")
|
|
50
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
51
|
+
|
|
52
|
+
echo "Package: ${NAME}"
|
|
53
|
+
echo "Version: ${VERSION}"
|
|
54
|
+
|
|
55
|
+
if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
|
|
56
|
+
echo "Skipping publish: ${NAME}@${VERSION} already exists on npm"
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
npm publish --access public
|
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Cipher Logger
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# Cipher Logger
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>See every request. Capture every error. Understand your application.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/cipher-logger"><img src="https://img.shields.io/npm/v/cipher-logger.svg" alt="npm version"></a>
|
|
9
|
+
<a href="https://github.com/cipherunits/CipherLogger/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg" alt="license"></a>
|
|
10
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="node version"></a>
|
|
11
|
+
<a href="https://www.typescriptlang.org"><img src="https://img.shields.io/badge/TypeScript-ready-3178C6.svg" alt="typescript"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
**Cipher Logger** is a lightweight, production-ready HTTP logging library for Node.js. It captures every request in **Express** and **Next.js** applications. You decide exactly which fields appear in each log — required fields are always recorded, optional fields are opt-in.
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Features](#features)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick Start](#quick-start)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [Express](#express)
|
|
25
|
+
- [Next.js](#nextjs)
|
|
26
|
+
- [Log Schema](#log-schema)
|
|
27
|
+
- [API Reference](#api-reference)
|
|
28
|
+
- [Architecture](#architecture)
|
|
29
|
+
- [Local Development](#local-development)
|
|
30
|
+
- [Contributing](#contributing)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **TypeScript-first** — full type coverage across the entire API
|
|
38
|
+
- **Configurable fields** — fine-grained control over optional log fields
|
|
39
|
+
- **Express middleware** — records real `status` and `duration` after the response finishes
|
|
40
|
+
- **Next.js middleware** — drop-in support for `middleware.ts`
|
|
41
|
+
- **Zero heavy dependencies** — only `express` or `next` as optional peer dependencies
|
|
42
|
+
- **Node.js 18+** compatible
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install cipher-logger
|
|
50
|
+
# or
|
|
51
|
+
pnpm add cipher-logger
|
|
52
|
+
# or
|
|
53
|
+
yarn add cipher-logger
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Peer Dependencies
|
|
57
|
+
|
|
58
|
+
Install the framework you use:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Express
|
|
62
|
+
npm install express
|
|
63
|
+
|
|
64
|
+
# Next.js
|
|
65
|
+
npm install next
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { createCipherLogger } from "cipher-logger";
|
|
74
|
+
|
|
75
|
+
const cipher = createCipherLogger({
|
|
76
|
+
fields: {
|
|
77
|
+
ip: true,
|
|
78
|
+
userAgent: true,
|
|
79
|
+
query: true,
|
|
80
|
+
},
|
|
81
|
+
level: "info",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { createCipherLogger } from "cipher-logger";
|
|
91
|
+
|
|
92
|
+
const cipher = createCipherLogger({
|
|
93
|
+
// Optional fields — default: false (disabled)
|
|
94
|
+
fields: {
|
|
95
|
+
ip: true,
|
|
96
|
+
userAgent: true,
|
|
97
|
+
referer: false,
|
|
98
|
+
protocol: true,
|
|
99
|
+
host: true,
|
|
100
|
+
query: true,
|
|
101
|
+
requestId: true,
|
|
102
|
+
metadata: false,
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Log level: debug | info | warn | error
|
|
106
|
+
level: "info",
|
|
107
|
+
|
|
108
|
+
// Optional prefix in console output
|
|
109
|
+
prefix: "api",
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Option | Type | Default | Description |
|
|
114
|
+
|--------|------|---------|-------------|
|
|
115
|
+
| `fields` | `Partial<Record<OptionalRequestField, boolean>>` | all `false` | Optional log fields to include |
|
|
116
|
+
| `level` | `"debug" \| "info" \| "warn" \| "error"` | `"debug"` | Minimum log level |
|
|
117
|
+
| `prefix` | `string` | — | Prefix in console output |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Express
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import express from "express";
|
|
125
|
+
import { createCipherLogger } from "cipher-logger";
|
|
126
|
+
|
|
127
|
+
const app = express();
|
|
128
|
+
|
|
129
|
+
const cipher = createCipherLogger({
|
|
130
|
+
fields: {
|
|
131
|
+
ip: true,
|
|
132
|
+
userAgent: true,
|
|
133
|
+
query: true,
|
|
134
|
+
requestId: true,
|
|
135
|
+
},
|
|
136
|
+
level: "info",
|
|
137
|
+
prefix: "express",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Mount before your routes
|
|
141
|
+
app.use(cipher.express());
|
|
142
|
+
|
|
143
|
+
app.get("/users", (req, res) => {
|
|
144
|
+
res.json({ users: [] });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
app.listen(3000, () => {
|
|
148
|
+
console.log("Server running on http://localhost:3000");
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Sample Output
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
[2026-09-01T20:00:00.000Z] [INFO] [express] HTTP Request {
|
|
156
|
+
id: 'a1b2c3d4-...',
|
|
157
|
+
type: 'http',
|
|
158
|
+
timestamp: '2026-09-01T20:00:00.000Z',
|
|
159
|
+
method: 'GET',
|
|
160
|
+
path: '/users?page=1',
|
|
161
|
+
status: 200,
|
|
162
|
+
duration: 12,
|
|
163
|
+
ip: '::1',
|
|
164
|
+
userAgent: 'Mozilla/5.0 ...',
|
|
165
|
+
query: { page: '1' }
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> In Express, `status` and `duration` are recorded after `res.finish` and reflect the actual response.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Next.js
|
|
174
|
+
|
|
175
|
+
Create `middleware.ts` at the project root (or `src/middleware.ts`):
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { createCipherLogger } from "cipher-logger";
|
|
179
|
+
import type { NextRequest } from "next/server";
|
|
180
|
+
|
|
181
|
+
const cipher = createCipherLogger({
|
|
182
|
+
fields: {
|
|
183
|
+
ip: true,
|
|
184
|
+
userAgent: true,
|
|
185
|
+
host: true,
|
|
186
|
+
query: true,
|
|
187
|
+
},
|
|
188
|
+
level: "info",
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export function middleware(request: NextRequest) {
|
|
192
|
+
return cipher.next()(request);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const config = {
|
|
196
|
+
matcher: [
|
|
197
|
+
/*
|
|
198
|
+
* Match all routes except static files and images
|
|
199
|
+
*/
|
|
200
|
+
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
201
|
+
],
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Or more concisely:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { createCipherLogger } from "cipher-logger";
|
|
209
|
+
|
|
210
|
+
const cipher = createCipherLogger({
|
|
211
|
+
fields: { ip: true, userAgent: true },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
export default cipher.next();
|
|
215
|
+
|
|
216
|
+
export const config = {
|
|
217
|
+
matcher: ["/api/:path*", "/dashboard/:path*"],
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
> **Note:** Next.js middleware runs before the route handler, so `status` and `duration` reflect the middleware execution, not the final route response. Route handler wrappers for more accurate logging are planned for future releases.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Log Schema
|
|
226
|
+
|
|
227
|
+
### Required Fields
|
|
228
|
+
|
|
229
|
+
Always included in every HTTP log:
|
|
230
|
+
|
|
231
|
+
| Field | Type | Description |
|
|
232
|
+
|-------|------|-------------|
|
|
233
|
+
| `id` | `string` | Unique identifier (UUID) |
|
|
234
|
+
| `type` | `"http"` | Log type |
|
|
235
|
+
| `timestamp` | `string` | ISO 8601 timestamp |
|
|
236
|
+
| `method` | `string` | HTTP method |
|
|
237
|
+
| `path` | `string` | Request path |
|
|
238
|
+
| `status` | `number` | HTTP status code |
|
|
239
|
+
| `duration` | `number` | Duration in milliseconds |
|
|
240
|
+
|
|
241
|
+
### Optional Fields
|
|
242
|
+
|
|
243
|
+
Enabled via the `fields` config:
|
|
244
|
+
|
|
245
|
+
| Field | Type | Description |
|
|
246
|
+
|-------|------|-------------|
|
|
247
|
+
| `ip` | `string` | Client IP address |
|
|
248
|
+
| `userAgent` | `string` | User-Agent header |
|
|
249
|
+
| `referer` | `string` | Referer header |
|
|
250
|
+
| `protocol` | `string` | Protocol (`http` / `https`) |
|
|
251
|
+
| `host` | `string` | Host header |
|
|
252
|
+
| `query` | `Record<string, string>` | Query string parameters |
|
|
253
|
+
| `requestId` | `string` | From `x-request-id` header |
|
|
254
|
+
| `metadata` | `Record<string, unknown>` | Custom metadata |
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## API Reference
|
|
259
|
+
|
|
260
|
+
### `createCipherLogger(config?)`
|
|
261
|
+
|
|
262
|
+
Creates a Cipher Logger instance.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const cipher = createCipherLogger(config);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Methods:**
|
|
269
|
+
|
|
270
|
+
| Method | Description |
|
|
271
|
+
|--------|-------------|
|
|
272
|
+
| `cipher.logRequest(input)` | Manually log an HTTP request |
|
|
273
|
+
| `cipher.express()` | Returns Express middleware |
|
|
274
|
+
| `cipher.next()` | Returns Next.js middleware |
|
|
275
|
+
|
|
276
|
+
### `Logger`
|
|
277
|
+
|
|
278
|
+
Base logging class (independent of HTTP):
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { Logger } from "cipher-logger";
|
|
282
|
+
|
|
283
|
+
const logger = new Logger({ level: "info", prefix: "app" });
|
|
284
|
+
|
|
285
|
+
logger.info("Server started");
|
|
286
|
+
logger.warn("Deprecated API used", { route: "/old" });
|
|
287
|
+
logger.error("Unhandled error", { err: "..." });
|
|
288
|
+
logger.debug("Debug info");
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### TypeScript Exports
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import type {
|
|
295
|
+
CipherLogger,
|
|
296
|
+
CipherLoggerConfig,
|
|
297
|
+
RequestLog,
|
|
298
|
+
RequestLogInput,
|
|
299
|
+
OptionalRequestField,
|
|
300
|
+
LogLevel,
|
|
301
|
+
LoggerOptions,
|
|
302
|
+
ExpressMiddleware,
|
|
303
|
+
NextMiddleware,
|
|
304
|
+
} from "cipher-logger";
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Architecture
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
cipher-logger/
|
|
313
|
+
├── src/
|
|
314
|
+
│ ├── core/ # Core — field config & log building
|
|
315
|
+
│ │ ├── logger.ts
|
|
316
|
+
│ │ ├── types.ts
|
|
317
|
+
│ │ ├── build-request-log.ts
|
|
318
|
+
│ │ └── create-cipher-logger.ts
|
|
319
|
+
│ ├── express/ # Express adapter
|
|
320
|
+
│ │ └── middleware.ts
|
|
321
|
+
│ ├── next/ # Next.js adapter
|
|
322
|
+
│ │ └── middleware.ts
|
|
323
|
+
│ └── index.ts # Public entry point
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
┌─────────────────────────────────────────┐
|
|
328
|
+
│ Core │
|
|
329
|
+
│ fields config → buildRequestLog │
|
|
330
|
+
└──────────────────┬──────────────────────┘
|
|
331
|
+
│
|
|
332
|
+
┌─────────┴─────────┐
|
|
333
|
+
▼ ▼
|
|
334
|
+
┌───────────┐ ┌───────────┐
|
|
335
|
+
│ Express │ │ Next.js │
|
|
336
|
+
│ middleware│ │ middleware│
|
|
337
|
+
└───────────┘ └───────────┘
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Local Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
git clone https://github.com/cipherunits/CipherLogger.git
|
|
346
|
+
cd CipherLogger
|
|
347
|
+
pnpm install
|
|
348
|
+
pnpm run build
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Contributing
|
|
354
|
+
|
|
355
|
+
Contributions are welcome and appreciated.
|
|
356
|
+
|
|
357
|
+
### Report a Bug or Request a Feature
|
|
358
|
+
|
|
359
|
+
1. Check [Issues](https://github.com/cipherunits/CipherLogger/issues) first
|
|
360
|
+
2. If no existing issue matches, open a new one with a clear description
|
|
361
|
+
|
|
362
|
+
### Submit a Pull Request
|
|
363
|
+
|
|
364
|
+
1. Fork the repository
|
|
365
|
+
2. Create a branch: `git checkout -b feature/my-feature`
|
|
366
|
+
3. Commit your changes: `git commit -m "feat: add something useful"`
|
|
367
|
+
4. Push the branch: `git push origin feature/my-feature`
|
|
368
|
+
5. Open a Pull Request
|
|
369
|
+
|
|
370
|
+
### Guidelines
|
|
371
|
+
|
|
372
|
+
- Use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages
|
|
373
|
+
- Run `pnpm run build` before submitting a PR
|
|
374
|
+
- Keep changes focused and scoped
|
|
375
|
+
- Update the README for any API changes
|
|
376
|
+
|
|
377
|
+
### Contact
|
|
378
|
+
|
|
379
|
+
- **GitHub Issues:** [cipherunits/CipherLogger/issues](https://github.com/cipherunits/CipherLogger/issues)
|
|
380
|
+
- **Organization:** [CipherUnits](https://github.com/cipherunits)
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
This project is licensed under the [BSD 3-Clause License](LICENSE).
|
|
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
|
+
---
|
|
395
|
+
|
|
396
|
+
<p align="center">
|
|
397
|
+
Built with ❤️ by <a href="https://github.com/cipherunits">CipherUnits</a>
|
|
398
|
+
</p>
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
|
|
4
|
+
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
5
|
+
type LogMeta = Record<string, unknown>;
|
|
6
|
+
interface LoggerOptions {
|
|
7
|
+
level?: LogLevel;
|
|
8
|
+
prefix?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class Logger {
|
|
11
|
+
private readonly level;
|
|
12
|
+
private readonly prefix?;
|
|
13
|
+
constructor(options?: LoggerOptions);
|
|
14
|
+
private shouldLog;
|
|
15
|
+
private format;
|
|
16
|
+
private log;
|
|
17
|
+
info(message: string, meta?: LogMeta): void;
|
|
18
|
+
warn(message: string, meta?: LogMeta): void;
|
|
19
|
+
error(message: string, meta?: LogMeta): void;
|
|
20
|
+
debug(message: string, meta?: LogMeta): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare const OPTIONAL_REQUEST_FIELDS: readonly ["ip", "userAgent", "referer", "protocol", "host", "query", "requestId", "metadata"];
|
|
24
|
+
type OptionalRequestField = (typeof OPTIONAL_REQUEST_FIELDS)[number];
|
|
25
|
+
type RequestLog = {
|
|
26
|
+
id: string;
|
|
27
|
+
type: "http";
|
|
28
|
+
timestamp: string;
|
|
29
|
+
method: string;
|
|
30
|
+
path: string;
|
|
31
|
+
status: number;
|
|
32
|
+
duration: number;
|
|
33
|
+
ip?: string;
|
|
34
|
+
userAgent?: string;
|
|
35
|
+
referer?: string;
|
|
36
|
+
protocol?: string;
|
|
37
|
+
host?: string;
|
|
38
|
+
query?: Record<string, string>;
|
|
39
|
+
requestId?: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
type RequestLogInput = {
|
|
43
|
+
method: string;
|
|
44
|
+
path: string;
|
|
45
|
+
status: number;
|
|
46
|
+
duration: number;
|
|
47
|
+
ip?: string;
|
|
48
|
+
userAgent?: string;
|
|
49
|
+
referer?: string;
|
|
50
|
+
protocol?: string;
|
|
51
|
+
host?: string;
|
|
52
|
+
query?: Record<string, string>;
|
|
53
|
+
requestId?: string;
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
type CipherLoggerConfig = {
|
|
57
|
+
fields?: Partial<Record<OptionalRequestField, boolean>>;
|
|
58
|
+
level?: LogLevel;
|
|
59
|
+
prefix?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
63
|
+
declare function createExpressMiddleware(cipher: CipherLogger): ExpressMiddleware;
|
|
64
|
+
|
|
65
|
+
type NextMiddleware = (request: NextRequest) => NextResponse | Promise<NextResponse>;
|
|
66
|
+
declare function createNextMiddleware(cipher: CipherLogger): NextMiddleware;
|
|
67
|
+
|
|
68
|
+
interface CipherLogger {
|
|
69
|
+
logRequest(input: RequestLogInput): RequestLog;
|
|
70
|
+
express(): ExpressMiddleware;
|
|
71
|
+
next(): NextMiddleware;
|
|
72
|
+
}
|
|
73
|
+
declare function createCipherLogger(config?: CipherLoggerConfig): CipherLogger;
|
|
74
|
+
|
|
75
|
+
declare const logger: Logger;
|
|
76
|
+
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
|
|
4
|
+
type LogLevel = "info" | "warn" | "error" | "debug";
|
|
5
|
+
type LogMeta = Record<string, unknown>;
|
|
6
|
+
interface LoggerOptions {
|
|
7
|
+
level?: LogLevel;
|
|
8
|
+
prefix?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class Logger {
|
|
11
|
+
private readonly level;
|
|
12
|
+
private readonly prefix?;
|
|
13
|
+
constructor(options?: LoggerOptions);
|
|
14
|
+
private shouldLog;
|
|
15
|
+
private format;
|
|
16
|
+
private log;
|
|
17
|
+
info(message: string, meta?: LogMeta): void;
|
|
18
|
+
warn(message: string, meta?: LogMeta): void;
|
|
19
|
+
error(message: string, meta?: LogMeta): void;
|
|
20
|
+
debug(message: string, meta?: LogMeta): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare const OPTIONAL_REQUEST_FIELDS: readonly ["ip", "userAgent", "referer", "protocol", "host", "query", "requestId", "metadata"];
|
|
24
|
+
type OptionalRequestField = (typeof OPTIONAL_REQUEST_FIELDS)[number];
|
|
25
|
+
type RequestLog = {
|
|
26
|
+
id: string;
|
|
27
|
+
type: "http";
|
|
28
|
+
timestamp: string;
|
|
29
|
+
method: string;
|
|
30
|
+
path: string;
|
|
31
|
+
status: number;
|
|
32
|
+
duration: number;
|
|
33
|
+
ip?: string;
|
|
34
|
+
userAgent?: string;
|
|
35
|
+
referer?: string;
|
|
36
|
+
protocol?: string;
|
|
37
|
+
host?: string;
|
|
38
|
+
query?: Record<string, string>;
|
|
39
|
+
requestId?: string;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
};
|
|
42
|
+
type RequestLogInput = {
|
|
43
|
+
method: string;
|
|
44
|
+
path: string;
|
|
45
|
+
status: number;
|
|
46
|
+
duration: number;
|
|
47
|
+
ip?: string;
|
|
48
|
+
userAgent?: string;
|
|
49
|
+
referer?: string;
|
|
50
|
+
protocol?: string;
|
|
51
|
+
host?: string;
|
|
52
|
+
query?: Record<string, string>;
|
|
53
|
+
requestId?: string;
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
type CipherLoggerConfig = {
|
|
57
|
+
fields?: Partial<Record<OptionalRequestField, boolean>>;
|
|
58
|
+
level?: LogLevel;
|
|
59
|
+
prefix?: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
|
|
63
|
+
declare function createExpressMiddleware(cipher: CipherLogger): ExpressMiddleware;
|
|
64
|
+
|
|
65
|
+
type NextMiddleware = (request: NextRequest) => NextResponse | Promise<NextResponse>;
|
|
66
|
+
declare function createNextMiddleware(cipher: CipherLogger): NextMiddleware;
|
|
67
|
+
|
|
68
|
+
interface CipherLogger {
|
|
69
|
+
logRequest(input: RequestLogInput): RequestLog;
|
|
70
|
+
express(): ExpressMiddleware;
|
|
71
|
+
next(): NextMiddleware;
|
|
72
|
+
}
|
|
73
|
+
declare function createCipherLogger(config?: CipherLoggerConfig): CipherLogger;
|
|
74
|
+
|
|
75
|
+
declare const logger: Logger;
|
|
76
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Logger: () => Logger,
|
|
24
|
+
createCipherLogger: () => createCipherLogger,
|
|
25
|
+
createExpressMiddleware: () => createExpressMiddleware,
|
|
26
|
+
createNextMiddleware: () => createNextMiddleware,
|
|
27
|
+
logger: () => logger
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
|
|
31
|
+
// src/core/logger.ts
|
|
32
|
+
var LEVEL_PRIORITY = {
|
|
33
|
+
debug: 0,
|
|
34
|
+
info: 1,
|
|
35
|
+
warn: 2,
|
|
36
|
+
error: 3
|
|
37
|
+
};
|
|
38
|
+
var Logger = class {
|
|
39
|
+
level;
|
|
40
|
+
prefix;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.level = options.level ?? "debug";
|
|
43
|
+
this.prefix = options.prefix;
|
|
44
|
+
}
|
|
45
|
+
shouldLog(level) {
|
|
46
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.level];
|
|
47
|
+
}
|
|
48
|
+
format(level, message) {
|
|
49
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
50
|
+
const prefix = this.prefix ? ` [${this.prefix}]` : "";
|
|
51
|
+
return `[${timestamp}] [${level.toUpperCase()}]${prefix} ${message}`;
|
|
52
|
+
}
|
|
53
|
+
log(level, message, meta) {
|
|
54
|
+
if (!this.shouldLog(level)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const output = this.format(level, message);
|
|
58
|
+
switch (level) {
|
|
59
|
+
case "error":
|
|
60
|
+
meta ? console.error(output, meta) : console.error(output);
|
|
61
|
+
break;
|
|
62
|
+
case "warn":
|
|
63
|
+
meta ? console.warn(output, meta) : console.warn(output);
|
|
64
|
+
break;
|
|
65
|
+
case "debug":
|
|
66
|
+
meta ? console.debug(output, meta) : console.debug(output);
|
|
67
|
+
break;
|
|
68
|
+
case "info":
|
|
69
|
+
meta ? console.info(output, meta) : console.info(output);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
info(message, meta) {
|
|
74
|
+
this.log("info", message, meta);
|
|
75
|
+
}
|
|
76
|
+
warn(message, meta) {
|
|
77
|
+
this.log("warn", message, meta);
|
|
78
|
+
}
|
|
79
|
+
error(message, meta) {
|
|
80
|
+
this.log("error", message, meta);
|
|
81
|
+
}
|
|
82
|
+
debug(message, meta) {
|
|
83
|
+
this.log("debug", message, meta);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/core/build-request-log.ts
|
|
88
|
+
var import_node_crypto = require("crypto");
|
|
89
|
+
|
|
90
|
+
// src/core/types.ts
|
|
91
|
+
var OPTIONAL_REQUEST_FIELDS = [
|
|
92
|
+
"ip",
|
|
93
|
+
"userAgent",
|
|
94
|
+
"referer",
|
|
95
|
+
"protocol",
|
|
96
|
+
"host",
|
|
97
|
+
"query",
|
|
98
|
+
"requestId",
|
|
99
|
+
"metadata"
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// src/core/build-request-log.ts
|
|
103
|
+
function resolveFieldConfig(fields) {
|
|
104
|
+
const resolved = {};
|
|
105
|
+
for (const field of OPTIONAL_REQUEST_FIELDS) {
|
|
106
|
+
resolved[field] = fields?.[field] ?? false;
|
|
107
|
+
}
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
function buildRequestLog(fields, input) {
|
|
111
|
+
const log = {
|
|
112
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
113
|
+
type: "http",
|
|
114
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
115
|
+
method: input.method,
|
|
116
|
+
path: input.path,
|
|
117
|
+
status: input.status,
|
|
118
|
+
duration: input.duration
|
|
119
|
+
};
|
|
120
|
+
for (const field of OPTIONAL_REQUEST_FIELDS) {
|
|
121
|
+
if (!fields[field] || input[field] === void 0) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
switch (field) {
|
|
125
|
+
case "ip":
|
|
126
|
+
log.ip = input.ip;
|
|
127
|
+
break;
|
|
128
|
+
case "userAgent":
|
|
129
|
+
log.userAgent = input.userAgent;
|
|
130
|
+
break;
|
|
131
|
+
case "referer":
|
|
132
|
+
log.referer = input.referer;
|
|
133
|
+
break;
|
|
134
|
+
case "protocol":
|
|
135
|
+
log.protocol = input.protocol;
|
|
136
|
+
break;
|
|
137
|
+
case "host":
|
|
138
|
+
log.host = input.host;
|
|
139
|
+
break;
|
|
140
|
+
case "query":
|
|
141
|
+
log.query = input.query;
|
|
142
|
+
break;
|
|
143
|
+
case "requestId":
|
|
144
|
+
log.requestId = input.requestId;
|
|
145
|
+
break;
|
|
146
|
+
case "metadata":
|
|
147
|
+
log.metadata = input.metadata;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return log;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/express/middleware.ts
|
|
155
|
+
function parseQuery(query) {
|
|
156
|
+
const result = {};
|
|
157
|
+
for (const [key, value] of Object.entries(query)) {
|
|
158
|
+
if (value === void 0) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
result[key] = typeof value === "string" ? value : Array.isArray(value) ? String(value[0] ?? "") : String(value);
|
|
162
|
+
}
|
|
163
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
164
|
+
}
|
|
165
|
+
function getHeader(req, name) {
|
|
166
|
+
const value = req.headers[name];
|
|
167
|
+
if (value === void 0) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
return Array.isArray(value) ? value[0] : value;
|
|
171
|
+
}
|
|
172
|
+
function createExpressMiddleware(cipher) {
|
|
173
|
+
return (req, res, next) => {
|
|
174
|
+
const start = Date.now();
|
|
175
|
+
res.on("finish", () => {
|
|
176
|
+
cipher.logRequest({
|
|
177
|
+
method: req.method,
|
|
178
|
+
path: req.originalUrl || req.url,
|
|
179
|
+
status: res.statusCode,
|
|
180
|
+
duration: Date.now() - start,
|
|
181
|
+
ip: req.ip || req.socket.remoteAddress,
|
|
182
|
+
userAgent: getHeader(req, "user-agent"),
|
|
183
|
+
referer: getHeader(req, "referer"),
|
|
184
|
+
protocol: req.protocol,
|
|
185
|
+
host: getHeader(req, "host"),
|
|
186
|
+
query: parseQuery(req.query),
|
|
187
|
+
requestId: getHeader(req, "x-request-id")
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
next();
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/next/middleware.ts
|
|
195
|
+
var import_server = require("next/server");
|
|
196
|
+
function getHeader2(request, name) {
|
|
197
|
+
return request.headers.get(name) ?? void 0;
|
|
198
|
+
}
|
|
199
|
+
function parseQuery2(request) {
|
|
200
|
+
const result = {};
|
|
201
|
+
for (const [key, value] of request.nextUrl.searchParams.entries()) {
|
|
202
|
+
result[key] = value;
|
|
203
|
+
}
|
|
204
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
205
|
+
}
|
|
206
|
+
function createNextMiddleware(cipher) {
|
|
207
|
+
return (request) => {
|
|
208
|
+
const start = Date.now();
|
|
209
|
+
const response = import_server.NextResponse.next();
|
|
210
|
+
cipher.logRequest({
|
|
211
|
+
method: request.method,
|
|
212
|
+
path: request.nextUrl.pathname + request.nextUrl.search,
|
|
213
|
+
status: response.status,
|
|
214
|
+
duration: Date.now() - start,
|
|
215
|
+
ip: getHeader2(request, "x-forwarded-for")?.split(",")[0]?.trim() || getHeader2(request, "x-real-ip"),
|
|
216
|
+
userAgent: getHeader2(request, "user-agent"),
|
|
217
|
+
referer: getHeader2(request, "referer"),
|
|
218
|
+
protocol: request.nextUrl.protocol.replace(":", ""),
|
|
219
|
+
host: getHeader2(request, "host"),
|
|
220
|
+
query: parseQuery2(request),
|
|
221
|
+
requestId: getHeader2(request, "x-request-id")
|
|
222
|
+
});
|
|
223
|
+
return response;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/core/create-cipher-logger.ts
|
|
228
|
+
function createCipherLogger(config = {}) {
|
|
229
|
+
const fields = resolveFieldConfig(config.fields);
|
|
230
|
+
const logger2 = new Logger({
|
|
231
|
+
level: config.level,
|
|
232
|
+
prefix: config.prefix
|
|
233
|
+
});
|
|
234
|
+
const cipherLogger = {
|
|
235
|
+
logRequest(input) {
|
|
236
|
+
const log = buildRequestLog(fields, input);
|
|
237
|
+
logger2.info("HTTP Request", log);
|
|
238
|
+
return log;
|
|
239
|
+
},
|
|
240
|
+
express() {
|
|
241
|
+
return createExpressMiddleware(cipherLogger);
|
|
242
|
+
},
|
|
243
|
+
next() {
|
|
244
|
+
return createNextMiddleware(cipherLogger);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
return cipherLogger;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/index.ts
|
|
251
|
+
var logger = new Logger();
|
|
252
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
253
|
+
0 && (module.exports = {
|
|
254
|
+
Logger,
|
|
255
|
+
createCipherLogger,
|
|
256
|
+
createExpressMiddleware,
|
|
257
|
+
createNextMiddleware,
|
|
258
|
+
logger
|
|
259
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// src/core/logger.ts
|
|
2
|
+
var LEVEL_PRIORITY = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3
|
|
7
|
+
};
|
|
8
|
+
var Logger = class {
|
|
9
|
+
level;
|
|
10
|
+
prefix;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.level = options.level ?? "debug";
|
|
13
|
+
this.prefix = options.prefix;
|
|
14
|
+
}
|
|
15
|
+
shouldLog(level) {
|
|
16
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.level];
|
|
17
|
+
}
|
|
18
|
+
format(level, message) {
|
|
19
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
20
|
+
const prefix = this.prefix ? ` [${this.prefix}]` : "";
|
|
21
|
+
return `[${timestamp}] [${level.toUpperCase()}]${prefix} ${message}`;
|
|
22
|
+
}
|
|
23
|
+
log(level, message, meta) {
|
|
24
|
+
if (!this.shouldLog(level)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const output = this.format(level, message);
|
|
28
|
+
switch (level) {
|
|
29
|
+
case "error":
|
|
30
|
+
meta ? console.error(output, meta) : console.error(output);
|
|
31
|
+
break;
|
|
32
|
+
case "warn":
|
|
33
|
+
meta ? console.warn(output, meta) : console.warn(output);
|
|
34
|
+
break;
|
|
35
|
+
case "debug":
|
|
36
|
+
meta ? console.debug(output, meta) : console.debug(output);
|
|
37
|
+
break;
|
|
38
|
+
case "info":
|
|
39
|
+
meta ? console.info(output, meta) : console.info(output);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
info(message, meta) {
|
|
44
|
+
this.log("info", message, meta);
|
|
45
|
+
}
|
|
46
|
+
warn(message, meta) {
|
|
47
|
+
this.log("warn", message, meta);
|
|
48
|
+
}
|
|
49
|
+
error(message, meta) {
|
|
50
|
+
this.log("error", message, meta);
|
|
51
|
+
}
|
|
52
|
+
debug(message, meta) {
|
|
53
|
+
this.log("debug", message, meta);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/core/build-request-log.ts
|
|
58
|
+
import { randomUUID } from "crypto";
|
|
59
|
+
|
|
60
|
+
// src/core/types.ts
|
|
61
|
+
var OPTIONAL_REQUEST_FIELDS = [
|
|
62
|
+
"ip",
|
|
63
|
+
"userAgent",
|
|
64
|
+
"referer",
|
|
65
|
+
"protocol",
|
|
66
|
+
"host",
|
|
67
|
+
"query",
|
|
68
|
+
"requestId",
|
|
69
|
+
"metadata"
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// src/core/build-request-log.ts
|
|
73
|
+
function resolveFieldConfig(fields) {
|
|
74
|
+
const resolved = {};
|
|
75
|
+
for (const field of OPTIONAL_REQUEST_FIELDS) {
|
|
76
|
+
resolved[field] = fields?.[field] ?? false;
|
|
77
|
+
}
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
function buildRequestLog(fields, input) {
|
|
81
|
+
const log = {
|
|
82
|
+
id: randomUUID(),
|
|
83
|
+
type: "http",
|
|
84
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
85
|
+
method: input.method,
|
|
86
|
+
path: input.path,
|
|
87
|
+
status: input.status,
|
|
88
|
+
duration: input.duration
|
|
89
|
+
};
|
|
90
|
+
for (const field of OPTIONAL_REQUEST_FIELDS) {
|
|
91
|
+
if (!fields[field] || input[field] === void 0) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
switch (field) {
|
|
95
|
+
case "ip":
|
|
96
|
+
log.ip = input.ip;
|
|
97
|
+
break;
|
|
98
|
+
case "userAgent":
|
|
99
|
+
log.userAgent = input.userAgent;
|
|
100
|
+
break;
|
|
101
|
+
case "referer":
|
|
102
|
+
log.referer = input.referer;
|
|
103
|
+
break;
|
|
104
|
+
case "protocol":
|
|
105
|
+
log.protocol = input.protocol;
|
|
106
|
+
break;
|
|
107
|
+
case "host":
|
|
108
|
+
log.host = input.host;
|
|
109
|
+
break;
|
|
110
|
+
case "query":
|
|
111
|
+
log.query = input.query;
|
|
112
|
+
break;
|
|
113
|
+
case "requestId":
|
|
114
|
+
log.requestId = input.requestId;
|
|
115
|
+
break;
|
|
116
|
+
case "metadata":
|
|
117
|
+
log.metadata = input.metadata;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return log;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/express/middleware.ts
|
|
125
|
+
function parseQuery(query) {
|
|
126
|
+
const result = {};
|
|
127
|
+
for (const [key, value] of Object.entries(query)) {
|
|
128
|
+
if (value === void 0) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
result[key] = typeof value === "string" ? value : Array.isArray(value) ? String(value[0] ?? "") : String(value);
|
|
132
|
+
}
|
|
133
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
134
|
+
}
|
|
135
|
+
function getHeader(req, name) {
|
|
136
|
+
const value = req.headers[name];
|
|
137
|
+
if (value === void 0) {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
return Array.isArray(value) ? value[0] : value;
|
|
141
|
+
}
|
|
142
|
+
function createExpressMiddleware(cipher) {
|
|
143
|
+
return (req, res, next) => {
|
|
144
|
+
const start = Date.now();
|
|
145
|
+
res.on("finish", () => {
|
|
146
|
+
cipher.logRequest({
|
|
147
|
+
method: req.method,
|
|
148
|
+
path: req.originalUrl || req.url,
|
|
149
|
+
status: res.statusCode,
|
|
150
|
+
duration: Date.now() - start,
|
|
151
|
+
ip: req.ip || req.socket.remoteAddress,
|
|
152
|
+
userAgent: getHeader(req, "user-agent"),
|
|
153
|
+
referer: getHeader(req, "referer"),
|
|
154
|
+
protocol: req.protocol,
|
|
155
|
+
host: getHeader(req, "host"),
|
|
156
|
+
query: parseQuery(req.query),
|
|
157
|
+
requestId: getHeader(req, "x-request-id")
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
next();
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/next/middleware.ts
|
|
165
|
+
import { NextResponse } from "next/server";
|
|
166
|
+
function getHeader2(request, name) {
|
|
167
|
+
return request.headers.get(name) ?? void 0;
|
|
168
|
+
}
|
|
169
|
+
function parseQuery2(request) {
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const [key, value] of request.nextUrl.searchParams.entries()) {
|
|
172
|
+
result[key] = value;
|
|
173
|
+
}
|
|
174
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
175
|
+
}
|
|
176
|
+
function createNextMiddleware(cipher) {
|
|
177
|
+
return (request) => {
|
|
178
|
+
const start = Date.now();
|
|
179
|
+
const response = NextResponse.next();
|
|
180
|
+
cipher.logRequest({
|
|
181
|
+
method: request.method,
|
|
182
|
+
path: request.nextUrl.pathname + request.nextUrl.search,
|
|
183
|
+
status: response.status,
|
|
184
|
+
duration: Date.now() - start,
|
|
185
|
+
ip: getHeader2(request, "x-forwarded-for")?.split(",")[0]?.trim() || getHeader2(request, "x-real-ip"),
|
|
186
|
+
userAgent: getHeader2(request, "user-agent"),
|
|
187
|
+
referer: getHeader2(request, "referer"),
|
|
188
|
+
protocol: request.nextUrl.protocol.replace(":", ""),
|
|
189
|
+
host: getHeader2(request, "host"),
|
|
190
|
+
query: parseQuery2(request),
|
|
191
|
+
requestId: getHeader2(request, "x-request-id")
|
|
192
|
+
});
|
|
193
|
+
return response;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/core/create-cipher-logger.ts
|
|
198
|
+
function createCipherLogger(config = {}) {
|
|
199
|
+
const fields = resolveFieldConfig(config.fields);
|
|
200
|
+
const logger2 = new Logger({
|
|
201
|
+
level: config.level,
|
|
202
|
+
prefix: config.prefix
|
|
203
|
+
});
|
|
204
|
+
const cipherLogger = {
|
|
205
|
+
logRequest(input) {
|
|
206
|
+
const log = buildRequestLog(fields, input);
|
|
207
|
+
logger2.info("HTTP Request", log);
|
|
208
|
+
return log;
|
|
209
|
+
},
|
|
210
|
+
express() {
|
|
211
|
+
return createExpressMiddleware(cipherLogger);
|
|
212
|
+
},
|
|
213
|
+
next() {
|
|
214
|
+
return createNextMiddleware(cipherLogger);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return cipherLogger;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/index.ts
|
|
221
|
+
var logger = new Logger();
|
|
222
|
+
export {
|
|
223
|
+
Logger,
|
|
224
|
+
createCipherLogger,
|
|
225
|
+
createExpressMiddleware,
|
|
226
|
+
createNextMiddleware,
|
|
227
|
+
logger
|
|
228
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cipher-logger",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "index.js",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "See every request. Capture every error. Understand your application.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
6
8
|
"scripts": {
|
|
7
|
-
"
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
8
11
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/cipherunits/CipherLogger.git"
|
|
15
|
+
},
|
|
16
|
+
"author": "CipherUnits",
|
|
17
|
+
"license": "BSD-3-Clause",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/cipherunits/CipherLogger/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/cipherunits/CipherLogger#readme",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"logger",
|
|
27
|
+
"logging",
|
|
28
|
+
"log",
|
|
29
|
+
"logging-library",
|
|
30
|
+
"node-logger",
|
|
31
|
+
"nodejs-logger",
|
|
32
|
+
"javascript-logger",
|
|
33
|
+
"typescript-logger",
|
|
34
|
+
"http-logger",
|
|
35
|
+
"request-logger",
|
|
36
|
+
"error-logger",
|
|
37
|
+
"request-logging",
|
|
38
|
+
"error-logging",
|
|
39
|
+
"monitoring",
|
|
40
|
+
"observability",
|
|
41
|
+
"nextjs",
|
|
42
|
+
"nextjs-logger",
|
|
43
|
+
"nextjs-middleware",
|
|
44
|
+
"express",
|
|
45
|
+
"express-logger",
|
|
46
|
+
"cli",
|
|
47
|
+
"dashboard",
|
|
48
|
+
"cipherlogger",
|
|
49
|
+
"cipherunits"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
53
|
+
},
|
|
54
|
+
"type": "commonjs",
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"express": ">=4",
|
|
57
|
+
"next": ">=13"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"express": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"next": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/express": "^5.0.3",
|
|
69
|
+
"@types/node": "^26.1.2",
|
|
70
|
+
"express": "^5.1.0",
|
|
71
|
+
"next": "^15.5.2",
|
|
72
|
+
"tsup": "^8.5.1",
|
|
73
|
+
"typescript": "^6.0.3"
|
|
74
|
+
}
|
|
75
|
+
}
|