@fetchmax/plugin-logger 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/dist/index.cjs ADDED
@@ -0,0 +1,200 @@
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
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ default: () => index_default,
24
+ loggerPlugin: () => loggerPlugin
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ function loggerPlugin(config = {}) {
28
+ const {
29
+ logRequests = true,
30
+ logResponses = true,
31
+ logErrors = true,
32
+ verbose = false,
33
+ colors = true,
34
+ filter,
35
+ logger = console
36
+ } = config;
37
+ const emoji = {
38
+ request: "\u{1F680}",
39
+ success: "\u2705",
40
+ error: "\u274C",
41
+ redirect: "\u21AA\uFE0F",
42
+ info: "\u2139\uFE0F",
43
+ warning: "\u26A0\uFE0F"
44
+ };
45
+ function shouldLog(request) {
46
+ return !filter || filter(request);
47
+ }
48
+ function formatDuration(ms) {
49
+ if (ms < 1e3) return `${ms.toFixed(0)}ms`;
50
+ return `${(ms / 1e3).toFixed(2)}s`;
51
+ }
52
+ function formatBytes(bytes) {
53
+ if (bytes === 0) return "0 B";
54
+ const k = 1024;
55
+ const sizes = ["B", "KB", "MB", "GB"];
56
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
57
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
58
+ }
59
+ function getStatusColor(status) {
60
+ if (!colors) return "";
61
+ if (status >= 200 && status < 300) return "\x1B[32m";
62
+ if (status >= 300 && status < 400) return "\x1B[36m";
63
+ if (status >= 400 && status < 500) return "\x1B[33m";
64
+ if (status >= 500) return "\x1B[31m";
65
+ return "\x1B[0m";
66
+ }
67
+ const resetColor = colors ? "\x1B[0m" : "";
68
+ const boldColor = colors ? "\x1B[1m" : "";
69
+ return {
70
+ name: "logger",
71
+ async onRequest(request, _context) {
72
+ request.__startTime = Date.now();
73
+ if (!logRequests || !shouldLog(request)) {
74
+ request.__skipLogging = true;
75
+ return request;
76
+ }
77
+ const method = request.method?.toUpperCase() || "GET";
78
+ const url = request.url || "";
79
+ if (logger.group) {
80
+ logger.group(`${emoji.request} ${boldColor}${method}${resetColor} ${url}`);
81
+ } else {
82
+ logger.log(`${emoji.request} ${boldColor}${method}${resetColor} ${url}`);
83
+ }
84
+ if (verbose) {
85
+ if (request.headers && Object.keys(request.headers).length > 0) {
86
+ logger.log(" Headers:", request.headers);
87
+ }
88
+ if (request.params && Object.keys(request.params).length > 0) {
89
+ logger.log(" Params:", request.params);
90
+ }
91
+ if (request.body) {
92
+ logger.log(" Body:", request.body);
93
+ }
94
+ }
95
+ if (logger.groupEnd) {
96
+ logger.groupEnd();
97
+ }
98
+ return request;
99
+ },
100
+ async onResponse(response, request, _context) {
101
+ if (request.__skipLogging) {
102
+ return response;
103
+ }
104
+ if (!logResponses || !shouldLog(request)) {
105
+ return response;
106
+ }
107
+ const duration = Date.now() - (request.__startTime || Date.now());
108
+ const method = request.method?.toUpperCase() || "GET";
109
+ const url = request.url || "";
110
+ const status = response.status;
111
+ const statusColor = getStatusColor(status);
112
+ let icon = emoji.success;
113
+ if (status >= 300 && status < 400) icon = emoji.redirect;
114
+ else if (status >= 400) icon = emoji.error;
115
+ const statusText = `${statusColor}${status} ${response.statusText}${resetColor}`;
116
+ const durationText = formatDuration(duration);
117
+ if (logger.group) {
118
+ logger.group(
119
+ `${icon} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
120
+ );
121
+ } else {
122
+ logger.log(
123
+ `${icon} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
124
+ );
125
+ }
126
+ if (verbose) {
127
+ logger.log(" Status:", status, response.statusText);
128
+ const headers = {};
129
+ response.headers.forEach((value, key) => {
130
+ headers[key] = value;
131
+ });
132
+ if (Object.keys(headers).length > 0) {
133
+ logger.log(" Headers:", headers);
134
+ }
135
+ const contentLength = response.headers.get("content-length");
136
+ if (contentLength) {
137
+ logger.log(" Size:", formatBytes(parseInt(contentLength, 10)));
138
+ }
139
+ if (response.data) {
140
+ const dataStr = JSON.stringify(response.data);
141
+ if (dataStr.length > 1e3) {
142
+ logger.log(" Data:", dataStr.substring(0, 1e3) + "... (truncated)");
143
+ } else {
144
+ logger.log(" Data:", response.data);
145
+ }
146
+ }
147
+ }
148
+ if (logger.groupEnd) {
149
+ logger.groupEnd();
150
+ }
151
+ return response;
152
+ },
153
+ async onError(error, request, _context) {
154
+ if (request.__skipLogging) {
155
+ throw error;
156
+ }
157
+ if (!logErrors || !shouldLog(request)) {
158
+ throw error;
159
+ }
160
+ const duration = Date.now() - (request.__startTime || Date.now());
161
+ const method = request.method?.toUpperCase() || "GET";
162
+ const url = request.url || "";
163
+ const durationText = formatDuration(duration);
164
+ const statusText = error.status ? `${getStatusColor(error.status)}${error.status} ${error.statusText}${resetColor}` : `${emoji.error} ERROR`;
165
+ if (logger.group) {
166
+ logger.group(
167
+ `${emoji.error} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
168
+ );
169
+ } else {
170
+ logger.error(
171
+ `${emoji.error} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
172
+ );
173
+ }
174
+ logger.error(" Error:", error.message);
175
+ if (verbose) {
176
+ if (error.code) {
177
+ logger.error(" Code:", error.code);
178
+ }
179
+ if (error.data) {
180
+ logger.error(" Data:", error.data);
181
+ }
182
+ if (error.stack) {
183
+ logger.error(" Stack:", error.stack);
184
+ }
185
+ if (error.config) {
186
+ logger.error(" Config:", error.config);
187
+ }
188
+ }
189
+ if (logger.groupEnd) {
190
+ logger.groupEnd();
191
+ }
192
+ throw error;
193
+ }
194
+ };
195
+ }
196
+ var index_default = loggerPlugin;
197
+ // Annotate the CommonJS export names for ESM import in node:
198
+ 0 && (module.exports = {
199
+ loggerPlugin
200
+ });
@@ -0,0 +1,39 @@
1
+ import { Plugin } from '@fetchmax/core';
2
+
3
+ interface LoggerConfig {
4
+ /** Log requests (default: true) */
5
+ logRequests?: boolean;
6
+ /** Log responses (default: true) */
7
+ logResponses?: boolean;
8
+ /** Log errors (default: true) */
9
+ logErrors?: boolean;
10
+ /** Show detailed information (default: false) */
11
+ verbose?: boolean;
12
+ /** Use colors in console output (default: true) */
13
+ colors?: boolean;
14
+ /** Filter function to determine which requests to log */
15
+ filter?: (request: any) => boolean;
16
+ /** Custom logger function */
17
+ logger?: {
18
+ log: (...args: any[]) => void;
19
+ error: (...args: any[]) => void;
20
+ group?: (...args: any[]) => void;
21
+ groupEnd?: () => void;
22
+ };
23
+ }
24
+ /**
25
+ * Logger Plugin
26
+ *
27
+ * Logs HTTP requests, responses, and errors to the console for debugging.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const client = new HttpClient().use(loggerPlugin({
32
+ * verbose: true,
33
+ * filter: (request) => !request.url.includes('/health')
34
+ * }));
35
+ * ```
36
+ */
37
+ declare function loggerPlugin(config?: LoggerConfig): Plugin;
38
+
39
+ export { type LoggerConfig, loggerPlugin as default, loggerPlugin };
@@ -0,0 +1,39 @@
1
+ import { Plugin } from '@fetchmax/core';
2
+
3
+ interface LoggerConfig {
4
+ /** Log requests (default: true) */
5
+ logRequests?: boolean;
6
+ /** Log responses (default: true) */
7
+ logResponses?: boolean;
8
+ /** Log errors (default: true) */
9
+ logErrors?: boolean;
10
+ /** Show detailed information (default: false) */
11
+ verbose?: boolean;
12
+ /** Use colors in console output (default: true) */
13
+ colors?: boolean;
14
+ /** Filter function to determine which requests to log */
15
+ filter?: (request: any) => boolean;
16
+ /** Custom logger function */
17
+ logger?: {
18
+ log: (...args: any[]) => void;
19
+ error: (...args: any[]) => void;
20
+ group?: (...args: any[]) => void;
21
+ groupEnd?: () => void;
22
+ };
23
+ }
24
+ /**
25
+ * Logger Plugin
26
+ *
27
+ * Logs HTTP requests, responses, and errors to the console for debugging.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const client = new HttpClient().use(loggerPlugin({
32
+ * verbose: true,
33
+ * filter: (request) => !request.url.includes('/health')
34
+ * }));
35
+ * ```
36
+ */
37
+ declare function loggerPlugin(config?: LoggerConfig): Plugin;
38
+
39
+ export { type LoggerConfig, loggerPlugin as default, loggerPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,175 @@
1
+ // src/index.ts
2
+ function loggerPlugin(config = {}) {
3
+ const {
4
+ logRequests = true,
5
+ logResponses = true,
6
+ logErrors = true,
7
+ verbose = false,
8
+ colors = true,
9
+ filter,
10
+ logger = console
11
+ } = config;
12
+ const emoji = {
13
+ request: "\u{1F680}",
14
+ success: "\u2705",
15
+ error: "\u274C",
16
+ redirect: "\u21AA\uFE0F",
17
+ info: "\u2139\uFE0F",
18
+ warning: "\u26A0\uFE0F"
19
+ };
20
+ function shouldLog(request) {
21
+ return !filter || filter(request);
22
+ }
23
+ function formatDuration(ms) {
24
+ if (ms < 1e3) return `${ms.toFixed(0)}ms`;
25
+ return `${(ms / 1e3).toFixed(2)}s`;
26
+ }
27
+ function formatBytes(bytes) {
28
+ if (bytes === 0) return "0 B";
29
+ const k = 1024;
30
+ const sizes = ["B", "KB", "MB", "GB"];
31
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
32
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
33
+ }
34
+ function getStatusColor(status) {
35
+ if (!colors) return "";
36
+ if (status >= 200 && status < 300) return "\x1B[32m";
37
+ if (status >= 300 && status < 400) return "\x1B[36m";
38
+ if (status >= 400 && status < 500) return "\x1B[33m";
39
+ if (status >= 500) return "\x1B[31m";
40
+ return "\x1B[0m";
41
+ }
42
+ const resetColor = colors ? "\x1B[0m" : "";
43
+ const boldColor = colors ? "\x1B[1m" : "";
44
+ return {
45
+ name: "logger",
46
+ async onRequest(request, _context) {
47
+ request.__startTime = Date.now();
48
+ if (!logRequests || !shouldLog(request)) {
49
+ request.__skipLogging = true;
50
+ return request;
51
+ }
52
+ const method = request.method?.toUpperCase() || "GET";
53
+ const url = request.url || "";
54
+ if (logger.group) {
55
+ logger.group(`${emoji.request} ${boldColor}${method}${resetColor} ${url}`);
56
+ } else {
57
+ logger.log(`${emoji.request} ${boldColor}${method}${resetColor} ${url}`);
58
+ }
59
+ if (verbose) {
60
+ if (request.headers && Object.keys(request.headers).length > 0) {
61
+ logger.log(" Headers:", request.headers);
62
+ }
63
+ if (request.params && Object.keys(request.params).length > 0) {
64
+ logger.log(" Params:", request.params);
65
+ }
66
+ if (request.body) {
67
+ logger.log(" Body:", request.body);
68
+ }
69
+ }
70
+ if (logger.groupEnd) {
71
+ logger.groupEnd();
72
+ }
73
+ return request;
74
+ },
75
+ async onResponse(response, request, _context) {
76
+ if (request.__skipLogging) {
77
+ return response;
78
+ }
79
+ if (!logResponses || !shouldLog(request)) {
80
+ return response;
81
+ }
82
+ const duration = Date.now() - (request.__startTime || Date.now());
83
+ const method = request.method?.toUpperCase() || "GET";
84
+ const url = request.url || "";
85
+ const status = response.status;
86
+ const statusColor = getStatusColor(status);
87
+ let icon = emoji.success;
88
+ if (status >= 300 && status < 400) icon = emoji.redirect;
89
+ else if (status >= 400) icon = emoji.error;
90
+ const statusText = `${statusColor}${status} ${response.statusText}${resetColor}`;
91
+ const durationText = formatDuration(duration);
92
+ if (logger.group) {
93
+ logger.group(
94
+ `${icon} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
95
+ );
96
+ } else {
97
+ logger.log(
98
+ `${icon} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
99
+ );
100
+ }
101
+ if (verbose) {
102
+ logger.log(" Status:", status, response.statusText);
103
+ const headers = {};
104
+ response.headers.forEach((value, key) => {
105
+ headers[key] = value;
106
+ });
107
+ if (Object.keys(headers).length > 0) {
108
+ logger.log(" Headers:", headers);
109
+ }
110
+ const contentLength = response.headers.get("content-length");
111
+ if (contentLength) {
112
+ logger.log(" Size:", formatBytes(parseInt(contentLength, 10)));
113
+ }
114
+ if (response.data) {
115
+ const dataStr = JSON.stringify(response.data);
116
+ if (dataStr.length > 1e3) {
117
+ logger.log(" Data:", dataStr.substring(0, 1e3) + "... (truncated)");
118
+ } else {
119
+ logger.log(" Data:", response.data);
120
+ }
121
+ }
122
+ }
123
+ if (logger.groupEnd) {
124
+ logger.groupEnd();
125
+ }
126
+ return response;
127
+ },
128
+ async onError(error, request, _context) {
129
+ if (request.__skipLogging) {
130
+ throw error;
131
+ }
132
+ if (!logErrors || !shouldLog(request)) {
133
+ throw error;
134
+ }
135
+ const duration = Date.now() - (request.__startTime || Date.now());
136
+ const method = request.method?.toUpperCase() || "GET";
137
+ const url = request.url || "";
138
+ const durationText = formatDuration(duration);
139
+ const statusText = error.status ? `${getStatusColor(error.status)}${error.status} ${error.statusText}${resetColor}` : `${emoji.error} ERROR`;
140
+ if (logger.group) {
141
+ logger.group(
142
+ `${emoji.error} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
143
+ );
144
+ } else {
145
+ logger.error(
146
+ `${emoji.error} ${statusText} ${boldColor}${method}${resetColor} ${url} ${emoji.info} ${durationText}`
147
+ );
148
+ }
149
+ logger.error(" Error:", error.message);
150
+ if (verbose) {
151
+ if (error.code) {
152
+ logger.error(" Code:", error.code);
153
+ }
154
+ if (error.data) {
155
+ logger.error(" Data:", error.data);
156
+ }
157
+ if (error.stack) {
158
+ logger.error(" Stack:", error.stack);
159
+ }
160
+ if (error.config) {
161
+ logger.error(" Config:", error.config);
162
+ }
163
+ }
164
+ if (logger.groupEnd) {
165
+ logger.groupEnd();
166
+ }
167
+ throw error;
168
+ }
169
+ };
170
+ }
171
+ var index_default = loggerPlugin;
172
+ export {
173
+ index_default as default,
174
+ loggerPlugin
175
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@fetchmax/plugin-logger",
3
+ "version": "1.0.0",
4
+ "description": "Logger plugin for FetchMax with detailed request/response logging",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
23
+ },
24
+ "keywords": [
25
+ "fetchmax",
26
+ "plugin",
27
+ "logger",
28
+ "logging",
29
+ "debug",
30
+ "http",
31
+ "fetch"
32
+ ],
33
+ "author": "FetchMax Contributors",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/fetchmax/fetchmax.git",
38
+ "directory": "packages/plugins/logger"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/fetchmax/fetchmax/issues"
42
+ },
43
+ "homepage": "https://github.com/fetchmax/fetchmax#readme",
44
+ "sideEffects": false,
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "peerDependencies": {
52
+ "@fetchmax/core": "^1.0.0"
53
+ },
54
+ "devDependencies": {
55
+ "@fetchmax/core": "*",
56
+ "tsup": "^8.0.1",
57
+ "typescript": "^5.3.3"
58
+ }
59
+ }