zario 0.2.11 → 0.3.1

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.
Files changed (46) hide show
  1. package/README.md +10 -1
  2. package/dist/cjs/core/CustomLogLevel.js +2 -0
  3. package/dist/cjs/core/Formatter.js +75 -0
  4. package/dist/cjs/core/LogLevel.js +2 -0
  5. package/dist/cjs/core/Logger.js +234 -0
  6. package/dist/cjs/index.js +19 -0
  7. package/dist/cjs/package.json +1 -0
  8. package/dist/cjs/transports/ConsoleTransport.js +39 -0
  9. package/dist/cjs/transports/FileTransport.js +260 -0
  10. package/dist/cjs/transports/HttpTransport.js +150 -0
  11. package/dist/cjs/transports/Transport.js +2 -0
  12. package/dist/cjs/transports/index.js +20 -0
  13. package/dist/cjs/utils/ColorUtil.js +42 -0
  14. package/dist/cjs/utils/TimeUtil.js +26 -0
  15. package/dist/cjs/utils/Timerutil.js +22 -0
  16. package/dist/{core → esm/core}/CustomLogLevel.d.ts +1 -1
  17. package/dist/esm/core/CustomLogLevel.js +1 -0
  18. package/dist/{core → esm/core}/Formatter.d.ts +1 -1
  19. package/dist/esm/core/Formatter.js +71 -0
  20. package/dist/esm/core/LogLevel.js +1 -0
  21. package/dist/{core → esm/core}/Logger.d.ts +4 -5
  22. package/dist/esm/core/Logger.js +230 -0
  23. package/dist/esm/index.d.ts +8 -0
  24. package/dist/esm/index.js +13 -0
  25. package/dist/{transports → esm/transports}/ConsoleTransport.d.ts +3 -3
  26. package/dist/esm/transports/ConsoleTransport.js +35 -0
  27. package/dist/{transports → esm/transports}/FileTransport.d.ts +7 -5
  28. package/dist/esm/transports/FileTransport.js +223 -0
  29. package/dist/{transports → esm/transports}/HttpTransport.d.ts +3 -3
  30. package/dist/esm/transports/HttpTransport.js +113 -0
  31. package/dist/{transports → esm/transports}/Transport.d.ts +2 -2
  32. package/dist/esm/transports/Transport.js +1 -0
  33. package/dist/esm/transports/index.d.ts +4 -0
  34. package/dist/esm/transports/index.js +4 -0
  35. package/dist/esm/utils/ColorUtil.js +38 -0
  36. package/dist/esm/utils/TimeUtil.js +22 -0
  37. package/dist/esm/utils/Timerutil.js +18 -0
  38. package/package.json +19 -8
  39. package/dist/index.d.ts +0 -8
  40. package/dist/index.js +0 -3
  41. package/dist/index.mjs +0 -3
  42. package/dist/transports/index.d.ts +0 -28
  43. /package/dist/{core → esm/core}/LogLevel.d.ts +0 -0
  44. /package/dist/{utils → esm/utils}/ColorUtil.d.ts +0 -0
  45. /package/dist/{utils → esm/utils}/TimeUtil.d.ts +0 -0
  46. /package/dist/{utils → esm/utils}/Timerutil.d.ts +0 -0
@@ -0,0 +1,230 @@
1
+ import { Formatter } from "./Formatter.js";
2
+ import { ConsoleTransport } from "../transports/ConsoleTransport.js";
3
+ export class Logger {
4
+ constructor(options = {}) {
5
+ this.transports = [];
6
+ const { level, colorize, json, transports = [], timestampFormat = "YYYY-MM-DD HH:mm:ss", prefix, timestamp, context = {}, parent, asyncMode, customLevels = {}, customColors = {}, } = options;
7
+ this.parent = parent; // Set parent
8
+ this.context = { ...context }; // Init context
9
+ this.customLevels = customLevels; // custom log store
10
+ this.asyncMode = false;
11
+ if (this.parent) {
12
+ this.level = level ?? this.parent.level;
13
+ this.prefix = prefix ?? this.parent.prefix;
14
+ this.timestamp = timestamp ?? this.parent.timestamp;
15
+ this.asyncMode = asyncMode ?? this.parent.asyncMode;
16
+ this.transports =
17
+ transports && transports.length > 0
18
+ ? this.initTransports(transports)
19
+ : this.parent.transports;
20
+ // Merge colors; child overrides parent
21
+ const mergedCColors = {
22
+ ...this.parent.formatter.getCustomColors(),
23
+ ...customColors,
24
+ };
25
+ this.formatter = new Formatter({
26
+ colorize: this.getDefaultColorizeValue(colorize) ??
27
+ this.parent.formatter.isColorized(),
28
+ json: json ?? this.parent.formatter.isJson(),
29
+ timestampFormat: timestampFormat ?? this.parent.formatter.getTimestampFormat(),
30
+ timestamp: timestamp ?? this.parent.formatter.hasTimestamp(),
31
+ customColors: mergedCColors,
32
+ });
33
+ this.context = { ...this.parent.context, ...this.context };
34
+ // Merge custom levels with parent's custom levels
35
+ this.customLevels = { ...this.parent.customLevels, ...customLevels };
36
+ }
37
+ else {
38
+ // Auto-configure based on environment
39
+ const isProd = this.isProductionEnvironment();
40
+ this.level = level ?? this.getDefaultLevel(isProd);
41
+ this.prefix = prefix ?? "";
42
+ this.timestamp = timestamp ?? this.getDefaultTimestamp(isProd);
43
+ const defaultTransports = transports && transports.length > 0
44
+ ? transports
45
+ : this.getDefaultTransports(isProd);
46
+ this.asyncMode = asyncMode ?? this.getDefaultAsyncMode(isProd);
47
+ this.transports = this.initTransports(defaultTransports);
48
+ this.formatter = new Formatter({
49
+ colorize: this.getDefaultColorizeValue(colorize),
50
+ json: json ?? this.getDefaultJson(isProd),
51
+ timestampFormat,
52
+ timestamp: this.getDefaultTimestamp(isProd),
53
+ customColors,
54
+ });
55
+ }
56
+ if (!Logger._global) {
57
+ Logger._global = this;
58
+ }
59
+ }
60
+ isProductionEnvironment() {
61
+ const env = process.env.NODE_ENV?.toLowerCase();
62
+ return env === "production" || env === "prod";
63
+ }
64
+ getDefaultLevel(isProd) {
65
+ return isProd ? "warn" : "debug";
66
+ }
67
+ getDefaultColorizeValue(colorize) {
68
+ if (colorize !== undefined) {
69
+ return colorize;
70
+ }
71
+ const isProd = this.isProductionEnvironment();
72
+ return !isProd;
73
+ }
74
+ getDefaultJson(isProd) {
75
+ return isProd;
76
+ }
77
+ getDefaultTimestamp(isProd) {
78
+ return true;
79
+ }
80
+ getDefaultTransports(isProd) {
81
+ if (Logger.defaultTransportsFactory) {
82
+ return Logger.defaultTransportsFactory(isProd);
83
+ }
84
+ return [new ConsoleTransport()];
85
+ }
86
+ getDefaultAsyncMode(isProd) {
87
+ return isProd;
88
+ }
89
+ initTransports(transportConfigs) {
90
+ const initializedTransports = [];
91
+ for (const transportConfig of transportConfigs) {
92
+ if (this.isTransport(transportConfig)) {
93
+ initializedTransports.push(transportConfig);
94
+ }
95
+ }
96
+ return initializedTransports;
97
+ }
98
+ isTransport(transport) {
99
+ return (typeof transport === "object" &&
100
+ transport !== null &&
101
+ typeof transport.write === "function");
102
+ }
103
+ shouldLog(level) {
104
+ // Get the priority of the current logger level
105
+ const currentLevelPriority = this.getLevelPriority(this.level);
106
+ // Get the priority of the message level
107
+ const messageLevelPriority = this.getLevelPriority(level);
108
+ return messageLevelPriority >= currentLevelPriority;
109
+ }
110
+ getLevelPriority(level) {
111
+ // use a static map to avoid repeated allocations
112
+ if (Logger.LEVEL_PRIORITIES.hasOwnProperty(level)) {
113
+ return Logger.LEVEL_PRIORITIES[level];
114
+ }
115
+ // Check if it's a custom level
116
+ if (this.customLevels && level in this.customLevels) {
117
+ const customPriority = this.customLevels[level];
118
+ return customPriority !== undefined ? customPriority : 999;
119
+ }
120
+ return 999;
121
+ }
122
+ log(level, message, metadata) {
123
+ if (!this.shouldLog(level) || level === "silent") {
124
+ return;
125
+ }
126
+ const timestamp = new Date();
127
+ // Optimize metadata merging
128
+ let finalMetadata;
129
+ const hasContext = this.context && Object.keys(this.context).length > 0;
130
+ if (hasContext && metadata) {
131
+ finalMetadata = { ...this.context, ...metadata };
132
+ }
133
+ else if (hasContext) {
134
+ finalMetadata = this.context;
135
+ }
136
+ else if (metadata) {
137
+ finalMetadata = metadata;
138
+ }
139
+ // Only add metadata if it's not empty after merging
140
+ const logData = {
141
+ level,
142
+ message,
143
+ timestamp,
144
+ metadata: finalMetadata && Object.keys(finalMetadata).length > 0
145
+ ? finalMetadata
146
+ : undefined,
147
+ prefix: this.prefix,
148
+ };
149
+ if (this.asyncMode) {
150
+ for (const transport of this.transports) {
151
+ if (transport.writeAsync) {
152
+ transport.writeAsync(logData, this.formatter).catch((error) => {
153
+ console.error("Error during async logging:", error);
154
+ });
155
+ }
156
+ else {
157
+ setImmediate(() => {
158
+ transport.write(logData, this.formatter);
159
+ });
160
+ }
161
+ }
162
+ }
163
+ else {
164
+ for (const transport of this.transports) {
165
+ transport.write(logData, this.formatter);
166
+ }
167
+ }
168
+ }
169
+ debug(message, metadata) {
170
+ this.log("debug", message, metadata);
171
+ }
172
+ info(message, metadata) {
173
+ this.log("info", message, metadata);
174
+ }
175
+ warn(message, metadata) {
176
+ this.log("warn", message, metadata);
177
+ }
178
+ error(message, metadata) {
179
+ this.log("error", message, metadata);
180
+ }
181
+ silent(message, metadata) {
182
+ this.log("silent", message, metadata);
183
+ }
184
+ boring(message, metadata) {
185
+ this.log("boring", message, metadata);
186
+ }
187
+ /**
188
+ * Generic log method that allows logging with custom levels
189
+ */
190
+ logWithLevel(level, message, metadata) {
191
+ this.log(level, message, metadata);
192
+ }
193
+ setLevel(level) {
194
+ this.level = level;
195
+ }
196
+ setFormat(format) {
197
+ this.formatter.setJson(format === "json");
198
+ }
199
+ setAsyncMode(asyncMode) {
200
+ this.asyncMode = asyncMode;
201
+ }
202
+ addTransport(transport) {
203
+ this.transports.push(transport);
204
+ }
205
+ getTimestampSetting() {
206
+ return this.timestamp;
207
+ }
208
+ static get global() {
209
+ if (!Logger._global) {
210
+ Logger._global = new Logger();
211
+ }
212
+ return Logger._global;
213
+ }
214
+ createChild(options = {}) {
215
+ return new Logger({ ...options, parent: this });
216
+ }
217
+ startTimer(name) {
218
+ const { Timer } = require("../utils/Timerutil");
219
+ return new Timer(name, (message) => this.info(message));
220
+ }
221
+ }
222
+ Logger.defaultTransportsFactory = null;
223
+ Logger.LEVEL_PRIORITIES = {
224
+ silent: 0,
225
+ boring: 1,
226
+ debug: 2,
227
+ info: 3,
228
+ warn: 4,
229
+ error: 5,
230
+ };
@@ -0,0 +1,8 @@
1
+ import { Logger } from './core/Logger.js';
2
+ import { LogLevel } from './core/LogLevel.js';
3
+ import { ConsoleTransport, FileTransport, HttpTransport, Transport } from './transports/index.js';
4
+ import { TransportConfig, LoggerConfig } from './types/index.js';
5
+ import { CustomLogLevelConfig } from './core/CustomLogLevel.js';
6
+ export { Logger, ConsoleTransport, FileTransport, HttpTransport };
7
+ export type { LogLevel, Transport, TransportConfig, LoggerConfig, CustomLogLevelConfig };
8
+ export default Logger;
@@ -0,0 +1,13 @@
1
+ import { Logger } from './core/Logger.js';
2
+ import { ConsoleTransport, FileTransport, HttpTransport } from './transports/index.js';
3
+ // Configure default transports to maintain backward compatibility
4
+ Logger.defaultTransportsFactory = (isProd) => {
5
+ if (isProd) {
6
+ return [new ConsoleTransport(), new FileTransport({ path: "./logs/app.log" })];
7
+ }
8
+ else {
9
+ return [new ConsoleTransport()];
10
+ }
11
+ };
12
+ export { Logger, ConsoleTransport, FileTransport, HttpTransport };
13
+ export default Logger;
@@ -1,6 +1,6 @@
1
- import { Transport } from "./Transport";
2
- import { LogData } from "../types";
3
- import { Formatter } from "../core/Formatter";
1
+ import { Transport } from "./Transport.js";
2
+ import { LogData } from "../types/index.js";
3
+ import { Formatter } from "../core/Formatter.js";
4
4
  export interface ConsoleTransportOptions {
5
5
  colorize?: boolean;
6
6
  }
@@ -0,0 +1,35 @@
1
+ export class ConsoleTransport {
2
+ constructor(options = {}) {
3
+ const { colorize = true } = options;
4
+ this.colorize = colorize;
5
+ }
6
+ write(data, formatter) {
7
+ // Toggle colorize temporarily, then restore it
8
+ const originalColorizeSetting = formatter["colorize"];
9
+ if (this.colorize !== originalColorizeSetting) {
10
+ formatter["colorize"] = this.colorize;
11
+ }
12
+ const output = formatter.format(data);
13
+ // Restore
14
+ if (this.colorize !== originalColorizeSetting) {
15
+ formatter["colorize"] = originalColorizeSetting;
16
+ }
17
+ switch (data.level) {
18
+ case "error":
19
+ console.error(output);
20
+ break;
21
+ case "warn":
22
+ console.warn(output);
23
+ break;
24
+ default:
25
+ console.log(output);
26
+ break;
27
+ }
28
+ }
29
+ async writeAsync(data, formatter) {
30
+ setImmediate(() => {
31
+ this.write(data, formatter);
32
+ });
33
+ return Promise.resolve();
34
+ }
35
+ }
@@ -1,6 +1,6 @@
1
- import { Transport } from "./Transport";
2
- import { LogData } from "../types";
3
- import { Formatter } from "../core/Formatter";
1
+ import { Transport } from "./Transport.js";
2
+ import { LogData } from "../types/index.js";
3
+ import { Formatter } from "../core/Formatter.js";
4
4
  export type CompressionType = "gzip" | "deflate" | "none";
5
5
  export interface FileTransportOptions {
6
6
  path: string;
@@ -26,11 +26,13 @@ export declare class FileTransport implements Transport {
26
26
  constructor(options: FileTransportOptions);
27
27
  write(data: LogData, formatter: Formatter): void;
28
28
  writeAsync(data: LogData, formatter: Formatter): Promise<void>;
29
- private rotateIfNeeded;
30
- private rotateIfNeededAsync;
29
+ private shouldRotate;
31
30
  private rotateFiles;
32
31
  private rotateFilesAsync;
32
+ private performRotation;
33
+ private performRotationAsync;
33
34
  private getRotatedFilePath;
35
+ private filterRotatedFiles;
34
36
  private cleanupOldFiles;
35
37
  private cleanupOldFilesAsync;
36
38
  private startBatching;
@@ -0,0 +1,223 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as zlib from "zlib";
4
+ import { promisify } from "util";
5
+ const compressGzip = promisify(zlib.gzip);
6
+ const compressDeflate = promisify(zlib.deflate);
7
+ export class FileTransport {
8
+ constructor(options) {
9
+ // batching funct
10
+ this.batchQueue = [];
11
+ this.batchTimer = null;
12
+ const { path: filePath, maxSize = 10 * 1024 * 1024, maxFiles = 5, compression = "none", batchInterval = 0, // no batching
13
+ compressOldFiles = true, } = options;
14
+ this.filePath = filePath;
15
+ this.maxSize = maxSize;
16
+ this.maxFiles = maxFiles;
17
+ this.compression = compression;
18
+ this.batchInterval = batchInterval;
19
+ this.compressOldFiles = compressOldFiles;
20
+ const dir = path.dirname(this.filePath);
21
+ if (!fs.existsSync(dir)) {
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+ if (!fs.existsSync(this.filePath)) {
25
+ fs.writeFileSync(this.filePath, "", "utf8");
26
+ }
27
+ // Start batching if an interval is set
28
+ if (batchInterval > 0) {
29
+ this.startBatching();
30
+ }
31
+ }
32
+ write(data, formatter) {
33
+ const output = formatter.format(data);
34
+ const formattedOutput = output + "\n";
35
+ if (this.batchInterval > 0) {
36
+ // Queue entry if batching is enabled
37
+ this.batchQueue.push({
38
+ data: formattedOutput,
39
+ timestamp: new Date(),
40
+ });
41
+ }
42
+ else {
43
+ // Write immediately when batching is disabled
44
+ fs.appendFileSync(this.filePath, formattedOutput);
45
+ if (this.shouldRotate()) {
46
+ this.rotateFiles();
47
+ }
48
+ }
49
+ }
50
+ async writeAsync(data, formatter) {
51
+ const formattedOutput = formatter.format(data) + "\n";
52
+ if (this.batchInterval > 0) {
53
+ this.batchQueue.push({
54
+ data: formattedOutput,
55
+ timestamp: new Date(),
56
+ });
57
+ }
58
+ else {
59
+ try {
60
+ await fs.promises.appendFile(this.filePath, formattedOutput);
61
+ if (this.shouldRotate()) {
62
+ await this.rotateFilesAsync();
63
+ }
64
+ }
65
+ catch (err) {
66
+ throw err;
67
+ }
68
+ }
69
+ }
70
+ shouldRotate() {
71
+ if (!fs.existsSync(this.filePath))
72
+ return false;
73
+ try {
74
+ return fs.statSync(this.filePath).size >= this.maxSize;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ rotateFiles() {
81
+ try {
82
+ if (!fs.existsSync(this.filePath))
83
+ return;
84
+ const currentContent = fs.readFileSync(this.filePath, "utf8");
85
+ this.performRotation(currentContent, fs.writeFileSync);
86
+ this.cleanupOldFiles();
87
+ }
88
+ catch (error) {
89
+ console.error("Error during file rotation:", error);
90
+ }
91
+ }
92
+ async rotateFilesAsync() {
93
+ try {
94
+ if (!fs.existsSync(this.filePath))
95
+ return;
96
+ const currentContent = await fs.promises.readFile(this.filePath, "utf8");
97
+ await this.performRotationAsync(currentContent);
98
+ await this.cleanupOldFilesAsync();
99
+ }
100
+ catch (error) {
101
+ console.error("Error during async file rotation:", error);
102
+ }
103
+ }
104
+ performRotation(content, writeFn) {
105
+ let rotatedFilePath = this.getRotatedFilePath();
106
+ if (this.compression !== "none" && this.compressOldFiles) {
107
+ rotatedFilePath += `.${this.compression === "gzip" ? "gz" : "zz"}`;
108
+ const compressed = this.compression === "gzip" ? zlib.gzipSync(content) : zlib.deflateSync(content);
109
+ writeFn(rotatedFilePath, compressed);
110
+ }
111
+ else {
112
+ writeFn(rotatedFilePath, content, "utf8");
113
+ }
114
+ writeFn(this.filePath, "", "utf8");
115
+ }
116
+ async performRotationAsync(content) {
117
+ let rotatedFilePath = this.getRotatedFilePath();
118
+ if (this.compression !== "none" && this.compressOldFiles) {
119
+ rotatedFilePath += `.${this.compression === "gzip" ? "gz" : "zz"}`;
120
+ const compressed = this.compression === "gzip" ? await compressGzip(content) : await compressDeflate(content);
121
+ await fs.promises.writeFile(rotatedFilePath, compressed);
122
+ }
123
+ else {
124
+ await fs.promises.writeFile(rotatedFilePath, content, "utf8");
125
+ }
126
+ await fs.promises.writeFile(this.filePath, "", "utf8");
127
+ }
128
+ getRotatedFilePath() {
129
+ const dir = path.dirname(this.filePath);
130
+ return path.join(dir, `${path.basename(this.filePath)}.${Date.now()}`);
131
+ }
132
+ filterRotatedFiles(files, baseName) {
133
+ return files
134
+ .filter(f => f !== baseName && f.startsWith(baseName + "."))
135
+ .sort((a, b) => {
136
+ const getTs = (s) => parseInt(s.slice(baseName.length + 1).split(".")[0] ?? "0");
137
+ return getTs(b) - getTs(a);
138
+ });
139
+ }
140
+ cleanupOldFiles() {
141
+ const dir = path.dirname(this.filePath);
142
+ const baseName = path.basename(this.filePath);
143
+ try {
144
+ const files = fs.readdirSync(dir);
145
+ const rotated = this.filterRotatedFiles(files, baseName);
146
+ for (let i = this.maxFiles; i < rotated.length; i++) {
147
+ const file = rotated[i];
148
+ if (file) {
149
+ try {
150
+ fs.unlinkSync(path.join(dir, file));
151
+ }
152
+ catch { }
153
+ }
154
+ }
155
+ }
156
+ catch { }
157
+ }
158
+ async cleanupOldFilesAsync() {
159
+ const dir = path.dirname(this.filePath);
160
+ const baseName = path.basename(this.filePath);
161
+ try {
162
+ const files = await fs.promises.readdir(dir);
163
+ const rotated = this.filterRotatedFiles(files, baseName);
164
+ await Promise.all(rotated.slice(this.maxFiles).map(f => fs.promises.unlink(path.join(dir, f)).catch(() => { })));
165
+ }
166
+ catch { }
167
+ }
168
+ startBatching() {
169
+ if (this.batchInterval > 0) {
170
+ this.batchTimer = setInterval(() => {
171
+ this.processBatch().catch((error) => {
172
+ console.error("Error in batch processing timer:", error);
173
+ });
174
+ }, this.batchInterval);
175
+ }
176
+ }
177
+ async processBatch() {
178
+ if (this.batchQueue.length === 0) {
179
+ return;
180
+ }
181
+ // Atomically capture and clear queue
182
+ const currentBatch = this.batchQueue;
183
+ this.batchQueue = [];
184
+ // Combine queued entries into one batch
185
+ const batchContent = currentBatch.map((entry) => entry.data).join("");
186
+ try {
187
+ await new Promise((resolve, reject) => {
188
+ fs.appendFile(this.filePath, batchContent, (err) => {
189
+ if (err) {
190
+ reject(err);
191
+ return;
192
+ }
193
+ resolve();
194
+ });
195
+ });
196
+ // Rotate if needed after writing
197
+ if (this.shouldRotate()) {
198
+ await this.rotateFilesAsync();
199
+ }
200
+ }
201
+ catch (error) {
202
+ console.error("Error processing log batch:", error);
203
+ // On error, restore entries for retry (prepend to preserve order)
204
+ this.batchQueue = [...currentBatch, ...this.batchQueue];
205
+ }
206
+ }
207
+ // Clean up resources when the transport is disposed
208
+ async destroy() {
209
+ if (this.batchTimer) {
210
+ clearInterval(this.batchTimer);
211
+ this.batchTimer = null;
212
+ }
213
+ // Flush remaining queued entries
214
+ if (this.batchQueue.length > 0) {
215
+ try {
216
+ await this.processBatch();
217
+ }
218
+ catch (error) {
219
+ console.error("Error processing final batch:", error);
220
+ }
221
+ }
222
+ }
223
+ }
@@ -1,6 +1,6 @@
1
- import { Transport } from "./Transport";
2
- import { LogData } from "../types";
3
- import { Formatter } from "../core/Formatter";
1
+ import { Transport } from "./Transport.js";
2
+ import { LogData } from "../types/index.js";
3
+ import { Formatter } from "../core/Formatter.js";
4
4
  export interface HttpTransportOptions {
5
5
  url: string;
6
6
  method?: string;
@@ -0,0 +1,113 @@
1
+ import * as http from "http";
2
+ import * as https from "https";
3
+ import * as url from "url";
4
+ export class HttpTransport {
5
+ constructor(options) {
6
+ const { url, method = 'POST', headers = {}, timeout = 5000, retries = 3 // defaults
7
+ } = options;
8
+ if (!url) {
9
+ throw new Error('HttpTransport requires a URL option');
10
+ }
11
+ this.url = url;
12
+ this.method = method.toUpperCase();
13
+ this.headers = { ...headers };
14
+ this.timeout = timeout;
15
+ this.retries = retries;
16
+ // Set default Content-Type if not provided
17
+ if (!this.headers['Content-Type'] && !this.headers['content-type']) {
18
+ this.headers['Content-Type'] = 'application/json';
19
+ }
20
+ }
21
+ write(data, formatter) {
22
+ // Format the data as JSON for HTTP transport
23
+ const logObject = this.parseFormattedData(data);
24
+ const body = JSON.stringify(logObject);
25
+ setImmediate(() => {
26
+ this.sendHttpRequestWithRetry(body, 0)
27
+ .catch((error) => {
28
+ console.error('HttpTransport error (sync mode):', error.message);
29
+ });
30
+ });
31
+ }
32
+ async writeAsync(data, formatter) {
33
+ // json formating for HttpTransport
34
+ const logObject = this.parseFormattedData(data);
35
+ const body = JSON.stringify(logObject);
36
+ await this.sendHttpRequestWithRetry(body, this.retries);
37
+ }
38
+ parseFormattedData(originalData) {
39
+ // structured log overide original params
40
+ return {
41
+ level: originalData.level,
42
+ message: originalData.message,
43
+ timestamp: originalData.timestamp.toISOString(),
44
+ ...(originalData.prefix && { prefix: originalData.prefix }),
45
+ ...(originalData.metadata && { metadata: originalData.metadata })
46
+ };
47
+ }
48
+ async sendHttpRequestWithRetry(body, maxRetries) {
49
+ let lastError = null;
50
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
51
+ try {
52
+ await this.sendHttpRequest(body);
53
+ return; // success then exit
54
+ }
55
+ catch (error) {
56
+ lastError = error;
57
+ // stop if last attempt
58
+ if (attempt === maxRetries) {
59
+ break;
60
+ }
61
+ // timer wait before continue
62
+ await this.delay(Math.pow(2, attempt) * 1000);
63
+ }
64
+ }
65
+ if (lastError) {
66
+ throw lastError;
67
+ }
68
+ }
69
+ sendHttpRequest(body) {
70
+ return new Promise((resolve, reject) => {
71
+ const parsedUrl = new url.URL(this.url);
72
+ const isHttps = parsedUrl.protocol === 'https:';
73
+ const client = isHttps ? https : http;
74
+ const requestOptions = {
75
+ hostname: parsedUrl.hostname,
76
+ port: parsedUrl.port,
77
+ path: parsedUrl.pathname + parsedUrl.search,
78
+ method: this.method,
79
+ headers: {
80
+ ...this.headers,
81
+ 'Content-Length': Buffer.byteLength(body, 'utf8'),
82
+ },
83
+ timeout: this.timeout,
84
+ };
85
+ const req = client.request(requestOptions, (res) => {
86
+ let responseData = '';
87
+ res.on('data', (chunk) => {
88
+ responseData += chunk;
89
+ });
90
+ res.on('end', () => {
91
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
92
+ resolve();
93
+ }
94
+ else {
95
+ reject(new Error(`HTTP request failed with status ${res.statusCode}: ${responseData}`));
96
+ }
97
+ });
98
+ });
99
+ req.on('error', (error) => {
100
+ reject(error);
101
+ });
102
+ req.on('timeout', () => {
103
+ req.destroy();
104
+ reject(new Error('Request timeout'));
105
+ });
106
+ req.write(body);
107
+ req.end();
108
+ });
109
+ }
110
+ delay(ms) {
111
+ return new Promise(resolve => setTimeout(resolve, ms));
112
+ }
113
+ }
@@ -1,5 +1,5 @@
1
- import { LogData } from '../types';
2
- import { Formatter } from '../core/Formatter';
1
+ import { LogData } from '../types/index.js';
2
+ import { Formatter } from '../core/Formatter.js';
3
3
  export interface Transport {
4
4
  write(data: LogData, formatter: Formatter): void;
5
5
  writeAsync?(data: LogData, formatter: Formatter): Promise<void>;
@@ -0,0 +1 @@
1
+ export {};