Util應用框架基礎(六) - 日誌記錄(一) - 正文

本文介紹Util應用框架如何記錄日誌.

日誌記錄共分4篇,本文是正文,後續還有3篇分別介紹寫入不同日誌接收器的安裝和配置方法.

概述

日誌記錄對於瞭解系統執行情況非常重要.

Asp.Net Core 抽象了日誌基礎架構,支持使用日誌提供程序進行擴展,提供控制檯日誌等簡單實現.

Serilog 是 .Net 流行的第三方日誌框架,支持結構化日誌,並能與 Asp.Net Core 日誌集成.

Serilog 支持多種日誌接收器,可以將日誌發送到不同的地方.

我們可以將日誌寫入文本文件,但查看文本文件比較困難,文件如果很大,查找問題非常費力.

對於生產環境,我們需要包含管理界面的日誌系統.

Seq 是一個日誌系統,可以很好的展示結構化日誌數據,並提供模糊搜索功能.

Exceptionless 是基於 Asp.Net Core 開發的日誌系統.

與 Seq 相比,Exceptionless 搜索能力較弱.

Seq 和 Exceptionless 都提供了 Serilog 日誌接收器,可以使用 Serilog 接入它們.

Util應用框架使用 Serilog 日誌框架,同時集成了 SeqExceptionless 日誌系統.

Util簡化了日誌配置,並對常用功能進行擴展.

日誌配置

選擇日誌接收器

Util應用框架默認支持三種 Serilog 日誌接收器:

  • 日誌文件
  • Seq
  • Exceptionless

你可以從中選擇一種或多種,如果都不能滿足要求,你也可以引用 Serilog 支持的其它日誌接收器,或自行實現.

配置日誌接收器

請轉到特定日誌接收器章節查看配置方法.

配置日誌級別

Asp.Net Core 使用日誌級別表示日誌的嚴重程度,定義如下:

  • Trace = 0
  • Debug = 1
  • Information = 2
  • Warning = 3
  • Error = 4
  • Critical = 5
  • None = 6

None不開啓日誌,Trace的嚴重程度最低,Critical的嚴重程度最高,需要高度關注.

可以在 appsettings.json 配置文件設置日誌級別.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

Logging 配置節用於配置日誌.

LogLevel 爲所有日誌提供程序配置日誌級別.

Default 爲所有日誌類別設置默認的日誌級別.

上面的配置將默認日誌級別設置爲 Information.

意味着只輸出日誌級別等於或大於 Information 的日誌.

現在 Trace 和 Debug 兩個級別的日誌被禁用了.

可以爲特定日誌類別設置日誌級別.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
    }
  }
}

配置增加了 Microsoft 日誌類別,並設置爲 Debug 日誌級別.

日誌類別用來給日誌分類,一般使用帶命名空間的類名作爲日誌類別.

日誌類別支持模糊匹配, Microsoft 日誌類別不僅匹配 Microsoft ,而且還能匹配以 Microsoft 開頭的所有日誌類別,比如 Microsoft.AspNetCore .

Serilog 的日誌級別

Serilog 定義了自己的日誌級別,不支持上面介紹的標準配置方式.

Exceptionless 也是如此.

使用第三方框架的日誌級別會導致複雜性.

Util應用框架擴展了 Serilog 和 Exceptionless 的日誌級別配置,允許以統一的標準方式進行配置.

記錄日誌

.Net 提供了標準的日誌記錄接口 Microsoft.Extensions.Logging.ILogger.

你可以使用 ILogger 記錄日誌.

Util應用框架還提供了一個 Util.Logging.ILog 接口.

當你需要寫入很長的日誌時,可能需要使用 StringBuilder 拼接日誌內容.

ILog 提供了一種更簡單的方式寫入長內容日誌.

使用 ILogger 記錄日誌

ILogger 支持泛型參數, 用來指定日誌類別,使用帶命名空間的類名作爲日誌類別.

namespace Demo;

public class DemoController : WebApiControllerBase {
    public DemoController( ILogger<DemoController> logger ) {
        logger.LogDebug( "Util" );
    }
}

示例在控制器構造方法注入 ILogger<DemoController> ,日誌類別爲 Demo.DemoController .

ILogger 擴展了一些以 Log 開頭的方法,比如 LogDebug,表示寫入日誌級別爲 Debug 的消息.

logger.LogDebug( "Util" ) 以 Debug 日誌級別寫入消息'Util'.

使用 ILog 記錄日誌

Util應用框架定義了 ILog 接口.

ILog 是對 ILogger 接口的簡單包裝, 對日誌內容的設置進行了擴展.

ILog 也使用泛型參數來指定日誌類別.

使用 ILog 完成上面相同的示例.

public class DemoController : WebApiControllerBase {
    public DemoController( ILog<DemoController> log ) {
        log.Message( "Util" ).LogDebug();
    }
}

Message 是 ILog 定義的方法, 用來設置日誌消息,可以多次調用它拼接內容.

當你需要寫比較長的日誌內容, ILog 可以幫你拼接內容,這樣省去了定義 StringBuilder 的麻煩.

可以在 ILog 添加自定義擴展方法來設置內容, Util應用框架內置了一些設置日誌消息的擴展方法, 比如 AppendLine.

public class DemoController : WebApiControllerBase {
    public DemoController( ILog<DemoController> log ) {
        log.AppendLine( "內容1" )
            .AppendLine( "內容2" )
            .LogDebug();
    }
}

你可以定義自己的擴展方法,以更加語義化的方式記錄日誌.

範例:

public class DemoController : WebApiControllerBase {
    public DemoController( ILog<DemoController> log ) {
        log.Caption( "標題" )
            .Content( "內容" )
            .Sql( "Sql" )
            .LogDebug();
    }
}

ILog 與 ILogger 比較:

ILog 更擅長記錄內容很長的日誌.

ILog 是有狀態服務,不能在多個線程共享使用.

可以使用 ILog 記錄業務日誌,其它場景應使用 ILogger.

結構化日誌支持

Serilog 日誌框架對結構化日誌提供了支持.

結構化日誌使用特定語法的消息模板日誌格式,可以從日誌文本中提取搜索元素.

結構化日誌的優勢主要體現在日誌系統對日誌消息的展示和搜索方式上.

不同的日誌系統對結構化日誌的展示方式和搜索能力不同.

請參考 Seq 和 Exceptionless 的 結構化日誌支持 小節.

日誌操作上下文

記錄日誌時,我們除了需要記錄業務內容,還需要知道一些額外的信息,比如操作用戶是誰.

我們希望記錄日誌時僅設置業務內容,這些額外的信息最好能自動記錄.

Util應用框架通過日誌上下文自動設置這些額外信息.

  • UserId 設置當前操作用戶標識

  • Application 設置當前應用程序名稱.

  • Environment 設置當前環境名稱.

  • TraceId 設置跟蹤號.

  • Stopwatch 設置計時器,用於記錄請求執行花了多長時間.

在 Asp.Net Core 環境, 日誌上下文由日誌上下文中間件 Util.Applications.Logging.LogContextMiddleware 創建.

無需手工添加日誌上下文中間件,只要引用 Util.Application.WebApi 類庫, 就會自動添加到中間件管道.

對於 Web 請求, 跟蹤號是一個重要的信息,可以通過查詢跟蹤號,將相關的請求日誌全部查出來.

另外, Exceptionless 會自動收集很多系統信息.

源碼解析

ILog 日誌操作

ILog 日誌操作接口提供鏈式調用方式設置日誌內容.

  • Message 方法設置日誌消息.

  • Property 方法設置擴展屬性.

  • State 設置日誌參數對象.

Log 開頭的日誌記錄方法,將日誌操作委託給 ILogger 相關方法.

/// <summary>
/// 日誌操作
/// </summary>
/// <typeparam name="TCategoryName">日誌類別</typeparam>
public interface ILog<out TCategoryName> : ILog {
}

/// <summary>
/// 日誌操作
/// </summary>
public interface ILog {
    /// <summary>
    /// 設置日誌事件標識
    /// </summary>
    /// <param name="eventId">日誌事件標識</param>
    ILog EventId( EventId eventId );
    /// <summary>
    /// 設置異常
    /// </summary>
    /// <param name="exception">異常</param>
    ILog Exception( Exception exception );
    /// <summary>
    /// 設置自定義擴展屬性
    /// </summary>
    /// <param name="propertyName">屬性名</param>
    /// <param name="propertyValue">屬性值</param>
    ILog Property( string propertyName, string propertyValue );
    /// <summary>
    /// 設置日誌狀態對象
    /// </summary>
    /// <param name="state">狀態對象</param>
    ILog State( object state );
    /// <summary>
    /// 設置日誌消息
    /// </summary>
    /// <param name="message">日誌消息</param>
    /// <param name="args">日誌消息參數</param>
    ILog Message( string message, params object[] args );
    /// <summary>
    /// 是否啓用
    /// </summary>
    /// <param name="logLevel">日誌級別</param>
    bool IsEnabled( LogLevel logLevel );
    /// <summary>
    /// 開啓日誌範圍
    /// </summary>
    /// <typeparam name="TState">日誌狀態類型</typeparam>
    /// <param name="state">日誌狀態</param>
    IDisposable BeginScope<TState>( TState state );
    /// <summary>
    /// 寫跟蹤日誌
    /// </summary>
    ILog LogTrace();
    /// <summary>
    /// 寫調試日誌
    /// </summary>
    ILog LogDebug();
    /// <summary>
    /// 寫信息日誌
    /// </summary>
    ILog LogInformation();
    /// <summary>
    /// 寫警告日誌
    /// </summary>
    ILog LogWarning();
    /// <summary>
    /// 寫錯誤日誌
    /// </summary>
    ILog LogError();
    /// <summary>
    /// 寫致命日誌
    /// </summary>
    ILog LogCritical();
}

ILogExtensions 日誌操作擴展

Util應用框架內置了幾個日誌操作擴展方法,你可以定義自己的擴展方法,以方便內容設置.

/// <summary>
/// 日誌操作擴展
/// </summary>
public static class ILogExtensions {
    /// <summary>
    /// 添加消息
    /// </summary>
    /// <param name="log">配置項</param>
    /// <param name="message">消息</param>
    /// <param name="args">日誌消息參數</param>
    public static ILog Append( this ILog log,string message, params object[] args ) {
        log.CheckNull( nameof( log ) );
        log.Message( message, args );
        return log;
    }

    /// <summary>
    /// 當條件爲true添加消息
    /// </summary>
    /// <param name="log">配置項</param>
    /// <param name="message">消息</param>
    /// <param name="condition">條件,值爲true則添加消息</param>
    /// <param name="args">日誌消息參數</param>
    public static ILog AppendIf( this ILog log, string message,bool condition, params object[] args ) {
        log.CheckNull( nameof( log ) );
        if ( condition )
            log.Message( message, args );
        return log;
    }

    /// <summary>
    /// 添加消息並換行
    /// </summary>
    /// <param name="log">配置項</param>
    /// <param name="message">消息</param>
    /// <param name="args">日誌消息參數</param>
    public static ILog AppendLine( this ILog log, string message, params object[] args ) {
        log.CheckNull( nameof( log ) );
        log.Message( message, args );
        log.Message( Environment.NewLine );
        return log;
    }

    /// <summary>
    /// 當條件爲true添加消息並換行
    /// </summary>
    /// <param name="log">配置項</param>
    /// <param name="message">消息</param>
    /// <param name="condition">條件,值爲true則添加消息</param>
    /// <param name="args">日誌消息參數</param>
    public static ILog AppendLineIf( this ILog log, string message, bool condition, params object[] args ) {
        log.CheckNull( nameof( log ) );
        if ( condition ) {
            log.Message( message, args );
            log.Message( Environment.NewLine );
        }
        return log;
    }

    /// <summary>
    /// 消息換行
    /// </summary>
    /// <param name="log">配置項</param>
    public static ILog Line( this ILog log ) {
        log.CheckNull( nameof(log) );
        log.Message( Environment.NewLine );
        return log;
    }
}

LogContext 日誌上下文

通過日誌上下文自動記錄重要的額外信息.

/// <summary>
/// 日誌上下文
/// </summary>
public class LogContext {
    /// <summary>
    /// 初始化日誌上下文
    /// </summary>
    public LogContext() {
        Data = new Dictionary<string, object>();
    }

    /// <summary>
    /// 計時器
    /// </summary>
    public Stopwatch Stopwatch { get; set; }
    /// <summary>
    /// 跟蹤標識
    /// </summary>
    public string TraceId { get; set; }
    /// <summary>
    /// 用戶標識
    /// </summary>
    public string UserId { get; set; }
    /// <summary>
    /// 應用程序
    /// </summary>
    public string Application { get; set; }
    /// <summary>
    /// 執行環境
    /// </summary>
    public string Environment { get; set; }
    /// <summary>
    /// 擴展數據
    /// </summary>
    public IDictionary<string, object> Data { get; }
}

LogContextMiddleware 日誌上下文中間件

日誌上下文中間件創建日誌上下文,並添加到 HttpContext 對象的 Items .

/// <summary>
/// 日誌上下文中間件
/// </summary>
public class LogContextMiddleware {
    /// <summary>
    /// 下箇中間件
    /// </summary>
    private readonly RequestDelegate _next;

    /// <summary>
    /// 初始化日誌上下文中間件
    /// </summary>
    /// <param name="next">下箇中間件</param>
    public LogContextMiddleware( RequestDelegate next ) {
        _next = next;
    }

    /// <summary>
    /// 執行中間件
    /// </summary>
    /// <param name="context">Http上下文</param>
    public async Task Invoke( HttpContext context ) {
        var traceId = context.Request.Headers["x-correlation-id"].SafeString();
        if ( traceId.IsEmpty() )
            traceId = context.TraceIdentifier;
        var session = context.RequestServices.GetService<Util.Sessions.ISession>();
        var environment = context.RequestServices.GetService<IWebHostEnvironment>();
        var logContext = new LogContext {
            Stopwatch = Stopwatch.StartNew(), 
            TraceId = traceId, 
            UserId = session?.UserId,
            Application = environment?.ApplicationName,
            Environment = environment?.EnvironmentName
        };
        context.Items[LogContextAccessor.LogContextKey] = logContext;
        await _next( context );
    }
}

ILogContextAccessor 日誌上下文訪問器

日誌上下文訪問器從 HttpContext.Items 獲取日誌上下文.

/// <summary>
/// 日誌上下文訪問器
/// </summary>
public interface ILogContextAccessor {
    /// <summary>
    /// 日誌上下文
    /// </summary>
    LogContext Context { get; set; }
}

/// <summary>
/// 日誌上下文訪問器
/// </summary>
public class LogContextAccessor : ILogContextAccessor {
    /// <summary>
    /// 日誌上下文鍵名
    /// </summary>
    public const string LogContextKey = "Util.Logging.LogContext";

    /// <summary>
    /// 日誌上下文
    /// </summary>
    public LogContext Context {
        get => Util.Helpers.Convert.To<LogContext>( Web.HttpContext.Items[LogContextKey] );
        set => Web.HttpContext.Items[LogContextKey] = value;
    }
}

LogContextEnricher 日誌上下文擴展

Serilog 提供 ILogEventEnricher 接口用於設置擴展屬性.

LogContextEnricher 使用 Ioc.Create 方法獲取依賴服務 ILogContextAccessor.

這是因爲不能使用依賴注入,它要求實現類必須是無參構造函數.

Ioc.Create 在 Asp.Net Core 環境獲取依賴服務是安全的,但在其它環境則可能獲取失敗.

如果獲取日誌上下文失敗,也不會對功能造成影響,只是丟失了一些上下文信息.

/// <summary>
/// 日誌上下文擴展屬性
/// </summary>
public class LogContextEnricher : ILogEventEnricher {
    /// <summary>
    /// 擴展屬性
    /// </summary>
    /// <param name="logEvent">日誌事件</param>
    /// <param name="propertyFactory">日誌事件屬性工廠</param>
    public void Enrich( LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        var accessor = Ioc.Create<ILogContextAccessor>();
        if ( accessor == null )
            return;
        var context = accessor.Context;
        if ( context == null )
            return;
        if ( logEvent == null )
            return;
        if ( propertyFactory == null )
            return;
        RemoveProperties( logEvent );
        AddDuration( context,logEvent, propertyFactory );
        AddTraceId( context, logEvent, propertyFactory );
        AddUserId( context, logEvent, propertyFactory );
        AddApplication( context, logEvent, propertyFactory );
        AddEnvironment( context, logEvent, propertyFactory );
        AddData( context, logEvent, propertyFactory );
    }

    /// <summary>
    /// 移除默認設置的部分屬性
    /// </summary>
    private void RemoveProperties( LogEvent logEvent ) {
        logEvent.RemovePropertyIfPresent( "ActionId" );
        logEvent.RemovePropertyIfPresent( "ActionName" );
        logEvent.RemovePropertyIfPresent( "RequestId" );
        logEvent.RemovePropertyIfPresent( "RequestPath" );
        logEvent.RemovePropertyIfPresent( "ConnectionId" );
    }

    /// <summary>
    /// 添加執行持續時間
    /// </summary>
    private void AddDuration( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context?.Stopwatch == null )
            return;
        var property = propertyFactory.CreateProperty( "Duration", context.Stopwatch.Elapsed.Description() );
        logEvent.AddOrUpdateProperty( property );
    }

    /// <summary>
    /// 添加跟蹤號
    /// </summary>
    private void AddTraceId( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context == null || context.TraceId.IsEmpty() )
            return;
        var property = propertyFactory.CreateProperty( "TraceId", context.TraceId );
        logEvent.AddOrUpdateProperty( property );
    }

    /// <summary>
    /// 添加用戶標識
    /// </summary>
    private void AddUserId( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context == null || context.UserId.IsEmpty() )
            return;
        var property = propertyFactory.CreateProperty( "UserId", context.UserId );
        logEvent.AddOrUpdateProperty( property );
    }

    /// <summary>
    /// 添加應用程序
    /// </summary>
    private void AddApplication( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context == null || context.Application.IsEmpty() )
            return;
        var property = propertyFactory.CreateProperty( "Application", context.Application );
        logEvent.AddOrUpdateProperty( property );
    }

    /// <summary>
    /// 添加執行環境
    /// </summary>
    private void AddEnvironment( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context == null || context.Environment.IsEmpty() )
            return;
        var property = propertyFactory.CreateProperty( "Environment", context.Environment );
        logEvent.AddOrUpdateProperty( property );
    }

    /// <summary>
    /// 添加擴展數據
    /// </summary>
    private void AddData( LogContext context, LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
        if ( context?.Data == null || context.Data.Count == 0 )
            return;
        foreach ( var item in context.Data ) {
            var property = propertyFactory.CreateProperty( item.Key, item.Value );
            logEvent.AddOrUpdateProperty( property );
        }
    }
}

LoggerEnrichmentConfigurationExtensions

將 LogContextEnricher 擴展到 LoggerEnrichmentConfiguration 上.

/// <summary>
/// Serilog擴展屬性配置擴展
/// </summary>
public static class LoggerEnrichmentConfigurationExtensions {
    /// <summary>
    /// 添加日誌上下文擴展屬性
    /// </summary>
    /// <param name="source">日誌擴展配置</param>
    public static LoggerConfiguration WithLogContext( this LoggerEnrichmentConfiguration source ) {
        source.CheckNull( nameof( source ) );
        return source.With<LogContextEnricher>();
    }

    /// <summary>
    /// 添加日誌級別擴展屬性
    /// </summary>
    /// <param name="source">日誌擴展配置</param>
    public static LoggerConfiguration WithLogLevel( this LoggerEnrichmentConfiguration source ) {
        source.CheckNull( nameof( source ) );
        return source.With<LogLevelEnricher>();
    }
}

AddSerilog 配置方法

AddSerilog 配置方法封裝了 Serilog 的配置.

  • 配置 ILog 接口服務依賴.

  • 將 Asp.Net Core 日誌級別轉換爲 Serilog 日誌級別.

  • 設置日誌上下文擴展.

/// <summary>
/// Serilog日誌操作擴展
/// </summary>
public static class AppBuilderExtensions {
    /// <summary>
    /// 配置Serilog日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    public static IAppBuilder AddSerilog( this IAppBuilder builder ) {
        return builder.AddSerilog( false );
    }

    /// <summary>
    /// 配置Serilog日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="isClearProviders">是否清除默認設置的日誌提供程序</param>
    public static IAppBuilder AddSerilog( this IAppBuilder builder, bool isClearProviders ) {
        return builder.AddSerilog( options => {
            options.IsClearProviders = isClearProviders;
        } );
    }

    /// <summary>
    /// 配置Serilog日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="appName">應用程序名稱</param>
    public static IAppBuilder AddSerilog( this IAppBuilder builder, string appName ) {
        return builder.AddSerilog( options => {
            options.Application = appName;
        } );
    }

    /// <summary>
    /// 配置Serilog日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="setupAction">日誌配置操作</param>
    public static IAppBuilder AddSerilog( this IAppBuilder builder, Action<LogOptions> setupAction ) {
        builder.CheckNull( nameof( builder ) );
        var options = new LogOptions();
        setupAction?.Invoke( options );
        builder.Host.ConfigureServices( ( context, services ) => {
            services.AddSingleton<ILogFactory, LogFactory>();
            services.AddTransient( typeof( ILog<> ), typeof( Log<> ) );
            services.AddTransient( typeof( ILog ), t => t.GetService<ILogFactory>()?.CreateLog( "default" ) ?? NullLog.Instance );
            var configuration = context.Configuration;
            services.AddLogging( loggingBuilder => {
                if ( options.IsClearProviders )
                    loggingBuilder.ClearProviders();
                SerilogLog.Logger = new LoggerConfiguration()
                    .Enrich.WithProperty( "Application", options.Application )
                    .Enrich.FromLogContext()
                    .Enrich.WithLogLevel()
                    .Enrich.WithLogContext()
                    .ReadFrom.Configuration( configuration )
                    .ConfigLogLevel( configuration )
                    .CreateLogger();
                loggingBuilder.AddSerilog();
            } );
        } );
        return builder;
    }
}

AddExceptionless 配置方法

AddExceptionless 配置方法封裝了 Serilog 和 Exceptionless 的配置.

  • 配置 ILog 接口服務依賴.

  • 將 Asp.Net Core 日誌級別轉換爲 Exceptionless 日誌級別.

  • 設置日誌上下文擴展.

/// <summary>
/// Exceptionless日誌操作擴展
/// </summary>
public static class AppBuilderExtensions {
    /// <summary>
    /// 配置Exceptionless日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="isClearProviders">是否清除默認設置的日誌提供程序</param>
    public static IAppBuilder AddExceptionless( this IAppBuilder builder, bool isClearProviders = false ) {
        return builder.AddExceptionless( null, isClearProviders );
    }

    /// <summary>
    /// 配置Exceptionless日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="appName">應用程序名稱</param>
    public static IAppBuilder AddExceptionless( this IAppBuilder builder, string appName ) {
        return builder.AddExceptionless( null, appName );
    }

    /// <summary>
    /// 配置Exceptionless日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="configAction">Exceptionless日誌配置操作</param>
    /// <param name="isClearProviders">是否清除默認設置的日誌提供程序</param>
    public static IAppBuilder AddExceptionless( this IAppBuilder builder, Action<ExceptionlessConfiguration> configAction, bool isClearProviders = false ) {
        return builder.AddExceptionless( configAction, t => t.IsClearProviders = isClearProviders );
    }

    /// <summary>
    /// 配置Exceptionless日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="configAction">Exceptionless日誌配置操作</param>
    /// <param name="appName">應用程序名稱</param>
    public static IAppBuilder AddExceptionless( this IAppBuilder builder, Action<ExceptionlessConfiguration> configAction, string appName ) {
        return builder.AddExceptionless( configAction, t => t.Application = appName );
    }

    /// <summary>
    /// 配置Exceptionless日誌操作
    /// </summary>
    /// <param name="builder">應用生成器</param>
    /// <param name="configAction">Exceptionless日誌配置操作</param>
    /// <param name="setupAction">日誌配置</param>
    public static IAppBuilder AddExceptionless( this IAppBuilder builder, Action<ExceptionlessConfiguration> configAction, Action<LogOptions> setupAction ) {
        builder.CheckNull( nameof( builder ) );
        var options = new LogOptions();
        setupAction?.Invoke( options );
        builder.Host.ConfigureServices( ( context, services ) => {
            services.AddSingleton<ILogFactory, LogFactory>();
            services.AddTransient( typeof( ILog<> ), typeof( Log<> ) );
            services.AddTransient( typeof( ILog ), t => t.GetService<ILogFactory>()?.CreateLog( "default" ) ?? NullLog.Instance );
            var configuration = context.Configuration;
            services.AddLogging( loggingBuilder => {
                if ( options.IsClearProviders )
                    loggingBuilder.ClearProviders();
                ConfigExceptionless( configAction, configuration );
                SerilogLog.Logger = new LoggerConfiguration()
                    .Enrich.WithProperty( "Application", options.Application )
                    .Enrich.FromLogContext()
                    .Enrich.WithLogLevel()
                    .Enrich.WithLogContext()
                    .WriteTo.Exceptionless()
                    .ReadFrom.Configuration( configuration )
                    .ConfigLogLevel( configuration )
                    .CreateLogger();
                loggingBuilder.AddSerilog();
            } );
        } );
        return builder;
    }

    /// <summary>
    /// 配置Exceptionless
    /// </summary>
    private static void ConfigExceptionless( Action<ExceptionlessConfiguration> configAction, IConfiguration configuration ) {
        ExceptionlessClient.Default.Startup();
        if ( configAction != null ) {
            configAction( ExceptionlessClient.Default.Configuration );
            ConfigLogLevel( configuration, ExceptionlessClient.Default.Configuration );
            return;
        }
        ExceptionlessClient.Default.Configuration.ReadFromConfiguration( configuration );
        ConfigLogLevel( configuration, ExceptionlessClient.Default.Configuration );
    }

    /// <summary>
    /// 配置日誌級別
    /// </summary>
    private static void ConfigLogLevel( IConfiguration configuration, ExceptionlessConfiguration options ) {
        var section = configuration.GetSection( "Logging:LogLevel" );
        foreach ( var item in section.GetChildren() ) {
            if ( item.Key == "Default" ) {
                options.Settings.Add( "@@log:*", GetLogLevel( item.Value ) );
                continue;
            }
            options.Settings.Add( $"@@log:{item.Key}*", GetLogLevel( item.Value ) );
        }
    }

    /// <summary>
    /// 獲取日誌級別
    /// </summary>
    private static string GetLogLevel( string logLevel ) {
        switch ( logLevel.ToUpperInvariant() ) {
            case "TRACE":
                return "Trace";
            case "DEBUG":
                return "Debug";
            case "INFORMATION":
                return "Info";
            case "ERROR":
                return "Error";
            case "CRITICAL":
                return "Fatal";
            case "NONE":
                return "Off";
            default:
                return "Warn";
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章