scharff 1.1.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 CHANGED
@@ -1,41 +1,137 @@
1
- # Scharff Logger
2
- This project was created by Michael Scharff for logging purposes.
3
- The project is using the fetch-intercept NPM package.
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.
4
3
 
5
4
  ## Installation
6
- 1) run `npm install scharff`.
7
- 2) fork the git repository `https://github.com/Minka1902/scharff.git`, and place it next to your project.
8
-
9
- ## Usage
10
- 1) in your entry point, import unregister from the package: `const { unregister } = require('scharff');`
11
- 2) to stop the logger just run the unregister function: `unregister()`, or `npm uninstall scharff`.
12
-
13
- ## What to expect
14
- 1) The package will create a outgoingRequest.log file and start logging the requests to the file.
15
- 2) In there you can see the fetch requests you sent.
16
- 3) The request will be in the format below:</br>
17
- {</br>
18
- &nbsp;&nbsp;&nbsp;&nbsp; "url":"http://www.example.com/update/ad423kbr1om82hu3d58a73g4",</br>
19
- &nbsp;&nbsp;&nbsp;&nbsp; "originUrl":"/update/ad423kbr1om82hu3d58a73g4",</br>
20
- &nbsp;&nbsp;&nbsp;&nbsp; "method":"GET",</br>
21
- &nbsp;&nbsp;&nbsp;&nbsp; "headers": </br>
22
- &nbsp;&nbsp;&nbsp;&nbsp; {</br>
23
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Content-Type":"application/json",</br>
24
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Access-Control-Allow-Origin":"*"</br>
25
- &nbsp;&nbsp;&nbsp;&nbsp; },</br>
26
- &nbsp;&nbsp;&nbsp;&nbsp; "body": </br>
27
- &nbsp;&nbsp;&nbsp;&nbsp; {</br>
28
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "isActive":true,</br>
29
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "status":200,</br>
30
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "date":"2023-08-21T11:25:47.023Z"</br>
31
- &nbsp;&nbsp;&nbsp;&nbsp; }</br>
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
+ }
32
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
+ ```
33
131
 
34
- ## Next release
35
- 1) Adding the date and time to the log.
36
- 2) Adding the IP address to the log.
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`
37
136
 
38
- ## Future additions
39
- 1) We will add a request error logger.
40
- 2) We will add a response logger.
41
- 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 };
@@ -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,55 +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
+ const os = require('os');
5
+ const { loadConfig } = require('./config/default');
4
6
 
5
- module.exports.unregister = fetchIntercept.register({
6
- request: function (url, config) {
7
- let tempUrl = url;
8
- let tempReq = { url };
9
- let date = new Date().toLocaleString();
10
- tempReq.date = date;
11
- tempUrl = removeBaseUrl(tempUrl);
12
- if (tempUrl !== url) {
13
- tempReq.originUrl = tempUrl;
14
- }
15
- if (config === undefined) {
16
- tempReq.method = 'GET';
17
- } else {
18
- if (config) {
19
- if (config.method) {
20
- tempReq.method = config.method;
21
- }
22
- if (config.headers) {
23
- tempReq.headers = config.headers;
24
- }
25
- if (config.body) {
26
- tempReq.body = JSON.parse(config.body);
27
- }
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);
28
34
  }
29
35
  }
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();
57
+ }
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
+ };
30
80
 
31
- fs.open('./outgoingRequest.log', 'a', function (e, id) {
32
- fs.write(id, JSON.stringify(tempReq) + "\n", null, 'utf8', function () {
33
- fs.close(id, function () {
34
- });
35
- });
36
- });
81
+ const cleanUrl = removeBaseUrl(url);
82
+ if (cleanUrl !== url) {
83
+ tempReq.originUrl = cleanUrl;
84
+ }
85
+
86
+ await logEvent('request', tempReq);
87
+ } catch (error) {
88
+ console.error('Request logging error:', error);
89
+ }
37
90
 
38
91
  return [url, config];
39
92
  },
40
93
 
41
- // requestError: function (error) {
42
- // // Called when an error occurred during another 'request' interceptor call
43
- // return Promise.reject(error);
44
- // },
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
+ }
109
+
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
+ }
132
+
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
+ };
45
147
 
46
- // response: function (response) {
47
- // // Modify the response object
48
- // return response;
49
- // },
148
+ await logEvent('responseError', payload);
149
+ } catch (logError) {
150
+ console.error('Response error logging failed:', logError);
151
+ }
50
152
 
51
- // responseError: function (error) {
52
- // // Handle an fetch error
53
- // return Promise.reject(error);
54
- // }
153
+ return Promise.reject(error);
154
+ }
55
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": "1.1.0",
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
  },
@@ -22,9 +33,16 @@
22
33
  "type": "git",
23
34
  "url": "https://github.com/Minka1902/scharff.git"
24
35
  },
25
- "author": "Michael Scharff",
36
+ "author": {
37
+ "name": "Michael Scharff",
38
+ "email": "minka.scharff@gmail.com"
39
+ },
26
40
  "license": "ISC",
27
41
  "dependencies": {
28
42
  "fetch-intercept": "^2.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.0.0",
46
+ "typescript": "^5.4.0"
29
47
  }
30
48
  }
@@ -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;