@abbacchio/transport 0.1.1 → 0.1.3
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 +121 -0
- package/package.json +16 -2
- package/pino-transport.cjs +30 -0
- package/src/client.ts +148 -148
- package/src/encrypt.ts +112 -112
- package/src/index.ts +19 -19
- package/src/transports/bunyan.ts +99 -99
- package/src/transports/console.ts +147 -147
- package/src/transports/index.ts +15 -15
- package/src/transports/pino.ts +49 -49
- package/src/transports/winston.ts +100 -100
- package/tsconfig.json +19 -19
package/src/encrypt.ts
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generate a cryptographically secure random key for encryption.
|
|
5
|
-
* Use this on the server/producer side to create a key for each channel.
|
|
6
|
-
*
|
|
7
|
-
* @param length - Length of the key in bytes (default: 32 for 256-bit key)
|
|
8
|
-
* @returns A hex-encoded random key
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* import { generateKey } from "@abbacchio/client";
|
|
13
|
-
*
|
|
14
|
-
* // Generate a key for a channel
|
|
15
|
-
* const key = generateKey();
|
|
16
|
-
*
|
|
17
|
-
* // Use in pino transport
|
|
18
|
-
* const logger = pino({
|
|
19
|
-
* transport: {
|
|
20
|
-
* target: "@abbacchio/client/transports/pino",
|
|
21
|
-
* options: {
|
|
22
|
-
* url: "http://localhost:4000/api/logs",
|
|
23
|
-
* channel: "my-app",
|
|
24
|
-
* secretKey: key,
|
|
25
|
-
* },
|
|
26
|
-
* },
|
|
27
|
-
* });
|
|
28
|
-
*
|
|
29
|
-
* // Share the dashboard URL with the key
|
|
30
|
-
* console.log(`Dashboard: http://localhost:4000?channel=my-app&key=${key}`);
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export function generateKey(length: number = 32): string {
|
|
34
|
-
return randomBytes(length).toString("hex");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const ALGORITHM = "aes-256-gcm";
|
|
38
|
-
const IV_LENGTH = 16;
|
|
39
|
-
const AUTH_TAG_LENGTH = 16;
|
|
40
|
-
const SALT_LENGTH = 32;
|
|
41
|
-
const PBKDF2_ITERATIONS = 100000;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Derive a key from a password using PBKDF2 (browser-compatible)
|
|
45
|
-
*/
|
|
46
|
-
function deriveKey(password: string, salt: Buffer): Buffer {
|
|
47
|
-
return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Encrypt data with AES-256-GCM
|
|
52
|
-
* Returns base64 encoded string: salt:iv:authTag:ciphertext
|
|
53
|
-
*/
|
|
54
|
-
export function encrypt(data: string, secretKey: string): string {
|
|
55
|
-
const salt = randomBytes(SALT_LENGTH);
|
|
56
|
-
const key = deriveKey(secretKey, salt);
|
|
57
|
-
const iv = randomBytes(IV_LENGTH);
|
|
58
|
-
|
|
59
|
-
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
60
|
-
const encrypted = Buffer.concat([
|
|
61
|
-
cipher.update(data, "utf8"),
|
|
62
|
-
cipher.final(),
|
|
63
|
-
]);
|
|
64
|
-
const authTag = cipher.getAuthTag();
|
|
65
|
-
|
|
66
|
-
// Combine: salt + iv + authTag + ciphertext
|
|
67
|
-
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
|
|
68
|
-
return combined.toString("base64");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Decrypt data encrypted with encrypt()
|
|
73
|
-
*/
|
|
74
|
-
export function decrypt(encryptedData: string, secretKey: string): string {
|
|
75
|
-
const combined = Buffer.from(encryptedData, "base64");
|
|
76
|
-
|
|
77
|
-
// Extract components
|
|
78
|
-
const salt = combined.subarray(0, SALT_LENGTH);
|
|
79
|
-
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
80
|
-
const authTag = combined.subarray(
|
|
81
|
-
SALT_LENGTH + IV_LENGTH,
|
|
82
|
-
SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
|
|
83
|
-
);
|
|
84
|
-
const ciphertext = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
85
|
-
|
|
86
|
-
const key = deriveKey(secretKey, salt);
|
|
87
|
-
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
88
|
-
decipher.setAuthTag(authTag);
|
|
89
|
-
|
|
90
|
-
const decrypted = Buffer.concat([
|
|
91
|
-
decipher.update(ciphertext),
|
|
92
|
-
decipher.final(),
|
|
93
|
-
]);
|
|
94
|
-
|
|
95
|
-
return decrypted.toString("utf8");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Encrypt a log object
|
|
100
|
-
*/
|
|
101
|
-
export function encryptLog(log: unknown, secretKey: string): { encrypted: string } {
|
|
102
|
-
const jsonStr = JSON.stringify(log);
|
|
103
|
-
return { encrypted: encrypt(jsonStr, secretKey) };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Decrypt an encrypted log
|
|
108
|
-
*/
|
|
109
|
-
export function decryptLog<T = unknown>(encryptedLog: { encrypted: string }, secretKey: string): T {
|
|
110
|
-
const jsonStr = decrypt(encryptedLog.encrypted, secretKey);
|
|
111
|
-
return JSON.parse(jsonStr);
|
|
112
|
-
}
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a cryptographically secure random key for encryption.
|
|
5
|
+
* Use this on the server/producer side to create a key for each channel.
|
|
6
|
+
*
|
|
7
|
+
* @param length - Length of the key in bytes (default: 32 for 256-bit key)
|
|
8
|
+
* @returns A hex-encoded random key
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { generateKey } from "@abbacchio/client";
|
|
13
|
+
*
|
|
14
|
+
* // Generate a key for a channel
|
|
15
|
+
* const key = generateKey();
|
|
16
|
+
*
|
|
17
|
+
* // Use in pino transport
|
|
18
|
+
* const logger = pino({
|
|
19
|
+
* transport: {
|
|
20
|
+
* target: "@abbacchio/client/transports/pino",
|
|
21
|
+
* options: {
|
|
22
|
+
* url: "http://localhost:4000/api/logs",
|
|
23
|
+
* channel: "my-app",
|
|
24
|
+
* secretKey: key,
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Share the dashboard URL with the key
|
|
30
|
+
* console.log(`Dashboard: http://localhost:4000?channel=my-app&key=${key}`);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function generateKey(length: number = 32): string {
|
|
34
|
+
return randomBytes(length).toString("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ALGORITHM = "aes-256-gcm";
|
|
38
|
+
const IV_LENGTH = 16;
|
|
39
|
+
const AUTH_TAG_LENGTH = 16;
|
|
40
|
+
const SALT_LENGTH = 32;
|
|
41
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Derive a key from a password using PBKDF2 (browser-compatible)
|
|
45
|
+
*/
|
|
46
|
+
function deriveKey(password: string, salt: Buffer): Buffer {
|
|
47
|
+
return pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Encrypt data with AES-256-GCM
|
|
52
|
+
* Returns base64 encoded string: salt:iv:authTag:ciphertext
|
|
53
|
+
*/
|
|
54
|
+
export function encrypt(data: string, secretKey: string): string {
|
|
55
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
56
|
+
const key = deriveKey(secretKey, salt);
|
|
57
|
+
const iv = randomBytes(IV_LENGTH);
|
|
58
|
+
|
|
59
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
60
|
+
const encrypted = Buffer.concat([
|
|
61
|
+
cipher.update(data, "utf8"),
|
|
62
|
+
cipher.final(),
|
|
63
|
+
]);
|
|
64
|
+
const authTag = cipher.getAuthTag();
|
|
65
|
+
|
|
66
|
+
// Combine: salt + iv + authTag + ciphertext
|
|
67
|
+
const combined = Buffer.concat([salt, iv, authTag, encrypted]);
|
|
68
|
+
return combined.toString("base64");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Decrypt data encrypted with encrypt()
|
|
73
|
+
*/
|
|
74
|
+
export function decrypt(encryptedData: string, secretKey: string): string {
|
|
75
|
+
const combined = Buffer.from(encryptedData, "base64");
|
|
76
|
+
|
|
77
|
+
// Extract components
|
|
78
|
+
const salt = combined.subarray(0, SALT_LENGTH);
|
|
79
|
+
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
80
|
+
const authTag = combined.subarray(
|
|
81
|
+
SALT_LENGTH + IV_LENGTH,
|
|
82
|
+
SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
|
|
83
|
+
);
|
|
84
|
+
const ciphertext = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
|
|
85
|
+
|
|
86
|
+
const key = deriveKey(secretKey, salt);
|
|
87
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
88
|
+
decipher.setAuthTag(authTag);
|
|
89
|
+
|
|
90
|
+
const decrypted = Buffer.concat([
|
|
91
|
+
decipher.update(ciphertext),
|
|
92
|
+
decipher.final(),
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
return decrypted.toString("utf8");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Encrypt a log object
|
|
100
|
+
*/
|
|
101
|
+
export function encryptLog(log: unknown, secretKey: string): { encrypted: string } {
|
|
102
|
+
const jsonStr = JSON.stringify(log);
|
|
103
|
+
return { encrypted: encrypt(jsonStr, secretKey) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Decrypt an encrypted log
|
|
108
|
+
*/
|
|
109
|
+
export function decryptLog<T = unknown>(encryptedLog: { encrypted: string }, secretKey: string): T {
|
|
110
|
+
const jsonStr = decrypt(encryptedLog.encrypted, secretKey);
|
|
111
|
+
return JSON.parse(jsonStr);
|
|
112
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// Core client
|
|
2
|
-
export { AbbacchioClient, createClient } from "./client.js";
|
|
3
|
-
export type { AbbacchioClientOptions } from "./client.js";
|
|
4
|
-
|
|
5
|
-
// Encryption utilities
|
|
6
|
-
export { generateKey, encrypt, decrypt, encryptLog, decryptLog } from "./encrypt.js";
|
|
7
|
-
|
|
8
|
-
// Re-export transports for convenience
|
|
9
|
-
export { default as pinoTransport } from "./transports/pino.js";
|
|
10
|
-
export type { PinoTransportOptions } from "./transports/pino.js";
|
|
11
|
-
|
|
12
|
-
export { winstonTransport, AbbacchioWinstonTransport } from "./transports/winston.js";
|
|
13
|
-
export type { WinstonTransportOptions } from "./transports/winston.js";
|
|
14
|
-
|
|
15
|
-
export { bunyanStream, AbbacchioBunyanStream } from "./transports/bunyan.js";
|
|
16
|
-
export type { BunyanStreamOptions } from "./transports/bunyan.js";
|
|
17
|
-
|
|
18
|
-
export { interceptConsole, restoreConsole, getActiveClient } from "./transports/console.js";
|
|
19
|
-
export type { ConsoleInterceptorOptions } from "./transports/console.js";
|
|
1
|
+
// Core client
|
|
2
|
+
export { AbbacchioClient, createClient } from "./client.js";
|
|
3
|
+
export type { AbbacchioClientOptions } from "./client.js";
|
|
4
|
+
|
|
5
|
+
// Encryption utilities
|
|
6
|
+
export { generateKey, encrypt, decrypt, encryptLog, decryptLog } from "./encrypt.js";
|
|
7
|
+
|
|
8
|
+
// Re-export transports for convenience
|
|
9
|
+
export { default as pinoTransport } from "./transports/pino.js";
|
|
10
|
+
export type { PinoTransportOptions } from "./transports/pino.js";
|
|
11
|
+
|
|
12
|
+
export { winstonTransport, AbbacchioWinstonTransport } from "./transports/winston.js";
|
|
13
|
+
export type { WinstonTransportOptions } from "./transports/winston.js";
|
|
14
|
+
|
|
15
|
+
export { bunyanStream, AbbacchioBunyanStream } from "./transports/bunyan.js";
|
|
16
|
+
export type { BunyanStreamOptions } from "./transports/bunyan.js";
|
|
17
|
+
|
|
18
|
+
export { interceptConsole, restoreConsole, getActiveClient } from "./transports/console.js";
|
|
19
|
+
export type { ConsoleInterceptorOptions } from "./transports/console.js";
|
package/src/transports/bunyan.ts
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { Writable } from "stream";
|
|
2
|
-
import { AbbacchioClient, type AbbacchioClientOptions } from "../client.js";
|
|
3
|
-
|
|
4
|
-
export interface BunyanStreamOptions extends AbbacchioClientOptions {
|
|
5
|
-
/** Bunyan log level (optional) */
|
|
6
|
-
level?: number | string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Bunyan stream for Abbacchio.
|
|
11
|
-
* Implements the Node.js Writable stream interface for Bunyan.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* import bunyan from "bunyan";
|
|
16
|
-
* import { bunyanStream } from "@abbacchio/client/transports/bunyan";
|
|
17
|
-
*
|
|
18
|
-
* const logger = bunyan.createLogger({
|
|
19
|
-
* name: "myapp",
|
|
20
|
-
* streams: [
|
|
21
|
-
* { stream: process.stdout },
|
|
22
|
-
* bunyanStream({
|
|
23
|
-
* url: "http://localhost:4000/api/logs",
|
|
24
|
-
* channel: "my-app",
|
|
25
|
-
* secretKey: "optional-encryption-key",
|
|
26
|
-
* }),
|
|
27
|
-
* ],
|
|
28
|
-
* });
|
|
29
|
-
*
|
|
30
|
-
* logger.info("Hello from Bunyan!");
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export class AbbacchioBunyanStream extends Writable {
|
|
34
|
-
private client: AbbacchioClient;
|
|
35
|
-
public level?: number | string;
|
|
36
|
-
|
|
37
|
-
constructor(opts: BunyanStreamOptions = {}) {
|
|
38
|
-
super({ objectMode: true });
|
|
39
|
-
this.client = new AbbacchioClient(opts);
|
|
40
|
-
this.level = opts.level;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Writable stream _write method - called for each log entry
|
|
45
|
-
*/
|
|
46
|
-
_write(
|
|
47
|
-
chunk: Record<string, unknown>,
|
|
48
|
-
_encoding: BufferEncoding,
|
|
49
|
-
callback: (error?: Error | null) => void
|
|
50
|
-
): void {
|
|
51
|
-
try {
|
|
52
|
-
// Transform Bunyan format to Abbacchio format
|
|
53
|
-
const log = this.transformLog(chunk);
|
|
54
|
-
this.client.add(log);
|
|
55
|
-
callback();
|
|
56
|
-
} catch (err) {
|
|
57
|
-
callback(err as Error);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Transform Bunyan log format to a normalized format
|
|
63
|
-
*/
|
|
64
|
-
private transformLog(record: Record<string, unknown>): Record<string, unknown> {
|
|
65
|
-
const { name, hostname, pid, level, msg, time, v, ...rest } = record;
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
level: level as number,
|
|
69
|
-
msg,
|
|
70
|
-
time: time ? new Date(time as string).getTime() : Date.now(),
|
|
71
|
-
name,
|
|
72
|
-
hostname,
|
|
73
|
-
pid,
|
|
74
|
-
...rest,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Close the stream
|
|
80
|
-
*/
|
|
81
|
-
_final(callback: (error?: Error | null) => void): void {
|
|
82
|
-
this.client.close().then(() => callback()).catch(callback);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Factory function to create a Bunyan stream
|
|
88
|
-
* Returns an object with stream and optional level for Bunyan's streams array
|
|
89
|
-
*/
|
|
90
|
-
export function bunyanStream(opts?: BunyanStreamOptions): { stream: AbbacchioBunyanStream; level?: number | string; type: "raw" } {
|
|
91
|
-
const stream = new AbbacchioBunyanStream(opts);
|
|
92
|
-
return {
|
|
93
|
-
stream,
|
|
94
|
-
level: opts?.level,
|
|
95
|
-
type: "raw",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export default bunyanStream;
|
|
1
|
+
import { Writable } from "stream";
|
|
2
|
+
import { AbbacchioClient, type AbbacchioClientOptions } from "../client.js";
|
|
3
|
+
|
|
4
|
+
export interface BunyanStreamOptions extends AbbacchioClientOptions {
|
|
5
|
+
/** Bunyan log level (optional) */
|
|
6
|
+
level?: number | string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bunyan stream for Abbacchio.
|
|
11
|
+
* Implements the Node.js Writable stream interface for Bunyan.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import bunyan from "bunyan";
|
|
16
|
+
* import { bunyanStream } from "@abbacchio/client/transports/bunyan";
|
|
17
|
+
*
|
|
18
|
+
* const logger = bunyan.createLogger({
|
|
19
|
+
* name: "myapp",
|
|
20
|
+
* streams: [
|
|
21
|
+
* { stream: process.stdout },
|
|
22
|
+
* bunyanStream({
|
|
23
|
+
* url: "http://localhost:4000/api/logs",
|
|
24
|
+
* channel: "my-app",
|
|
25
|
+
* secretKey: "optional-encryption-key",
|
|
26
|
+
* }),
|
|
27
|
+
* ],
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* logger.info("Hello from Bunyan!");
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class AbbacchioBunyanStream extends Writable {
|
|
34
|
+
private client: AbbacchioClient;
|
|
35
|
+
public level?: number | string;
|
|
36
|
+
|
|
37
|
+
constructor(opts: BunyanStreamOptions = {}) {
|
|
38
|
+
super({ objectMode: true });
|
|
39
|
+
this.client = new AbbacchioClient(opts);
|
|
40
|
+
this.level = opts.level;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Writable stream _write method - called for each log entry
|
|
45
|
+
*/
|
|
46
|
+
_write(
|
|
47
|
+
chunk: Record<string, unknown>,
|
|
48
|
+
_encoding: BufferEncoding,
|
|
49
|
+
callback: (error?: Error | null) => void
|
|
50
|
+
): void {
|
|
51
|
+
try {
|
|
52
|
+
// Transform Bunyan format to Abbacchio format
|
|
53
|
+
const log = this.transformLog(chunk);
|
|
54
|
+
this.client.add(log);
|
|
55
|
+
callback();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
callback(err as Error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Transform Bunyan log format to a normalized format
|
|
63
|
+
*/
|
|
64
|
+
private transformLog(record: Record<string, unknown>): Record<string, unknown> {
|
|
65
|
+
const { name, hostname, pid, level, msg, time, v, ...rest } = record;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
level: level as number,
|
|
69
|
+
msg,
|
|
70
|
+
time: time ? new Date(time as string).getTime() : Date.now(),
|
|
71
|
+
name,
|
|
72
|
+
hostname,
|
|
73
|
+
pid,
|
|
74
|
+
...rest,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Close the stream
|
|
80
|
+
*/
|
|
81
|
+
_final(callback: (error?: Error | null) => void): void {
|
|
82
|
+
this.client.close().then(() => callback()).catch(callback);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Factory function to create a Bunyan stream
|
|
88
|
+
* Returns an object with stream and optional level for Bunyan's streams array
|
|
89
|
+
*/
|
|
90
|
+
export function bunyanStream(opts?: BunyanStreamOptions): { stream: AbbacchioBunyanStream; level?: number | string; type: "raw" } {
|
|
91
|
+
const stream = new AbbacchioBunyanStream(opts);
|
|
92
|
+
return {
|
|
93
|
+
stream,
|
|
94
|
+
level: opts?.level,
|
|
95
|
+
type: "raw",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default bunyanStream;
|