@logtape/syslog 1.3.0-dev.388 → 1.3.0-dev.397
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/deno.json +1 -1
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/syslog.cjs +61 -15
- package/dist/syslog.d.cts +32 -1
- package/dist/syslog.d.cts.map +1 -1
- package/dist/syslog.d.ts +32 -1
- package/dist/syslog.d.ts.map +1 -1
- package/dist/syslog.js +61 -15
- package/dist/syslog.js.map +1 -1
- package/package.json +2 -2
- package/src/mod.ts +1 -0
- package/src/syslog.test.ts +312 -112
- package/src/syslog.ts +158 -32
package/src/syslog.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createSocket } from "node:dgram";
|
|
|
3
3
|
import { Socket } from "node:net";
|
|
4
4
|
import { hostname } from "node:os";
|
|
5
5
|
import process from "node:process";
|
|
6
|
+
import * as tls from "node:tls";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Syslog protocol type.
|
|
@@ -84,6 +85,26 @@ const SEVERITY_LEVELS: Record<LogLevel, number> = {
|
|
|
84
85
|
trace: 7, // Debug: debug-level messages (same as debug)
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* TLS options for secure TCP connections.
|
|
90
|
+
* @since 1.3.0
|
|
91
|
+
*/
|
|
92
|
+
export interface SyslogTlsOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Whether to reject connections with invalid certificates.
|
|
95
|
+
* Setting this to `false` disables certificate validation, which makes
|
|
96
|
+
* the connection vulnerable to man-in-the-middle attacks.
|
|
97
|
+
* @default `true`
|
|
98
|
+
*/
|
|
99
|
+
readonly rejectUnauthorized?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Custom CA certificates to trust. If not provided, the default system
|
|
103
|
+
* CA certificates are used.
|
|
104
|
+
*/
|
|
105
|
+
readonly ca?: string | readonly string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
/**
|
|
88
109
|
* Options for the syslog sink.
|
|
89
110
|
* @since 0.12.0
|
|
@@ -107,6 +128,21 @@ export interface SyslogSinkOptions {
|
|
|
107
128
|
*/
|
|
108
129
|
readonly protocol?: SyslogProtocol;
|
|
109
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Whether to use TLS for TCP connections.
|
|
133
|
+
* This option is ignored for UDP connections.
|
|
134
|
+
* @default `false`
|
|
135
|
+
* @since 1.3.0
|
|
136
|
+
*/
|
|
137
|
+
readonly secure?: boolean;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* TLS options for secure TCP connections.
|
|
141
|
+
* This option is only used when `secure` is `true` and `protocol` is `"tcp"`.
|
|
142
|
+
* @since 1.3.0
|
|
143
|
+
*/
|
|
144
|
+
readonly tlsOptions?: SyslogTlsOptions;
|
|
145
|
+
|
|
110
146
|
/**
|
|
111
147
|
* The syslog facility to use for all messages.
|
|
112
148
|
* @default "local0"
|
|
@@ -411,46 +447,97 @@ export class NodeUdpSyslogConnection implements SyslogConnection {
|
|
|
411
447
|
* @since 0.12.0
|
|
412
448
|
*/
|
|
413
449
|
export class DenoTcpSyslogConnection implements SyslogConnection {
|
|
414
|
-
private connection?: Deno.TcpConn;
|
|
450
|
+
private connection?: Deno.TcpConn | Deno.TlsConn;
|
|
415
451
|
private encoder = new TextEncoder();
|
|
416
452
|
|
|
417
453
|
constructor(
|
|
418
454
|
private hostname: string,
|
|
419
455
|
private port: number,
|
|
420
456
|
private timeout: number,
|
|
457
|
+
private secure: boolean,
|
|
458
|
+
private tlsOptions?: SyslogTlsOptions,
|
|
421
459
|
) {}
|
|
422
460
|
|
|
423
461
|
async connect(): Promise<void> {
|
|
424
462
|
try {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
463
|
+
const connectOptions: Deno.ConnectOptions = {
|
|
464
|
+
hostname: this.hostname,
|
|
465
|
+
port: this.port,
|
|
466
|
+
transport: "tcp",
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
if (this.secure) {
|
|
470
|
+
const tlsConnectOptions: Deno.ConnectTlsOptions = {
|
|
471
|
+
hostname: this.hostname,
|
|
472
|
+
port: this.port,
|
|
473
|
+
caCerts: this.tlsOptions?.ca
|
|
474
|
+
? (Array.isArray(this.tlsOptions.ca)
|
|
475
|
+
? [...this.tlsOptions.ca]
|
|
476
|
+
: [this.tlsOptions.ca])
|
|
477
|
+
: undefined,
|
|
478
|
+
};
|
|
479
|
+
const connectPromise = Deno.connectTls(tlsConnectOptions);
|
|
480
|
+
if (this.timeout > 0) {
|
|
481
|
+
let timeoutId: number;
|
|
482
|
+
let timedOut = false;
|
|
483
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
484
|
+
timeoutId = setTimeout(() => {
|
|
485
|
+
timedOut = true;
|
|
486
|
+
reject(new Error("TCP connection timeout"));
|
|
487
|
+
}, this.timeout);
|
|
438
488
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
this.connection = await Promise.race([
|
|
492
|
+
connectPromise,
|
|
493
|
+
timeoutPromise,
|
|
494
|
+
]);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
// If timed out, clean up the connection when it eventually completes
|
|
497
|
+
if (timedOut) {
|
|
498
|
+
connectPromise
|
|
499
|
+
.then((conn) => {
|
|
500
|
+
try {
|
|
501
|
+
conn.close();
|
|
502
|
+
} catch {
|
|
503
|
+
// Ignore close errors
|
|
504
|
+
}
|
|
505
|
+
})
|
|
506
|
+
.catch(() => {
|
|
507
|
+
// Ignore connection errors
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
throw error;
|
|
511
|
+
} finally {
|
|
512
|
+
clearTimeout(timeoutId!);
|
|
444
513
|
}
|
|
445
|
-
|
|
514
|
+
} else {
|
|
515
|
+
this.connection = await connectPromise;
|
|
516
|
+
}
|
|
517
|
+
} else { // Insecure TCP connection
|
|
518
|
+
if (this.timeout > 0) {
|
|
519
|
+
// Use AbortController for proper timeout handling
|
|
520
|
+
const controller = new AbortController();
|
|
521
|
+
const timeoutId = setTimeout(() => {
|
|
522
|
+
controller.abort();
|
|
523
|
+
}, this.timeout);
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
this.connection = await Deno.connect({
|
|
527
|
+
...connectOptions,
|
|
528
|
+
signal: controller.signal,
|
|
529
|
+
});
|
|
530
|
+
clearTimeout(timeoutId);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
clearTimeout(timeoutId);
|
|
533
|
+
if (controller.signal.aborted) {
|
|
534
|
+
throw new Error("TCP connection timeout");
|
|
535
|
+
}
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
this.connection = await Deno.connect(connectOptions);
|
|
446
540
|
}
|
|
447
|
-
} else {
|
|
448
|
-
// No timeout
|
|
449
|
-
this.connection = await Deno.connect({
|
|
450
|
-
hostname: this.hostname,
|
|
451
|
-
port: this.port,
|
|
452
|
-
transport: "tcp",
|
|
453
|
-
});
|
|
454
541
|
}
|
|
455
542
|
} catch (error) {
|
|
456
543
|
throw new Error(`Failed to connect to syslog server: ${error}`);
|
|
@@ -502,19 +589,35 @@ export class DenoTcpSyslogConnection implements SyslogConnection {
|
|
|
502
589
|
* @since 0.12.0
|
|
503
590
|
*/
|
|
504
591
|
export class NodeTcpSyslogConnection implements SyslogConnection {
|
|
505
|
-
private connection?: Socket;
|
|
592
|
+
private connection?: Socket | tls.TLSSocket;
|
|
506
593
|
private encoder = new TextEncoder();
|
|
507
594
|
|
|
508
595
|
constructor(
|
|
509
596
|
private hostname: string,
|
|
510
597
|
private port: number,
|
|
511
598
|
private timeout: number,
|
|
599
|
+
private secure: boolean,
|
|
600
|
+
private tlsOptions?: SyslogTlsOptions,
|
|
512
601
|
) {}
|
|
513
602
|
|
|
514
603
|
connect(): Promise<void> {
|
|
515
604
|
try {
|
|
516
605
|
return new Promise<void>((resolve, reject) => {
|
|
517
|
-
const
|
|
606
|
+
const connectionOptions: tls.ConnectionOptions = {
|
|
607
|
+
port: this.port,
|
|
608
|
+
host: this.hostname,
|
|
609
|
+
timeout: this.timeout,
|
|
610
|
+
rejectUnauthorized: this.tlsOptions?.rejectUnauthorized ?? true,
|
|
611
|
+
ca: this.tlsOptions?.ca
|
|
612
|
+
? (Array.isArray(this.tlsOptions.ca)
|
|
613
|
+
? [...this.tlsOptions.ca]
|
|
614
|
+
: [this.tlsOptions.ca])
|
|
615
|
+
: undefined,
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const socket: Socket | tls.TLSSocket = this.secure
|
|
619
|
+
? tls.connect(connectionOptions)
|
|
620
|
+
: new Socket();
|
|
518
621
|
|
|
519
622
|
const timeout = setTimeout(() => {
|
|
520
623
|
socket.destroy();
|
|
@@ -532,7 +635,10 @@ export class NodeTcpSyslogConnection implements SyslogConnection {
|
|
|
532
635
|
reject(error);
|
|
533
636
|
});
|
|
534
637
|
|
|
535
|
-
|
|
638
|
+
// For non-TLS sockets, explicitly call connect
|
|
639
|
+
if (!this.secure) {
|
|
640
|
+
(socket as Socket).connect(this.port, this.hostname);
|
|
641
|
+
}
|
|
536
642
|
});
|
|
537
643
|
} catch (error) {
|
|
538
644
|
throw new Error(`Failed to connect to syslog server: ${error}`);
|
|
@@ -671,6 +777,8 @@ export function getSyslogSink(
|
|
|
671
777
|
const hostname = options.hostname ?? "localhost";
|
|
672
778
|
const port = options.port ?? 514;
|
|
673
779
|
const protocol = options.protocol ?? "udp";
|
|
780
|
+
const secure = options.secure ?? false;
|
|
781
|
+
const tlsOptions = options.tlsOptions;
|
|
674
782
|
const facility = options.facility ?? "local0";
|
|
675
783
|
const appName = options.appName ?? "logtape";
|
|
676
784
|
const syslogHostname = options.syslogHostname ?? getSystemHostname();
|
|
@@ -693,12 +801,24 @@ export function getSyslogSink(
|
|
|
693
801
|
if (typeof Deno !== "undefined") {
|
|
694
802
|
// Deno runtime
|
|
695
803
|
return protocol === "tcp"
|
|
696
|
-
? new DenoTcpSyslogConnection(
|
|
804
|
+
? new DenoTcpSyslogConnection(
|
|
805
|
+
hostname,
|
|
806
|
+
port,
|
|
807
|
+
timeout,
|
|
808
|
+
secure,
|
|
809
|
+
tlsOptions,
|
|
810
|
+
)
|
|
697
811
|
: new DenoUdpSyslogConnection(hostname, port, timeout);
|
|
698
812
|
} else {
|
|
699
813
|
// Node.js runtime (and Bun, which uses Node.js APIs)
|
|
700
814
|
return protocol === "tcp"
|
|
701
|
-
? new NodeTcpSyslogConnection(
|
|
815
|
+
? new NodeTcpSyslogConnection(
|
|
816
|
+
hostname,
|
|
817
|
+
port,
|
|
818
|
+
timeout,
|
|
819
|
+
secure,
|
|
820
|
+
tlsOptions,
|
|
821
|
+
)
|
|
702
822
|
: new NodeUdpSyslogConnection(hostname, port, timeout);
|
|
703
823
|
}
|
|
704
824
|
})();
|
|
@@ -730,5 +850,11 @@ export function getSyslogSink(
|
|
|
730
850
|
isConnected = false;
|
|
731
851
|
};
|
|
732
852
|
|
|
853
|
+
// Expose for testing purposes
|
|
854
|
+
Object.defineProperty(sink, "_internal_lastPromise", {
|
|
855
|
+
get: () => lastPromise,
|
|
856
|
+
enumerable: false,
|
|
857
|
+
});
|
|
858
|
+
|
|
733
859
|
return sink;
|
|
734
860
|
}
|