当前位置:网站首页>Source code analysis of serilog -- implementation of sink

Source code analysis of serilog -- implementation of sink

2020-11-09 15:45:00 iskcal

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 .

  1. First , Let's look at inheritance ,Logger Class division inheritance ILogger outside , Also inherited ILogEventSink 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 inherits IDisposable 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 in Logger Passive release after recycling , therefore , This led to the Logger You need to maintain a set of objects to be released for release . stay Logger Inside , By adding Action Function hook to release .

  2. 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 corresponding LogEvent object . Finally through Dispatch Method to distribute logs to ILogEventSink. stay Dispatch in , The first half of logic has little to do with this article , Finally through ILogEventSink 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 .

  1. To realize with complex functions through decorative patterns Sink, Usually by inheritance ILogEventSink And keep one inside ILogEventSink Object to extend the function , As mentioned above ConditionalSinkFilteringSinkRestrictedSink All of them belong to the extended function Sink, You can see , Its constructors require additional ILogEventSink 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 .

  2. Combine a group of Sink With a single Sink The way objects are exposed ,AggregrateSink and SafeAggregrateSink That's what I did . Even if the Logger Need to log to multiple Sink in , from Logger From the perspective of , It's just written into a ILogEventSink In the object , This makes Logger 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 use Logger Object to output the log to both the console and the file , We just need to build one AggregateSink And will ConsoleSink and FileSink Object is placed in its internal array , then AggregrateSink As Logger Medium ILogEventSink The object of , that Logger 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 .

版权声明
本文为[iskcal]所创,转载请带上原文链接,感谢