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 CHANGED
@@ -1,39 +1,137 @@
1
- # Scharff Logger
2
- Scharff is a lightweight and versatile Node.js package designed to enhance your application's logging capabilities by seamlessly integrating a powerful outgoing request logger. With Scharff, you can effortlessly gain insights into the interactions your application has with external services, APIs, and resources.
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
- 1) run `npm install scharff`.
6
- 2) fork the git repository `https://github.com/Minka1902/scharff.git`, and place it next to your project.
7
-
8
- ## Usage
9
- 1) in your entry point, import unregister from the package: `const { unregister } = require('scharff');`
10
- 2) to stop the logger just run the unregister function: `unregister()`, or `npm uninstall scharff`.
11
-
12
- ## What to expect
13
- 1) The package will create a outgoingRequest.log file and start logging the requests to the file.
14
- 2) In there you can see the fetch requests you sent.
15
- 3) The request will be in the format below:</br>
16
- {</br>
17
- &nbsp;&nbsp;&nbsp;&nbsp; "url":"http://www.example.com/update/ad423kbr1om82hu3d58a73g4",</br>
18
- &nbsp;&nbsp;&nbsp;&nbsp; "originUrl":"/update/ad423kbr1om82hu3d58a73g4",</br>
19
- &nbsp;&nbsp;&nbsp;&nbsp; "method":"GET",</br>
20
- &nbsp;&nbsp;&nbsp;&nbsp; "headers": </br>
21
- &nbsp;&nbsp;&nbsp;&nbsp; {</br>
22
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Content-Type":"application/json",</br>
23
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Access-Control-Allow-Origin":"*"</br>
24
- &nbsp;&nbsp;&nbsp;&nbsp; },</br>
25
- &nbsp;&nbsp;&nbsp;&nbsp; "body": </br>
26
- &nbsp;&nbsp;&nbsp;&nbsp; {</br>
27
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "isActive":true,</br>
28
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "status":200,</br>
29
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "date":"2023-08-21T11:25:47.023Z"</br>
30
- &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
+ }
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
- ## Next release
34
- 1)
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
- ## Future additions
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 };
@@ -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
- module.exports.unregister = fetchIntercept.register({
7
- request: function (url, config) {
8
- const interfaces = os.networkInterfaces();
9
- let addresses = [];
10
- let tempUrl = url;
11
- for (let i in interfaces) {
12
- for (var i2 in interfaces[i]) {
13
- var address = interfaces[i][i2];
14
- if (address.family === 'IPv4' && !address.internal) {
15
- addresses.push(address.address);
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
- let tempReq = { url, ip: addresses[0] };
20
- let date = new Date().toLocaleString();
21
- tempReq.date = date;
22
- tempUrl = removeBaseUrl(tempUrl);
23
- if (tempUrl !== url) {
24
- tempReq.originUrl = tempUrl;
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
- if (config === undefined) {
27
- tempReq.method = 'GET';
28
- } else {
29
- if (config) {
30
- if (config.method) {
31
- tempReq.method = config.method;
32
- }
33
- if (config.headers) {
34
- tempReq.headers = config.headers;
35
- }
36
- if (config.body) {
37
- tempReq.body = JSON.parse(config.body);
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
- fs.open('./outgoingRequest.log', 'a', function (e, id) {
43
- fs.write(id, JSON.stringify(tempReq) + "\n", null, 'utf8', function () {
44
- fs.close(id, function () {
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
- // requestError: function (error) {
53
- // // Called when an error occurred during another 'request' interceptor call
54
- // return Promise.reject(error);
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
- // response: function (response) {
58
- // // Modify the response object
59
- // return response;
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
- // responseError: function (error) {
63
- // // Handle an fetch error
64
- // return Promise.reject(error);
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": "1.3.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
  },
@@ -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
  }
@@ -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;