stay Last one in , We looked briefly at Serilog The overall needs and general structure of . From this one on , This article begins with Serilog The related implementation of , Focus on the first problem , namely Serilog Where to write log data .( Series catalog )
Basic function
At the beginning Serilog How to log to Sinks Before , Look at the whole picture first . First , We need to understand Serilog One of the most commonly used interfaces in ILogger
, It provides all the functions of external logging API Method .
ILogger
( The core interface )
stay Serilog The root directory , There is 4 Code files . Be similar to LogDemo,ILogger
It contains various functions API Method ,LogConfiguration
Used to build the corresponding ILogger
object . in addition ,LogExtensions
Is to ILogger
Add a new method , No LogConfiguration
.
.
For convenience , Let's first look at how to use , After understanding how to use , Let's go back to how to create . First of all ILogger
, It provides a lot of ways to use it , According to the function, it can be divided into the following three categories .
Method name | explain |
---|---|
ForContext series | Construct child logging object , And add extra data |
Write series ,XXX( Log level name ) series | Logging function |
BindXXX series | Output template 、 Attribute binding is related to |
The method in this , For us , The second kind of method is the most used , Let's see first Serilog How to record the log .
Log
( Static method classes )
This is a static class , You can see that the inside is essentially about ILogger
Further packing of , And put all API Methods are exposed , as follows .
public static class Log
{
static ILogger _logger = SilentLogger.Instance;
public static Logger
{
get => _logger;
set => _logger = value ?? throw ...
}
public static void Write(LogEventLevel level, string messageTemplate)
...
}
By the way , Class library SilentLogger
Class is right ILogger
An empty implementation of , It can be seen as an empty class with call function .
In understanding the core of ILogger
After the interface , The next thing you need to understand is to describe log events LogEvent
class , In the class Events Under the folder , As a Write
Input parameters of , Think of it as LogDemo Medium LogData
class , It's just that it contains more data information . in addition ,LogEventLevel
It's an enumeration , Also located in Events Under the folder , The content of this class and LogDemo Medium LogLevel
Exactly the same .
LogEvent
( Log event class )
stay Serilog in , Every time we have a logging behavior ,Serilog They are encapsulated in a class for easy use , namely LogEvent
class . and LogDemo Medium LogData
equally ,LogEvent
Class contains some data that describes log events .
public class LogEvent
{
public DateTimeOffset Timestamp { get; }
public LogEventLevel Level { get; }
public Exception Exception { get; }
public MessageTemplate MessageTemplate { get; }
private readonly Dictionary<string, LogEventPropertyValue> _properties;
internal LogEvent Copy()
{
...
}
}
You can see , stay LogEvent in , There are several fields and properties that describe a log event .Timestamp
Log description of the time attribute of the log , use DateTimeOffset
This type can unify the server time points under different time zones , Make sure there's a unity of time .Level
There's no need to say more , Describe the level of the log .Exception
Property can hold any exception class data , This property is often used in Error and Fatal In the hierarchy , Use when you need to save exception information . As for the follow-up MessageTemplate
and LogEventPropertyValue
, Literally , It belongs to string message template and used when recording data , At present, our main research records that Sink Processing logic , So these two pieces don't care about .
Besides , stay LogEvent
Class , There's a very special function , be known as Copy
function , This function is based on the current LogEvent
Object copies the same LogEvent
object . This approach can be seen as an implementation of prototype patterns in design patterns , It's just that this class doesn't take advantage of IClonable
Interface to implement .
Core Function classes under the directory
ILogEventSink
Interface
stay LogDemo in , We go through ILogTarget
Interface defines different logging destinations . Similarly , stay Serilog in , be-all Sink adopt ILogEventSink
Define unified logging interface . The interface is shown below .
public interface ILogEventSink
{
void Emit(LogEvent logEvent);
}
The interface form is simple , There is only one function , Input parameter is LogEvent
object , No return value , This and LogDemo Medium ILogTarget
The interface is very similar . If you want to achieve a ConsoleSink, Just inherit the interface and LogEvent
Object string data is written to Console
that will do . actually , stay Serilog.Sinks.Console This is how its core functions are realized .
Logger
class
Logger
Class is right ILogger
The default implementation of the interface . Be similar to LogDemo Medium Logger
, This class provides... For the use of all logging API Method . Considering that this article only cares about where the log is written to . therefore , We only care about some of its internal field properties and methods .
public sealed class Logger : ILogger, ILogEventSink, IDisposable
{
readonly ILogEventSink _sink;
readonly Action _dispose;
readonly LogEventLevel _minimumLevel;
// 361 Row to 375 That's ok
public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues)
{
if (!IsEnabled(level)) return;
if (messageTemplate == null) return;
if (propertyValues != null && propertyValues.GetType() != typeof(object[]))
propertyValues = new object[] {propertyValues};
// Parse log template
_messageTemplateProcessor.Process(messageTemplate, propertyValues, out var parsedTemplate, out var boundProperties);
// Construct log event objects
var logEvent = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, boundProperties);
// Distribute log events
Dispatch(logEvent);
}
public void Dispatch(LogEvent logEvent)
{
...
// Give log events to Sink For recording
_sink.Emit(logEvent);
}
}
Considering the length , Here I've removed some code that has nothing to do with the current function , Keep only the core code .
-
First , Let's look at inheritance ,
Logger
Class division inheritanceILogger
outside , Also inheritedILogEventSink
Interface , The inheritance seems strange , But it's normal to think about it , A logger can not only be a generator of log events , It can also be a receiver . In other words , You can write a log event to another logger , From another logger to the other Sinks in . Besides , This class also inheritsIDisposable
Interface , In terms of logical requirements ,Logger
There's nothing to release , What it needs to release is usually some objects contained inside , for instance FileSink If a file handle is maintained for a long time , You need to inLogger
Passive release after recycling , therefore , This led to theLogger
You need to maintain a set of objects to be released for release . stayLogger
Inside , By addingAction
Function hook to release . -
after , We will find that all the write log methods directly or indirectly call the above given Write Method . In the logic of the method , The first line is used to determine whether the log level meets the criteria , That is, a kind of global filtering conditions , The second line is to determine whether the output template of the log is given . And then
_messageTemplateProcessor
Look, this means parsing templates and data ( I don't know , But pay more attention to ). following , It's the construction of the correspondingLogEvent
object . Finally throughDispatch
Method to distribute logs toILogEventSink
. stayDispatch
in , The first half of logic has little to do with this article , Finally throughILogEventSink
Send the log message out .
See here , There may be a little curiosity ,Logger
There should be a group of ILogEventSink
The object is right , Only in this way can we achieve multiple Sink Write log information in , but Logger
Only one... Is maintained ILogEventSink
object , How does it do it to more than one at a time Sink It's written in the log ? Let's move on .
Functionality Sink
stay Serilog Of ./Core/Sinks You can find , There's a lot of ILogEventSink
Implementation class of . These implementation classes are not specific to the media ( Console 、 Documents, etc. ) Write to the log , Instead, , They're all for other Sink Expand new features , An implementation of a typical decorative pattern . Under this folder , I've extracted some of the core functions , as follows .(v2.10.0 Some other decoration classes have been added , That's just a little more ).
class ConditionalSink : ILogEventSink
{
readonly ILogEventSink _warpped;
readonly Func<LogEvent, bool> _condition;
...
public void Emit(LogEvent logEvent)
{
if (_condition(logEvent)) _wrapped.Emit(logEvent);
}
...
}
ConditionalSink
The function is very simple , It also contains a ILogEventSink
object , Besides , It also includes a Func<LogEvent, bool>
Generic delegate for . This commission can be made according to LogEvent
Object to meet certain requirements for filtering . from Emit It can be seen in the function that , Log events will only be sent to the corresponding Sink in . It can be seen as a condition of writing Sink, This and that is the core of the local filtering function .
public interface ILogEventFilter
{
bool IsEnabled(LogEvent logEvent);
}
FilteringSink
What was done and ConditiaonalSink
equally , except Sink Outside the object , It also maintains a set of ILogEventFilter
Array is used to specify multiple log filtering conditions , and ILogEventFilter
The interface is shown above , Its interior is to filter by log object . and RestrictedSink
Internal division ILogEventSink
Outside the object , One more LoggingLevelSwitch
object , This object is used to describe the minimum level of logging that the logger can record , therefore RestrictedSink
What we realize is to judge whether to output the log according to the log level comparison .
sealed class SecondaryLoggerSink : ILogEventSink
{
readonly ILogger _logger;
readonly bool _attemptDispose;
...
public void Emit(LogEvent logEvent)
{
...
var copy = logEvent.Copy();
_logger.Write(copy);
}
}
And the rest of the above ILogEventSink
Compared with the inheritance class of ,SecondaryLoggerSink
There is no reservation within it for a certain ILogEventSink
References to . contrary , It preserves for given ILogger
References to objects , The advantage is that we can use one logger as another Sink
. Another variable of this class _attemptDispose
Indicates whether the class needs to execute internal ILogger
Release of objects , It's done because sometimes Logger
Objects don't have to be released , Generally, the child loggers created by the parent logger do not need to be released , Its resource release can be managed by the parent logger .
class SafeAggregateSink : ILogEventSink
{
readonly ILogEventSink[] _sinks;
...
public void Emit(LogEvent logEvent)
{
foreach (var sink in _sinks)
{
...
sink.Emit(logEvent);
...
}
}
}
besides , There is still left AggregrateSink
and SafeAggregrateSink
these two items. Sink Also inherited ILogEventSink
Interface , And they all quote ILogEventSink
Array , And in Emit
Functions are basically for the array of ILogEventSink
Object traversal , And call Emit
function . Both are in Emit
Function to capture all exceptions , but AggregateSink
After catching, these exceptions will be marked with AggreateException
The exception is thrown again . These two classes are different from the previous classes , They will be many Sink Get together , Let the outside world remain single Sink To use . The advantage is ,Logger
Designers don't need to focus on one or more Sink, If there are more than one Sink, Just use these two classes to make multiple Sink Wrap it up , The outside world will be this group Sink Think of it as a Sink To use .
Why is it designed like this ? actually , Yes Logger
Category , It doesn't need to care about the recorded Sink One or more , What kind of state is it , What conditions are met to record , After all, these are very complicated . about Logger
Speaking of , It has only one thing to do , Just send the log event to ILogEventSink
Object can be issued . In order to achieve this goal ,Serilog Use decorative patterns and combination patterns in design patterns to reduce Logger
The design burden of . It is mainly reflected in two aspects .
-
To realize with complex functions through decorative patterns Sink, Usually by inheritance
ILogEventSink
And keep one insideILogEventSink
Object to extend the function , As mentioned aboveConditionalSink
、FilteringSink
、RestrictedSink
All of them belong to the extended function Sink, You can see , Its constructors require additionalILogEventSink
object . Besides , These decoration classes can also be nested , That is, a decoration class can have another decoration class object , Realize the aggregation of functions . -
Combine a group of Sink With a single Sink The way objects are exposed ,
AggregrateSink
andSafeAggregrateSink
That's what I did . Even if theLogger
Need to log to multiple Sink in , fromLogger
From the perspective of , It's just written into aILogEventSink
In the object , This makesLogger
Designers don't have to be one or more Sink And headaches . for instance , If you have one ConsoleSink, Its function is to output the log to the console , And a log output to a file FileSink. If you want to useLogger
Object to output the log to both the console and the file , We just need to build oneAggregateSink
And will ConsoleSink and FileSink Object is placed in its internal array , thenAggregrateSink
AsLogger
MediumILogEventSink
The object of , thatLogger
It can automatically record the log to these two places .
summary
That's the whole thing Sink Function description , What you can see is , This is the same as the one mentioned earlier LogDemo The project is very much like . I believe that if we were to LogDemo People who can understand can find a very familiar feeling here . Start with the next one , I'm going to start exposing Serilog How will LogEvent Such log events are converted into final writes to each Sink Of string information in .