@logtape/syslog 1.2.2 → 1.2.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.
@@ -1,1475 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import {
3
- assert,
4
- assertEquals,
5
- assertInstanceOf,
6
- assertRejects,
7
- assertThrows,
8
- } from "@std/assert";
9
- import type { LogRecord } from "@logtape/logtape";
10
- import { createSocket } from "node:dgram";
11
- import { createServer } from "node:net";
12
- import {
13
- DenoTcpSyslogConnection,
14
- DenoUdpSyslogConnection,
15
- getSyslogSink,
16
- NodeTcpSyslogConnection,
17
- NodeUdpSyslogConnection,
18
- type SyslogFacility,
19
- } from "./syslog.ts";
20
-
21
- const test = suite(import.meta);
22
-
23
- // RFC 5424 syslog message parser for testing
24
- interface ParsedSyslogMessage {
25
- priority: number;
26
- version: number;
27
- timestamp: string;
28
- hostname: string;
29
- appName: string;
30
- procId: string;
31
- msgId: string;
32
- structuredData: string;
33
- message: string;
34
- }
35
-
36
- function parseSyslogMessage(rawMessage: string): ParsedSyslogMessage {
37
- // RFC 5424 format: <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSG
38
- const regex = /^<(\d+)>(\d+) (\S+) (\S+) (\S+) (\S+) (\S+) (.*)$/;
39
- const match = rawMessage.match(regex);
40
-
41
- if (!match) {
42
- throw new Error(`Invalid syslog message format: ${rawMessage}`);
43
- }
44
-
45
- const remaining = match[8];
46
- let structuredData: string;
47
- let message: string;
48
-
49
- if (remaining.startsWith("[")) {
50
- // Parse structured data - need to handle escaped brackets properly
51
- let pos = 0;
52
- let bracketCount = 0;
53
- let inQuotes = false;
54
- let escaped = false;
55
-
56
- for (let i = 0; i < remaining.length; i++) {
57
- const char = remaining[i];
58
-
59
- if (escaped) {
60
- escaped = false;
61
- continue;
62
- }
63
-
64
- if (char === "\\") {
65
- escaped = true;
66
- continue;
67
- }
68
-
69
- if (char === '"') {
70
- inQuotes = !inQuotes;
71
- continue;
72
- }
73
-
74
- if (!inQuotes) {
75
- if (char === "[") {
76
- bracketCount++;
77
- } else if (char === "]") {
78
- bracketCount--;
79
- if (bracketCount === 0) {
80
- // Found the end of structured data
81
- pos = i + 1;
82
- break;
83
- }
84
- }
85
- }
86
- }
87
-
88
- if (pos > 0 && pos < remaining.length && remaining[pos] === " ") {
89
- structuredData = remaining.substring(0, pos);
90
- message = remaining.substring(pos + 1);
91
- } else {
92
- // No message after structured data or structured data extends to end
93
- structuredData = remaining.substring(0, pos || remaining.length);
94
- message = "";
95
- }
96
- } else {
97
- // No structured data, it's just "-"
98
- const spaceIndex = remaining.indexOf(" ");
99
- if (spaceIndex === -1) {
100
- structuredData = remaining;
101
- message = "";
102
- } else {
103
- structuredData = remaining.substring(0, spaceIndex);
104
- message = remaining.substring(spaceIndex + 1);
105
- }
106
- }
107
-
108
- return {
109
- priority: parseInt(match[1]),
110
- version: parseInt(match[2]),
111
- timestamp: match[3],
112
- hostname: match[4],
113
- appName: match[5],
114
- procId: match[6],
115
- msgId: match[7],
116
- structuredData,
117
- message,
118
- };
119
- }
120
-
121
- function parseStructuredData(
122
- structuredDataStr: string,
123
- ): Record<string, string> {
124
- if (structuredDataStr === "-") {
125
- return {};
126
- }
127
-
128
- // Parse [id key1="value1" key2="value2"] format
129
- const match = structuredDataStr.match(/^\[([^\s]+)\s+(.*)\]$/);
130
- if (!match) {
131
- throw new Error(`Invalid structured data format: ${structuredDataStr}`);
132
- }
133
-
134
- const result: Record<string, string> = {};
135
- const keyValuePairs = match[2];
136
-
137
- // Parse key="value" pairs, handling escaped quotes
138
- const kvRegex = /(\w+)="([^"\\]*(\\.[^"\\]*)*)"/g;
139
- let kvMatch;
140
- while ((kvMatch = kvRegex.exec(keyValuePairs)) !== null) {
141
- const key = kvMatch[1];
142
- const value = kvMatch[2]
143
- .replace(/\\"/g, '"')
144
- .replace(/\\\\/g, "\\")
145
- .replace(/\\]/g, "]");
146
- result[key] = value;
147
- }
148
-
149
- return result;
150
- }
151
-
152
- // Create a mock log record for testing
153
- function createMockLogRecord(
154
- level: "trace" | "debug" | "info" | "warning" | "error" | "fatal" = "info",
155
- message: (string | unknown)[] = ["Test message"],
156
- properties?: Record<string, unknown>,
157
- ): LogRecord {
158
- return {
159
- category: ["test"],
160
- level,
161
- message,
162
- rawMessage: "Test message",
163
- timestamp: new Date("2024-01-01T12:00:00.000Z").getTime(),
164
- properties: properties ?? {},
165
- };
166
- }
167
-
168
- test("getSyslogSink() creates a sink function", () => {
169
- const sink = getSyslogSink();
170
- assertEquals(typeof sink, "function");
171
- assertInstanceOf(sink, Function);
172
- });
173
-
174
- test("getSyslogSink() creates an AsyncDisposable sink", () => {
175
- const sink = getSyslogSink();
176
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
177
- });
178
-
179
- // Deno-specific UDP test
180
- if (typeof Deno !== "undefined") {
181
- test("getSyslogSink() with actual UDP message transmission (Deno)", async () => {
182
- // For Deno, test UDP transmission to non-existent server (just verify no crash)
183
- const sink = getSyslogSink({
184
- hostname: "127.0.0.1",
185
- port: 12345, // Use a fixed port that likely has no server
186
- protocol: "udp",
187
- facility: "local1",
188
- appName: "test-app",
189
- timeout: 100, // Short timeout
190
- });
191
-
192
- try {
193
- // Send a log record - this should not crash even if server doesn't exist
194
- const logRecord = createMockLogRecord("info", ["Test message"], {
195
- userId: 123,
196
- });
197
- sink(logRecord);
198
-
199
- // Wait for transmission attempt
200
- await new Promise((resolve) => setTimeout(resolve, 150));
201
-
202
- // Test passes if no crash occurs
203
- assertEquals(true, true);
204
- } finally {
205
- await sink[Symbol.asyncDispose]();
206
- }
207
- });
208
- } else {
209
- test("getSyslogSink() with actual UDP message transmission (Node.js)", async () => {
210
- // Create a mock UDP server to receive messages
211
- let receivedMessage = "";
212
-
213
- // Node.js UDP server
214
- const server = createSocket("udp4");
215
-
216
- await new Promise<void>((resolve) => {
217
- server.bind(0, "127.0.0.1", resolve);
218
- });
219
-
220
- const address = server.address() as { port: number };
221
-
222
- server.on("message", (msg) => {
223
- receivedMessage = msg.toString();
224
- });
225
-
226
- try {
227
- // Create sink with UDP
228
- const sink = getSyslogSink({
229
- hostname: "127.0.0.1",
230
- port: address.port,
231
- protocol: "udp",
232
- facility: "local1",
233
- appName: "test-app",
234
- timeout: 1000,
235
- });
236
-
237
- // Send a log record
238
- const logRecord = createMockLogRecord("info", ["Test message"], {
239
- userId: 123,
240
- });
241
- sink(logRecord);
242
-
243
- // Wait for message transmission
244
- await new Promise((resolve) => setTimeout(resolve, 100));
245
- await sink[Symbol.asyncDispose]();
246
-
247
- // Wait for server to receive
248
- await new Promise((resolve) => setTimeout(resolve, 100));
249
-
250
- // Verify the message format
251
- assertEquals(receivedMessage.includes("Test message"), true);
252
- assertEquals(receivedMessage.includes("test-app"), true);
253
- // Priority should be local1 (17) * 8 + info (6) = 142
254
- assertEquals(receivedMessage.includes("<142>1"), true);
255
- } finally {
256
- server.close();
257
- }
258
- });
259
- }
260
-
261
- // Deno-specific TCP test
262
- if (typeof Deno !== "undefined") {
263
- test("getSyslogSink() with actual TCP message transmission (Deno)", async () => {
264
- // Create a mock TCP server to receive messages
265
- let receivedMessage = "";
266
-
267
- // Deno TCP server
268
- const server = Deno.listen({ port: 0 });
269
- const serverAddr = server.addr as Deno.NetAddr;
270
-
271
- const serverTask = (async () => {
272
- try {
273
- const conn = await server.accept();
274
- const buffer = new Uint8Array(1024);
275
- const bytesRead = await conn.read(buffer);
276
- if (bytesRead) {
277
- receivedMessage = new TextDecoder().decode(
278
- buffer.subarray(0, bytesRead),
279
- );
280
- }
281
- conn.close();
282
- } catch {
283
- // Server closed
284
- }
285
- })();
286
-
287
- try {
288
- // Create sink with TCP
289
- const sink = getSyslogSink({
290
- hostname: "127.0.0.1",
291
- port: serverAddr.port,
292
- protocol: "tcp",
293
- facility: "daemon",
294
- appName: "test-daemon",
295
- timeout: 0, // No timeout to avoid timer leaks
296
- includeStructuredData: true,
297
- structuredDataId: "test@12345",
298
- });
299
-
300
- // Send a log record with properties
301
- const logRecord = createMockLogRecord("error", [
302
- "Critical error occurred",
303
- ], {
304
- errorCode: 500,
305
- component: "auth",
306
- });
307
- sink(logRecord);
308
-
309
- // Wait for message transmission and disposal
310
- await new Promise((resolve) => setTimeout(resolve, 100));
311
- await sink[Symbol.asyncDispose]();
312
-
313
- // Wait for server task
314
- await serverTask;
315
-
316
- // Verify the message format
317
- assertEquals(receivedMessage.includes("Critical error occurred"), true);
318
- assertEquals(receivedMessage.includes("test-daemon"), true);
319
- // Priority should be daemon (3) * 8 + error (3) = 27
320
- assertEquals(receivedMessage.includes("<27>1"), true);
321
- // Should include structured data
322
- assertEquals(receivedMessage.includes("[test@12345"), true);
323
- assertEquals(receivedMessage.includes('errorCode="500"'), true);
324
- assertEquals(receivedMessage.includes('component="auth"'), true);
325
- } finally {
326
- server.close();
327
- await serverTask.catch(() => {});
328
- }
329
- });
330
- } else {
331
- test("getSyslogSink() with actual TCP message transmission (Node.js)", async () => {
332
- // Create a mock TCP server to receive messages
333
- let receivedMessage = "";
334
-
335
- // Node.js TCP server
336
- const server = createServer((socket) => {
337
- socket.on("data", (data) => {
338
- receivedMessage = data.toString();
339
- socket.end();
340
- });
341
- });
342
-
343
- await new Promise<void>((resolve) => {
344
- server.listen(0, "127.0.0.1", resolve);
345
- });
346
-
347
- const address = server.address() as { port: number };
348
-
349
- try {
350
- // Create sink with TCP
351
- const sink = getSyslogSink({
352
- hostname: "127.0.0.1",
353
- port: address.port,
354
- protocol: "tcp",
355
- facility: "daemon",
356
- appName: "test-daemon",
357
- timeout: 5000,
358
- includeStructuredData: true,
359
- structuredDataId: "test@12345",
360
- });
361
-
362
- // Send a log record with properties
363
- const logRecord = createMockLogRecord("error", [
364
- "Critical error occurred",
365
- ], {
366
- errorCode: 500,
367
- component: "auth",
368
- });
369
- sink(logRecord);
370
-
371
- // Wait for message transmission
372
- await new Promise((resolve) => setTimeout(resolve, 100));
373
- await sink[Symbol.asyncDispose]();
374
-
375
- // Wait for server to receive
376
- await new Promise((resolve) => setTimeout(resolve, 100));
377
-
378
- // Verify the message format
379
- assertEquals(receivedMessage.includes("Critical error occurred"), true);
380
- assertEquals(receivedMessage.includes("test-daemon"), true);
381
- // Priority should be daemon (3) * 8 + error (3) = 27
382
- assertEquals(receivedMessage.includes("<27>1"), true);
383
- // Should include structured data
384
- assertEquals(receivedMessage.includes("[test@12345"), true);
385
- assertEquals(receivedMessage.includes('errorCode="500"'), true);
386
- assertEquals(receivedMessage.includes('component="auth"'), true);
387
- } finally {
388
- server.close();
389
- }
390
- });
391
- }
392
-
393
- // Deno-specific multiple messages test
394
- if (typeof Deno !== "undefined") {
395
- test("getSyslogSink() with multiple messages and proper sequencing (Deno)", async () => {
396
- // Test that multiple messages are sent in sequence without blocking
397
- // Deno version - use UDP for simpler testing
398
- const sink = getSyslogSink({
399
- hostname: "127.0.0.1",
400
- port: 1234, // Non-existent port, but should handle gracefully
401
- protocol: "udp",
402
- facility: "local0",
403
- timeout: 100, // Short timeout
404
- });
405
-
406
- try {
407
- // Send multiple messages quickly
408
- const record1 = createMockLogRecord("info", ["Message 1"]);
409
- const record2 = createMockLogRecord("warning", ["Message 2"]);
410
- const record3 = createMockLogRecord("error", ["Message 3"]);
411
-
412
- // These should not block each other
413
- sink(record1);
414
- sink(record2);
415
- sink(record3);
416
-
417
- // All messages should be queued and attempted
418
- // Even if they fail due to no server, the sink should handle it gracefully
419
- } finally {
420
- await sink[Symbol.asyncDispose]();
421
- }
422
-
423
- // Test passes if no hanging or crashes occur
424
- assertEquals(true, true);
425
- });
426
- } else {
427
- test("getSyslogSink() with multiple messages and proper sequencing (Node.js)", async () => {
428
- // Test that multiple messages are sent in sequence without blocking
429
- // Node.js version
430
- const sink = getSyslogSink({
431
- hostname: "127.0.0.1",
432
- port: 1234, // Non-existent port
433
- protocol: "udp",
434
- facility: "local0",
435
- timeout: 100,
436
- });
437
-
438
- try {
439
- // Send multiple messages quickly
440
- const record1 = createMockLogRecord("info", ["Message 1"]);
441
- const record2 = createMockLogRecord("warning", ["Message 2"]);
442
- const record3 = createMockLogRecord("error", ["Message 3"]);
443
-
444
- sink(record1);
445
- sink(record2);
446
- sink(record3);
447
- } finally {
448
- await sink[Symbol.asyncDispose]();
449
- }
450
-
451
- assertEquals(true, true);
452
- });
453
- }
454
-
455
- // RFC 5424 Message Format Validation Tests
456
- if (typeof Deno !== "undefined") {
457
- test("getSyslogSink() RFC 5424 message format validation (Deno)", async () => {
458
- const receivedMessages: string[] = [];
459
-
460
- // Use Node.js dgram API for UDP server (available in Deno)
461
- const server = createSocket("udp4");
462
-
463
- await new Promise<void>((resolve) => {
464
- server.bind(0, "127.0.0.1", resolve);
465
- });
466
-
467
- const address = server.address() as { port: number };
468
-
469
- server.on("message", (msg) => {
470
- receivedMessages.push(msg.toString());
471
- });
472
-
473
- try {
474
- const sink = getSyslogSink({
475
- hostname: "127.0.0.1",
476
- port: address.port,
477
- protocol: "udp",
478
- facility: "daemon", // 3
479
- appName: "test-app",
480
- syslogHostname: "test-host",
481
- processId: "12345",
482
- timeout: 1000,
483
- includeStructuredData: true,
484
- structuredDataId: "test@54321",
485
- });
486
-
487
- // Test different log levels with specific data
488
- const infoRecord = createMockLogRecord("info", ["Info message"], {
489
- requestId: "req-123",
490
- userId: 456,
491
- });
492
- const errorRecord = createMockLogRecord("error", ["Error occurred"], {
493
- errorCode: 500,
494
- component: "auth",
495
- });
496
- const warningRecord = createMockLogRecord("warning", [
497
- "Warning: disk space low",
498
- ], {
499
- diskUsage: "85%",
500
- partition: "/var",
501
- });
502
-
503
- sink(infoRecord);
504
- sink(errorRecord);
505
- sink(warningRecord);
506
-
507
- await new Promise((resolve) => setTimeout(resolve, 200));
508
- await sink[Symbol.asyncDispose]();
509
-
510
- // Wait for server to receive all messages
511
- await new Promise((resolve) => setTimeout(resolve, 200));
512
-
513
- // Validate we received 3 messages
514
- assertEquals(receivedMessages.length, 3);
515
-
516
- // Parse and validate each message structure
517
- const parsedMessages = receivedMessages.map((msg) =>
518
- parseSyslogMessage(msg)
519
- );
520
-
521
- for (const parsed of parsedMessages) {
522
- // Validate RFC 5424 format structure
523
- assertEquals(parsed.version, 1);
524
- assertEquals(parsed.hostname, "test-host");
525
- assertEquals(parsed.appName, "test-app");
526
- assertEquals(parsed.procId, "12345");
527
- assertEquals(parsed.msgId, "-");
528
-
529
- // Validate timestamp format (ISO 8601)
530
- assert(
531
- parsed.timestamp.match(
532
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
533
- ) !== null,
534
- );
535
-
536
- // Validate structured data is present
537
- assert(parsed.structuredData.startsWith("[test@54321"));
538
- }
539
-
540
- // Find specific messages and validate their content
541
- const infoMessage = parsedMessages.find((p) =>
542
- p.message === "Info message"
543
- );
544
- const errorMessage = parsedMessages.find((p) =>
545
- p.message === "Error occurred"
546
- );
547
- const warningMessage = parsedMessages.find((p) =>
548
- p.message === "Warning: disk space low"
549
- );
550
-
551
- // Validate priorities: daemon (3) * 8 + severity
552
- assertEquals(infoMessage?.priority, 30); // daemon (3) * 8 + info (6) = 30
553
- assertEquals(errorMessage?.priority, 27); // daemon (3) * 8 + error (3) = 27
554
- assertEquals(warningMessage?.priority, 28); // daemon (3) * 8 + warning (4) = 28
555
-
556
- // Parse and validate structured data content
557
- const infoStructuredData = parseStructuredData(
558
- infoMessage!.structuredData,
559
- );
560
- assertEquals(infoStructuredData.requestId, "req-123");
561
- assertEquals(infoStructuredData.userId, "456");
562
-
563
- const errorStructuredData = parseStructuredData(
564
- errorMessage!.structuredData,
565
- );
566
- assertEquals(errorStructuredData.errorCode, "500");
567
- assertEquals(errorStructuredData.component, "auth");
568
-
569
- const warningStructuredData = parseStructuredData(
570
- warningMessage!.structuredData,
571
- );
572
- assertEquals(warningStructuredData.diskUsage, "85%");
573
- assertEquals(warningStructuredData.partition, "/var");
574
- } finally {
575
- server.close();
576
- }
577
- });
578
- } else {
579
- test("getSyslogSink() RFC 5424 message format validation (Node.js)", async () => {
580
- const receivedMessages: string[] = [];
581
-
582
- // Create UDP server to capture actual messages
583
- const server = createSocket("udp4");
584
-
585
- await new Promise<void>((resolve) => {
586
- server.bind(0, "127.0.0.1", resolve);
587
- });
588
-
589
- const address = server.address() as { port: number };
590
-
591
- server.on("message", (msg) => {
592
- receivedMessages.push(msg.toString());
593
- });
594
-
595
- try {
596
- const sink = getSyslogSink({
597
- hostname: "127.0.0.1",
598
- port: address.port,
599
- protocol: "udp",
600
- facility: "daemon", // 3
601
- appName: "test-app",
602
- syslogHostname: "test-host",
603
- processId: "12345",
604
- timeout: 1000,
605
- includeStructuredData: true,
606
- structuredDataId: "test@54321",
607
- });
608
-
609
- // Test different log levels with specific data
610
- const infoRecord = createMockLogRecord("info", ["Info message"], {
611
- requestId: "req-123",
612
- userId: 456,
613
- });
614
- const errorRecord = createMockLogRecord("error", ["Error occurred"], {
615
- errorCode: 500,
616
- component: "auth",
617
- });
618
- const warningRecord = createMockLogRecord("warning", [
619
- "Warning: disk space low",
620
- ], {
621
- diskUsage: "85%",
622
- partition: "/var",
623
- });
624
-
625
- sink(infoRecord);
626
- sink(errorRecord);
627
- sink(warningRecord);
628
-
629
- await new Promise((resolve) => setTimeout(resolve, 200));
630
- await sink[Symbol.asyncDispose]();
631
-
632
- // Wait for server to receive all messages
633
- await new Promise((resolve) => setTimeout(resolve, 200));
634
-
635
- // Validate we received 3 messages
636
- assertEquals(receivedMessages.length, 3);
637
-
638
- // Validate RFC 5424 format for each message
639
- for (const message of receivedMessages) {
640
- // Should start with priority in angle brackets
641
- assertEquals(message.match(/^<\d+>/) !== null, true);
642
-
643
- // Should have version number 1
644
- assertEquals(message.includes(">1 "), true);
645
-
646
- // Should contain timestamp in ISO format
647
- assertEquals(
648
- message.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/) !== null,
649
- true,
650
- );
651
-
652
- // Should contain our hostname
653
- assertEquals(message.includes("test-host"), true);
654
-
655
- // Should contain our app name
656
- assertEquals(message.includes("test-app"), true);
657
-
658
- // Should contain our process ID
659
- assertEquals(message.includes("12345"), true);
660
-
661
- // Should contain structured data
662
- assertEquals(message.includes("[test@54321"), true);
663
- }
664
-
665
- // Check specific priorities: daemon (3) * 8 + level
666
- const infoMessage = receivedMessages.find((m) =>
667
- m.includes("Info message")
668
- );
669
- const errorMessage = receivedMessages.find((m) =>
670
- m.includes("Error occurred")
671
- );
672
- const warningMessage = receivedMessages.find((m) =>
673
- m.includes("Warning: disk space low")
674
- );
675
-
676
- // Info: daemon (3) * 8 + info (6) = 30
677
- assertEquals(infoMessage?.includes("<30>1"), true);
678
-
679
- // Error: daemon (3) * 8 + error (3) = 27
680
- assertEquals(errorMessage?.includes("<27>1"), true);
681
-
682
- // Warning: daemon (3) * 8 + warning (4) = 28
683
- assertEquals(warningMessage?.includes("<28>1"), true);
684
-
685
- // Check structured data content
686
- assertEquals(infoMessage?.includes('requestId="req-123"'), true);
687
- assertEquals(infoMessage?.includes('userId="456"'), true);
688
- assertEquals(errorMessage?.includes('errorCode="500"'), true);
689
- assertEquals(errorMessage?.includes('component="auth"'), true);
690
- assertEquals(warningMessage?.includes('diskUsage="85%"'), true);
691
- assertEquals(warningMessage?.includes('partition="/var"'), true);
692
- } finally {
693
- server.close();
694
- }
695
- });
696
- }
697
-
698
- // Structured Data Escaping Tests
699
- if (typeof Deno !== "undefined") {
700
- test("getSyslogSink() structured data escaping validation (Deno)", async () => {
701
- let receivedMessage = "";
702
-
703
- const server = createSocket("udp4");
704
-
705
- await new Promise<void>((resolve) => {
706
- server.bind(0, "127.0.0.1", resolve);
707
- });
708
-
709
- const address = server.address() as { port: number };
710
-
711
- server.on("message", (msg) => {
712
- receivedMessage = msg.toString();
713
- });
714
-
715
- try {
716
- const sink = getSyslogSink({
717
- hostname: "127.0.0.1",
718
- port: address.port,
719
- protocol: "udp",
720
- facility: "local0",
721
- appName: "escape-test",
722
- timeout: 1000,
723
- includeStructuredData: true,
724
- structuredDataId: "escape@12345",
725
- });
726
-
727
- // Test message with special characters that need escaping
728
- const testRecord = createMockLogRecord("info", ["Test escaping"], {
729
- quote: 'Has "quotes" in value',
730
- backslash: "Has \\ backslash",
731
- bracket: "Has ] bracket",
732
- combined: 'Mix of "quotes", \\ and ] chars',
733
- });
734
-
735
- sink(testRecord);
736
- await new Promise((resolve) => setTimeout(resolve, 200));
737
- await sink[Symbol.asyncDispose]();
738
-
739
- await new Promise((resolve) => setTimeout(resolve, 100));
740
-
741
- // Parse and validate the complete message
742
- const parsed = parseSyslogMessage(receivedMessage);
743
-
744
- assertEquals(parsed.version, 1);
745
- assertEquals(parsed.hostname, Deno.hostname());
746
- assertEquals(parsed.appName, "escape-test");
747
- assertEquals(parsed.message, "Test escaping");
748
-
749
- // Parse structured data and verify proper unescaping
750
- const structuredData = parseStructuredData(parsed.structuredData);
751
- assertEquals(structuredData.quote, 'Has "quotes" in value');
752
- assertEquals(structuredData.backslash, "Has \\ backslash");
753
- assertEquals(structuredData.bracket, "Has ] bracket");
754
- assertEquals(structuredData.combined, 'Mix of "quotes", \\ and ] chars');
755
- } finally {
756
- server.close();
757
- }
758
- });
759
- } else {
760
- test("getSyslogSink() structured data escaping validation (Node.js)", async () => {
761
- let receivedMessage = "";
762
-
763
- const server = createSocket("udp4");
764
-
765
- await new Promise<void>((resolve) => {
766
- server.bind(0, "127.0.0.1", resolve);
767
- });
768
-
769
- const address = server.address() as { port: number };
770
-
771
- server.on("message", (msg) => {
772
- receivedMessage = msg.toString();
773
- });
774
-
775
- try {
776
- const sink = getSyslogSink({
777
- hostname: "127.0.0.1",
778
- port: address.port,
779
- protocol: "udp",
780
- facility: "local0",
781
- appName: "escape-test",
782
- timeout: 1000,
783
- includeStructuredData: true,
784
- structuredDataId: "escape@12345",
785
- });
786
-
787
- // Test message with special characters that need escaping
788
- const testRecord = createMockLogRecord("info", ["Test escaping"], {
789
- quote: 'Has "quotes" in value',
790
- backslash: "Has \\ backslash",
791
- bracket: "Has ] bracket",
792
- combined: 'Mix of "quotes", \\ and ] chars',
793
- });
794
-
795
- sink(testRecord);
796
- await new Promise((resolve) => setTimeout(resolve, 200));
797
- await sink[Symbol.asyncDispose]();
798
-
799
- await new Promise((resolve) => setTimeout(resolve, 100));
800
-
801
- // Parse and verify the message like the Deno test
802
- const parsed = parseSyslogMessage(receivedMessage);
803
-
804
- assertEquals(parsed.version, 1);
805
- assertEquals(parsed.message, "Test escaping");
806
- assertEquals(parsed.appName, "escape-test");
807
-
808
- // Parse structured data and verify escaping
809
- const structuredData = parseStructuredData(parsed.structuredData);
810
- assertEquals(structuredData.quote, 'Has "quotes" in value');
811
- assertEquals(structuredData.backslash, "Has \\ backslash");
812
- assertEquals(structuredData.bracket, "Has ] bracket");
813
- assertEquals(structuredData.combined, 'Mix of "quotes", \\ and ] chars');
814
- } finally {
815
- server.close();
816
- }
817
- });
818
- }
819
-
820
- // Template Literal Message Formatting Tests
821
- if (typeof Deno !== "undefined") {
822
- test("getSyslogSink() template literal message formatting (Deno)", async () => {
823
- let receivedMessage = "";
824
-
825
- const server = createSocket("udp4");
826
-
827
- await new Promise<void>((resolve) => {
828
- server.bind(0, "127.0.0.1", resolve);
829
- });
830
-
831
- const address = server.address() as { port: number };
832
-
833
- server.on("message", (msg) => {
834
- receivedMessage = msg.toString();
835
- });
836
-
837
- try {
838
- const sink = getSyslogSink({
839
- hostname: "127.0.0.1",
840
- port: address.port,
841
- protocol: "udp",
842
- facility: "local0",
843
- appName: "template-test",
844
- timeout: 1000,
845
- includeStructuredData: false,
846
- });
847
-
848
- // Test LogTape template literal style message
849
- const templateRecord = createMockLogRecord("info", [
850
- "User ",
851
- { userId: 123, name: "Alice" },
852
- " performed action ",
853
- "login",
854
- " with result ",
855
- { success: true, duration: 150 },
856
- ]);
857
-
858
- sink(templateRecord);
859
- await new Promise((resolve) => setTimeout(resolve, 200));
860
- await sink[Symbol.asyncDispose]();
861
-
862
- await new Promise((resolve) => setTimeout(resolve, 100));
863
-
864
- // Parse and validate the complete message
865
- const parsed = parseSyslogMessage(receivedMessage);
866
- assertEquals(parsed.appName, "template-test");
867
- assertEquals(parsed.structuredData, "-"); // No structured data for this test
868
-
869
- // Verify template literal message is correctly formatted
870
- const expectedMessage =
871
- 'User {"userId":123,"name":"Alice"} performed action "login" with result {"success":true,"duration":150}';
872
- assertEquals(parsed.message, expectedMessage);
873
- } finally {
874
- server.close();
875
- }
876
- });
877
- } else {
878
- test("getSyslogSink() template literal message formatting (Node.js)", async () => {
879
- let receivedMessage = "";
880
-
881
- const server = createSocket("udp4");
882
-
883
- await new Promise<void>((resolve) => {
884
- server.bind(0, "127.0.0.1", resolve);
885
- });
886
-
887
- const address = server.address() as { port: number };
888
-
889
- server.on("message", (msg) => {
890
- receivedMessage = msg.toString();
891
- });
892
-
893
- try {
894
- const sink = getSyslogSink({
895
- hostname: "127.0.0.1",
896
- port: address.port,
897
- protocol: "udp",
898
- facility: "local0",
899
- appName: "template-test",
900
- timeout: 1000,
901
- includeStructuredData: false,
902
- });
903
-
904
- // Test LogTape template literal style message
905
- const templateRecord = createMockLogRecord("info", [
906
- "User ",
907
- { userId: 123, name: "Alice" },
908
- " performed action ",
909
- "login",
910
- " with result ",
911
- { success: true, duration: 150 },
912
- ]);
913
-
914
- sink(templateRecord);
915
- await new Promise((resolve) => setTimeout(resolve, 200));
916
- await sink[Symbol.asyncDispose]();
917
-
918
- await new Promise((resolve) => setTimeout(resolve, 100));
919
-
920
- // Verify template literal parts are properly formatted
921
- assertEquals(receivedMessage.includes("User "), true);
922
- assertEquals(
923
- receivedMessage.includes('{"userId":123,"name":"Alice"}'),
924
- true,
925
- );
926
- assertEquals(receivedMessage.includes(" performed action "), true);
927
- assertEquals(receivedMessage.includes('"login"'), true);
928
- assertEquals(receivedMessage.includes(" with result "), true);
929
- assertEquals(
930
- receivedMessage.includes('{"success":true,"duration":150}'),
931
- true,
932
- );
933
- } finally {
934
- server.close();
935
- }
936
- });
937
- }
938
-
939
- // Facility and Priority Calculation Tests
940
- if (typeof Deno !== "undefined") {
941
- test("getSyslogSink() facility and priority calculation (Deno)", async () => {
942
- const receivedMessages: string[] = [];
943
-
944
- const server = createSocket("udp4");
945
-
946
- await new Promise<void>((resolve) => {
947
- server.bind(0, "127.0.0.1", resolve);
948
- });
949
-
950
- const address = server.address() as { port: number };
951
-
952
- server.on("message", (msg) => {
953
- receivedMessages.push(msg.toString());
954
- });
955
-
956
- try {
957
- // Test different facilities
958
- const facilities = [
959
- { name: "kernel", code: 0 },
960
- { name: "user", code: 1 },
961
- { name: "daemon", code: 3 },
962
- { name: "local0", code: 16 },
963
- { name: "local7", code: 23 },
964
- ] as const;
965
-
966
- for (const facility of facilities) {
967
- const sink = getSyslogSink({
968
- hostname: "127.0.0.1",
969
- port: address.port,
970
- protocol: "udp",
971
- facility: facility.name,
972
- appName: `${facility.name}-test`,
973
- timeout: 1000,
974
- });
975
-
976
- // Test with error level (severity 3)
977
- const errorRecord = createMockLogRecord("error", [
978
- `${facility.name} error message`,
979
- ]);
980
- sink(errorRecord);
981
-
982
- await new Promise((resolve) => setTimeout(resolve, 50));
983
- await sink[Symbol.asyncDispose]();
984
- }
985
-
986
- // Test one additional level combination
987
- const warningSink = getSyslogSink({
988
- hostname: "127.0.0.1",
989
- port: address.port,
990
- protocol: "udp",
991
- facility: "mail", // code 2
992
- appName: "mail-test",
993
- timeout: 1000,
994
- });
995
-
996
- const warningRecord = createMockLogRecord("warning", [
997
- "mail warning message",
998
- ]);
999
- warningSink(warningRecord);
1000
- await new Promise((resolve) => setTimeout(resolve, 50));
1001
- await warningSink[Symbol.asyncDispose]();
1002
-
1003
- await new Promise((resolve) => setTimeout(resolve, 200));
1004
-
1005
- // Verify priority calculations: Priority = Facility * 8 + Severity
1006
- assertEquals(receivedMessages.length, 6);
1007
-
1008
- // Parse all messages and verify priorities
1009
- const parsedMessages = receivedMessages.map((msg) =>
1010
- parseSyslogMessage(msg)
1011
- );
1012
-
1013
- // Find and verify each facility message
1014
- const kernelMsg = parsedMessages.find((p) =>
1015
- p.message === "kernel error message"
1016
- );
1017
- const userMsg = parsedMessages.find((p) =>
1018
- p.message === "user error message"
1019
- );
1020
- const daemonMsg = parsedMessages.find((p) =>
1021
- p.message === "daemon error message"
1022
- );
1023
- const local0Msg = parsedMessages.find((p) =>
1024
- p.message === "local0 error message"
1025
- );
1026
- const local7Msg = parsedMessages.find((p) =>
1027
- p.message === "local7 error message"
1028
- );
1029
- const mailMsg = parsedMessages.find((p) =>
1030
- p.message === "mail warning message"
1031
- );
1032
-
1033
- // Verify exact priority calculations
1034
- assertEquals(kernelMsg?.priority, 3); // kernel (0) * 8 + error (3) = 3
1035
- assertEquals(userMsg?.priority, 11); // user (1) * 8 + error (3) = 11
1036
- assertEquals(daemonMsg?.priority, 27); // daemon (3) * 8 + error (3) = 27
1037
- assertEquals(local0Msg?.priority, 131); // local0 (16) * 8 + error (3) = 131
1038
- assertEquals(local7Msg?.priority, 187); // local7 (23) * 8 + error (3) = 187
1039
- assertEquals(mailMsg?.priority, 20); // mail (2) * 8 + warning (4) = 20
1040
-
1041
- // Verify app names match facilities
1042
- assertEquals(kernelMsg?.appName, "kernel-test");
1043
- assertEquals(userMsg?.appName, "user-test");
1044
- assertEquals(daemonMsg?.appName, "daemon-test");
1045
- assertEquals(local0Msg?.appName, "local0-test");
1046
- assertEquals(local7Msg?.appName, "local7-test");
1047
- assertEquals(mailMsg?.appName, "mail-test");
1048
- } finally {
1049
- server.close();
1050
- }
1051
- });
1052
- }
1053
-
1054
- test("Syslog message format follows RFC 5424", () => {
1055
- // Test that the sink can be created and called without throwing
1056
- const sink = getSyslogSink({
1057
- facility: "local0",
1058
- appName: "test-app",
1059
- syslogHostname: "test-host",
1060
- processId: "1234",
1061
- includeStructuredData: false,
1062
- });
1063
-
1064
- // This should not throw during sink creation and call
1065
- // We don't send the message to avoid network operations
1066
- assertEquals(typeof sink, "function");
1067
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1068
- });
1069
-
1070
- test("Syslog priority calculation", () => {
1071
- // Test priority calculation: Priority = Facility * 8 + Severity
1072
- // local0 (16) + info (6) = 16 * 8 + 6 = 134
1073
-
1074
- const sink = getSyslogSink({
1075
- facility: "local0", // 16
1076
- appName: "test",
1077
- });
1078
-
1079
- // Test that sink is created correctly
1080
- assertEquals(typeof sink, "function");
1081
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1082
- });
1083
-
1084
- test("Syslog facility codes mapping", () => {
1085
- const facilities = [
1086
- "kernel", // 0
1087
- "user", // 1
1088
- "mail", // 2
1089
- "daemon", // 3
1090
- "local0", // 16
1091
- "local7", // 23
1092
- ];
1093
-
1094
- for (const facility of facilities) {
1095
- const sink = getSyslogSink({
1096
- facility: facility as SyslogFacility,
1097
- appName: "test",
1098
- });
1099
-
1100
- // Should not throw for any valid facility
1101
- assertEquals(typeof sink, "function");
1102
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1103
- }
1104
- });
1105
-
1106
- test("Syslog severity levels mapping", () => {
1107
- // Test that the sink works with all severity levels
1108
- const _levels: Array<
1109
- "fatal" | "error" | "warning" | "info" | "debug" | "trace"
1110
- > = [
1111
- "fatal", // 0 (Emergency)
1112
- "error", // 3 (Error)
1113
- "warning", // 4 (Warning)
1114
- "info", // 6 (Informational)
1115
- "debug", // 7 (Debug)
1116
- "trace", // 7 (Debug)
1117
- ];
1118
-
1119
- const sink = getSyslogSink({
1120
- facility: "local0",
1121
- appName: "test",
1122
- });
1123
-
1124
- // Should work with all valid levels
1125
- assertEquals(typeof sink, "function");
1126
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1127
- });
1128
-
1129
- test("Structured data formatting", () => {
1130
- const sink = getSyslogSink({
1131
- facility: "local0",
1132
- appName: "test",
1133
- includeStructuredData: true,
1134
- });
1135
-
1136
- // Should not throw when including structured data
1137
- assertEquals(typeof sink, "function");
1138
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1139
- });
1140
-
1141
- test("Message with template literals", () => {
1142
- const sink = getSyslogSink({
1143
- facility: "local0",
1144
- appName: "test",
1145
- });
1146
-
1147
- // Should not throw with template literal style messages
1148
- assertEquals(typeof sink, "function");
1149
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1150
- });
1151
-
1152
- test("Default options", () => {
1153
- const sink = getSyslogSink();
1154
-
1155
- // Should work with default options
1156
- assertEquals(typeof sink, "function");
1157
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1158
- });
1159
-
1160
- test("Custom options", () => {
1161
- const sink = getSyslogSink({
1162
- protocol: "tcp",
1163
- facility: "mail",
1164
- appName: "my-app",
1165
- syslogHostname: "web-server-01",
1166
- processId: "9999",
1167
- timeout: 1000,
1168
- includeStructuredData: true,
1169
- });
1170
-
1171
- // Should work with custom options
1172
- assertEquals(typeof sink, "function");
1173
- assertEquals(typeof sink[Symbol.asyncDispose], "function");
1174
- });
1175
-
1176
- test("AsyncDisposable cleanup", async () => {
1177
- const sink = getSyslogSink();
1178
-
1179
- // Send a message
1180
- const record = createMockLogRecord();
1181
- sink(record);
1182
-
1183
- // Should be able to dispose without throwing
1184
- await sink[Symbol.asyncDispose]();
1185
- });
1186
-
1187
- // Basic format validation test
1188
- test("Syslog message format validation", () => {
1189
- // Test the internal formatting functions directly
1190
- const timestamp = new Date("2024-01-01T12:00:00.000Z").getTime();
1191
-
1192
- // Test priority calculation: local0 (16) * 8 + info (6) = 134
1193
- const expectedPriority = 16 * 8 + 6; // 134
1194
- assertEquals(expectedPriority, 134);
1195
-
1196
- // Test timestamp formatting
1197
- const timestampStr = new Date(timestamp).toISOString();
1198
- assertEquals(timestampStr, "2024-01-01T12:00:00.000Z");
1199
- });
1200
-
1201
- // Runtime-specific connection tests
1202
- if (typeof Deno !== "undefined") {
1203
- // Deno-specific tests
1204
- test("DenoUdpSyslogConnection instantiation", () => {
1205
- const connection = new DenoUdpSyslogConnection("localhost", 514, 5000);
1206
- assertInstanceOf(connection, DenoUdpSyslogConnection);
1207
- });
1208
-
1209
- test("DenoUdpSyslogConnection connect and close", () => {
1210
- const connection = new DenoUdpSyslogConnection("localhost", 514, 5000);
1211
- // connect() should not throw for UDP
1212
- connection.connect();
1213
- // close() should not throw for UDP
1214
- connection.close();
1215
- });
1216
-
1217
- test("DenoUdpSyslogConnection send timeout", async () => {
1218
- // Use a non-routable IP to trigger timeout
1219
- const connection = new DenoUdpSyslogConnection("10.255.255.1", 9999, 50); // Very short timeout
1220
- connection.connect();
1221
-
1222
- try {
1223
- await connection.send("test message");
1224
- // If we reach here, the send didn't timeout as expected
1225
- // This might happen if the system is very fast or network conditions are unusual
1226
- } catch (error) {
1227
- // This is expected - either timeout or network unreachable
1228
- assertEquals(typeof (error as Error).message, "string");
1229
- } finally {
1230
- connection.close();
1231
- }
1232
- });
1233
-
1234
- test("DenoTcpSyslogConnection instantiation", () => {
1235
- const connection = new DenoTcpSyslogConnection("localhost", 514, 5000);
1236
- assertInstanceOf(connection, DenoTcpSyslogConnection);
1237
- });
1238
-
1239
- test("DenoTcpSyslogConnection close without connection", () => {
1240
- const connection = new DenoTcpSyslogConnection("localhost", 514, 5000);
1241
- // close() should not throw even without connection
1242
- connection.close();
1243
- });
1244
-
1245
- test("DenoTcpSyslogConnection connection timeout", async () => {
1246
- // Use a non-routable IP address to ensure connection failure
1247
- const connection = new DenoTcpSyslogConnection("10.255.255.1", 9999, 100); // Very short timeout
1248
-
1249
- try {
1250
- await assertRejects(
1251
- () => connection.connect(),
1252
- Error,
1253
- );
1254
- } finally {
1255
- // Ensure cleanup
1256
- connection.close();
1257
- }
1258
- });
1259
-
1260
- test("DenoTcpSyslogConnection send without connection", async () => {
1261
- const connection = new DenoTcpSyslogConnection("localhost", 514, 5000);
1262
-
1263
- await assertRejects(
1264
- () => connection.send("test message"),
1265
- Error,
1266
- "Connection not established",
1267
- );
1268
- });
1269
-
1270
- test("DenoUdpSyslogConnection actual send test", async () => {
1271
- // Try to send to a local UDP echo server or just verify the call doesn't crash
1272
- const connection = new DenoUdpSyslogConnection("127.0.0.1", 1514, 1000); // Non-privileged port
1273
- connection.connect();
1274
-
1275
- try {
1276
- // This will likely fail (no server listening), but should handle gracefully
1277
- await connection.send("test syslog message");
1278
- // If it succeeds, that's also fine - might have a server running
1279
- } catch (error) {
1280
- // Expected - likely no server listening, but the send mechanism should work
1281
- const errorMessage = (error as Error).message;
1282
- // Should contain either timeout or connection/network error
1283
- assertEquals(typeof errorMessage, "string");
1284
- } finally {
1285
- connection.close();
1286
- }
1287
- });
1288
-
1289
- test("DenoTcpSyslogConnection actual send test with mock server", async () => {
1290
- // Create a simple TCP server to receive the message
1291
- let receivedData = "";
1292
-
1293
- const server = Deno.listen({ port: 0 }); // Let system choose port
1294
- const serverAddr = server.addr as Deno.NetAddr;
1295
-
1296
- // Handle one connection in background
1297
- const serverTask = (async () => {
1298
- try {
1299
- const conn = await server.accept();
1300
- const buffer = new Uint8Array(1024);
1301
- const bytesRead = await conn.read(buffer);
1302
- if (bytesRead) {
1303
- receivedData = new TextDecoder().decode(
1304
- buffer.subarray(0, bytesRead),
1305
- );
1306
- }
1307
- conn.close();
1308
- } catch {
1309
- // Server closed or connection error
1310
- }
1311
- })();
1312
-
1313
- try {
1314
- // Give server a moment to start
1315
- await new Promise((resolve) => setTimeout(resolve, 10));
1316
-
1317
- // Connect and send message - create new connection with no timeout to avoid timer leaks
1318
- const connection = new DenoTcpSyslogConnection(
1319
- "127.0.0.1",
1320
- serverAddr.port,
1321
- 0,
1322
- ); // No timeout
1323
-
1324
- await connection.connect();
1325
- await connection.send("test syslog message from Deno TCP");
1326
- connection.close();
1327
-
1328
- // Give server time to process
1329
- await new Promise((resolve) => setTimeout(resolve, 50));
1330
-
1331
- // Verify message was received
1332
- assertEquals(
1333
- receivedData.includes("test syslog message from Deno TCP"),
1334
- true,
1335
- );
1336
- } finally {
1337
- server.close();
1338
- await serverTask.catch(() => {}); // Wait for server cleanup
1339
- }
1340
- });
1341
- }
1342
-
1343
- // Node.js/Bun-specific tests
1344
- test("NodeUdpSyslogConnection instantiation", () => {
1345
- const connection = new NodeUdpSyslogConnection("localhost", 514, 5000);
1346
- assertInstanceOf(connection, NodeUdpSyslogConnection);
1347
- });
1348
-
1349
- test("NodeUdpSyslogConnection connect and close", () => {
1350
- const connection = new NodeUdpSyslogConnection("localhost", 514, 5000);
1351
- // connect() should not throw for UDP
1352
- connection.connect();
1353
- // close() should not throw for UDP
1354
- connection.close();
1355
- });
1356
-
1357
- test("NodeUdpSyslogConnection send timeout", async () => {
1358
- // Use a non-routable IP to trigger timeout
1359
- const connection = new NodeUdpSyslogConnection("10.255.255.1", 9999, 50); // Very short timeout
1360
- connection.connect();
1361
-
1362
- try {
1363
- await connection.send("test message");
1364
- // If we reach here, the send didn't timeout as expected
1365
- // This might happen if the system is very fast or network conditions are unusual
1366
- } catch (error) {
1367
- // This is expected - either timeout or network unreachable
1368
- assertEquals(typeof (error as Error).message, "string");
1369
- } finally {
1370
- connection.close();
1371
- }
1372
- });
1373
-
1374
- test("NodeTcpSyslogConnection instantiation", () => {
1375
- const connection = new NodeTcpSyslogConnection("localhost", 514, 5000);
1376
- assertInstanceOf(connection, NodeTcpSyslogConnection);
1377
- });
1378
-
1379
- test("NodeTcpSyslogConnection close without connection", () => {
1380
- const connection = new NodeTcpSyslogConnection("localhost", 514, 5000);
1381
- // close() should not throw even without connection
1382
- connection.close();
1383
- });
1384
-
1385
- test("NodeTcpSyslogConnection connection timeout", {
1386
- sanitizeResources: false,
1387
- sanitizeOps: false,
1388
- }, async () => {
1389
- // Use a non-routable IP address to ensure connection failure
1390
- const connection = new NodeTcpSyslogConnection("10.255.255.1", 9999, 100); // Very short timeout
1391
-
1392
- try {
1393
- await assertRejects(
1394
- () => connection.connect(),
1395
- Error,
1396
- );
1397
- } finally {
1398
- // Ensure cleanup
1399
- connection.close();
1400
- }
1401
- });
1402
-
1403
- test("NodeTcpSyslogConnection send without connection", () => {
1404
- const connection = new NodeTcpSyslogConnection("localhost", 514, 5000);
1405
-
1406
- assertThrows(
1407
- () => connection.send("test message"),
1408
- Error,
1409
- "Connection not established",
1410
- );
1411
- });
1412
-
1413
- test("NodeUdpSyslogConnection actual send test", async () => {
1414
- // Try to send to a local UDP port
1415
- const connection = new NodeUdpSyslogConnection("127.0.0.1", 1514, 1000); // Non-privileged port
1416
- connection.connect();
1417
-
1418
- try {
1419
- // This will likely fail (no server listening), but should handle gracefully
1420
- await connection.send("test syslog message");
1421
- // If it succeeds, that's also fine - might have a server running
1422
- } catch (error) {
1423
- // Expected - likely no server listening, but the send mechanism should work
1424
- const errorMessage = (error as Error).message;
1425
- // Should contain either timeout or connection/network error
1426
- assertEquals(typeof errorMessage, "string");
1427
- } finally {
1428
- connection.close();
1429
- }
1430
- });
1431
-
1432
- test("NodeTcpSyslogConnection actual send test with mock server", async () => {
1433
- // Import Node.js modules for creating a server
1434
-
1435
- let receivedData = "";
1436
-
1437
- // Create a simple TCP server
1438
- const server = createServer((socket) => {
1439
- socket.on("data", (data) => {
1440
- receivedData = data.toString();
1441
- socket.end();
1442
- });
1443
- });
1444
-
1445
- // Start server on random port
1446
- await new Promise<void>((resolve) => {
1447
- server.listen(0, "127.0.0.1", resolve);
1448
- });
1449
-
1450
- const address = server.address() as { port: number };
1451
-
1452
- try {
1453
- // Connect and send message
1454
- const connection = new NodeTcpSyslogConnection(
1455
- "127.0.0.1",
1456
- address.port,
1457
- 5000,
1458
- );
1459
-
1460
- await connection.connect();
1461
- await connection.send("test syslog message from Node TCP");
1462
- connection.close();
1463
-
1464
- // Wait a bit for server to receive data
1465
- await new Promise((resolve) => setTimeout(resolve, 100));
1466
-
1467
- // Verify message was received
1468
- assertEquals(
1469
- receivedData.includes("test syslog message from Node TCP"),
1470
- true,
1471
- );
1472
- } finally {
1473
- server.close();
1474
- }
1475
- });