mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2025-12-22 09:29:50 +00:00
203 lines
8.0 KiB
C#
203 lines
8.0 KiB
C#
/*
|
|
Technitium DNS Server
|
|
Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
using Serilog;
|
|
using Serilog.Events;
|
|
using Serilog.Parsing;
|
|
using Serilog.Sinks.Syslog;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace LogExporter.Strategy
|
|
{
|
|
public class SyslogExportStrategy : IExportStrategy
|
|
{
|
|
#region variables
|
|
|
|
private const string _appName = "Technitium DNS Server";
|
|
|
|
private const string _sdId = "meta";
|
|
|
|
private const string DEFAUL_PROTOCOL = "udp";
|
|
|
|
private const int DEFAULT_PORT = 514;
|
|
|
|
private readonly Facility _facility = Facility.Local6;
|
|
|
|
private readonly Rfc5424Formatter _formatter;
|
|
|
|
private readonly Serilog.Core.Logger _sender;
|
|
|
|
private bool disposedValue;
|
|
|
|
#endregion variables
|
|
|
|
#region constructor
|
|
|
|
public SyslogExportStrategy(string address, int? port, string? protocol)
|
|
{
|
|
port ??= DEFAULT_PORT;
|
|
protocol ??= DEFAUL_PROTOCOL;
|
|
|
|
var conf = new LoggerConfiguration();
|
|
|
|
_sender = protocol.ToLowerInvariant() switch
|
|
{
|
|
"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"),
|
|
};
|
|
|
|
_formatter = new Rfc5424Formatter(_facility, _appName, null, _sdId, Environment.MachineName);
|
|
}
|
|
|
|
#endregion constructor
|
|
|
|
#region public
|
|
|
|
public Task ExportAsync(List<LogEntry> logs)
|
|
{
|
|
return Task.Run(() =>
|
|
{
|
|
foreach (var log in logs)
|
|
{
|
|
_sender.Information((string?)_formatter.FormatMessage((LogEvent?)Convert(log)));
|
|
}
|
|
});
|
|
}
|
|
#endregion public
|
|
|
|
#region IDisposable
|
|
|
|
public void Dispose()
|
|
{
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
Dispose(disposing: true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposedValue)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_sender.Dispose();
|
|
}
|
|
|
|
disposedValue = true;
|
|
}
|
|
}
|
|
|
|
#endregion IDisposable
|
|
|
|
#region private
|
|
|
|
private LogEvent Convert(LogEntry log)
|
|
{
|
|
// Initialize properties with base log details
|
|
var properties = new List<LogEventProperty>
|
|
{
|
|
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 as properties
|
|
if (log.Questions?.Count > 0)
|
|
{
|
|
for (int i = 0; i < log.Questions.Count; i++)
|
|
{
|
|
var question = log.Questions[i];
|
|
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 as properties
|
|
if (log.Answers?.Count > 0)
|
|
{
|
|
for (int i = 0; i < log.Answers.Count; i++)
|
|
{
|
|
var answer = log.Answers[i];
|
|
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)));
|
|
}
|
|
|
|
// Add request and response tags if present
|
|
if (log.RequestTag != null)
|
|
{
|
|
properties.Add(new LogEventProperty("requestTag", new ScalarValue(log.RequestTag.ToString())));
|
|
}
|
|
|
|
if (log.ResponseTag != null)
|
|
{
|
|
properties.Add(new LogEventProperty("responseTag", new ScalarValue(log.ResponseTag.ToString())));
|
|
}
|
|
|
|
// Define the message template to match the original summary format
|
|
const string templateText = "{questionsSummary}; RCODE: {rCode}; ANSWER: [{answersSummary}]";
|
|
|
|
// Parse the template
|
|
var template = new MessageTemplateParser().Parse(templateText);
|
|
|
|
// Create the LogEvent and return it
|
|
return new LogEvent(
|
|
timestamp: log.Timestamp,
|
|
level: LogEventLevel.Information,
|
|
exception: null,
|
|
messageTemplate: template,
|
|
properties: properties
|
|
);
|
|
}
|
|
|
|
#endregion private
|
|
}
|
|
} |