@logtape/syslog 0.12.0-dev.199
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/LICENSE +20 -0
- package/README.md +89 -0
- package/deno.json +34 -0
- package/dist/_virtual/rolldown_runtime.cjs +30 -0
- package/dist/mod.cjs +3 -0
- package/dist/mod.d.cts +2 -0
- package/dist/mod.d.ts +2 -0
- package/dist/mod.js +3 -0
- package/dist/syslog.cjs +468 -0
- package/dist/syslog.d.cts +173 -0
- package/dist/syslog.d.cts.map +1 -0
- package/dist/syslog.d.ts +173 -0
- package/dist/syslog.d.ts.map +1 -0
- package/dist/syslog.js +468 -0
- package/dist/syslog.js.map +1 -0
- package/mod.ts +15 -0
- package/package.json +61 -0
- package/syslog.test.ts +1475 -0
- package/syslog.ts +734 -0
- package/tsdown.config.ts +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2024 Hong Minhee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @logtape/syslog
|
|
2
|
+
|
|
3
|
+
[](https://jsr.io/@logtape/syslog)
|
|
4
|
+
[](https://www.npmjs.com/package/@logtape/syslog)
|
|
5
|
+
|
|
6
|
+
Syslog sink for [LogTape]. This package provides a syslog sink that sends log messages to a syslog server following the [RFC 5424] specification.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **RFC 5424 compliant**: Follows the official syslog protocol specification
|
|
11
|
+
- **Multiple transports**: Supports both UDP and TCP protocols
|
|
12
|
+
- **Cross-runtime**: Works on Deno, Node.js, and Bun
|
|
13
|
+
- **Non-blocking**: Asynchronous message sending with proper cleanup
|
|
14
|
+
- **Configurable**: Extensive configuration options for facility, hostname, etc.
|
|
15
|
+
- **Structured logging**: Optional structured data support
|
|
16
|
+
- **Zero dependencies**: No external dependencies
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
::: code-group
|
|
21
|
+
|
|
22
|
+
```sh [Deno]
|
|
23
|
+
deno add jsr:@logtape/syslog
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```sh [npm]
|
|
27
|
+
npm add @logtape/syslog
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```sh [pnpm]
|
|
31
|
+
pnpm add @logtape/syslog
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh [Yarn]
|
|
35
|
+
yarn add @logtape/syslog
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```sh [Bun]
|
|
39
|
+
bun add @logtape/syslog
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
:::
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { configure } from "@logtape/logtape";
|
|
48
|
+
import { getSyslogSink } from "@logtape/syslog";
|
|
49
|
+
|
|
50
|
+
await configure({
|
|
51
|
+
sinks: {
|
|
52
|
+
syslog: getSyslogSink({
|
|
53
|
+
hostname: "syslog.example.com",
|
|
54
|
+
port: 514,
|
|
55
|
+
protocol: "udp",
|
|
56
|
+
facility: "local0",
|
|
57
|
+
appName: "my-app",
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
loggers: [
|
|
61
|
+
{ category: [], sinks: ["syslog"], level: "info" },
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration Options
|
|
67
|
+
|
|
68
|
+
- **hostname**: Syslog server hostname (default: "localhost")
|
|
69
|
+
- **port**: Syslog server port (default: 514)
|
|
70
|
+
- **protocol**: Transport protocol "udp" or "tcp" (default: "udp")
|
|
71
|
+
- **facility**: Syslog facility (default: "local0")
|
|
72
|
+
- **appName**: Application name in syslog messages (default: "logtape")
|
|
73
|
+
- **syslogHostname**: Hostname field in syslog messages
|
|
74
|
+
- **processId**: Process ID field in syslog messages
|
|
75
|
+
- **timeout**: Connection timeout in milliseconds (default: 5000)
|
|
76
|
+
- **includeStructuredData**: Include LogTape properties as structured data (default: false)
|
|
77
|
+
|
|
78
|
+
## Facilities
|
|
79
|
+
|
|
80
|
+
Supports all RFC 5424 facilities:
|
|
81
|
+
- System facilities: `kernel`, `user`, `mail`, `daemon`, `security`, `syslog`, `lpr`, `news`, `uucp`, `cron`, `authpriv`, `ftp`, `ntp`, `logaudit`, `logalert`, `clock`
|
|
82
|
+
- Local facilities: `local0` through `local7`
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT License. See [LICENSE](../LICENSE) for details.
|
|
87
|
+
|
|
88
|
+
[LogTape]: https://logtape.org/
|
|
89
|
+
[RFC 5424]: https://tools.ietf.org/rfc/rfc5424.txt
|
package/deno.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@logtape/syslog",
|
|
3
|
+
"version": "0.12.0-dev.199+b454c29f",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"exports": "./mod.ts",
|
|
6
|
+
"exclude": [
|
|
7
|
+
"coverage/",
|
|
8
|
+
"npm/",
|
|
9
|
+
".dnt-import-map.json"
|
|
10
|
+
],
|
|
11
|
+
"tasks": {
|
|
12
|
+
"build": "pnpm build",
|
|
13
|
+
"test": "deno test --allow-net --allow-env=TEMP,TMP,TMPDIR,HOSTNAME --allow-sys",
|
|
14
|
+
"test:node": {
|
|
15
|
+
"dependencies": [
|
|
16
|
+
"build"
|
|
17
|
+
],
|
|
18
|
+
"command": "node --experimental-transform-types --test"
|
|
19
|
+
},
|
|
20
|
+
"test:bun": {
|
|
21
|
+
"dependencies": [
|
|
22
|
+
"build"
|
|
23
|
+
],
|
|
24
|
+
"command": "bun test"
|
|
25
|
+
},
|
|
26
|
+
"test-all": {
|
|
27
|
+
"dependencies": [
|
|
28
|
+
"test",
|
|
29
|
+
"test:node",
|
|
30
|
+
"test:bun"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(exports, '__toESM', {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () {
|
|
28
|
+
return __toESM;
|
|
29
|
+
}
|
|
30
|
+
});
|
package/dist/mod.cjs
ADDED
package/dist/mod.d.cts
ADDED
package/dist/mod.d.ts
ADDED
package/dist/mod.js
ADDED
package/dist/syslog.cjs
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const node_dgram = require_rolldown_runtime.__toESM(require("node:dgram"));
|
|
3
|
+
const node_net = require_rolldown_runtime.__toESM(require("node:net"));
|
|
4
|
+
const node_os = require_rolldown_runtime.__toESM(require("node:os"));
|
|
5
|
+
const node_process = require_rolldown_runtime.__toESM(require("node:process"));
|
|
6
|
+
|
|
7
|
+
//#region syslog.ts
|
|
8
|
+
/**
|
|
9
|
+
* Syslog facility code mapping.
|
|
10
|
+
* @since 0.12.0
|
|
11
|
+
*/
|
|
12
|
+
const FACILITY_CODES = {
|
|
13
|
+
kernel: 0,
|
|
14
|
+
user: 1,
|
|
15
|
+
mail: 2,
|
|
16
|
+
daemon: 3,
|
|
17
|
+
security: 4,
|
|
18
|
+
syslog: 5,
|
|
19
|
+
lpr: 6,
|
|
20
|
+
news: 7,
|
|
21
|
+
uucp: 8,
|
|
22
|
+
cron: 9,
|
|
23
|
+
authpriv: 10,
|
|
24
|
+
ftp: 11,
|
|
25
|
+
ntp: 12,
|
|
26
|
+
logaudit: 13,
|
|
27
|
+
logalert: 14,
|
|
28
|
+
clock: 15,
|
|
29
|
+
local0: 16,
|
|
30
|
+
local1: 17,
|
|
31
|
+
local2: 18,
|
|
32
|
+
local3: 19,
|
|
33
|
+
local4: 20,
|
|
34
|
+
local5: 21,
|
|
35
|
+
local6: 22,
|
|
36
|
+
local7: 23
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Syslog severity levels as defined in RFC 5424.
|
|
40
|
+
* @since 0.12.0
|
|
41
|
+
*/
|
|
42
|
+
const SEVERITY_LEVELS = {
|
|
43
|
+
fatal: 0,
|
|
44
|
+
error: 3,
|
|
45
|
+
warning: 4,
|
|
46
|
+
info: 6,
|
|
47
|
+
debug: 7,
|
|
48
|
+
trace: 7
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Calculates the priority value for a syslog message.
|
|
52
|
+
* Priority = Facility * 8 + Severity
|
|
53
|
+
* @since 0.12.0
|
|
54
|
+
*/
|
|
55
|
+
function calculatePriority(facility, severity) {
|
|
56
|
+
const facilityCode = FACILITY_CODES[facility];
|
|
57
|
+
return facilityCode * 8 + severity;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Formats a timestamp (number) as RFC 3339 timestamp for syslog.
|
|
61
|
+
* @since 0.12.0
|
|
62
|
+
*/
|
|
63
|
+
function formatTimestamp(timestamp) {
|
|
64
|
+
return new Date(timestamp).toISOString();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Escapes special characters in structured data values.
|
|
68
|
+
* @since 0.12.0
|
|
69
|
+
*/
|
|
70
|
+
function escapeStructuredDataValue(value) {
|
|
71
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/]/g, "\\]");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Formats structured data from log record properties.
|
|
75
|
+
* @since 0.12.0
|
|
76
|
+
*/
|
|
77
|
+
function formatStructuredData(record, structuredDataId) {
|
|
78
|
+
if (!record.properties || Object.keys(record.properties).length === 0) return "-";
|
|
79
|
+
const elements = [];
|
|
80
|
+
for (const [key, value] of Object.entries(record.properties)) {
|
|
81
|
+
const escapedValue = escapeStructuredDataValue(String(value));
|
|
82
|
+
elements.push(`${key}="${escapedValue}"`);
|
|
83
|
+
}
|
|
84
|
+
return `[${structuredDataId} ${elements.join(" ")}]`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Formats a log record as RFC 5424 syslog message.
|
|
88
|
+
* @since 0.12.0
|
|
89
|
+
*/
|
|
90
|
+
function formatSyslogMessage(record, options) {
|
|
91
|
+
const severity = SEVERITY_LEVELS[record.level];
|
|
92
|
+
const priority = calculatePriority(options.facility, severity);
|
|
93
|
+
const timestamp = formatTimestamp(record.timestamp);
|
|
94
|
+
const hostname$1 = options.syslogHostname || "-";
|
|
95
|
+
const appName = options.appName || "-";
|
|
96
|
+
const processId = options.processId || "-";
|
|
97
|
+
const msgId = "-";
|
|
98
|
+
let structuredData = "-";
|
|
99
|
+
if (options.includeStructuredData) structuredData = formatStructuredData(record, options.structuredDataId);
|
|
100
|
+
let message = "";
|
|
101
|
+
for (let i = 0; i < record.message.length; i++) if (i % 2 === 0) message += record.message[i];
|
|
102
|
+
else message += JSON.stringify(record.message[i]);
|
|
103
|
+
return `<${priority}>1 ${timestamp} ${hostname$1} ${appName} ${processId} ${msgId} ${structuredData} ${message}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Gets the system hostname.
|
|
107
|
+
* @since 0.12.0
|
|
108
|
+
*/
|
|
109
|
+
function getSystemHostname() {
|
|
110
|
+
try {
|
|
111
|
+
if (typeof Deno !== "undefined" && Deno.hostname) return Deno.hostname();
|
|
112
|
+
return (0, node_os.hostname)();
|
|
113
|
+
} catch {
|
|
114
|
+
return node_process.default.env.HOSTNAME || "localhost";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Gets the current process ID.
|
|
119
|
+
* @since 0.12.0
|
|
120
|
+
*/
|
|
121
|
+
function getProcessId() {
|
|
122
|
+
try {
|
|
123
|
+
if (typeof Deno !== "undefined" && Deno.pid) return Deno.pid.toString();
|
|
124
|
+
return node_process.default.pid.toString();
|
|
125
|
+
} catch {
|
|
126
|
+
return "-";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Deno UDP syslog connection implementation.
|
|
131
|
+
* @since 0.12.0
|
|
132
|
+
*/
|
|
133
|
+
var DenoUdpSyslogConnection = class {
|
|
134
|
+
encoder = new TextEncoder();
|
|
135
|
+
constructor(hostname$1, port, timeout) {
|
|
136
|
+
this.hostname = hostname$1;
|
|
137
|
+
this.port = port;
|
|
138
|
+
this.timeout = timeout;
|
|
139
|
+
}
|
|
140
|
+
connect() {}
|
|
141
|
+
send(message) {
|
|
142
|
+
const data = this.encoder.encode(message);
|
|
143
|
+
try {
|
|
144
|
+
const socket = (0, node_dgram.createSocket)("udp4");
|
|
145
|
+
if (this.timeout > 0) return new Promise((resolve, reject) => {
|
|
146
|
+
const timeout = setTimeout(() => {
|
|
147
|
+
socket.close();
|
|
148
|
+
reject(new Error("UDP send timeout"));
|
|
149
|
+
}, this.timeout);
|
|
150
|
+
socket.send(data, this.port, this.hostname, (error) => {
|
|
151
|
+
clearTimeout(timeout);
|
|
152
|
+
socket.close();
|
|
153
|
+
if (error) reject(error);
|
|
154
|
+
else resolve();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
else return new Promise((resolve, reject) => {
|
|
158
|
+
socket.send(data, this.port, this.hostname, (error) => {
|
|
159
|
+
socket.close();
|
|
160
|
+
if (error) reject(error);
|
|
161
|
+
else resolve();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Failed to send syslog message: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
close() {}
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Node.js UDP syslog connection implementation.
|
|
172
|
+
* @since 0.12.0
|
|
173
|
+
*/
|
|
174
|
+
var NodeUdpSyslogConnection = class {
|
|
175
|
+
encoder = new TextEncoder();
|
|
176
|
+
constructor(hostname$1, port, timeout) {
|
|
177
|
+
this.hostname = hostname$1;
|
|
178
|
+
this.port = port;
|
|
179
|
+
this.timeout = timeout;
|
|
180
|
+
}
|
|
181
|
+
connect() {}
|
|
182
|
+
send(message) {
|
|
183
|
+
const data = this.encoder.encode(message);
|
|
184
|
+
try {
|
|
185
|
+
const socket = (0, node_dgram.createSocket)("udp4");
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const timeout = setTimeout(() => {
|
|
188
|
+
socket.close();
|
|
189
|
+
reject(new Error("UDP send timeout"));
|
|
190
|
+
}, this.timeout);
|
|
191
|
+
socket.send(data, this.port, this.hostname, (error) => {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
socket.close();
|
|
194
|
+
if (error) reject(error);
|
|
195
|
+
else resolve();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw new Error(`Failed to send syslog message: ${error}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
close() {}
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Deno TCP syslog connection implementation.
|
|
206
|
+
* @since 0.12.0
|
|
207
|
+
*/
|
|
208
|
+
var DenoTcpSyslogConnection = class {
|
|
209
|
+
connection;
|
|
210
|
+
encoder = new TextEncoder();
|
|
211
|
+
constructor(hostname$1, port, timeout) {
|
|
212
|
+
this.hostname = hostname$1;
|
|
213
|
+
this.port = port;
|
|
214
|
+
this.timeout = timeout;
|
|
215
|
+
}
|
|
216
|
+
async connect() {
|
|
217
|
+
try {
|
|
218
|
+
if (this.timeout > 0) {
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const timeoutId = setTimeout(() => {
|
|
221
|
+
controller.abort();
|
|
222
|
+
}, this.timeout);
|
|
223
|
+
try {
|
|
224
|
+
this.connection = await Deno.connect({
|
|
225
|
+
hostname: this.hostname,
|
|
226
|
+
port: this.port,
|
|
227
|
+
transport: "tcp",
|
|
228
|
+
signal: controller.signal
|
|
229
|
+
});
|
|
230
|
+
clearTimeout(timeoutId);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
clearTimeout(timeoutId);
|
|
233
|
+
if (controller.signal.aborted) throw new Error("TCP connection timeout");
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
} else this.connection = await Deno.connect({
|
|
237
|
+
hostname: this.hostname,
|
|
238
|
+
port: this.port,
|
|
239
|
+
transport: "tcp"
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
throw new Error(`Failed to connect to syslog server: ${error}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async send(message) {
|
|
246
|
+
if (!this.connection) throw new Error("Connection not established");
|
|
247
|
+
const data = this.encoder.encode(message + "\n");
|
|
248
|
+
try {
|
|
249
|
+
if (this.timeout > 0) {
|
|
250
|
+
const writePromise = this.connection.write(data);
|
|
251
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
reject(new Error("TCP send timeout"));
|
|
254
|
+
}, this.timeout);
|
|
255
|
+
});
|
|
256
|
+
await Promise.race([writePromise, timeoutPromise]);
|
|
257
|
+
} else await this.connection.write(data);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
throw new Error(`Failed to send syslog message: ${error}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
close() {
|
|
263
|
+
if (this.connection) {
|
|
264
|
+
try {
|
|
265
|
+
this.connection.close();
|
|
266
|
+
} catch {}
|
|
267
|
+
this.connection = void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* Node.js TCP syslog connection implementation.
|
|
273
|
+
* @since 0.12.0
|
|
274
|
+
*/
|
|
275
|
+
var NodeTcpSyslogConnection = class {
|
|
276
|
+
connection;
|
|
277
|
+
encoder = new TextEncoder();
|
|
278
|
+
constructor(hostname$1, port, timeout) {
|
|
279
|
+
this.hostname = hostname$1;
|
|
280
|
+
this.port = port;
|
|
281
|
+
this.timeout = timeout;
|
|
282
|
+
}
|
|
283
|
+
connect() {
|
|
284
|
+
try {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
const socket = new node_net.Socket();
|
|
287
|
+
const timeout = setTimeout(() => {
|
|
288
|
+
socket.destroy();
|
|
289
|
+
reject(new Error("TCP connection timeout"));
|
|
290
|
+
}, this.timeout);
|
|
291
|
+
socket.on("connect", () => {
|
|
292
|
+
clearTimeout(timeout);
|
|
293
|
+
this.connection = socket;
|
|
294
|
+
resolve();
|
|
295
|
+
});
|
|
296
|
+
socket.on("error", (error) => {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
reject(error);
|
|
299
|
+
});
|
|
300
|
+
socket.connect(this.port, this.hostname);
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
throw new Error(`Failed to connect to syslog server: ${error}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
send(message) {
|
|
307
|
+
if (!this.connection) throw new Error("Connection not established");
|
|
308
|
+
const data = this.encoder.encode(message + "\n");
|
|
309
|
+
try {
|
|
310
|
+
return new Promise((resolve, reject) => {
|
|
311
|
+
this.connection.write(data, (error) => {
|
|
312
|
+
if (error) reject(error);
|
|
313
|
+
else resolve();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
} catch (error) {
|
|
317
|
+
throw new Error(`Failed to send syslog message: ${error}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
close() {
|
|
321
|
+
if (this.connection) {
|
|
322
|
+
try {
|
|
323
|
+
this.connection.end();
|
|
324
|
+
} catch {}
|
|
325
|
+
this.connection = void 0;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Creates a syslog sink that sends log messages to a syslog server using the
|
|
331
|
+
* RFC 5424 syslog protocol format.
|
|
332
|
+
*
|
|
333
|
+
* This sink supports both UDP and TCP protocols for reliable log transmission
|
|
334
|
+
* to centralized logging systems. It automatically formats log records according
|
|
335
|
+
* to RFC 5424 specification, including structured data support for log properties.
|
|
336
|
+
*
|
|
337
|
+
* ## Features
|
|
338
|
+
*
|
|
339
|
+
* - **RFC 5424 Compliance**: Full implementation of the RFC 5424 syslog protocol
|
|
340
|
+
* - **Cross-Runtime Support**: Works with Deno, Node.js, Bun, and browsers
|
|
341
|
+
* - **Multiple Protocols**: Supports both UDP (fire-and-forget) and TCP (reliable) delivery
|
|
342
|
+
* - **Structured Data**: Automatically includes log record properties as RFC 5424 structured data
|
|
343
|
+
* - **Facility Support**: All standard syslog facilities (kern, user, mail, daemon, local0-7, etc.)
|
|
344
|
+
* - **Automatic Escaping**: Proper escaping of special characters in structured data values
|
|
345
|
+
* - **Connection Management**: Automatic connection handling with configurable timeouts
|
|
346
|
+
*
|
|
347
|
+
* ## Protocol Differences
|
|
348
|
+
*
|
|
349
|
+
* - **UDP**: Fast, connectionless delivery suitable for high-throughput logging.
|
|
350
|
+
* Messages may be lost during network issues but has minimal performance impact.
|
|
351
|
+
* - **TCP**: Reliable, connection-based delivery that ensures message delivery.
|
|
352
|
+
* Higher overhead but guarantees that log messages reach the server.
|
|
353
|
+
*
|
|
354
|
+
* @param options Configuration options for the syslog sink
|
|
355
|
+
* @returns A sink function that sends log records to the syslog server, implementing AsyncDisposable for proper cleanup
|
|
356
|
+
*
|
|
357
|
+
* @example Basic usage with default options
|
|
358
|
+
* ```typescript
|
|
359
|
+
* import { configure } from "@logtape/logtape";
|
|
360
|
+
* import { getSyslogSink } from "@logtape/syslog";
|
|
361
|
+
*
|
|
362
|
+
* await configure({
|
|
363
|
+
* sinks: {
|
|
364
|
+
* syslog: getSyslogSink(), // Sends to localhost:514 via UDP
|
|
365
|
+
* },
|
|
366
|
+
* loggers: [
|
|
367
|
+
* { category: [], sinks: ["syslog"], lowestLevel: "info" },
|
|
368
|
+
* ],
|
|
369
|
+
* });
|
|
370
|
+
* ```
|
|
371
|
+
*
|
|
372
|
+
* @example Custom syslog server configuration
|
|
373
|
+
* ```typescript
|
|
374
|
+
* import { configure } from "@logtape/logtape";
|
|
375
|
+
* import { getSyslogSink } from "@logtape/syslog";
|
|
376
|
+
*
|
|
377
|
+
* await configure({
|
|
378
|
+
* sinks: {
|
|
379
|
+
* syslog: getSyslogSink({
|
|
380
|
+
* hostname: "log-server.example.com",
|
|
381
|
+
* port: 1514,
|
|
382
|
+
* protocol: "tcp",
|
|
383
|
+
* facility: "mail",
|
|
384
|
+
* appName: "my-application",
|
|
385
|
+
* timeout: 10000,
|
|
386
|
+
* }),
|
|
387
|
+
* },
|
|
388
|
+
* loggers: [
|
|
389
|
+
* { category: [], sinks: ["syslog"], lowestLevel: "debug" },
|
|
390
|
+
* ],
|
|
391
|
+
* });
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @example Using structured data for log properties
|
|
395
|
+
* ```typescript
|
|
396
|
+
* import { configure, getLogger } from "@logtape/logtape";
|
|
397
|
+
* import { getSyslogSink } from "@logtape/syslog";
|
|
398
|
+
*
|
|
399
|
+
* await configure({
|
|
400
|
+
* sinks: {
|
|
401
|
+
* syslog: getSyslogSink({
|
|
402
|
+
* includeStructuredData: true,
|
|
403
|
+
* structuredDataId: "myapp@12345",
|
|
404
|
+
* }),
|
|
405
|
+
* },
|
|
406
|
+
* loggers: [
|
|
407
|
+
* { category: [], sinks: ["syslog"], lowestLevel: "info" },
|
|
408
|
+
* ],
|
|
409
|
+
* });
|
|
410
|
+
*
|
|
411
|
+
* const logger = getLogger();
|
|
412
|
+
* // This will include userId and action as structured data
|
|
413
|
+
* logger.info("User action completed", { userId: 123, action: "login" });
|
|
414
|
+
* // Results in: <134>1 2024-01-01T12:00:00.000Z hostname myapp 1234 - [myapp@12345 userId="123" action="login"] User action completed
|
|
415
|
+
* ```
|
|
416
|
+
*
|
|
417
|
+
* @since 0.12.0
|
|
418
|
+
* @see {@link https://tools.ietf.org/html/rfc5424} RFC 5424 - The Syslog Protocol
|
|
419
|
+
* @see {@link SyslogSinkOptions} for detailed configuration options
|
|
420
|
+
*/
|
|
421
|
+
function getSyslogSink(options = {}) {
|
|
422
|
+
const hostname$1 = options.hostname ?? "localhost";
|
|
423
|
+
const port = options.port ?? 514;
|
|
424
|
+
const protocol = options.protocol ?? "udp";
|
|
425
|
+
const facility = options.facility ?? "local0";
|
|
426
|
+
const appName = options.appName ?? "logtape";
|
|
427
|
+
const syslogHostname = options.syslogHostname ?? getSystemHostname();
|
|
428
|
+
const processId = options.processId ?? getProcessId();
|
|
429
|
+
const timeout = options.timeout ?? 5e3;
|
|
430
|
+
const includeStructuredData = options.includeStructuredData ?? false;
|
|
431
|
+
const structuredDataId = options.structuredDataId ?? "logtape@32473";
|
|
432
|
+
const formatOptions = {
|
|
433
|
+
facility,
|
|
434
|
+
appName,
|
|
435
|
+
syslogHostname,
|
|
436
|
+
processId,
|
|
437
|
+
includeStructuredData,
|
|
438
|
+
structuredDataId
|
|
439
|
+
};
|
|
440
|
+
const connection = (() => {
|
|
441
|
+
if (typeof Deno !== "undefined") return protocol === "tcp" ? new DenoTcpSyslogConnection(hostname$1, port, timeout) : new DenoUdpSyslogConnection(hostname$1, port, timeout);
|
|
442
|
+
else return protocol === "tcp" ? new NodeTcpSyslogConnection(hostname$1, port, timeout) : new NodeUdpSyslogConnection(hostname$1, port, timeout);
|
|
443
|
+
})();
|
|
444
|
+
let isConnected = false;
|
|
445
|
+
let lastPromise = Promise.resolve();
|
|
446
|
+
const sink = (record) => {
|
|
447
|
+
const syslogMessage = formatSyslogMessage(record, formatOptions);
|
|
448
|
+
lastPromise = lastPromise.then(async () => {
|
|
449
|
+
if (!isConnected) {
|
|
450
|
+
await connection.connect();
|
|
451
|
+
isConnected = true;
|
|
452
|
+
}
|
|
453
|
+
await connection.send(syslogMessage);
|
|
454
|
+
}).catch((error) => {
|
|
455
|
+
isConnected = false;
|
|
456
|
+
throw error;
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
sink[Symbol.asyncDispose] = async () => {
|
|
460
|
+
await lastPromise.catch(() => {});
|
|
461
|
+
connection.close();
|
|
462
|
+
isConnected = false;
|
|
463
|
+
};
|
|
464
|
+
return sink;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
//#endregion
|
|
468
|
+
exports.getSyslogSink = getSyslogSink;
|