@fabbahiense/pulsar-pino-transport 0.1.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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @pulsar/pino-transport
2
+
3
+ Pino transport for [Pulsar](https://github.com/your-org/pulsar) — send your application logs to Pulsar in real-time.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @pulsar/pino-transport
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import pino from 'pino'
15
+
16
+ const logger = pino({
17
+ transport: {
18
+ target: '@pulsar/pino-transport',
19
+ options: {
20
+ url: 'https://your-pulsar-instance.com',
21
+ apiKey: 'psk_your_api_key',
22
+ source: 'my-api', // optional, default: 'default'
23
+ batchSize: 50, // optional, default: 50
24
+ flushInterval: 2000, // optional, default: 2000ms
25
+ },
26
+ },
27
+ })
28
+
29
+ logger.info('Server started')
30
+ logger.error({ traceId: 'abc-123' }, 'Something went wrong')
31
+ ```
32
+
33
+ ## Features
34
+
35
+ - Batched log delivery (configurable batch size)
36
+ - Automatic retry with exponential backoff (3 attempts)
37
+ - In-memory queue — no logs lost on temporary network failures
38
+ - Zero dependencies (uses native `fetch`)
39
+ - TypeScript types included
40
+ - Dual CJS/ESM package
41
+
42
+ ## Options
43
+
44
+ | Option | Type | Default | Description |
45
+ |--------|------|---------|-------------|
46
+ | `url` | `string` | — | Pulsar server URL (required) |
47
+ | `apiKey` | `string` | — | API key for authentication (required) |
48
+ | `source` | `string` | `'default'` | Service name in Pulsar |
49
+ | `batchSize` | `number` | `50` | Logs buffered before flush |
50
+ | `flushInterval` | `number` | `2000` | Flush interval in ms |
51
+
52
+ ## Trace ID
53
+
54
+ The transport automatically picks up trace IDs from these fields:
55
+
56
+ - `traceId`
57
+ - `trace_id`
58
+ - `requestId`
59
+ - `req.id`
60
+
61
+ ```typescript
62
+ logger.info({ traceId: 'my-trace-id' }, 'Processing request')
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createPulsarTransport: () => createPulsarTransport,
34
+ default: () => index_default
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+ var LEVEL_MAP = {
38
+ 10: "trace",
39
+ 20: "debug",
40
+ 30: "info",
41
+ 40: "warn",
42
+ 50: "error",
43
+ 60: "fatal"
44
+ };
45
+ var MAX_RETRIES = 3;
46
+ var RETRY_BASE_MS = 1e3;
47
+ function parseTimestamp(time) {
48
+ if (!time) return Date.now();
49
+ if (typeof time === "number") return time;
50
+ if (/^\d{13}$/.test(time)) return parseInt(time, 10);
51
+ const parsed = new Date(time).getTime();
52
+ return isNaN(parsed) ? Date.now() : parsed;
53
+ }
54
+ async function flushWithRetry(url, apiKey, batch, retryQueue) {
55
+ const headers = {
56
+ "Content-Type": "application/json",
57
+ "Authorization": `Bearer ${apiKey}`
58
+ };
59
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
60
+ try {
61
+ const res = await fetch(`${url}/api/ingest/batch`, {
62
+ method: "POST",
63
+ headers,
64
+ body: JSON.stringify({ logs: batch })
65
+ });
66
+ if (res.ok) return;
67
+ if (res.status >= 400 && res.status < 500) return;
68
+ } catch {
69
+ }
70
+ if (attempt < MAX_RETRIES - 1) {
71
+ const delay = RETRY_BASE_MS * Math.pow(2, attempt);
72
+ await new Promise((resolve) => setTimeout(resolve, delay));
73
+ }
74
+ }
75
+ retryQueue.push(...batch);
76
+ }
77
+ function createPulsarTransport(options) {
78
+ const {
79
+ url,
80
+ apiKey,
81
+ source,
82
+ serviceName,
83
+ batchSize = 50,
84
+ flushInterval = 2e3
85
+ } = options;
86
+ const resolvedSource = source || serviceName || "default";
87
+ return {
88
+ transport: {
89
+ target: "@pulsar/pino-transport",
90
+ options: { url, apiKey, source: resolvedSource, batchSize, flushInterval }
91
+ }
92
+ };
93
+ }
94
+ async function index_default(options) {
95
+ const {
96
+ url = "http://localhost:4000",
97
+ apiKey = "",
98
+ source,
99
+ serviceName,
100
+ batchSize = 50,
101
+ flushInterval = 2e3
102
+ } = options;
103
+ const resolvedSource = source || serviceName || "default";
104
+ const { default: build } = await import("pino-abstract-transport");
105
+ const buffer = [];
106
+ const retryQueue = [];
107
+ let timer = null;
108
+ async function flush() {
109
+ if (retryQueue.length > 0) {
110
+ buffer.unshift(...retryQueue.splice(0));
111
+ }
112
+ if (buffer.length === 0) return;
113
+ const batch = buffer.splice(0, batchSize);
114
+ await flushWithRetry(url, apiKey, batch, retryQueue);
115
+ }
116
+ return build(async function(stream) {
117
+ for await (const obj of stream) {
118
+ buffer.push({
119
+ level: LEVEL_MAP[obj.level] || "info",
120
+ msg: obj.msg,
121
+ timestamp: parseTimestamp(obj.time),
122
+ serviceName: resolvedSource,
123
+ traceId: obj.traceId || obj.trace_id || obj.requestId || obj.req?.id || null,
124
+ method: obj.req?.method,
125
+ url: obj.req?.url,
126
+ statusCode: obj.res?.statusCode,
127
+ responseTime: obj.responseTime,
128
+ errorMessage: obj.err?.message,
129
+ errorStack: obj.err?.stack,
130
+ metadata: obj.metadata || null
131
+ });
132
+ if (buffer.length >= batchSize) {
133
+ await flush();
134
+ }
135
+ if (!timer) {
136
+ timer = setInterval(() => flush(), flushInterval);
137
+ }
138
+ }
139
+ if (timer) clearInterval(timer);
140
+ await flush();
141
+ if (retryQueue.length > 0) {
142
+ buffer.push(...retryQueue.splice(0));
143
+ await flush();
144
+ }
145
+ });
146
+ }
147
+ // Annotate the CommonJS export names for ESM import in node:
148
+ 0 && (module.exports = {
149
+ createPulsarTransport
150
+ });
@@ -0,0 +1,50 @@
1
+ import * as pino_abstract_transport from 'pino-abstract-transport';
2
+ import * as node_stream from 'node:stream';
3
+
4
+ interface PulsarTransportOptions {
5
+ /** Pulsar server URL (e.g., 'https://pulsar.example.com') */
6
+ url: string;
7
+ /** API key for authentication (psk_...) */
8
+ apiKey: string;
9
+ /** Service name that will appear in Pulsar (default: 'default') */
10
+ source?: string;
11
+ /** Alias for source */
12
+ serviceName?: string;
13
+ /** Number of logs to buffer before flushing (default: 50) */
14
+ batchSize?: number;
15
+ /** Flush interval in milliseconds (default: 2000) */
16
+ flushInterval?: number;
17
+ }
18
+ /**
19
+ * Creates a Pino transport that sends logs to a Pulsar server.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import pino from 'pino'
24
+ * import { createPulsarTransport } from '@pulsar/pino-transport'
25
+ *
26
+ * const logger = pino(
27
+ * createPulsarTransport({
28
+ * url: 'https://pulsar.example.com',
29
+ * apiKey: 'psk_...',
30
+ * })
31
+ * )
32
+ *
33
+ * logger.info('Server started')
34
+ * ```
35
+ */
36
+ declare function createPulsarTransport(options: PulsarTransportOptions): {
37
+ transport: {
38
+ target: string;
39
+ options: {
40
+ url: string;
41
+ apiKey: string;
42
+ source: string;
43
+ batchSize: number;
44
+ flushInterval: number;
45
+ };
46
+ };
47
+ };
48
+ declare function export_default(options: PulsarTransportOptions): Promise<node_stream.Transform & pino_abstract_transport.OnUnknown>;
49
+
50
+ export { type PulsarTransportOptions, createPulsarTransport, export_default as default };
@@ -0,0 +1,50 @@
1
+ import * as pino_abstract_transport from 'pino-abstract-transport';
2
+ import * as node_stream from 'node:stream';
3
+
4
+ interface PulsarTransportOptions {
5
+ /** Pulsar server URL (e.g., 'https://pulsar.example.com') */
6
+ url: string;
7
+ /** API key for authentication (psk_...) */
8
+ apiKey: string;
9
+ /** Service name that will appear in Pulsar (default: 'default') */
10
+ source?: string;
11
+ /** Alias for source */
12
+ serviceName?: string;
13
+ /** Number of logs to buffer before flushing (default: 50) */
14
+ batchSize?: number;
15
+ /** Flush interval in milliseconds (default: 2000) */
16
+ flushInterval?: number;
17
+ }
18
+ /**
19
+ * Creates a Pino transport that sends logs to a Pulsar server.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import pino from 'pino'
24
+ * import { createPulsarTransport } from '@pulsar/pino-transport'
25
+ *
26
+ * const logger = pino(
27
+ * createPulsarTransport({
28
+ * url: 'https://pulsar.example.com',
29
+ * apiKey: 'psk_...',
30
+ * })
31
+ * )
32
+ *
33
+ * logger.info('Server started')
34
+ * ```
35
+ */
36
+ declare function createPulsarTransport(options: PulsarTransportOptions): {
37
+ transport: {
38
+ target: string;
39
+ options: {
40
+ url: string;
41
+ apiKey: string;
42
+ source: string;
43
+ batchSize: number;
44
+ flushInterval: number;
45
+ };
46
+ };
47
+ };
48
+ declare function export_default(options: PulsarTransportOptions): Promise<node_stream.Transform & pino_abstract_transport.OnUnknown>;
49
+
50
+ export { type PulsarTransportOptions, createPulsarTransport, export_default as default };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ // src/index.ts
2
+ var LEVEL_MAP = {
3
+ 10: "trace",
4
+ 20: "debug",
5
+ 30: "info",
6
+ 40: "warn",
7
+ 50: "error",
8
+ 60: "fatal"
9
+ };
10
+ var MAX_RETRIES = 3;
11
+ var RETRY_BASE_MS = 1e3;
12
+ function parseTimestamp(time) {
13
+ if (!time) return Date.now();
14
+ if (typeof time === "number") return time;
15
+ if (/^\d{13}$/.test(time)) return parseInt(time, 10);
16
+ const parsed = new Date(time).getTime();
17
+ return isNaN(parsed) ? Date.now() : parsed;
18
+ }
19
+ async function flushWithRetry(url, apiKey, batch, retryQueue) {
20
+ const headers = {
21
+ "Content-Type": "application/json",
22
+ "Authorization": `Bearer ${apiKey}`
23
+ };
24
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
25
+ try {
26
+ const res = await fetch(`${url}/api/ingest/batch`, {
27
+ method: "POST",
28
+ headers,
29
+ body: JSON.stringify({ logs: batch })
30
+ });
31
+ if (res.ok) return;
32
+ if (res.status >= 400 && res.status < 500) return;
33
+ } catch {
34
+ }
35
+ if (attempt < MAX_RETRIES - 1) {
36
+ const delay = RETRY_BASE_MS * Math.pow(2, attempt);
37
+ await new Promise((resolve) => setTimeout(resolve, delay));
38
+ }
39
+ }
40
+ retryQueue.push(...batch);
41
+ }
42
+ function createPulsarTransport(options) {
43
+ const {
44
+ url,
45
+ apiKey,
46
+ source,
47
+ serviceName,
48
+ batchSize = 50,
49
+ flushInterval = 2e3
50
+ } = options;
51
+ const resolvedSource = source || serviceName || "default";
52
+ return {
53
+ transport: {
54
+ target: "@pulsar/pino-transport",
55
+ options: { url, apiKey, source: resolvedSource, batchSize, flushInterval }
56
+ }
57
+ };
58
+ }
59
+ async function index_default(options) {
60
+ const {
61
+ url = "http://localhost:4000",
62
+ apiKey = "",
63
+ source,
64
+ serviceName,
65
+ batchSize = 50,
66
+ flushInterval = 2e3
67
+ } = options;
68
+ const resolvedSource = source || serviceName || "default";
69
+ const { default: build } = await import("pino-abstract-transport");
70
+ const buffer = [];
71
+ const retryQueue = [];
72
+ let timer = null;
73
+ async function flush() {
74
+ if (retryQueue.length > 0) {
75
+ buffer.unshift(...retryQueue.splice(0));
76
+ }
77
+ if (buffer.length === 0) return;
78
+ const batch = buffer.splice(0, batchSize);
79
+ await flushWithRetry(url, apiKey, batch, retryQueue);
80
+ }
81
+ return build(async function(stream) {
82
+ for await (const obj of stream) {
83
+ buffer.push({
84
+ level: LEVEL_MAP[obj.level] || "info",
85
+ msg: obj.msg,
86
+ timestamp: parseTimestamp(obj.time),
87
+ serviceName: resolvedSource,
88
+ traceId: obj.traceId || obj.trace_id || obj.requestId || obj.req?.id || null,
89
+ method: obj.req?.method,
90
+ url: obj.req?.url,
91
+ statusCode: obj.res?.statusCode,
92
+ responseTime: obj.responseTime,
93
+ errorMessage: obj.err?.message,
94
+ errorStack: obj.err?.stack,
95
+ metadata: obj.metadata || null
96
+ });
97
+ if (buffer.length >= batchSize) {
98
+ await flush();
99
+ }
100
+ if (!timer) {
101
+ timer = setInterval(() => flush(), flushInterval);
102
+ }
103
+ }
104
+ if (timer) clearInterval(timer);
105
+ await flush();
106
+ if (retryQueue.length > 0) {
107
+ buffer.push(...retryQueue.splice(0));
108
+ await flush();
109
+ }
110
+ });
111
+ }
112
+ export {
113
+ createPulsarTransport,
114
+ index_default as default
115
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@fabbahiense/pulsar-pino-transport",
3
+ "version": "0.1.0",
4
+ "description": "Pino transport for Pulsar observability platform",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch"
20
+ },
21
+ "peerDependencies": {
22
+ "pino-abstract-transport": ">=2.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "pino-abstract-transport": "^3.0.0",
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.9.0"
28
+ },
29
+ "keywords": ["pino", "transport", "pulsar", "observability", "logging"],
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }