axiom-purifylog 1.0.0
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 +21 -0
- package/README.md +156 -0
- package/index.js +261 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alessandro Ghilardi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# axiom-purifylog
|
|
2
|
+
|
|
3
|
+
A privacy-first Node.js logger with automatic sensitive data redaction, file rotation, and a built-in HTTP log viewer.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔒 **Automatic Sensitive Data Redaction**: Recursively masks emails, UUIDs, long tokens, and blacklisted keys (passwords, API keys, IPs, etc.) in both strings and deep JSON objects.
|
|
8
|
+
- 🔄 **Built-in File Rotation**: Automatically rotates and suffixes log files when they reach the maximum size limit (defaults to 10MB).
|
|
9
|
+
- 📂 **Structured Level Directory Separation**: Automatically organizes logs into structured directories based on their level (`/logs/info`, `/logs/error`, etc.).
|
|
10
|
+
- 🌐 **On-the-fly HTTP Log Viewer**: Expose a lightweight endpoint to safely browse and read your raw log files directly from a browser or via API.
|
|
11
|
+
- 🔌 **Express Middleware**: Out-of-the-box HTTP request logging middleware with automatic `/status` health-check filtering.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install axiom-purifylog
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const logger = require('axiom-purifylog');
|
|
23
|
+
|
|
24
|
+
// Simple logs
|
|
25
|
+
logger.info('Application started successfully');
|
|
26
|
+
logger.warn('Database connection retrying...');
|
|
27
|
+
logger.error('Failed to load resource');
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Feature Deep Dive
|
|
31
|
+
|
|
32
|
+
### 1. Automatic Sensitive Data Masking (Redaction)
|
|
33
|
+
|
|
34
|
+
axiom-purifylog automatically intercepts strings and objects to sanitize sensitive information before printing to stdout or writing to disk.
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
logger.info({
|
|
38
|
+
message: "User login attempt",
|
|
39
|
+
email: "john.doe@example.com", // Will be redacted
|
|
40
|
+
metadata: {
|
|
41
|
+
ip: "192.168.1.1", // Will be redacted
|
|
42
|
+
sessionToken: "a-very-long-token-identifier-that-should-not-leak", // Will be redacted
|
|
43
|
+
nested: {
|
|
44
|
+
password: "super-secret-password-123", // Will be redacted
|
|
45
|
+
address: "123 Main St, Anytown, USA", // Will be redacted
|
|
46
|
+
active: true // Kept intact
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Output in Console / Log File:**
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"message": "User login attempt",
|
|
57
|
+
"email": "DATA-REDACTED",
|
|
58
|
+
"metadata": {
|
|
59
|
+
"ip": "DATA-REDACTED",
|
|
60
|
+
"sessionToken": "DATA-REDACTED",
|
|
61
|
+
"nested": {
|
|
62
|
+
"password": "DATA-REDACTED",
|
|
63
|
+
"active": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Default Redacted Keys & Patterns:**
|
|
70
|
+
|
|
71
|
+
- **Regex Patterns**: Emails, UUIDs, and strings longer than 30 characters matching token formats.
|
|
72
|
+
- **Sensitive Keys**: `to`, `email`, `username`, `subject`, `code`, `otp`, `password`, `pwd`, `secret`, `token`, `auth`, `uuid`, `phone`, `vat`, `device`, `address`, `key`, `host`, `database`, `port`, `env`, `node_version`, `ip`.
|
|
73
|
+
|
|
74
|
+
### 2. Configuration (.setup())
|
|
75
|
+
|
|
76
|
+
Configure the logger instance at the entry point of your application.
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
logger.setup({
|
|
80
|
+
logDir: 'custom-logs-folder', // Relative or absolute path (default: './logs')
|
|
81
|
+
maxFileSize: 5 * 1024 * 1024, // Max file size in bytes before rotation (default: 10MB)
|
|
82
|
+
minLevel: 'debug' // Minimum level to log: 'error' | 'warn' | 'info' | 'debug'
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Note**: You can also set the minimum logging level globally using the `LOG_LEVEL` environment variable.
|
|
87
|
+
|
|
88
|
+
### 3. Express Middleware
|
|
89
|
+
|
|
90
|
+
Easily log incoming HTTP requests. The middleware automatically measures response duration and skips noisy `/status` endpoints.
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const express = require('express');
|
|
94
|
+
const logger = require('axiom-purifylog');
|
|
95
|
+
|
|
96
|
+
const app = express();
|
|
97
|
+
|
|
98
|
+
app.use(logger.getRequestMiddleware());
|
|
99
|
+
|
|
100
|
+
app.get('/users', (req, res) => {
|
|
101
|
+
res.json({ ok: true });
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 4. Built-in HTTP Log Viewer
|
|
106
|
+
|
|
107
|
+
Expose an administrative endpoint to browse directories and read log files directly over HTTP. Path traversal attacks are prevented out-of-the-box by strict directory boundaries.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const http = require('http');
|
|
111
|
+
const logger = require('axiom-purifylog');
|
|
112
|
+
|
|
113
|
+
http.createServer((req, res) => {
|
|
114
|
+
// Route logs requests to the logger handler
|
|
115
|
+
if (req.url.startsWith('/admin/logs')) {
|
|
116
|
+
// Strip the custom prefix so the logger maps to the correct internal directories
|
|
117
|
+
req.url = req.url.replace('/admin/logs', '') || '/';
|
|
118
|
+
return logger.handleRequest(req, res);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
res.writeHead(404);
|
|
122
|
+
res.end('Not Found');
|
|
123
|
+
}).listen(3000, () => {
|
|
124
|
+
console.log('Admin log viewer available at http://localhost:3000/admin/logs');
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**API / Viewer Behavior:**
|
|
129
|
+
|
|
130
|
+
- `GET /`: Returns JSON list of available log level directories: `{"levels":["info","error"]}`
|
|
131
|
+
- `GET /info`: Returns JSON list of files in that folder: `{"category":"info","files":["info-2026-06-04.txt"]}`
|
|
132
|
+
- `GET /info/info-2026-06-04.txt`: Streams the raw log file back as `text/plain`.
|
|
133
|
+
|
|
134
|
+
## Log File Structure & Rotation
|
|
135
|
+
|
|
136
|
+
Logs are saved on disk using the following hierarchy:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
logs/
|
|
140
|
+
├── info/
|
|
141
|
+
│ ├── info-2026-06-04.txt
|
|
142
|
+
│ └── info-2026-06-04-1.txt (Rotated file)
|
|
143
|
+
├── error/
|
|
144
|
+
│ └── error-2026-06-04.txt
|
|
145
|
+
└── debug/
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Each line in the log file is a structured JSON string, making it easy to parse with log aggregators:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{"timestamp":"2026-06-04 18:45:00","timestamp_us":"06/04/2026, 18:45:00","level":"INFO","message":"Server listening on port 3000"}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
This project is licensed under the MIT License.
|
package/index.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const fs = require('node:fs').promises;
|
|
2
|
+
const fsSync = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A logger class that provides structured logging with automatic file rotation,
|
|
7
|
+
* sensitive data redaction, and Express middleware support.
|
|
8
|
+
* @class
|
|
9
|
+
*/
|
|
10
|
+
class Logger {
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new Logger instance with default configuration.
|
|
13
|
+
* @constructor
|
|
14
|
+
*/
|
|
15
|
+
constructor() {
|
|
16
|
+
this.baseLogDir = path.join(process.cwd(), 'logs');
|
|
17
|
+
this.maxFileSize = 10 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
this.priorities = { 'error': 0, 'warn': 1, 'info': 2, 'debug': 3 };
|
|
20
|
+
this.minLevel = process.env.LOG_LEVEL || 'info';
|
|
21
|
+
|
|
22
|
+
this.emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
23
|
+
this.uuidRegex = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
24
|
+
this.longTokenRegex = /\b[a-z0-9-_]{30,}/gi;
|
|
25
|
+
|
|
26
|
+
this.sensitiveKeys = [
|
|
27
|
+
'to', 'email', 'username', 'subject', 'code', 'otp',
|
|
28
|
+
'password', 'pwd', 'secret', 'token', 'auth', 'uuid',
|
|
29
|
+
'phone', 'vat', 'device', 'address', 'key', 'host', 'database',
|
|
30
|
+
'port', 'env', 'node_version', 'ip'
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configures the logger with custom settings.
|
|
36
|
+
* @param {Object} config - Configuration options
|
|
37
|
+
* @param {string} [config.logDir] - Directory for log files (relative or absolute path)
|
|
38
|
+
* @param {number} [config.maxFileSize] - Maximum file size in bytes before rotation (default: 10MB)
|
|
39
|
+
* @param {string} [config.minLevel] - Minimum log level ('error', 'warn', 'info', 'debug')
|
|
40
|
+
* @returns {Logger} The logger instance for method chaining
|
|
41
|
+
*/
|
|
42
|
+
setup(config = {}) {
|
|
43
|
+
if (config.logDir) {
|
|
44
|
+
this.baseLogDir = path.isAbsolute(config.logDir) ? config.logDir : path.join(process.cwd(), config.logDir);
|
|
45
|
+
}
|
|
46
|
+
if (config.maxFileSize) this.maxFileSize = config.maxFileSize;
|
|
47
|
+
if (config.minLevel && this.priorities[config.minLevel] !== undefined) {
|
|
48
|
+
this.minLevel = config.minLevel;
|
|
49
|
+
}
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Gets the current timestamp in ISO format without milliseconds.
|
|
55
|
+
* @private
|
|
56
|
+
* @returns {string} Formatted timestamp (YYYY-MM-DD HH:MM:SS)
|
|
57
|
+
*/
|
|
58
|
+
_getTimestamp() {
|
|
59
|
+
return new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Gets the current timestamp in US locale format.
|
|
64
|
+
* @private
|
|
65
|
+
* @returns {string} Formatted US timestamp (MM/DD/YYYY, HH:MM:SS)
|
|
66
|
+
*/
|
|
67
|
+
_getUSTimestamp() {
|
|
68
|
+
return new Date().toLocaleString('en-US', {
|
|
69
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
70
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Gets the current date as a tag for log file naming.
|
|
76
|
+
* @private
|
|
77
|
+
* @returns {string} Date tag in YYYY-MM-DD format
|
|
78
|
+
*/
|
|
79
|
+
_getDateTag() {
|
|
80
|
+
const d = new Date();
|
|
81
|
+
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Redacts sensitive information from log messages.
|
|
86
|
+
* Masks emails, UUIDs, long tokens, and sensitive key values.
|
|
87
|
+
* @private
|
|
88
|
+
* @param {string|Object} message - The message to redact
|
|
89
|
+
* @returns {string|Object} The redacted message
|
|
90
|
+
*/
|
|
91
|
+
_redact(message) {
|
|
92
|
+
const maskValue = (val) => {
|
|
93
|
+
if (typeof val === 'string') {
|
|
94
|
+
let m = val.replace(this.emailRegex, 'DATA-REDACTED').replace(this.uuidRegex, 'DATA-REDACTED');
|
|
95
|
+
if (m !== 'DATA-REDACTED' && m.length > 30) m = m.replace(this.longTokenRegex, 'DATA-REDACTED');
|
|
96
|
+
return m;
|
|
97
|
+
}
|
|
98
|
+
return val;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const recursiveRedact = (obj) => {
|
|
102
|
+
if (Array.isArray(obj)) return obj.map(item => recursiveRedact(item));
|
|
103
|
+
if (obj !== null && typeof obj === 'object') {
|
|
104
|
+
const newObj = {};
|
|
105
|
+
for (const key in obj) {
|
|
106
|
+
const lowKey = key.toLowerCase();
|
|
107
|
+
newObj[key] = this.sensitiveKeys.some(k => lowKey.includes(k)) ? 'DATA-REDACTED' : recursiveRedact(obj[key]);
|
|
108
|
+
}
|
|
109
|
+
return newObj;
|
|
110
|
+
}
|
|
111
|
+
return maskValue(obj);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
if (typeof message === 'object' && message !== null) {
|
|
116
|
+
return recursiveRedact(message);
|
|
117
|
+
}
|
|
118
|
+
return maskValue(String(message));
|
|
119
|
+
} catch (e) { return '[REDACTION_ERROR]'; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Internal logging method that handles message output and file writing.
|
|
124
|
+
* @private
|
|
125
|
+
* @async
|
|
126
|
+
* @param {string} level - The log level ('error', 'warn', 'info', 'debug')
|
|
127
|
+
* @param {string|Object} message - The message to log
|
|
128
|
+
*/
|
|
129
|
+
async _log(level, message) {
|
|
130
|
+
if (this.priorities[level] > this.priorities[this.minLevel]) return;
|
|
131
|
+
|
|
132
|
+
const tsStd = this._getTimestamp();
|
|
133
|
+
const tsUS = this._getUSTimestamp();
|
|
134
|
+
const redacted = this._redact(message);
|
|
135
|
+
|
|
136
|
+
const logObject = {
|
|
137
|
+
timestamp: tsStd,
|
|
138
|
+
timestamp_us: tsUS,
|
|
139
|
+
level: level.toUpperCase(),
|
|
140
|
+
message: redacted
|
|
141
|
+
};
|
|
142
|
+
const logEntry = JSON.stringify(logObject) + '\n';
|
|
143
|
+
|
|
144
|
+
const colors = { info: '\x1b[32m', error: '\x1b[31m', warn: '\x1b[33m', debug: '\x1b[36m', reset: '\x1b[0m' };
|
|
145
|
+
const color = colors[level] || colors.reset;
|
|
146
|
+
process.stdout.write(`${color}[${level.toUpperCase()}]${colors.reset} [${tsUS}] ${typeof redacted === 'object' ? JSON.stringify(redacted, null, 2) : redacted}\n`);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const dirPath = path.join(this.baseLogDir, level);
|
|
150
|
+
if (!fsSync.existsSync(dirPath)) await fs.mkdir(dirPath, { recursive: true });
|
|
151
|
+
|
|
152
|
+
const dateTag = this._getDateTag();
|
|
153
|
+
let filePath = path.join(dirPath, `${level}-${dateTag}.txt`);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const stats = await fs.stat(filePath);
|
|
157
|
+
if (stats.size >= this.maxFileSize) {
|
|
158
|
+
let suffix = 1;
|
|
159
|
+
while (fsSync.existsSync(path.join(dirPath, `${level}-${dateTag}-${suffix}.txt`))) suffix++;
|
|
160
|
+
filePath = path.join(dirPath, `${level}-${dateTag}-${suffix}.txt`);
|
|
161
|
+
}
|
|
162
|
+
} catch (e) { }
|
|
163
|
+
|
|
164
|
+
await fs.appendFile(filePath, logEntry, 'utf8');
|
|
165
|
+
} catch (err) {
|
|
166
|
+
process.stderr.write(`Logger Critical Error: ${err.message}\n`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Logs an info level message.
|
|
172
|
+
* @param {string|Object} msg - The message to log
|
|
173
|
+
*/
|
|
174
|
+
info(msg) { this._log('info', msg); }
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Logs an error level message.
|
|
178
|
+
* @param {string|Object} msg - The message to log
|
|
179
|
+
*/
|
|
180
|
+
error(msg) { this._log('error', msg); }
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Logs a warning level message.
|
|
184
|
+
* @param {string|Object} msg - The message to log
|
|
185
|
+
*/
|
|
186
|
+
warn(msg) { this._log('warn', msg); }
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Logs a debug level message.
|
|
190
|
+
* @param {string|Object} msg - The message to log
|
|
191
|
+
*/
|
|
192
|
+
debug(msg) { this._log('debug', msg); }
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns Express middleware for logging HTTP requests.
|
|
196
|
+
* Skips logging for /status endpoint.
|
|
197
|
+
* @returns {Function} Express middleware function
|
|
198
|
+
*/
|
|
199
|
+
getRequestMiddleware() {
|
|
200
|
+
return (req, res, next) => {
|
|
201
|
+
if (req.url === '/status' || req.path === '/status') {
|
|
202
|
+
return next();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const start = Date.now();
|
|
206
|
+
res.on('finish', () => {
|
|
207
|
+
const duration = Date.now() - start;
|
|
208
|
+
this.info({
|
|
209
|
+
event: 'HTTP_REQUEST',
|
|
210
|
+
method: req.method,
|
|
211
|
+
url: req.url,
|
|
212
|
+
status: res.statusCode,
|
|
213
|
+
duration: `${duration}ms`
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
next();
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handles HTTP requests for viewing log files and directories.
|
|
222
|
+
* Provides a simple API to browse and read log files.
|
|
223
|
+
* @param {Object} req - HTTP request object
|
|
224
|
+
* @param {Object} res - HTTP response object
|
|
225
|
+
*/
|
|
226
|
+
handleRequest(req, res) {
|
|
227
|
+
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
|
|
228
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
229
|
+
res.setHeader('Content-Type', 'application/json');
|
|
230
|
+
|
|
231
|
+
const safePath = path.resolve(this.baseLogDir, ...parts);
|
|
232
|
+
if (!safePath.startsWith(path.resolve(this.baseLogDir))) {
|
|
233
|
+
res.statusCode = 403;
|
|
234
|
+
return res.end(JSON.stringify({ error: 'Access Denied' }));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (parts.length === 0) {
|
|
238
|
+
const levels = fsSync.existsSync(this.baseLogDir) ? fsSync.readdirSync(this.baseLogDir) : [];
|
|
239
|
+
return res.end(JSON.stringify({ levels }));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!fsSync.existsSync(safePath)) {
|
|
243
|
+
res.statusCode = 404;
|
|
244
|
+
return res.end(JSON.stringify({ error: 'Not Found' }));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const stats = fsSync.statSync(safePath);
|
|
248
|
+
if (stats.isDirectory()) {
|
|
249
|
+
return res.end(JSON.stringify({ category: parts[parts.length - 1], files: fsSync.readdirSync(safePath) }));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (stats.isFile()) {
|
|
253
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
254
|
+
const readStream = fsSync.createReadStream(safePath);
|
|
255
|
+
readStream.on('error', () => { res.statusCode = 500; res.end("Read Error"); });
|
|
256
|
+
return readStream.pipe(res);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = new Logger();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "axiom-purifylog",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A privacy-first Node.js logger with automatic sensitive data redaction, file rotation, and a built-in HTTP log viewer.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"logger",
|
|
8
|
+
"logging",
|
|
9
|
+
"privacy",
|
|
10
|
+
"security",
|
|
11
|
+
"redaction",
|
|
12
|
+
"sensitive-data",
|
|
13
|
+
"file-rotation",
|
|
14
|
+
"log-viewer",
|
|
15
|
+
"http",
|
|
16
|
+
"nodejs",
|
|
17
|
+
"privacy-first",
|
|
18
|
+
"data-protection",
|
|
19
|
+
"gdpr",
|
|
20
|
+
"pii",
|
|
21
|
+
"personal-data"
|
|
22
|
+
],
|
|
23
|
+
"author": "Alessandro Ghilardi",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=14"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
]
|
|
33
|
+
}
|