Implemented syslog with Serilog

This commit is contained in:
Zafer Balkan
2024-11-20 14:30:35 +02:00
parent 82e116d38f
commit 2220161e87
2 changed files with 77 additions and 72 deletions

View File

@@ -20,7 +20,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SyslogNet.Client" Version="0.3.3" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Http" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.SyslogMessages" Version="4.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -17,9 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using SyslogNet.Client;
using SyslogNet.Client.Serialization;
using SyslogNet.Client.Transport;
using Serilog;
using Serilog.Events;
using Serilog.Parsing;
using Serilog.Sinks.Syslog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -33,21 +34,17 @@ namespace LogExporter.Strategy
private const string _appName = "Technitium DNS Server";
private const string _msgId = "dnslog";
private const string _sdId = "dnsparams";
private const string _sdId = "meta";
private const string DEFAUL_PROTOCOL = "udp";
private const int DEFAULT_PORT = 514;
private readonly string _host;
private readonly Facility _facility = Facility.Local6;
private readonly string _processId;
private readonly Rfc5424Formatter _formatter;
private readonly ISyslogMessageSender _sender;
private readonly ISyslogMessageSerializer _serializer;
private readonly Serilog.Core.Logger _sender;
private bool disposedValue;
@@ -60,18 +57,18 @@ namespace LogExporter.Strategy
port ??= DEFAULT_PORT;
protocol ??= DEFAUL_PROTOCOL;
var conf = new LoggerConfiguration();
_sender = protocol.ToLowerInvariant() switch
{
"tls" => new SyslogEncryptedTcpSender(address, port.Value),
"tcp" => new SyslogTcpSender(address, port.Value),
"udp" => new SyslogUdpSender(address, port.Value),
"local" => new SyslogLocalSender(),
"tls" => conf.WriteTo.TcpSyslog(address, port.Value, _appName, FramingType.OCTET_COUNTING, SyslogFormat.RFC5424, _facility, useTls: true).Enrich.FromLogContext().CreateLogger(),
"tcp" => conf.WriteTo.TcpSyslog(address, port.Value, _appName, FramingType.OCTET_COUNTING, SyslogFormat.RFC5424, _facility, useTls: false).Enrich.FromLogContext().CreateLogger(),
"udp" => conf.WriteTo.UdpSyslog(address, port.Value, _appName, SyslogFormat.RFC5424, _facility).Enrich.FromLogContext().CreateLogger(),
"local" => conf.WriteTo.LocalSyslog(_appName, _facility).Enrich.FromLogContext().CreateLogger(),
_ => throw new Exception("Invalid protocol specified"),
};
_serializer = new SyslogRfc5424MessageSerializer();
_processId = Environment.ProcessId.ToString();
_host = Environment.MachineName;
_formatter = new Rfc5424Formatter(_facility, _appName, null, _sdId, Environment.MachineName);
}
#endregion constructor
@@ -80,11 +77,12 @@ namespace LogExporter.Strategy
public Task ExportAsync(List<LogEntry> logs)
{
return Task.Run(() =>
{
var messages = new List<SyslogMessage>(logs.Select(Convert));
_sender.Send(messages, _serializer);
});
var tasks = logs
.Select(log => Task.Run(() =>
Log.Information(_formatter.FormatMessage(Convert(log))))
);
return Task.WhenAll(tasks);
}
#endregion public
@@ -115,83 +113,88 @@ namespace LogExporter.Strategy
#region private
private SyslogMessage Convert(LogEntry log)
private LogEvent Convert(LogEntry log)
{
// Create the structured data with all key details from LogEntry
var elements = new StructuredDataElement(_sdId, new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
// Initialize properties with base log details
var properties = new List<LogEventProperty>
{
{ "timestamp", log.Timestamp.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") },
{ "clientIp", log.ClientIp },
{ "clientPort", log.ClientPort.ToString() },
{ "dnssecOk", log.DnssecOk.ToString() },
{ "protocol", log.Protocol.ToString() },
{ "rCode", log.ResponseCode.ToString() }
});
new LogEventProperty("timestamp", new ScalarValue(log.Timestamp.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ"))),
new LogEventProperty("clientIp", new ScalarValue(log.ClientIp)),
new LogEventProperty("clientPort", new ScalarValue(log.ClientPort.ToString())),
new LogEventProperty("dnssecOk", new ScalarValue(log.DnssecOk.ToString())),
new LogEventProperty("protocol", new ScalarValue(log.Protocol.ToString())),
new LogEventProperty("rCode", new ScalarValue(log.ResponseCode.ToString()))
};
// Add each question to the structured data
// Add each question as properties
if (log.Questions?.Count > 0)
{
for (int i = 0; i < log.Questions.Count; i++)
{
var question = log.Questions[i];
elements.Parameters.Add($"qName_{i}", question.QuestionName);
elements.Parameters.Add($"qType_{i}", question.QuestionType.HasValue ? question.QuestionType.Value.ToString() : "unknown");
elements.Parameters.Add($"qClass_{i}", question.QuestionClass.HasValue ? question.QuestionClass.Value.ToString() : "unknown");
elements.Parameters.Add($"qSize_{i}", question.Size.ToString());
properties.Add(new LogEventProperty($"qName_{i}", new ScalarValue(question.QuestionName)));
properties.Add(new LogEventProperty($"qType_{i}", new ScalarValue(question.QuestionType?.ToString() ?? "unknown")));
properties.Add(new LogEventProperty($"qClass_{i}", new ScalarValue(question.QuestionClass?.ToString() ?? "unknown")));
properties.Add(new LogEventProperty($"qSize_{i}", new ScalarValue(question.Size.ToString())));
}
// Generate questions summary
var questionSummary = string.Join("; ", log.Questions.Select((q, i) =>
$"QNAME_{i}: {q.QuestionName}, QTYPE: {q.QuestionType?.ToString() ?? "unknown"}, QCLASS: {q.QuestionClass?.ToString() ?? "unknown"}"));
properties.Add(new LogEventProperty("questionsSummary", new ScalarValue(questionSummary)));
}
else
{
properties.Add(new LogEventProperty("questionsSummary", new ScalarValue(string.Empty)));
}
// Add each answer to the structured data
// Add each answer as properties
if (log.Answers?.Count > 0)
{
for (int i = 0; i < log.Answers.Count; i++)
{
var answer = log.Answers[i];
elements.Parameters.Add($"aType_{i}", answer.RecordType.ToString());
elements.Parameters.Add($"aData_{i}", answer.RecordData);
elements.Parameters.Add($"aClass_{i}", answer.RecordClass.ToString());
elements.Parameters.Add($"aTtl_{i}", answer.RecordTtl.ToString());
elements.Parameters.Add($"aSize_{i}", answer.Size.ToString());
elements.Parameters.Add($"aDnssecStatus_{i}", answer.DnssecStatus.ToString());
properties.Add(new LogEventProperty($"aType_{i}", new ScalarValue(answer.RecordType.ToString())));
properties.Add(new LogEventProperty($"aData_{i}", new ScalarValue(answer.RecordData)));
properties.Add(new LogEventProperty($"aClass_{i}", new ScalarValue(answer.RecordClass.ToString())));
properties.Add(new LogEventProperty($"aTtl_{i}", new ScalarValue(answer.RecordTtl.ToString())));
properties.Add(new LogEventProperty($"aSize_{i}", new ScalarValue(answer.Size.ToString())));
properties.Add(new LogEventProperty($"aDnssecStatus_{i}", new ScalarValue(answer.DnssecStatus.ToString())));
}
// Generate answers summary
var answerSummary = string.Join(", ", log.Answers.Select(a => a.RecordData));
properties.Add(new LogEventProperty("answersSummary", new ScalarValue(answerSummary)));
}
else
{
properties.Add(new LogEventProperty("answersSummary", new ScalarValue(string.Empty)));
}
// Include request and response tags if present
// Add request and response tags if present
if (log.RequestTag != null)
{
elements.Parameters.Add("requestTag", log.RequestTag.ToString());
properties.Add(new LogEventProperty("requestTag", new ScalarValue(log.RequestTag.ToString())));
}
if (log.ResponseTag != null)
{
elements.Parameters.Add("responseTag", log.ResponseTag.ToString());
properties.Add(new LogEventProperty("responseTag", new ScalarValue(log.ResponseTag.ToString())));
}
// Build a comprehensive message summary
string questionSummary = log.Questions?.Count > 0
? string.Join("; ", log.Questions.Select(q =>
$"QNAME: {q.QuestionName}; QTYPE: {q.QuestionType?.ToString() ?? "unknown"}; QCLASS: {q.QuestionClass?.ToString() ?? "unknown"}"))
: string.Empty;
// Define the message template to match the original summary format
const string templateText = "{questionsSummary}; RCODE: {rCode}; ANSWER: [{answersSummary}]";
// Build the answer summary in the desired format
string answerSummary = log.Answers?.Count > 0
? string.Join(", ", log.Answers.Select(a => a.RecordData))
: string.Empty;
// Parse the template
var template = new MessageTemplateParser().Parse(templateText);
// Construct the message summary string to match the desired format
string messageSummary = $"{questionSummary}; RCODE: {log.ResponseCode}; ANSWER: [{answerSummary}]";
// Create and return the syslog message
return new SyslogMessage(
log.Timestamp,
Facility.UserLevelMessages,
Severity.Informational,
_host,
_appName,
_processId,
_msgId,
messageSummary,
elements
// Create the LogEvent and return it
return new LogEvent(
timestamp: log.Timestamp,
level: LogEventLevel.Information,
exception: null,
messageTemplate: template,
properties: properties
);
}