@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/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @abbacchio/transport
|
|
2
|
+
|
|
3
|
+
Node.js log transports for sending logs to [Abbacchio](https://github.com/pekonchan/pino-live) - a real-time log viewer dashboard.
|
|
4
|
+
|
|
5
|
+
Supports **Pino**, **Winston**, **Bunyan**, and **Console**.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @abbacchio/transport
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Pino
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import pino from "pino";
|
|
19
|
+
|
|
20
|
+
const logger = pino({
|
|
21
|
+
transport: {
|
|
22
|
+
targets: [
|
|
23
|
+
{ target: "pino-pretty" },
|
|
24
|
+
{
|
|
25
|
+
target: "@abbacchio/transport/transports/pino",
|
|
26
|
+
options: {
|
|
27
|
+
url: "http://localhost:4000/api/logs",
|
|
28
|
+
channel: "my-app",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
logger.info({ user: "john" }, "User logged in");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Winston
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import winston from "winston";
|
|
42
|
+
import { winstonTransport } from "@abbacchio/transport/transports/winston";
|
|
43
|
+
|
|
44
|
+
const logger = winston.createLogger({
|
|
45
|
+
transports: [
|
|
46
|
+
new winston.transports.Console(),
|
|
47
|
+
winstonTransport({
|
|
48
|
+
url: "http://localhost:4000/api/logs",
|
|
49
|
+
channel: "my-app",
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
logger.info("User logged in", { user: "john" });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Bunyan
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import bunyan from "bunyan";
|
|
61
|
+
import { bunyanStream } from "@abbacchio/transport/transports/bunyan";
|
|
62
|
+
|
|
63
|
+
const logger = bunyan.createLogger({
|
|
64
|
+
name: "myapp",
|
|
65
|
+
streams: [
|
|
66
|
+
{ stream: process.stdout },
|
|
67
|
+
bunyanStream({
|
|
68
|
+
url: "http://localhost:4000/api/logs",
|
|
69
|
+
channel: "my-app",
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
logger.info({ user: "john" }, "User logged in");
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Console
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { interceptConsole, restoreConsole } from "@abbacchio/transport/transports/console";
|
|
81
|
+
|
|
82
|
+
interceptConsole({
|
|
83
|
+
url: "http://localhost:4000/api/logs",
|
|
84
|
+
channel: "my-app",
|
|
85
|
+
passthrough: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log("This will be sent to Abbacchio!");
|
|
89
|
+
|
|
90
|
+
restoreConsole();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Options
|
|
94
|
+
|
|
95
|
+
| Option | Type | Default | Description |
|
|
96
|
+
| ----------- | ------ | -------------------------------- | --------------------------------------- |
|
|
97
|
+
| `url` | string | `http://localhost:4000/api/logs` | Abbacchio server URL |
|
|
98
|
+
| `channel` | string | `default` | Channel name for multi-app support |
|
|
99
|
+
| `secretKey` | string | - | Encryption key (enables E2E encryption) |
|
|
100
|
+
| `batchSize` | number | `10` | Send batch when this many logs accumulate |
|
|
101
|
+
| `interval` | number | `1000` | Send batch after this many ms |
|
|
102
|
+
| `headers` | object | `{}` | Additional HTTP headers |
|
|
103
|
+
|
|
104
|
+
## End-to-End Encryption
|
|
105
|
+
|
|
106
|
+
Add `secretKey` to encrypt logs before sending:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
{
|
|
110
|
+
target: "@abbacchio/transport/transports/pino",
|
|
111
|
+
options: {
|
|
112
|
+
url: "http://localhost:4000/api/logs",
|
|
113
|
+
channel: "my-app",
|
|
114
|
+
secretKey: process.env.LOG_SECRET_KEY,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abbacchio/transport",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Node.js log transports for Pino, Winston, and Bunyan - send logs to Abbacchio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
-
"./
|
|
13
|
+
"./pino": {
|
|
14
14
|
"types": "./dist/transports/pino.d.ts",
|
|
15
|
+
"require": "./pino-transport.cjs",
|
|
15
16
|
"default": "./dist/transports/pino.js"
|
|
16
17
|
},
|
|
17
18
|
"./transports/winston": {
|
|
@@ -46,11 +47,24 @@
|
|
|
46
47
|
"pino-abstract-transport": "^3.0.0",
|
|
47
48
|
"winston-transport": "^4.9.0"
|
|
48
49
|
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"pino": "^8.0.0 || ^9.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"pino": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
49
58
|
"devDependencies": {
|
|
50
59
|
"@types/node": "^22.10.5",
|
|
60
|
+
"pino": "^9.0.0",
|
|
51
61
|
"typescript": "^5.7.2"
|
|
52
62
|
},
|
|
53
63
|
"engines": {
|
|
54
64
|
"node": ">=18"
|
|
65
|
+
},
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "https://github.com/mood-agency/abbacchio"
|
|
55
69
|
}
|
|
56
70
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CommonJS wrapper for the Pino transport.
|
|
5
|
+
*
|
|
6
|
+
* Pino runs transports in worker threads using require(), which doesn't
|
|
7
|
+
* work well with ESM subpath exports. This CJS wrapper allows users to
|
|
8
|
+
* simply use: target: '@abbacchio/transport'
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```javascript
|
|
12
|
+
* const pino = require('pino');
|
|
13
|
+
*
|
|
14
|
+
* const logger = pino({
|
|
15
|
+
* transport: {
|
|
16
|
+
* target: '@abbacchio/transport',
|
|
17
|
+
* options: {
|
|
18
|
+
* url: 'http://localhost:4000/api/logs',
|
|
19
|
+
* channel: 'my-app',
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// Dynamic import the ESM module
|
|
27
|
+
module.exports = async function(opts) {
|
|
28
|
+
const { default: pinoTransport } = await import('./dist/transports/pino.js');
|
|
29
|
+
return pinoTransport(opts);
|
|
30
|
+
};
|
package/src/client.ts
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import { encrypt } from "./encrypt.js";
|
|
2
|
-
|
|
3
|
-
export interface AbbacchioClientOptions {
|
|
4
|
-
/** Server URL endpoint */
|
|
5
|
-
url?: string;
|
|
6
|
-
/** Secret key for encryption. If provided, logs will be encrypted before sending */
|
|
7
|
-
secretKey?: string;
|
|
8
|
-
/** Channel/app name for multi-app support. Defaults to 'default' */
|
|
9
|
-
channel?: string;
|
|
10
|
-
/** Number of logs to batch before sending. Defaults to 10 */
|
|
11
|
-
batchSize?: number;
|
|
12
|
-
/** Interval in ms between flushes. Defaults to 1000 */
|
|
13
|
-
interval?: number;
|
|
14
|
-
/** Additional headers to send with requests */
|
|
15
|
-
headers?: Record<string, string>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Shared HTTP client for all Abbacchio transports.
|
|
20
|
-
* Handles batching, encryption, and HTTP communication.
|
|
21
|
-
*/
|
|
22
|
-
export class AbbacchioClient {
|
|
23
|
-
private url: string;
|
|
24
|
-
private secretKey?: string;
|
|
25
|
-
private channel?: string;
|
|
26
|
-
private batchSize: number;
|
|
27
|
-
private interval: number;
|
|
28
|
-
private headers: Record<string, string>;
|
|
29
|
-
|
|
30
|
-
private buffer: unknown[] = [];
|
|
31
|
-
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
-
|
|
33
|
-
constructor(options: AbbacchioClientOptions = {}) {
|
|
34
|
-
this.url = options.url || "http://localhost:4000/api/logs";
|
|
35
|
-
this.secretKey = options.secretKey;
|
|
36
|
-
this.channel = options.channel;
|
|
37
|
-
this.batchSize = options.batchSize || 10;
|
|
38
|
-
this.interval = options.interval || 1000;
|
|
39
|
-
this.headers = options.headers || {};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Process a log entry (encrypt if secretKey is provided)
|
|
44
|
-
*/
|
|
45
|
-
private processLog(log: unknown): unknown {
|
|
46
|
-
if (this.secretKey) {
|
|
47
|
-
return { encrypted: encrypt(JSON.stringify(log), this.secretKey) };
|
|
48
|
-
}
|
|
49
|
-
return log;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Add a log to the buffer and trigger send if needed
|
|
54
|
-
*/
|
|
55
|
-
add(log: unknown): void {
|
|
56
|
-
this.buffer.push(this.processLog(log));
|
|
57
|
-
|
|
58
|
-
if (this.buffer.length >= this.batchSize) {
|
|
59
|
-
this.flush();
|
|
60
|
-
} else {
|
|
61
|
-
this.scheduleSend();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Add multiple logs at once
|
|
67
|
-
*/
|
|
68
|
-
addBatch(logs: unknown[]): void {
|
|
69
|
-
for (const log of logs) {
|
|
70
|
-
this.buffer.push(this.processLog(log));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (this.buffer.length >= this.batchSize) {
|
|
74
|
-
this.flush();
|
|
75
|
-
} else {
|
|
76
|
-
this.scheduleSend();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Send logs immediately without batching
|
|
82
|
-
*/
|
|
83
|
-
async send(logs: unknown[]): Promise<void> {
|
|
84
|
-
const processedLogs = logs.map(log => this.processLog(log));
|
|
85
|
-
await this.sendToServer(processedLogs);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Schedule a send after the interval
|
|
90
|
-
*/
|
|
91
|
-
private scheduleSend(): void {
|
|
92
|
-
if (this.timer) return;
|
|
93
|
-
this.timer = setTimeout(() => {
|
|
94
|
-
this.timer = null;
|
|
95
|
-
this.flush();
|
|
96
|
-
}, this.interval);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Flush the buffer and send to server
|
|
101
|
-
*/
|
|
102
|
-
async flush(): Promise<void> {
|
|
103
|
-
if (this.buffer.length === 0) return;
|
|
104
|
-
|
|
105
|
-
const toSend = this.buffer;
|
|
106
|
-
this.buffer = [];
|
|
107
|
-
|
|
108
|
-
await this.sendToServer(toSend);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Send logs to the Abbacchio server
|
|
113
|
-
*/
|
|
114
|
-
private async sendToServer(logs: unknown[]): Promise<void> {
|
|
115
|
-
try {
|
|
116
|
-
await fetch(this.url, {
|
|
117
|
-
method: "POST",
|
|
118
|
-
headers: {
|
|
119
|
-
"Content-Type": "application/json",
|
|
120
|
-
"X-Encrypted": this.secretKey ? "true" : "false",
|
|
121
|
-
...(this.channel ? { "X-Channel": this.channel } : {}),
|
|
122
|
-
...this.headers,
|
|
123
|
-
},
|
|
124
|
-
body: JSON.stringify({ logs }),
|
|
125
|
-
});
|
|
126
|
-
} catch {
|
|
127
|
-
// Silently fail - don't break the app if Abbacchio server is down
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Close the client and flush any remaining logs
|
|
133
|
-
*/
|
|
134
|
-
async close(): Promise<void> {
|
|
135
|
-
if (this.timer) {
|
|
136
|
-
clearTimeout(this.timer);
|
|
137
|
-
this.timer = null;
|
|
138
|
-
}
|
|
139
|
-
await this.flush();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Create a new Abbacchio client instance
|
|
145
|
-
*/
|
|
146
|
-
export function createClient(options?: AbbacchioClientOptions): AbbacchioClient {
|
|
147
|
-
return new AbbacchioClient(options);
|
|
148
|
-
}
|
|
1
|
+
import { encrypt } from "./encrypt.js";
|
|
2
|
+
|
|
3
|
+
export interface AbbacchioClientOptions {
|
|
4
|
+
/** Server URL endpoint */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Secret key for encryption. If provided, logs will be encrypted before sending */
|
|
7
|
+
secretKey?: string;
|
|
8
|
+
/** Channel/app name for multi-app support. Defaults to 'default' */
|
|
9
|
+
channel?: string;
|
|
10
|
+
/** Number of logs to batch before sending. Defaults to 10 */
|
|
11
|
+
batchSize?: number;
|
|
12
|
+
/** Interval in ms between flushes. Defaults to 1000 */
|
|
13
|
+
interval?: number;
|
|
14
|
+
/** Additional headers to send with requests */
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Shared HTTP client for all Abbacchio transports.
|
|
20
|
+
* Handles batching, encryption, and HTTP communication.
|
|
21
|
+
*/
|
|
22
|
+
export class AbbacchioClient {
|
|
23
|
+
private url: string;
|
|
24
|
+
private secretKey?: string;
|
|
25
|
+
private channel?: string;
|
|
26
|
+
private batchSize: number;
|
|
27
|
+
private interval: number;
|
|
28
|
+
private headers: Record<string, string>;
|
|
29
|
+
|
|
30
|
+
private buffer: unknown[] = [];
|
|
31
|
+
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
+
|
|
33
|
+
constructor(options: AbbacchioClientOptions = {}) {
|
|
34
|
+
this.url = options.url || "http://localhost:4000/api/logs";
|
|
35
|
+
this.secretKey = options.secretKey;
|
|
36
|
+
this.channel = options.channel;
|
|
37
|
+
this.batchSize = options.batchSize || 10;
|
|
38
|
+
this.interval = options.interval || 1000;
|
|
39
|
+
this.headers = options.headers || {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Process a log entry (encrypt if secretKey is provided)
|
|
44
|
+
*/
|
|
45
|
+
private processLog(log: unknown): unknown {
|
|
46
|
+
if (this.secretKey) {
|
|
47
|
+
return { encrypted: encrypt(JSON.stringify(log), this.secretKey) };
|
|
48
|
+
}
|
|
49
|
+
return log;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Add a log to the buffer and trigger send if needed
|
|
54
|
+
*/
|
|
55
|
+
add(log: unknown): void {
|
|
56
|
+
this.buffer.push(this.processLog(log));
|
|
57
|
+
|
|
58
|
+
if (this.buffer.length >= this.batchSize) {
|
|
59
|
+
this.flush();
|
|
60
|
+
} else {
|
|
61
|
+
this.scheduleSend();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add multiple logs at once
|
|
67
|
+
*/
|
|
68
|
+
addBatch(logs: unknown[]): void {
|
|
69
|
+
for (const log of logs) {
|
|
70
|
+
this.buffer.push(this.processLog(log));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.buffer.length >= this.batchSize) {
|
|
74
|
+
this.flush();
|
|
75
|
+
} else {
|
|
76
|
+
this.scheduleSend();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Send logs immediately without batching
|
|
82
|
+
*/
|
|
83
|
+
async send(logs: unknown[]): Promise<void> {
|
|
84
|
+
const processedLogs = logs.map(log => this.processLog(log));
|
|
85
|
+
await this.sendToServer(processedLogs);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Schedule a send after the interval
|
|
90
|
+
*/
|
|
91
|
+
private scheduleSend(): void {
|
|
92
|
+
if (this.timer) return;
|
|
93
|
+
this.timer = setTimeout(() => {
|
|
94
|
+
this.timer = null;
|
|
95
|
+
this.flush();
|
|
96
|
+
}, this.interval);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Flush the buffer and send to server
|
|
101
|
+
*/
|
|
102
|
+
async flush(): Promise<void> {
|
|
103
|
+
if (this.buffer.length === 0) return;
|
|
104
|
+
|
|
105
|
+
const toSend = this.buffer;
|
|
106
|
+
this.buffer = [];
|
|
107
|
+
|
|
108
|
+
await this.sendToServer(toSend);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Send logs to the Abbacchio server
|
|
113
|
+
*/
|
|
114
|
+
private async sendToServer(logs: unknown[]): Promise<void> {
|
|
115
|
+
try {
|
|
116
|
+
await fetch(this.url, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
"X-Encrypted": this.secretKey ? "true" : "false",
|
|
121
|
+
...(this.channel ? { "X-Channel": this.channel } : {}),
|
|
122
|
+
...this.headers,
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({ logs }),
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
// Silently fail - don't break the app if Abbacchio server is down
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Close the client and flush any remaining logs
|
|
133
|
+
*/
|
|
134
|
+
async close(): Promise<void> {
|
|
135
|
+
if (this.timer) {
|
|
136
|
+
clearTimeout(this.timer);
|
|
137
|
+
this.timer = null;
|
|
138
|
+
}
|
|
139
|
+
await this.flush();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a new Abbacchio client instance
|
|
145
|
+
*/
|
|
146
|
+
export function createClient(options?: AbbacchioClientOptions): AbbacchioClient {
|
|
147
|
+
return new AbbacchioClient(options);
|
|
148
|
+
}
|