@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Transport System
|
|
3
|
+
*
|
|
4
|
+
* Provides transport implementations for external log aggregation services
|
|
5
|
+
* like Datadog, generic HTTP webhooks, and console output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LogEntry, LogLevel, LoggerConfig } from "../index";
|
|
9
|
+
|
|
10
|
+
// ============= Types =============
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Error callback for transport errors
|
|
14
|
+
*/
|
|
15
|
+
export type TransportErrorCallback = (error: Error, transport: LogTransport) => void;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base interface for log transports
|
|
19
|
+
*/
|
|
20
|
+
export interface LogTransport {
|
|
21
|
+
/** Transport name for identification */
|
|
22
|
+
readonly name: string;
|
|
23
|
+
|
|
24
|
+
/** Send a single log entry */
|
|
25
|
+
send(entry: LogEntry): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/** Send multiple log entries (batch) */
|
|
28
|
+
sendBatch(entries: LogEntry[]): Promise<void>;
|
|
29
|
+
|
|
30
|
+
/** Flush any pending logs */
|
|
31
|
+
flush?(): Promise<void>;
|
|
32
|
+
|
|
33
|
+
/** Close the transport and cleanup resources */
|
|
34
|
+
close?(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for retry behavior
|
|
39
|
+
*/
|
|
40
|
+
export interface RetryOptions {
|
|
41
|
+
/** Maximum number of retry attempts */
|
|
42
|
+
maxRetries: number;
|
|
43
|
+
/** Initial delay in milliseconds */
|
|
44
|
+
initialDelay: number;
|
|
45
|
+
/** Maximum delay in milliseconds */
|
|
46
|
+
maxDelay: number;
|
|
47
|
+
/** Backoff multiplier */
|
|
48
|
+
backoffMultiplier: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Options for batching behavior
|
|
53
|
+
*/
|
|
54
|
+
export interface BatchOptions {
|
|
55
|
+
/** Maximum batch size before auto-flush */
|
|
56
|
+
batchSize: number;
|
|
57
|
+
/** Flush interval in milliseconds */
|
|
58
|
+
flushInterval: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============= HTTP Webhook Transport =============
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Options for HTTPWebhookTransport
|
|
65
|
+
*/
|
|
66
|
+
export interface HTTPWebhookTransportOptions {
|
|
67
|
+
/** Webhook URL */
|
|
68
|
+
url: string;
|
|
69
|
+
/** Additional headers */
|
|
70
|
+
headers?: Record<string, string>;
|
|
71
|
+
/** Batch options */
|
|
72
|
+
batchSize?: number;
|
|
73
|
+
/** Flush interval in milliseconds */
|
|
74
|
+
flushInterval?: number;
|
|
75
|
+
/** Retry options */
|
|
76
|
+
retries?: Partial<RetryOptions>;
|
|
77
|
+
/** Error callback */
|
|
78
|
+
onError?: TransportErrorCallback;
|
|
79
|
+
/** Request timeout in milliseconds */
|
|
80
|
+
timeout?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generic HTTP webhook transport for sending logs to any HTTP endpoint
|
|
85
|
+
*/
|
|
86
|
+
export class HTTPWebhookTransport implements LogTransport {
|
|
87
|
+
readonly name = "HTTPWebhookTransport";
|
|
88
|
+
private url: string;
|
|
89
|
+
private headers: Record<string, string>;
|
|
90
|
+
private batchSize: number;
|
|
91
|
+
private flushInterval: number;
|
|
92
|
+
private retryOptions: RetryOptions;
|
|
93
|
+
private onError?: TransportErrorCallback;
|
|
94
|
+
private timeout: number;
|
|
95
|
+
private queue: LogEntry[] = [];
|
|
96
|
+
private flushTimer: ReturnType<typeof setInterval> | null = null;
|
|
97
|
+
private isFlushing = false;
|
|
98
|
+
private isClosed = false;
|
|
99
|
+
|
|
100
|
+
constructor(options: HTTPWebhookTransportOptions) {
|
|
101
|
+
this.url = options.url;
|
|
102
|
+
this.headers = options.headers ?? {};
|
|
103
|
+
this.batchSize = options.batchSize ?? 100;
|
|
104
|
+
this.flushInterval = options.flushInterval ?? 5000;
|
|
105
|
+
this.timeout = options.timeout ?? 30000;
|
|
106
|
+
this.retryOptions = {
|
|
107
|
+
maxRetries: 3,
|
|
108
|
+
initialDelay: 100,
|
|
109
|
+
maxDelay: 10000,
|
|
110
|
+
backoffMultiplier: 2,
|
|
111
|
+
...options.retries,
|
|
112
|
+
};
|
|
113
|
+
this.onError = options.onError;
|
|
114
|
+
|
|
115
|
+
this.startFlushTimer();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Start the flush timer
|
|
120
|
+
*/
|
|
121
|
+
private startFlushTimer(): void {
|
|
122
|
+
if (this.flushTimer) {
|
|
123
|
+
clearInterval(this.flushTimer);
|
|
124
|
+
}
|
|
125
|
+
this.flushTimer = setInterval(() => {
|
|
126
|
+
this.flush().catch((err) => this.handleError(err));
|
|
127
|
+
}, this.flushInterval);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Send a single log entry
|
|
132
|
+
*/
|
|
133
|
+
async send(entry: LogEntry): Promise<void> {
|
|
134
|
+
if (this.isClosed) return;
|
|
135
|
+
|
|
136
|
+
this.queue.push(entry);
|
|
137
|
+
|
|
138
|
+
if (this.queue.length >= this.batchSize) {
|
|
139
|
+
await this.flush();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Send multiple log entries
|
|
145
|
+
*/
|
|
146
|
+
async sendBatch(entries: LogEntry[]): Promise<void> {
|
|
147
|
+
if (this.isClosed) return;
|
|
148
|
+
|
|
149
|
+
this.queue.push(...entries);
|
|
150
|
+
|
|
151
|
+
if (this.queue.length >= this.batchSize) {
|
|
152
|
+
await this.flush();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Flush pending logs
|
|
158
|
+
*/
|
|
159
|
+
async flush(): Promise<void> {
|
|
160
|
+
if (this.isFlushing || this.queue.length === 0 || this.isClosed) return;
|
|
161
|
+
|
|
162
|
+
this.isFlushing = true;
|
|
163
|
+
const entriesToSend = [...this.queue];
|
|
164
|
+
this.queue = [];
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await this.sendWithRetry(entriesToSend);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Re-add entries to queue on failure
|
|
170
|
+
this.queue.unshift(...entriesToSend);
|
|
171
|
+
throw error;
|
|
172
|
+
} finally {
|
|
173
|
+
this.isFlushing = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Send entries with retry logic
|
|
179
|
+
*/
|
|
180
|
+
private async sendWithRetry(entries: LogEntry[]): Promise<void> {
|
|
181
|
+
let lastError: Error | null = null;
|
|
182
|
+
let delay = this.retryOptions.initialDelay;
|
|
183
|
+
|
|
184
|
+
for (let attempt = 0; attempt <= this.retryOptions.maxRetries; attempt++) {
|
|
185
|
+
try {
|
|
186
|
+
await this.makeRequest(entries);
|
|
187
|
+
return;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
190
|
+
|
|
191
|
+
if (attempt < this.retryOptions.maxRetries) {
|
|
192
|
+
await this.sleep(delay);
|
|
193
|
+
delay = Math.min(
|
|
194
|
+
delay * this.retryOptions.backoffMultiplier,
|
|
195
|
+
this.retryOptions.maxDelay
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw lastError;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Make HTTP request
|
|
206
|
+
*/
|
|
207
|
+
private async makeRequest(entries: LogEntry[]): Promise<void> {
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const response = await fetch(this.url, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: {
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
...this.headers,
|
|
217
|
+
},
|
|
218
|
+
body: JSON.stringify(entries),
|
|
219
|
+
signal: controller.signal,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
224
|
+
}
|
|
225
|
+
} finally {
|
|
226
|
+
clearTimeout(timeoutId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Sleep utility
|
|
232
|
+
*/
|
|
233
|
+
private sleep(ms: number): Promise<void> {
|
|
234
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Handle errors
|
|
239
|
+
*/
|
|
240
|
+
private handleError(error: Error): void {
|
|
241
|
+
if (this.onError) {
|
|
242
|
+
this.onError(error, this);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Close the transport
|
|
248
|
+
*/
|
|
249
|
+
async close(): Promise<void> {
|
|
250
|
+
this.isClosed = true;
|
|
251
|
+
|
|
252
|
+
if (this.flushTimer) {
|
|
253
|
+
clearInterval(this.flushTimer);
|
|
254
|
+
this.flushTimer = null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Flush remaining entries
|
|
258
|
+
if (this.queue.length > 0) {
|
|
259
|
+
try {
|
|
260
|
+
await this.flush();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============= Datadog Transport =============
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Options for DatadogTransport
|
|
272
|
+
*/
|
|
273
|
+
export interface DatadogTransportOptions {
|
|
274
|
+
/** Datadog API key */
|
|
275
|
+
apiKey: string;
|
|
276
|
+
/** Service name */
|
|
277
|
+
service: string;
|
|
278
|
+
/** Environment (e.g., production, staging) */
|
|
279
|
+
env?: string;
|
|
280
|
+
/** Hostname */
|
|
281
|
+
hostname?: string;
|
|
282
|
+
/** Default tags */
|
|
283
|
+
tags?: string[];
|
|
284
|
+
/** Batch options */
|
|
285
|
+
batchSize?: number;
|
|
286
|
+
/** Flush interval in milliseconds */
|
|
287
|
+
flushInterval?: number;
|
|
288
|
+
/** Retry options */
|
|
289
|
+
retries?: Partial<RetryOptions>;
|
|
290
|
+
/** Error callback */
|
|
291
|
+
onError?: TransportErrorCallback;
|
|
292
|
+
/** Custom Datadog API endpoint */
|
|
293
|
+
endpoint?: string;
|
|
294
|
+
/** Request timeout in milliseconds */
|
|
295
|
+
timeout?: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Datadog log entry format
|
|
300
|
+
*/
|
|
301
|
+
interface DatadogLogEntry {
|
|
302
|
+
ddsource: string;
|
|
303
|
+
ddsourcecategory: string;
|
|
304
|
+
ddtags: string;
|
|
305
|
+
hostname: string;
|
|
306
|
+
service: string;
|
|
307
|
+
status: string;
|
|
308
|
+
message: string;
|
|
309
|
+
timestamp: string;
|
|
310
|
+
[key: string]: unknown;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Datadog Logs API transport
|
|
315
|
+
*/
|
|
316
|
+
export class DatadogTransport implements LogTransport {
|
|
317
|
+
readonly name = "DatadogTransport";
|
|
318
|
+
private apiKey: string;
|
|
319
|
+
private service: string;
|
|
320
|
+
private env: string;
|
|
321
|
+
private hostname: string;
|
|
322
|
+
private tags: string[];
|
|
323
|
+
private endpoint: string;
|
|
324
|
+
private batchSize: number;
|
|
325
|
+
private flushInterval: number;
|
|
326
|
+
private retryOptions: RetryOptions;
|
|
327
|
+
private onError?: TransportErrorCallback;
|
|
328
|
+
private timeout: number;
|
|
329
|
+
private queue: LogEntry[] = [];
|
|
330
|
+
private flushTimer: ReturnType<typeof setInterval> | null = null;
|
|
331
|
+
private isFlushing = false;
|
|
332
|
+
private isClosed = false;
|
|
333
|
+
|
|
334
|
+
constructor(options: DatadogTransportOptions) {
|
|
335
|
+
this.apiKey = options.apiKey;
|
|
336
|
+
this.service = options.service;
|
|
337
|
+
this.env = options.env ?? process.env.NODE_ENV ?? "development";
|
|
338
|
+
this.hostname = options.hostname ?? this.getDefaultHostname();
|
|
339
|
+
this.tags = options.tags ?? [];
|
|
340
|
+
this.endpoint = options.endpoint ?? "https://http-intake.logs.datadoghq.com/v1/input";
|
|
341
|
+
this.batchSize = options.batchSize ?? 100;
|
|
342
|
+
this.flushInterval = options.flushInterval ?? 5000;
|
|
343
|
+
this.timeout = options.timeout ?? 30000;
|
|
344
|
+
this.retryOptions = {
|
|
345
|
+
maxRetries: 3,
|
|
346
|
+
initialDelay: 100,
|
|
347
|
+
maxDelay: 10000,
|
|
348
|
+
backoffMultiplier: 2,
|
|
349
|
+
...options.retries,
|
|
350
|
+
};
|
|
351
|
+
this.onError = options.onError;
|
|
352
|
+
|
|
353
|
+
this.startFlushTimer();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get default hostname
|
|
358
|
+
*/
|
|
359
|
+
private getDefaultHostname(): string {
|
|
360
|
+
try {
|
|
361
|
+
return process.env.HOSTNAME ?? process.env.COMPUTERNAME ?? "unknown";
|
|
362
|
+
} catch {
|
|
363
|
+
return "unknown";
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Start the flush timer
|
|
369
|
+
*/
|
|
370
|
+
private startFlushTimer(): void {
|
|
371
|
+
if (this.flushTimer) {
|
|
372
|
+
clearInterval(this.flushTimer);
|
|
373
|
+
}
|
|
374
|
+
this.flushTimer = setInterval(() => {
|
|
375
|
+
this.flush().catch((err) => this.handleError(err));
|
|
376
|
+
}, this.flushInterval);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Convert log level to Datadog status
|
|
381
|
+
*/
|
|
382
|
+
private toDatadogStatus(level: LogLevel): string {
|
|
383
|
+
const statusMap: Record<LogLevel, string> = {
|
|
384
|
+
debug: "debug",
|
|
385
|
+
info: "info",
|
|
386
|
+
warn: "warn",
|
|
387
|
+
error: "error",
|
|
388
|
+
fatal: "emerg",
|
|
389
|
+
};
|
|
390
|
+
return statusMap[level];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Convert LogEntry to Datadog format
|
|
395
|
+
*/
|
|
396
|
+
private toDatadogFormat(entry: LogEntry): DatadogLogEntry {
|
|
397
|
+
const allTags = [...this.tags];
|
|
398
|
+
|
|
399
|
+
// Add environment tag
|
|
400
|
+
if (this.env) {
|
|
401
|
+
allTags.push(`env:${this.env}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Add context as tags
|
|
405
|
+
if (entry.context) {
|
|
406
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
407
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
408
|
+
allTags.push(`${key}:${value}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const datadogEntry: DatadogLogEntry = {
|
|
414
|
+
ddsource: "bueno",
|
|
415
|
+
ddsourcecategory: "framework",
|
|
416
|
+
ddtags: allTags.join(","),
|
|
417
|
+
hostname: this.hostname,
|
|
418
|
+
service: this.service,
|
|
419
|
+
status: this.toDatadogStatus(entry.level),
|
|
420
|
+
message: entry.message,
|
|
421
|
+
timestamp: entry.timestamp,
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Add error details
|
|
425
|
+
if (entry.error) {
|
|
426
|
+
datadogEntry.error = {
|
|
427
|
+
kind: entry.error.name,
|
|
428
|
+
message: entry.error.message,
|
|
429
|
+
stack: entry.error.stack,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Add duration if present
|
|
434
|
+
if (entry.duration !== undefined) {
|
|
435
|
+
datadogEntry.duration = entry.duration;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Add any additional fields from the entry
|
|
439
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
440
|
+
if (!["level", "message", "timestamp", "context", "error", "duration"].includes(key)) {
|
|
441
|
+
datadogEntry[key] = value;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return datadogEntry;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Send a single log entry
|
|
450
|
+
*/
|
|
451
|
+
async send(entry: LogEntry): Promise<void> {
|
|
452
|
+
if (this.isClosed) return;
|
|
453
|
+
|
|
454
|
+
this.queue.push(entry);
|
|
455
|
+
|
|
456
|
+
if (this.queue.length >= this.batchSize) {
|
|
457
|
+
await this.flush();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Send multiple log entries
|
|
463
|
+
*/
|
|
464
|
+
async sendBatch(entries: LogEntry[]): Promise<void> {
|
|
465
|
+
if (this.isClosed) return;
|
|
466
|
+
|
|
467
|
+
this.queue.push(...entries);
|
|
468
|
+
|
|
469
|
+
if (this.queue.length >= this.batchSize) {
|
|
470
|
+
await this.flush();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Flush pending logs
|
|
476
|
+
*/
|
|
477
|
+
async flush(): Promise<void> {
|
|
478
|
+
if (this.isFlushing || this.queue.length === 0 || this.isClosed) return;
|
|
479
|
+
|
|
480
|
+
this.isFlushing = true;
|
|
481
|
+
const entriesToSend = [...this.queue];
|
|
482
|
+
this.queue = [];
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
await this.sendWithRetry(entriesToSend);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
// Re-add entries to queue on failure
|
|
488
|
+
this.queue.unshift(...entriesToSend);
|
|
489
|
+
throw error;
|
|
490
|
+
} finally {
|
|
491
|
+
this.isFlushing = false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Send entries with retry logic
|
|
497
|
+
*/
|
|
498
|
+
private async sendWithRetry(entries: LogEntry[]): Promise<void> {
|
|
499
|
+
let lastError: Error | null = null;
|
|
500
|
+
let delay = this.retryOptions.initialDelay;
|
|
501
|
+
|
|
502
|
+
for (let attempt = 0; attempt <= this.retryOptions.maxRetries; attempt++) {
|
|
503
|
+
try {
|
|
504
|
+
await this.makeRequest(entries);
|
|
505
|
+
return;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
508
|
+
|
|
509
|
+
if (attempt < this.retryOptions.maxRetries) {
|
|
510
|
+
await this.sleep(delay);
|
|
511
|
+
delay = Math.min(
|
|
512
|
+
delay * this.retryOptions.backoffMultiplier,
|
|
513
|
+
this.retryOptions.maxDelay
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
throw lastError;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Make HTTP request to Datadog
|
|
524
|
+
*/
|
|
525
|
+
private async makeRequest(entries: LogEntry[]): Promise<void> {
|
|
526
|
+
const datadogEntries = entries.map((e) => this.toDatadogFormat(e));
|
|
527
|
+
|
|
528
|
+
const controller = new AbortController();
|
|
529
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const response = await fetch(this.endpoint, {
|
|
533
|
+
method: "POST",
|
|
534
|
+
headers: {
|
|
535
|
+
"Content-Type": "application/json",
|
|
536
|
+
"DD-API-KEY": this.apiKey,
|
|
537
|
+
},
|
|
538
|
+
body: datadogEntries.map((e) => JSON.stringify(e)).join("\n"),
|
|
539
|
+
signal: controller.signal,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
if (!response.ok) {
|
|
543
|
+
throw new Error(`Datadog API error: HTTP ${response.status}: ${response.statusText}`);
|
|
544
|
+
}
|
|
545
|
+
} finally {
|
|
546
|
+
clearTimeout(timeoutId);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Sleep utility
|
|
552
|
+
*/
|
|
553
|
+
private sleep(ms: number): Promise<void> {
|
|
554
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Handle errors
|
|
559
|
+
*/
|
|
560
|
+
private handleError(error: Error): void {
|
|
561
|
+
if (this.onError) {
|
|
562
|
+
this.onError(error, this);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Close the transport
|
|
568
|
+
*/
|
|
569
|
+
async close(): Promise<void> {
|
|
570
|
+
this.isClosed = true;
|
|
571
|
+
|
|
572
|
+
if (this.flushTimer) {
|
|
573
|
+
clearInterval(this.flushTimer);
|
|
574
|
+
this.flushTimer = null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Flush remaining entries
|
|
578
|
+
if (this.queue.length > 0) {
|
|
579
|
+
try {
|
|
580
|
+
await this.flush();
|
|
581
|
+
} catch (error) {
|
|
582
|
+
this.handleError(error instanceof Error ? error : new Error(String(error)));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ============= Console Transport =============
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Options for ConsoleTransport
|
|
592
|
+
*/
|
|
593
|
+
export interface ConsoleTransportOptions {
|
|
594
|
+
/** Use pretty printing */
|
|
595
|
+
pretty?: boolean;
|
|
596
|
+
/** Output stream for logs */
|
|
597
|
+
stream?: "stdout" | "stderr";
|
|
598
|
+
/** Error callback */
|
|
599
|
+
onError?: TransportErrorCallback;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Enhanced console transport for local development
|
|
604
|
+
*/
|
|
605
|
+
export class ConsoleTransport implements LogTransport {
|
|
606
|
+
readonly name = "ConsoleTransport";
|
|
607
|
+
private pretty: boolean;
|
|
608
|
+
private stream: "stdout" | "stderr";
|
|
609
|
+
private onError?: TransportErrorCallback;
|
|
610
|
+
|
|
611
|
+
constructor(options: ConsoleTransportOptions = {}) {
|
|
612
|
+
this.pretty = options.pretty ?? process.env.NODE_ENV !== "production";
|
|
613
|
+
this.stream = options.stream ?? "stdout";
|
|
614
|
+
this.onError = options.onError;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Get level color for pretty printing
|
|
619
|
+
*/
|
|
620
|
+
private getLevelColor(level: LogLevel): string {
|
|
621
|
+
const colors: Record<LogLevel, string> = {
|
|
622
|
+
debug: "\x1b[36m", // cyan
|
|
623
|
+
info: "\x1b[32m", // green
|
|
624
|
+
warn: "\x1b[33m", // yellow
|
|
625
|
+
error: "\x1b[31m", // red
|
|
626
|
+
fatal: "\x1b[35m", // magenta
|
|
627
|
+
};
|
|
628
|
+
return colors[level];
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Format log entry for console output
|
|
633
|
+
*/
|
|
634
|
+
private formatEntry(entry: LogEntry): string {
|
|
635
|
+
if (this.pretty) {
|
|
636
|
+
const color = this.getLevelColor(entry.level);
|
|
637
|
+
const reset = "\x1b[0m";
|
|
638
|
+
let output = `${entry.timestamp} ${color}[${entry.level.toUpperCase()}]${reset} ${entry.message}`;
|
|
639
|
+
|
|
640
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
641
|
+
output += ` ${reset}\x1b[90m${JSON.stringify(entry.context)}\x1b[0m`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (entry.duration !== undefined) {
|
|
645
|
+
output += ` \x1b[90m(${entry.duration}ms)\x1b[0m`;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (entry.error) {
|
|
649
|
+
output += `\n \x1b[31m${entry.error.name}: ${entry.error.message}\x1b[0m`;
|
|
650
|
+
if (entry.error.stack) {
|
|
651
|
+
output += `\n \x1b[90m${entry.error.stack}\x1b[0m`;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return output;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return JSON.stringify(entry);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Output to console
|
|
663
|
+
*/
|
|
664
|
+
private output(formatted: string, level: LogLevel): void {
|
|
665
|
+
if (this.stream === "stderr" || level === "error" || level === "fatal") {
|
|
666
|
+
console.error(formatted);
|
|
667
|
+
} else if (level === "warn") {
|
|
668
|
+
console.warn(formatted);
|
|
669
|
+
} else {
|
|
670
|
+
console.log(formatted);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Send a single log entry
|
|
676
|
+
*/
|
|
677
|
+
async send(entry: LogEntry): Promise<void> {
|
|
678
|
+
try {
|
|
679
|
+
const formatted = this.formatEntry(entry);
|
|
680
|
+
this.output(formatted, entry.level);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (this.onError) {
|
|
683
|
+
this.onError(error instanceof Error ? error : new Error(String(error)), this);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Send multiple log entries
|
|
690
|
+
*/
|
|
691
|
+
async sendBatch(entries: LogEntry[]): Promise<void> {
|
|
692
|
+
for (const entry of entries) {
|
|
693
|
+
await this.send(entry);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Flush (no-op for console)
|
|
699
|
+
*/
|
|
700
|
+
async flush(): Promise<void> {
|
|
701
|
+
// Console transport doesn't buffer
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Close (no-op for console)
|
|
706
|
+
*/
|
|
707
|
+
async close(): Promise<void> {
|
|
708
|
+
// Console transport doesn't need cleanup
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ============= Transport Manager =============
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Manages multiple log transports
|
|
716
|
+
*/
|
|
717
|
+
export class TransportManager {
|
|
718
|
+
private transports: Set<LogTransport> = new Set();
|
|
719
|
+
private onError?: TransportErrorCallback;
|
|
720
|
+
|
|
721
|
+
constructor(options?: { onError?: TransportErrorCallback }) {
|
|
722
|
+
this.onError = options?.onError;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Add a transport
|
|
727
|
+
*/
|
|
728
|
+
addTransport(transport: LogTransport): void {
|
|
729
|
+
this.transports.add(transport);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Remove a transport
|
|
734
|
+
*/
|
|
735
|
+
removeTransport(transport: LogTransport): boolean {
|
|
736
|
+
return this.transports.delete(transport);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Get all transports
|
|
741
|
+
*/
|
|
742
|
+
getTransports(): LogTransport[] {
|
|
743
|
+
return [...this.transports];
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Check if a transport is registered
|
|
748
|
+
*/
|
|
749
|
+
hasTransport(transport: LogTransport): boolean {
|
|
750
|
+
return this.transports.has(transport);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Clear all transports
|
|
755
|
+
*/
|
|
756
|
+
clearTransports(): void {
|
|
757
|
+
this.transports.clear();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Broadcast a log entry to all transports
|
|
762
|
+
*/
|
|
763
|
+
async broadcast(entry: LogEntry): Promise<void> {
|
|
764
|
+
const promises = [...this.transports].map(async (transport) => {
|
|
765
|
+
try {
|
|
766
|
+
await transport.send(entry);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
if (this.onError) {
|
|
769
|
+
this.onError(error instanceof Error ? error : new Error(String(error)), transport);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
await Promise.allSettled(promises);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Broadcast multiple entries to all transports
|
|
779
|
+
*/
|
|
780
|
+
async broadcastBatch(entries: LogEntry[]): Promise<void> {
|
|
781
|
+
const promises = [...this.transports].map(async (transport) => {
|
|
782
|
+
try {
|
|
783
|
+
await transport.sendBatch(entries);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
if (this.onError) {
|
|
786
|
+
this.onError(error instanceof Error ? error : new Error(String(error)), transport);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
await Promise.allSettled(promises);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Flush all transports
|
|
796
|
+
*/
|
|
797
|
+
async flushAll(): Promise<void> {
|
|
798
|
+
const promises = [...this.transports].map(async (transport) => {
|
|
799
|
+
if (transport.flush) {
|
|
800
|
+
try {
|
|
801
|
+
await transport.flush();
|
|
802
|
+
} catch (error) {
|
|
803
|
+
if (this.onError) {
|
|
804
|
+
this.onError(error instanceof Error ? error : new Error(String(error)), transport);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
await Promise.allSettled(promises);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Close all transports
|
|
815
|
+
*/
|
|
816
|
+
async closeAll(): Promise<void> {
|
|
817
|
+
const promises = [...this.transports].map(async (transport) => {
|
|
818
|
+
if (transport.close) {
|
|
819
|
+
try {
|
|
820
|
+
await transport.close();
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (this.onError) {
|
|
823
|
+
this.onError(error instanceof Error ? error : new Error(String(error)), transport);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
await Promise.allSettled(promises);
|
|
830
|
+
this.transports.clear();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ============= Logger Integration =============
|
|
835
|
+
|
|
836
|
+
import { Logger } from "../index";
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Configuration for logger with transports
|
|
840
|
+
*/
|
|
841
|
+
export interface LoggerWithTransportsConfig extends LoggerConfig {
|
|
842
|
+
/** Transports to add to the logger */
|
|
843
|
+
transports?: LogTransport[];
|
|
844
|
+
/** Error callback for transport errors */
|
|
845
|
+
onTransportError?: TransportErrorCallback;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Logger with transport support
|
|
850
|
+
*/
|
|
851
|
+
export class LoggerWithTransports extends Logger {
|
|
852
|
+
private transportManager: TransportManager;
|
|
853
|
+
|
|
854
|
+
constructor(config: LoggerWithTransportsConfig = {}) {
|
|
855
|
+
const { transports, onTransportError, ...loggerConfig } = config;
|
|
856
|
+
|
|
857
|
+
// Create transport manager
|
|
858
|
+
const transportManager = new TransportManager({ onError: onTransportError });
|
|
859
|
+
|
|
860
|
+
// Add transports
|
|
861
|
+
if (transports) {
|
|
862
|
+
for (const transport of transports) {
|
|
863
|
+
transportManager.addTransport(transport);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Create output function that broadcasts to transports
|
|
868
|
+
const originalOutput = loggerConfig.output;
|
|
869
|
+
const outputFn = (entry: LogEntry) => {
|
|
870
|
+
// Call original output if specified
|
|
871
|
+
if (originalOutput) {
|
|
872
|
+
if (typeof originalOutput === "function") {
|
|
873
|
+
originalOutput(entry);
|
|
874
|
+
}
|
|
875
|
+
} else {
|
|
876
|
+
// Default console output
|
|
877
|
+
console.log(JSON.stringify(entry));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Broadcast to transports (async, fire-and-forget)
|
|
881
|
+
transportManager.broadcast(entry).catch(() => {
|
|
882
|
+
// Errors are handled by the transport manager
|
|
883
|
+
});
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
super({
|
|
887
|
+
...loggerConfig,
|
|
888
|
+
output: outputFn,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
this.transportManager = transportManager;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Add a transport
|
|
896
|
+
*/
|
|
897
|
+
addTransport(transport: LogTransport): void {
|
|
898
|
+
this.transportManager.addTransport(transport);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Remove a transport
|
|
903
|
+
*/
|
|
904
|
+
removeTransport(transport: LogTransport): boolean {
|
|
905
|
+
return this.transportManager.removeTransport(transport);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Get all transports
|
|
910
|
+
*/
|
|
911
|
+
getTransports(): LogTransport[] {
|
|
912
|
+
return this.transportManager.getTransports();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Flush all transports
|
|
917
|
+
*/
|
|
918
|
+
async flushTransports(): Promise<void> {
|
|
919
|
+
await this.transportManager.flushAll();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Close all transports
|
|
924
|
+
*/
|
|
925
|
+
async closeTransports(): Promise<void> {
|
|
926
|
+
await this.transportManager.closeAll();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Create a logger with transports
|
|
932
|
+
*/
|
|
933
|
+
export function createLoggerWithTransports(
|
|
934
|
+
config: LoggerWithTransportsConfig = {}
|
|
935
|
+
): LoggerWithTransports {
|
|
936
|
+
return new LoggerWithTransports(config);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Create a transport output function for use with existing Logger
|
|
941
|
+
*/
|
|
942
|
+
export function createTransportOutput(
|
|
943
|
+
transports: LogTransport[],
|
|
944
|
+
options?: { onError?: TransportErrorCallback }
|
|
945
|
+
): (entry: LogEntry) => void {
|
|
946
|
+
const manager = new TransportManager({ onError: options?.onError });
|
|
947
|
+
|
|
948
|
+
for (const transport of transports) {
|
|
949
|
+
manager.addTransport(transport);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return (entry: LogEntry) => {
|
|
953
|
+
manager.broadcast(entry).catch(() => {
|
|
954
|
+
// Errors are handled by the transport manager
|
|
955
|
+
});
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ============= Exports =============
|
|
960
|
+
|
|
961
|
+
export default {
|
|
962
|
+
HTTPWebhookTransport,
|
|
963
|
+
DatadogTransport,
|
|
964
|
+
ConsoleTransport,
|
|
965
|
+
TransportManager,
|
|
966
|
+
LoggerWithTransports,
|
|
967
|
+
createLoggerWithTransports,
|
|
968
|
+
createTransportOutput,
|
|
969
|
+
};
|