scharff 1.3.0 → 2.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/README.md +132 -34
- package/config/default.js +15 -0
- package/constants/functions.js +8 -1
- package/index.js +145 -51
- package/package.json +17 -2
- package/types/index.d.ts +15 -0
package/README.md
CHANGED
|
@@ -1,39 +1,137 @@
|
|
|
1
|
-
# Scharff
|
|
2
|
-
Scharff is a lightweight
|
|
1
|
+
# Scharff
|
|
2
|
+
Scharff is a lightweight Node.js interceptor that logs all `fetch` activity — outgoing requests, incoming responses, and related errors. It gives you clear, file-based visibility into your app's HTTP interactions with minimal setup.
|
|
3
3
|
|
|
4
4
|
## Installation
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install scharff
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Or clone from GitHub:
|
|
10
|
+
```bash
|
|
11
|
+
git clone https://github.com/Minka1902/scharff.git
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
CommonJS:
|
|
16
|
+
```js
|
|
17
|
+
const listen = require('scharff');
|
|
18
|
+
|
|
19
|
+
// Optional: override destinations
|
|
20
|
+
listen.updateConfig({ outgoingLog: './logs/outgoing.log' });
|
|
21
|
+
|
|
22
|
+
// Stop logging later
|
|
23
|
+
listen();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
TypeScript / ESM:
|
|
27
|
+
```ts
|
|
28
|
+
import listen = require('scharff');
|
|
29
|
+
|
|
30
|
+
listen.updateConfig({ incomingLog: './logs/incoming.log' });
|
|
31
|
+
// ... your app code
|
|
32
|
+
listen(); // unregister
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What to Expect
|
|
36
|
+
1) The package creates newline-delimited JSON (NDJSON) logs (defaults):
|
|
37
|
+
- `outgoingRequest.log`
|
|
38
|
+
- `outgoingRequestError.log`
|
|
39
|
+
- `incomingResponse.log`
|
|
40
|
+
- `incomingResponseError.log`
|
|
41
|
+
2) Each `fetch` call is logged; request errors and response errors are separated.
|
|
42
|
+
3) Example outgoing request entry:
|
|
43
|
+
```jsx
|
|
44
|
+
{
|
|
45
|
+
"url":"http://127.0.0.1:3000/update/www.example.com",
|
|
46
|
+
"ip":"192.168.1.10",
|
|
47
|
+
"date":{
|
|
48
|
+
"date":"2025-12-22",
|
|
49
|
+
"time":"14:05:10"
|
|
50
|
+
},
|
|
51
|
+
"originUrl":"/update/www.example.com",
|
|
52
|
+
"method":"PUT",
|
|
53
|
+
"headers":{
|
|
54
|
+
"Content-Type":"application/json",
|
|
55
|
+
"Access-Control-Allow-Origin":"*"
|
|
56
|
+
},
|
|
57
|
+
"body":{
|
|
58
|
+
"isActive":true,
|
|
59
|
+
"status":200,
|
|
60
|
+
"lastChecked":"2025-12-22T14:05:10.200Z"
|
|
61
|
+
}
|
|
31
62
|
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## New in this release
|
|
66
|
+
1) Added request error logging
|
|
67
|
+
2) Added response logging
|
|
68
|
+
3) Added response error logging
|
|
69
|
+
4) Made log file names configurable via environment variables
|
|
70
|
+
5) Added runtime config API: `listen.updateConfig()` and `listen.getConfig()`
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
- Node.js v18+ (for native `fetch` and modern APIs)
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
Default log paths live in `config/default.js`. You can override them with environment variables or at runtime:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
SCHARFF_OUTGOING_LOG=./outgoingRequest.log
|
|
80
|
+
SCHARFF_OUTGOING_ERROR_LOG=./outgoingRequestError.log
|
|
81
|
+
SCHARFF_INCOMING_LOG=./incomingResponse.log
|
|
82
|
+
SCHARFF_INCOMING_ERROR_LOG=./incomingResponseError.log
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Runtime Overrides
|
|
86
|
+
```js
|
|
87
|
+
const listen = require('scharff');
|
|
88
|
+
|
|
89
|
+
// Change any log destination at runtime
|
|
90
|
+
listen.updateConfig({
|
|
91
|
+
outgoingLog: './logs/outgoing.log',
|
|
92
|
+
outgoingErrorLog: './logs/outgoing-errors.log',
|
|
93
|
+
incomingLog: './logs/incoming.log',
|
|
94
|
+
incomingErrorLog: './logs/incoming-errors.log'
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Inspect current config
|
|
98
|
+
console.log(listen.getConfig());
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API
|
|
102
|
+
- `listen()` – unregisters all interceptors (stops logging)
|
|
103
|
+
- `listen.updateConfig(overrides)` – update any of `outgoingLog`, `outgoingErrorLog`, `incomingLog`, `incomingErrorLog`
|
|
104
|
+
- `listen.getConfig()` – returns the current effective config
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
- **Outgoing request logging**: URL, method, headers, body, local IP, timestamps
|
|
108
|
+
- **Request error logging**: errors before a request is sent
|
|
109
|
+
- **Incoming response logging**: status, URL, redirects, timestamps
|
|
110
|
+
- **Response error logging**: errors from failed responses
|
|
111
|
+
- **Configurable destinations**: via env vars or runtime API
|
|
112
|
+
- **Runtime configuration**: update settings without restarting
|
|
113
|
+
- **IPv4 detection**: includes your machine’s IPv4 in entries
|
|
114
|
+
- **Safe JSON parsing**: gracefully handles non‑JSON bodies
|
|
115
|
+
- **Base URL stripping**: store clean paths alongside full URLs
|
|
116
|
+
- **TypeScript types**: ships `dist/index.d.ts`
|
|
117
|
+
|
|
118
|
+
## Project Structure
|
|
119
|
+
```
|
|
120
|
+
scharff/
|
|
121
|
+
├── src/
|
|
122
|
+
│ ├── config/default.ts # Configuration defaults and loader
|
|
123
|
+
│ ├── constants/functions.ts # Helper utilities (URL manipulation)
|
|
124
|
+
│ ├── index.ts # Main interceptor and API
|
|
125
|
+
│ └── types/fetch-intercept.d.ts
|
|
126
|
+
├── dist/ # Build output (generated by `npm run build`)
|
|
127
|
+
├── package.json
|
|
128
|
+
├── tsconfig.json
|
|
129
|
+
└── README.md
|
|
130
|
+
```
|
|
32
131
|
|
|
33
|
-
##
|
|
34
|
-
|
|
132
|
+
## Development / Publish
|
|
133
|
+
- Build: `npm run build` (outputs to `dist/`)
|
|
134
|
+
- Publish: `npm publish --access public` (runs build via `prepare`)
|
|
135
|
+
- Entry points: `main` → `dist/index.js`, `types` → `dist/index.d.ts`
|
|
35
136
|
|
|
36
|
-
|
|
37
|
-
1) We will add a request error logger.
|
|
38
|
-
2) We will add a response logger.
|
|
39
|
-
3) We will add a response error logger.
|
|
137
|
+
Tip: Logs are newline‑delimited JSON (NDJSON). Use tools like `jq`, `ripgrep`, or `grep` to filter and analyze.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const defaults = {
|
|
2
|
+
outgoingLog: './outgoingRequest.log',
|
|
3
|
+
outgoingErrorLog: './outgoingRequestError.log',
|
|
4
|
+
incomingLog: './incomingResponse.log',
|
|
5
|
+
incomingErrorLog: './incomingResponseError.log'
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const loadConfig = () => ({
|
|
9
|
+
outgoingLog: process.env.SCHARFF_OUTGOING_LOG || defaults.outgoingLog,
|
|
10
|
+
outgoingErrorLog: process.env.SCHARFF_OUTGOING_ERROR_LOG || defaults.outgoingErrorLog,
|
|
11
|
+
incomingLog: process.env.SCHARFF_INCOMING_LOG || defaults.incomingLog,
|
|
12
|
+
incomingErrorLog: process.env.SCHARFF_INCOMING_ERROR_LOG || defaults.incomingErrorLog
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
module.exports = { loadConfig, defaults };
|
package/constants/functions.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
module.exports.removeBaseUrl = (url) => {
|
|
2
|
-
if (url) {
|
|
2
|
+
if (typeof url !== 'object') {
|
|
3
3
|
const indexOfEnd = url.indexOf('/', 7);
|
|
4
4
|
if (indexOfEnd === -1) {
|
|
5
5
|
return url;
|
|
6
6
|
} else {
|
|
7
7
|
return url.slice(indexOfEnd, url.length);
|
|
8
8
|
}
|
|
9
|
+
} else {
|
|
10
|
+
const indexOfEnd = url.url.indexOf('/', 7);
|
|
11
|
+
if (indexOfEnd === -1) {
|
|
12
|
+
return url.url;
|
|
13
|
+
} else {
|
|
14
|
+
return url.url.slice(indexOfEnd, url.url.length);
|
|
15
|
+
}
|
|
9
16
|
}
|
|
10
17
|
};
|
package/index.js
CHANGED
|
@@ -1,66 +1,160 @@
|
|
|
1
|
-
const fetchIntercept = require('fetch-intercept');
|
|
2
|
-
const fs = require('fs');
|
|
3
1
|
const { removeBaseUrl } = require('./constants/functions');
|
|
2
|
+
const fetchIntercept = require('fetch-intercept');
|
|
3
|
+
const fs = require('fs').promises;
|
|
4
4
|
const os = require('os');
|
|
5
|
+
const { loadConfig } = require('./config/default');
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const allowedConfigKeys = ['outgoingLog', 'outgoingErrorLog', 'incomingLog', 'incomingErrorLog'];
|
|
9
|
+
|
|
10
|
+
const logFileMap = {
|
|
11
|
+
request: 'outgoingLog',
|
|
12
|
+
requestError: 'outgoingErrorLog',
|
|
13
|
+
response: 'incomingLog',
|
|
14
|
+
responseError: 'incomingErrorLog'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const logEvent = async (kind, payload) => {
|
|
18
|
+
const appendLogEntry = async (filePath, payload) => {
|
|
19
|
+
await fs.appendFile(filePath, JSON.stringify(payload) + '\n', 'utf8');
|
|
20
|
+
};
|
|
21
|
+
const configKey = logFileMap[kind];
|
|
22
|
+
if (!configKey || !config[configKey]) return;
|
|
23
|
+
await appendLogEntry(config[configKey], payload);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getIpv4Addresses = () => {
|
|
27
|
+
const interfaces = os.networkInterfaces();
|
|
28
|
+
const addresses = [];
|
|
29
|
+
|
|
30
|
+
for (const iface in interfaces) {
|
|
31
|
+
for (const addr of interfaces[iface]) {
|
|
32
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
33
|
+
addresses.push(addr.address);
|
|
17
34
|
}
|
|
18
35
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return addresses;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const safeParseBody = (body) => {
|
|
42
|
+
if (!body) return undefined;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(body);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return body;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const updateConfig = (overrides = {}) => {
|
|
51
|
+
if (!overrides || typeof overrides !== 'object') return { ...config };
|
|
52
|
+
|
|
53
|
+
for (const key of allowedConfigKeys) {
|
|
54
|
+
const value = overrides[key];
|
|
55
|
+
if (typeof value === 'string' && value.trim()) {
|
|
56
|
+
config[key] = value.trim();
|
|
25
57
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { ...config };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const listen = fetchIntercept.register({
|
|
64
|
+
request: async function (url, config) {
|
|
65
|
+
try {
|
|
66
|
+
const addresses = getIpv4Addresses();
|
|
67
|
+
|
|
68
|
+
const tempReq = {
|
|
69
|
+
type: 'Outgoing_Request',
|
|
70
|
+
url,
|
|
71
|
+
ip: addresses[0],
|
|
72
|
+
date: {
|
|
73
|
+
date: new Date().toLocaleDateString(),
|
|
74
|
+
time: new Date().toLocaleTimeString()
|
|
75
|
+
},
|
|
76
|
+
method: config?.method || 'GET',
|
|
77
|
+
headers: config?.headers,
|
|
78
|
+
body: safeParseBody(config?.body)
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const cleanUrl = removeBaseUrl(url);
|
|
82
|
+
if (cleanUrl !== url) {
|
|
83
|
+
tempReq.originUrl = cleanUrl;
|
|
39
84
|
}
|
|
40
|
-
}
|
|
41
85
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
});
|
|
86
|
+
await logEvent('request', tempReq);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Request logging error:', error);
|
|
89
|
+
}
|
|
48
90
|
|
|
49
91
|
return [url, config];
|
|
50
92
|
},
|
|
51
93
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
requestError: async function (error) {
|
|
95
|
+
try {
|
|
96
|
+
const payload = {
|
|
97
|
+
type: 'Outgoing_Request_Error',
|
|
98
|
+
message: error?.message,
|
|
99
|
+
stack: error?.stack,
|
|
100
|
+
date: {
|
|
101
|
+
date: new Date().toLocaleDateString(),
|
|
102
|
+
time: new Date().toLocaleTimeString()
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
await logEvent('requestError', payload);
|
|
106
|
+
} catch (logError) {
|
|
107
|
+
console.error('Request error logging failed:', logError);
|
|
108
|
+
}
|
|
56
109
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
110
|
+
return Promise.reject(error);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
response: async function (response) {
|
|
114
|
+
try {
|
|
115
|
+
const payload = {
|
|
116
|
+
type: 'Incoming_Response',
|
|
117
|
+
url: response?.url,
|
|
118
|
+
status: response?.status,
|
|
119
|
+
statusText: response?.statusText,
|
|
120
|
+
ok: response?.ok,
|
|
121
|
+
redirected: response?.redirected,
|
|
122
|
+
date: {
|
|
123
|
+
date: new Date().toLocaleDateString(),
|
|
124
|
+
time: new Date().toLocaleTimeString()
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await logEvent('response', payload);
|
|
129
|
+
} catch (logError) {
|
|
130
|
+
console.error('Response logging error:', logError);
|
|
131
|
+
}
|
|
61
132
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
133
|
+
return response;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
responseError: async function (error) {
|
|
137
|
+
try {
|
|
138
|
+
const payload = {
|
|
139
|
+
type: 'Incoming_Response_Error',
|
|
140
|
+
message: error?.message,
|
|
141
|
+
stack: error?.stack,
|
|
142
|
+
date: {
|
|
143
|
+
date: new Date().toLocaleDateString(),
|
|
144
|
+
time: new Date().toLocaleTimeString()
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
await logEvent('responseError', payload);
|
|
149
|
+
} catch (logError) {
|
|
150
|
+
console.error('Response error logging failed:', logError);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Promise.reject(error);
|
|
154
|
+
}
|
|
66
155
|
});
|
|
156
|
+
|
|
157
|
+
listen.updateConfig = updateConfig;
|
|
158
|
+
listen.getConfig = () => ({ ...config });
|
|
159
|
+
|
|
160
|
+
module.exports = listen;
|
package/package.json
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scharff",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Logger for outgoing requests",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Logger for outgoing and incoming fetch requests",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"constants",
|
|
10
|
+
"config",
|
|
11
|
+
"types",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
6
17
|
"scripts": {
|
|
7
18
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
19
|
},
|
|
@@ -29,5 +40,9 @@
|
|
|
29
40
|
"license": "ISC",
|
|
30
41
|
"dependencies": {
|
|
31
42
|
"fetch-intercept": "^2.4.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"typescript": "^5.4.0"
|
|
32
47
|
}
|
|
33
48
|
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ScharffConfig {
|
|
2
|
+
outgoingLog: string;
|
|
3
|
+
outgoingErrorLog: string;
|
|
4
|
+
incomingLog: string;
|
|
5
|
+
incomingErrorLog: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
declare function listen(): void;
|
|
9
|
+
|
|
10
|
+
declare namespace listen {
|
|
11
|
+
function updateConfig(overrides?: Partial<ScharffConfig>): ScharffConfig;
|
|
12
|
+
function getConfig(): ScharffConfig;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export = listen;
|