forked from jitbit/SyslogCore
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcSyslogClient.cs
More file actions
146 lines (127 loc) · 4.61 KB
/
cSyslogClient.cs
File metadata and controls
146 lines (127 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
* Original work (c) Jitbit - https://github.com/jitbit/SyslogCore
*
* Enhanced by ish-1313 (@ish-1313) - https://github.com/ish-1313/SyslogCore
* Major improvements:
* - Dual logging (local syslog + remote UDP)
* - RFC 5424 compliance (structured messages)
* - CallerMemberName auto-detection
* - Graceful resource cleanup (GCHandle)
*
* License: MIT
*/
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace common.Logging;
/*
* Future improvements (w/o breaking changes):
* 1. LibraryImport для AOT (.NET 7+)
* 2. Error-handling callback (like Action<Exception>)
* 3. Async-version SendRemoteSyslog
* 4. Batching for UDP
*/
internal static class LibC
{
[DllImport("libc")]
#pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
public static extern void openlog(IntPtr ident, int option, int facility);
[DllImport("libc", CharSet = CharSet.Auto)]
public static extern void syslog(int priority, string format, string level, string appname, string message);
[DllImport("libc", CharSet = CharSet.Auto)]
public extern static void closelog();
#pragma warning restore SYSLIB1054
}
public static class cSyslogClient
{
const int LOG_PID = 0x01;
const int LOG_CONS = 0x02;
const int LOG_USER = 1 << 3;
const int LOG_LOCAL0 = 16 << 3;
static IPEndPoint _remoteEndPoint;
static UdpClient _udpClient;
static string _appName;
static string _hostname;
static bool _isLocalEnabled;
static bool _isRemoteEnabled;
public enum LogTarget
{
LocalOnly,
RemoteOnly,
AllTargets
}
static GCHandle _handle = default;
static bool _isInited = false;
public static void Init(string appName, string remoteServer = null, int remotePort = 514, string customHostName = null)
{
_appName = appName;
_hostname = customHostName ?? Dns.GetHostName();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_handle = GCHandle.Alloc(GetNullTerminatedAscii(appName), GCHandleType.Pinned);
LibC.openlog(_handle.AddrOfPinnedObject(), LOG_PID | LOG_CONS, LOG_LOCAL0);
_isLocalEnabled = true;
}
if (!string.IsNullOrEmpty(remoteServer))
{
_remoteEndPoint = new IPEndPoint(IPAddress.Parse(remoteServer), remotePort);
_udpClient = new();
_isRemoteEnabled = true;
}
_isInited = true;
}
public static void Write(string message, SyslogLevel level = SyslogLevel.Info, LogTarget target = LogTarget.AllTargets, [CallerMemberName] string moduleName = "")
{
moduleName = (string.IsNullOrEmpty(moduleName)) ? "Unknown method" : moduleName;
try
{
bool writeLocal = _isLocalEnabled && (target == LogTarget.LocalOnly || target == LogTarget.AllTargets);
bool writeRemote = _isRemoteEnabled && (target == LogTarget.RemoteOnly || target == LogTarget.AllTargets);
if (writeLocal) LibC.syslog((int)level | LOG_LOCAL0, "[%s] %s: %s", level.ToString().ToUpper(), moduleName, message);
if (writeRemote) SendRemoteSyslog(moduleName, message, level);
}
catch { /* Suppress logging errors */ }
}
private static void SendRemoteSyslog(string moduleName, string message, SyslogLevel level)
{
try
{
var priority = (int)level + LOG_LOCAL0;
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
var syslogMsg = $"<{priority}>1 {timestamp} {_hostname} {_appName} {moduleName} - - {message}";
var bytes = Encoding.ASCII.GetBytes(syslogMsg);
_udpClient?.Send(bytes, bytes.Length, _remoteEndPoint);
}
catch {/* Suppress network errors */ }
}
private static byte[] GetNullTerminatedAscii(string input)
{
byte[] bytes = new byte[input.Length + 1]; // +1 для нуль-терминатора
Encoding.ASCII.GetBytes(input, 0, input.Length, bytes, 0);
return bytes;
}
public static void Shutdown()
{
if (_isInited)
{
if (_handle != default && _handle.IsAllocated) _handle.Free();
_udpClient?.Dispose();
_udpClient = null;
LibC.closelog();
_isInited = false;
}
}
}
public enum SyslogLevel
{
Emergency = 0,
Alert = 1,
Critical = 2,
Error = 3,
Warning = 4,
Notice = 5,
Info = 6,
Debug = 7
}