elegant-logger 1.0.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/LICENSE +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +193 -0
- package/dist/index.mjs +167 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the same
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# elegant-logs
|
|
2
|
+
|
|
3
|
+
A professional, beautiful, and highly customizable Express request and error logger middleware for Node.js services.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎨 **Colorized Terminal Output**: Beautifully structured logs with ANSI colors based on HTTP method, response time, and status codes.
|
|
8
|
+
- 🔒 **Auto-Redaction**: Automatically redacts sensitive fields from request bodies (e.g., `password`, `token`, `secret`, `apiKey`, `authorization`).
|
|
9
|
+
- ⚡ **Precision Timing**: High-resolution response timers formatted with appropriate units (e.g., microseconds `μs`, milliseconds `ms`, or seconds `s`).
|
|
10
|
+
- ⚙️ **Customizable Log Content**: Optionally include or skip request body, query parameters, or HTTP headers.
|
|
11
|
+
- 🛠️ **Custom Loggers**: Easily hook into your log stream (e.g., to send structured logs to Datadog, Logstash, or Sentry).
|
|
12
|
+
- 🐛 **Elegant Error Logging**: Standardized middleware to print clean error stack traces in development.
|
|
13
|
+
- 📦 **Zero-Configuration Bundling**: Pre-packaged as both ES Module (ESM) and CommonJS (CJS) with full TypeScript definitions.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Install via your preferred package manager:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Using npm
|
|
23
|
+
npm install elegant-logs
|
|
24
|
+
|
|
25
|
+
# Using pnpm
|
|
26
|
+
pnpm add elegant-logs
|
|
27
|
+
|
|
28
|
+
# Using yarn
|
|
29
|
+
yarn add elegant-logs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
*Note: Since `elegant-logs` is designed specifically for Express apps, make sure you have `express` installed in your project.*
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Getting Started
|
|
37
|
+
|
|
38
|
+
### 1. Request Logger Middleware
|
|
39
|
+
|
|
40
|
+
Import and use the middleware in your Express application:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import express from 'express';
|
|
44
|
+
import { loggerMiddleware } from 'elegant-logs';
|
|
45
|
+
|
|
46
|
+
const app = express();
|
|
47
|
+
|
|
48
|
+
app.use(express.json());
|
|
49
|
+
|
|
50
|
+
// Apply logger middleware
|
|
51
|
+
app.use(loggerMiddleware({
|
|
52
|
+
includeBody: true, // Log request body (defaults to true)
|
|
53
|
+
includeQuery: true, // Log query parameters (defaults to true)
|
|
54
|
+
includeHeaders: false // Log headers (defaults to false)
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
app.get('/api/users', (req, res) => {
|
|
58
|
+
res.json({ ok: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
app.listen(3000, () => {
|
|
62
|
+
console.log('Server is running on http://localhost:3000');
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Error Logger Middleware
|
|
67
|
+
|
|
68
|
+
Add the error logger middleware **after** all of your application routes:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { errorLoggerMiddleware } from 'elegant-logs';
|
|
72
|
+
|
|
73
|
+
// ... your routes here ...
|
|
74
|
+
|
|
75
|
+
// Apply error logger
|
|
76
|
+
app.use(errorLoggerMiddleware);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Options
|
|
82
|
+
|
|
83
|
+
`loggerMiddleware` accepts an optional configuration object of type `LoggerOptions`:
|
|
84
|
+
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
| :--- | :--- | :--- | :--- |
|
|
87
|
+
| `skip` | `(req, res) => boolean` | `undefined` | A function to determine if a specific request should skip logging. |
|
|
88
|
+
| `includeBody` | `boolean` | `true` | When true, logs the JSON request body (redacting sensitive fields). |
|
|
89
|
+
| `includeQuery` | `boolean` | `true` | When true, logs the request query parameters. |
|
|
90
|
+
| `includeHeaders` | `boolean` | `false` | When true, logs the request headers. |
|
|
91
|
+
| `customLogger` | `(logData: LogData) => void` | `undefined` | A custom function to handle raw structured log objects (disables default console printing). |
|
|
92
|
+
|
|
93
|
+
### Custom Logger Example
|
|
94
|
+
|
|
95
|
+
If you want to send logs to an external service or a filesytem logger:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
app.use(loggerMiddleware({
|
|
99
|
+
customLogger: (logData) => {
|
|
100
|
+
// Send structured data to external metrics/log store
|
|
101
|
+
myTelemetryClient.send(logData);
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Log Data Schema
|
|
107
|
+
|
|
108
|
+
When using `customLogger`, your function receives a `LogData` object with the following schema:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface LogData {
|
|
112
|
+
timestamp: string; // ISO format string (YYYY-MM-DD HH:mm:ss)
|
|
113
|
+
method: string; // HTTP Method (GET, POST, etc.)
|
|
114
|
+
url: string; // The request URL path
|
|
115
|
+
statusCode: number; // The response status code
|
|
116
|
+
responseTime: number; // Response duration in milliseconds
|
|
117
|
+
clientIP: string; // Client IP address (considers X-Forwarded-For)
|
|
118
|
+
userAgent?: string; // The browser or client user agent header
|
|
119
|
+
body?: unknown; // The request body (sanitized)
|
|
120
|
+
query?: unknown; // The request query params
|
|
121
|
+
headers?: unknown; // The request headers
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
ISC License (see [LICENSE](LICENSE) or package details).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
interface LoggerOptions {
|
|
4
|
+
skip?: (req: Request, res: Response) => boolean;
|
|
5
|
+
includeBody?: boolean;
|
|
6
|
+
includeQuery?: boolean;
|
|
7
|
+
includeHeaders?: boolean;
|
|
8
|
+
customLogger?: (logData: LogData) => void;
|
|
9
|
+
}
|
|
10
|
+
interface LogData {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
method: string;
|
|
13
|
+
url: string;
|
|
14
|
+
statusCode: number;
|
|
15
|
+
responseTime: number;
|
|
16
|
+
clientIP: string;
|
|
17
|
+
userAgent?: string;
|
|
18
|
+
body?: unknown;
|
|
19
|
+
query?: unknown;
|
|
20
|
+
headers?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Professional Express logger middleware
|
|
24
|
+
* Logs HTTP requests with colors, timing, and structured data
|
|
25
|
+
*/
|
|
26
|
+
declare const loggerMiddleware: (options?: LoggerOptions) => (req: Request, res: Response, next: NextFunction) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Simple error logger middleware
|
|
29
|
+
* Should be used after all routes
|
|
30
|
+
*/
|
|
31
|
+
declare const errorLoggerMiddleware: (err: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
32
|
+
|
|
33
|
+
export { type LogData, type LoggerOptions, loggerMiddleware as default, errorLoggerMiddleware, loggerMiddleware };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
interface LoggerOptions {
|
|
4
|
+
skip?: (req: Request, res: Response) => boolean;
|
|
5
|
+
includeBody?: boolean;
|
|
6
|
+
includeQuery?: boolean;
|
|
7
|
+
includeHeaders?: boolean;
|
|
8
|
+
customLogger?: (logData: LogData) => void;
|
|
9
|
+
}
|
|
10
|
+
interface LogData {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
method: string;
|
|
13
|
+
url: string;
|
|
14
|
+
statusCode: number;
|
|
15
|
+
responseTime: number;
|
|
16
|
+
clientIP: string;
|
|
17
|
+
userAgent?: string;
|
|
18
|
+
body?: unknown;
|
|
19
|
+
query?: unknown;
|
|
20
|
+
headers?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Professional Express logger middleware
|
|
24
|
+
* Logs HTTP requests with colors, timing, and structured data
|
|
25
|
+
*/
|
|
26
|
+
declare const loggerMiddleware: (options?: LoggerOptions) => (req: Request, res: Response, next: NextFunction) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Simple error logger middleware
|
|
29
|
+
* Should be used after all routes
|
|
30
|
+
*/
|
|
31
|
+
declare const errorLoggerMiddleware: (err: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
32
|
+
|
|
33
|
+
export { type LogData, type LoggerOptions, loggerMiddleware as default, errorLoggerMiddleware, loggerMiddleware };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
// index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default,
|
|
24
|
+
errorLoggerMiddleware: () => errorLoggerMiddleware,
|
|
25
|
+
loggerMiddleware: () => loggerMiddleware
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var colors = {
|
|
29
|
+
reset: "\x1B[0m",
|
|
30
|
+
bright: "\x1B[1m",
|
|
31
|
+
dim: "\x1B[2m",
|
|
32
|
+
// Text colors
|
|
33
|
+
red: "\x1B[31m",
|
|
34
|
+
green: "\x1B[32m",
|
|
35
|
+
yellow: "\x1B[33m",
|
|
36
|
+
blue: "\x1B[34m",
|
|
37
|
+
magenta: "\x1B[35m",
|
|
38
|
+
cyan: "\x1B[36m",
|
|
39
|
+
white: "\x1B[37m",
|
|
40
|
+
gray: "\x1B[90m",
|
|
41
|
+
// Background colors
|
|
42
|
+
bgRed: "\x1B[41m",
|
|
43
|
+
bgGreen: "\x1B[42m",
|
|
44
|
+
bgYellow: "\x1B[43m",
|
|
45
|
+
bgBlue: "\x1B[44m",
|
|
46
|
+
bgMagenta: "\x1B[45m",
|
|
47
|
+
bgCyan: "\x1B[46m"
|
|
48
|
+
};
|
|
49
|
+
var methodColors = {
|
|
50
|
+
GET: colors.green,
|
|
51
|
+
POST: colors.blue,
|
|
52
|
+
PUT: colors.yellow,
|
|
53
|
+
PATCH: colors.magenta,
|
|
54
|
+
DELETE: colors.red,
|
|
55
|
+
OPTIONS: colors.cyan,
|
|
56
|
+
HEAD: colors.gray
|
|
57
|
+
};
|
|
58
|
+
var getStatusColor = (statusCode) => {
|
|
59
|
+
if (statusCode >= 500) return colors.red;
|
|
60
|
+
if (statusCode >= 400) return colors.yellow;
|
|
61
|
+
if (statusCode >= 300) return colors.cyan;
|
|
62
|
+
if (statusCode >= 200) return colors.green;
|
|
63
|
+
return colors.white;
|
|
64
|
+
};
|
|
65
|
+
var formatResponseTime = (ms) => {
|
|
66
|
+
if (ms < 1) return `${(ms * 1e3).toFixed(0)}\u03BCs`;
|
|
67
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
68
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
69
|
+
};
|
|
70
|
+
var getTimestamp = () => {
|
|
71
|
+
const now = /* @__PURE__ */ new Date();
|
|
72
|
+
return now.toISOString().replace("T", " ").substring(0, 19);
|
|
73
|
+
};
|
|
74
|
+
var getClientIP = (req) => {
|
|
75
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
76
|
+
if (typeof forwarded === "string") {
|
|
77
|
+
return forwarded.split(",")[0].trim();
|
|
78
|
+
}
|
|
79
|
+
return req.socket.remoteAddress || "unknown";
|
|
80
|
+
};
|
|
81
|
+
var truncate = (str, maxLength = 100) => {
|
|
82
|
+
if (str.length <= maxLength) return str;
|
|
83
|
+
return str.substring(0, maxLength) + "...";
|
|
84
|
+
};
|
|
85
|
+
var formatBody = (body) => {
|
|
86
|
+
if (!body || typeof body !== "object") return "";
|
|
87
|
+
const bodyObj = body;
|
|
88
|
+
if (Object.keys(bodyObj).length === 0) return "";
|
|
89
|
+
const sanitized = { ...bodyObj };
|
|
90
|
+
const sensitiveFields = [
|
|
91
|
+
"password",
|
|
92
|
+
"token",
|
|
93
|
+
"secret",
|
|
94
|
+
"apiKey",
|
|
95
|
+
"authorization"
|
|
96
|
+
];
|
|
97
|
+
for (const field of sensitiveFields) {
|
|
98
|
+
if (sanitized[field]) {
|
|
99
|
+
sanitized[field] = "[REDACTED]";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return truncate(JSON.stringify(sanitized), 200);
|
|
104
|
+
} catch {
|
|
105
|
+
return "[Unable to stringify body]";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var loggerMiddleware = (options = {}) => {
|
|
109
|
+
const {
|
|
110
|
+
skip,
|
|
111
|
+
includeBody = true,
|
|
112
|
+
includeQuery = true,
|
|
113
|
+
includeHeaders = false,
|
|
114
|
+
customLogger
|
|
115
|
+
} = options;
|
|
116
|
+
return (req, res, next) => {
|
|
117
|
+
const startTime = process.hrtime.bigint();
|
|
118
|
+
const originalEnd = res.end.bind(res);
|
|
119
|
+
res.end = function(chunk, encodingOrCb, cb) {
|
|
120
|
+
const endTime = process.hrtime.bigint();
|
|
121
|
+
const responseTimeNs = Number(endTime - startTime);
|
|
122
|
+
const responseTimeMs = responseTimeNs / 1e6;
|
|
123
|
+
if (skip && skip(req, res)) {
|
|
124
|
+
return originalEnd(chunk, encodingOrCb, cb);
|
|
125
|
+
}
|
|
126
|
+
const logData = {
|
|
127
|
+
timestamp: getTimestamp(),
|
|
128
|
+
method: req.method,
|
|
129
|
+
url: req.originalUrl || req.url,
|
|
130
|
+
statusCode: res.statusCode,
|
|
131
|
+
responseTime: responseTimeMs,
|
|
132
|
+
clientIP: getClientIP(req),
|
|
133
|
+
userAgent: req.headers["user-agent"]
|
|
134
|
+
};
|
|
135
|
+
if (includeBody && req.body && Object.keys(req.body).length > 0) {
|
|
136
|
+
logData.body = req.body;
|
|
137
|
+
}
|
|
138
|
+
if (includeQuery && req.query && Object.keys(req.query).length > 0) {
|
|
139
|
+
logData.query = req.query;
|
|
140
|
+
}
|
|
141
|
+
if (includeHeaders) {
|
|
142
|
+
logData.headers = req.headers;
|
|
143
|
+
}
|
|
144
|
+
if (customLogger) {
|
|
145
|
+
customLogger(logData);
|
|
146
|
+
} else {
|
|
147
|
+
const methodColor = methodColors[req.method] || colors.white;
|
|
148
|
+
const statusColor = getStatusColor(res.statusCode);
|
|
149
|
+
const timeFormatted = formatResponseTime(responseTimeMs);
|
|
150
|
+
const logParts = [
|
|
151
|
+
`${colors.gray}[${logData.timestamp}]${colors.reset}`,
|
|
152
|
+
`${methodColor}${colors.bright}${req.method.padEnd(7)}${colors.reset}`,
|
|
153
|
+
`${colors.white}${logData.url.padEnd(40)}${colors.reset}`,
|
|
154
|
+
`${statusColor}${colors.bright}${res.statusCode}${colors.reset}`,
|
|
155
|
+
`${colors.dim}${timeFormatted.padEnd(10)}${colors.reset}`,
|
|
156
|
+
`${colors.gray}${logData.clientIP}${colors.reset}`
|
|
157
|
+
];
|
|
158
|
+
console.log(logParts.join(" \u2502 "));
|
|
159
|
+
if (includeBody && logData.body) {
|
|
160
|
+
console.log(
|
|
161
|
+
`${colors.gray} \u2514\u2500 Body: ${formatBody(logData.body)}${colors.reset}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (includeQuery && logData.query) {
|
|
165
|
+
console.log(
|
|
166
|
+
`${colors.gray} \u2514\u2500 Query: ${JSON.stringify(logData.query)}${colors.reset}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return originalEnd(chunk, encodingOrCb, cb);
|
|
171
|
+
};
|
|
172
|
+
next();
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
var errorLoggerMiddleware = (err, req, res, next) => {
|
|
176
|
+
const timestamp = getTimestamp();
|
|
177
|
+
console.error(
|
|
178
|
+
`${colors.bgRed}${colors.white}${colors.bright} ERROR ${colors.reset}`,
|
|
179
|
+
`${colors.gray}[${timestamp}]${colors.reset}`,
|
|
180
|
+
`${colors.red}${req.method} ${req.url}${colors.reset}`
|
|
181
|
+
);
|
|
182
|
+
console.error(`${colors.red} \u2514\u2500 ${err.message}${colors.reset}`);
|
|
183
|
+
if (process.env.NODE_ENV !== "production") {
|
|
184
|
+
console.error(`${colors.dim}${err.stack}${colors.reset}`);
|
|
185
|
+
}
|
|
186
|
+
next(err);
|
|
187
|
+
};
|
|
188
|
+
var index_default = loggerMiddleware;
|
|
189
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
190
|
+
0 && (module.exports = {
|
|
191
|
+
errorLoggerMiddleware,
|
|
192
|
+
loggerMiddleware
|
|
193
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// index.ts
|
|
2
|
+
var colors = {
|
|
3
|
+
reset: "\x1B[0m",
|
|
4
|
+
bright: "\x1B[1m",
|
|
5
|
+
dim: "\x1B[2m",
|
|
6
|
+
// Text colors
|
|
7
|
+
red: "\x1B[31m",
|
|
8
|
+
green: "\x1B[32m",
|
|
9
|
+
yellow: "\x1B[33m",
|
|
10
|
+
blue: "\x1B[34m",
|
|
11
|
+
magenta: "\x1B[35m",
|
|
12
|
+
cyan: "\x1B[36m",
|
|
13
|
+
white: "\x1B[37m",
|
|
14
|
+
gray: "\x1B[90m",
|
|
15
|
+
// Background colors
|
|
16
|
+
bgRed: "\x1B[41m",
|
|
17
|
+
bgGreen: "\x1B[42m",
|
|
18
|
+
bgYellow: "\x1B[43m",
|
|
19
|
+
bgBlue: "\x1B[44m",
|
|
20
|
+
bgMagenta: "\x1B[45m",
|
|
21
|
+
bgCyan: "\x1B[46m"
|
|
22
|
+
};
|
|
23
|
+
var methodColors = {
|
|
24
|
+
GET: colors.green,
|
|
25
|
+
POST: colors.blue,
|
|
26
|
+
PUT: colors.yellow,
|
|
27
|
+
PATCH: colors.magenta,
|
|
28
|
+
DELETE: colors.red,
|
|
29
|
+
OPTIONS: colors.cyan,
|
|
30
|
+
HEAD: colors.gray
|
|
31
|
+
};
|
|
32
|
+
var getStatusColor = (statusCode) => {
|
|
33
|
+
if (statusCode >= 500) return colors.red;
|
|
34
|
+
if (statusCode >= 400) return colors.yellow;
|
|
35
|
+
if (statusCode >= 300) return colors.cyan;
|
|
36
|
+
if (statusCode >= 200) return colors.green;
|
|
37
|
+
return colors.white;
|
|
38
|
+
};
|
|
39
|
+
var formatResponseTime = (ms) => {
|
|
40
|
+
if (ms < 1) return `${(ms * 1e3).toFixed(0)}\u03BCs`;
|
|
41
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
42
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
43
|
+
};
|
|
44
|
+
var getTimestamp = () => {
|
|
45
|
+
const now = /* @__PURE__ */ new Date();
|
|
46
|
+
return now.toISOString().replace("T", " ").substring(0, 19);
|
|
47
|
+
};
|
|
48
|
+
var getClientIP = (req) => {
|
|
49
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
50
|
+
if (typeof forwarded === "string") {
|
|
51
|
+
return forwarded.split(",")[0].trim();
|
|
52
|
+
}
|
|
53
|
+
return req.socket.remoteAddress || "unknown";
|
|
54
|
+
};
|
|
55
|
+
var truncate = (str, maxLength = 100) => {
|
|
56
|
+
if (str.length <= maxLength) return str;
|
|
57
|
+
return str.substring(0, maxLength) + "...";
|
|
58
|
+
};
|
|
59
|
+
var formatBody = (body) => {
|
|
60
|
+
if (!body || typeof body !== "object") return "";
|
|
61
|
+
const bodyObj = body;
|
|
62
|
+
if (Object.keys(bodyObj).length === 0) return "";
|
|
63
|
+
const sanitized = { ...bodyObj };
|
|
64
|
+
const sensitiveFields = [
|
|
65
|
+
"password",
|
|
66
|
+
"token",
|
|
67
|
+
"secret",
|
|
68
|
+
"apiKey",
|
|
69
|
+
"authorization"
|
|
70
|
+
];
|
|
71
|
+
for (const field of sensitiveFields) {
|
|
72
|
+
if (sanitized[field]) {
|
|
73
|
+
sanitized[field] = "[REDACTED]";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return truncate(JSON.stringify(sanitized), 200);
|
|
78
|
+
} catch {
|
|
79
|
+
return "[Unable to stringify body]";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var loggerMiddleware = (options = {}) => {
|
|
83
|
+
const {
|
|
84
|
+
skip,
|
|
85
|
+
includeBody = true,
|
|
86
|
+
includeQuery = true,
|
|
87
|
+
includeHeaders = false,
|
|
88
|
+
customLogger
|
|
89
|
+
} = options;
|
|
90
|
+
return (req, res, next) => {
|
|
91
|
+
const startTime = process.hrtime.bigint();
|
|
92
|
+
const originalEnd = res.end.bind(res);
|
|
93
|
+
res.end = function(chunk, encodingOrCb, cb) {
|
|
94
|
+
const endTime = process.hrtime.bigint();
|
|
95
|
+
const responseTimeNs = Number(endTime - startTime);
|
|
96
|
+
const responseTimeMs = responseTimeNs / 1e6;
|
|
97
|
+
if (skip && skip(req, res)) {
|
|
98
|
+
return originalEnd(chunk, encodingOrCb, cb);
|
|
99
|
+
}
|
|
100
|
+
const logData = {
|
|
101
|
+
timestamp: getTimestamp(),
|
|
102
|
+
method: req.method,
|
|
103
|
+
url: req.originalUrl || req.url,
|
|
104
|
+
statusCode: res.statusCode,
|
|
105
|
+
responseTime: responseTimeMs,
|
|
106
|
+
clientIP: getClientIP(req),
|
|
107
|
+
userAgent: req.headers["user-agent"]
|
|
108
|
+
};
|
|
109
|
+
if (includeBody && req.body && Object.keys(req.body).length > 0) {
|
|
110
|
+
logData.body = req.body;
|
|
111
|
+
}
|
|
112
|
+
if (includeQuery && req.query && Object.keys(req.query).length > 0) {
|
|
113
|
+
logData.query = req.query;
|
|
114
|
+
}
|
|
115
|
+
if (includeHeaders) {
|
|
116
|
+
logData.headers = req.headers;
|
|
117
|
+
}
|
|
118
|
+
if (customLogger) {
|
|
119
|
+
customLogger(logData);
|
|
120
|
+
} else {
|
|
121
|
+
const methodColor = methodColors[req.method] || colors.white;
|
|
122
|
+
const statusColor = getStatusColor(res.statusCode);
|
|
123
|
+
const timeFormatted = formatResponseTime(responseTimeMs);
|
|
124
|
+
const logParts = [
|
|
125
|
+
`${colors.gray}[${logData.timestamp}]${colors.reset}`,
|
|
126
|
+
`${methodColor}${colors.bright}${req.method.padEnd(7)}${colors.reset}`,
|
|
127
|
+
`${colors.white}${logData.url.padEnd(40)}${colors.reset}`,
|
|
128
|
+
`${statusColor}${colors.bright}${res.statusCode}${colors.reset}`,
|
|
129
|
+
`${colors.dim}${timeFormatted.padEnd(10)}${colors.reset}`,
|
|
130
|
+
`${colors.gray}${logData.clientIP}${colors.reset}`
|
|
131
|
+
];
|
|
132
|
+
console.log(logParts.join(" \u2502 "));
|
|
133
|
+
if (includeBody && logData.body) {
|
|
134
|
+
console.log(
|
|
135
|
+
`${colors.gray} \u2514\u2500 Body: ${formatBody(logData.body)}${colors.reset}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (includeQuery && logData.query) {
|
|
139
|
+
console.log(
|
|
140
|
+
`${colors.gray} \u2514\u2500 Query: ${JSON.stringify(logData.query)}${colors.reset}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return originalEnd(chunk, encodingOrCb, cb);
|
|
145
|
+
};
|
|
146
|
+
next();
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
var errorLoggerMiddleware = (err, req, res, next) => {
|
|
150
|
+
const timestamp = getTimestamp();
|
|
151
|
+
console.error(
|
|
152
|
+
`${colors.bgRed}${colors.white}${colors.bright} ERROR ${colors.reset}`,
|
|
153
|
+
`${colors.gray}[${timestamp}]${colors.reset}`,
|
|
154
|
+
`${colors.red}${req.method} ${req.url}${colors.reset}`
|
|
155
|
+
);
|
|
156
|
+
console.error(`${colors.red} \u2514\u2500 ${err.message}${colors.reset}`);
|
|
157
|
+
if (process.env.NODE_ENV !== "production") {
|
|
158
|
+
console.error(`${colors.dim}${err.stack}${colors.reset}`);
|
|
159
|
+
}
|
|
160
|
+
next(err);
|
|
161
|
+
};
|
|
162
|
+
var index_default = loggerMiddleware;
|
|
163
|
+
export {
|
|
164
|
+
index_default as default,
|
|
165
|
+
errorLoggerMiddleware,
|
|
166
|
+
loggerMiddleware
|
|
167
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "elegant-logger",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A professional, beautiful, and highly customizable Express request and error logger middleware for Node.js services.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"express",
|
|
20
|
+
"logger",
|
|
21
|
+
"middleware",
|
|
22
|
+
"typescript",
|
|
23
|
+
"ansi-colors",
|
|
24
|
+
"request-logger"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/express": "^5.0.6",
|
|
30
|
+
"express": "^5.2.1",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^6.0.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup index.ts --format cjs,esm --dts --clean",
|
|
39
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
40
|
+
}
|
|
41
|
+
}
|