mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2026-02-08 17:04:01 +00:00
Implemented syslog with Serilog
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user