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

来源:https://www.cnblogs.com/xiadao521/archive/2023/11/11/17825842.html
-Advertisement-
Play Games

本文介紹Util應用框架如何記錄日誌. 日誌記錄共分4篇,本文是正文,後續還有3篇分別介紹寫入不同日誌接收器的安裝和配置方法. 概述 日誌記錄對於瞭解系統執行情況非常重要. Asp.Net Core 抽象了日誌基礎架構,支持使用日誌提供程式進行擴展,提供控制台日誌等簡單實現. Serilog 是 . ...


本文介紹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";
        }
    }
}
Util應用框架交流群: 24791014 歡迎轉載 何鎮汐的技術博客 微信掃描二維碼支持Util
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前講了「如何配置普通的 RSA2 密鑰」,今天來捋一下如何配置證書~ 依照慣例先放一下官方文檔:[如何生成及配置公鑰證書],小伙伴們也可以直接按照文檔來~ 前期準備:證書的適用範圍 目前支付寶的所有產品都支持證書密鑰去進行調用。 其中「單筆轉賬」和「現金紅包」 只能使用 證書 進行介面調用。 詳情 ...
  • SciPy的linalg模塊是SciPy庫中的一個子模塊,它提供了許多用於線性代數運算的函數和工具,如矩陣求逆、特征值、行列式、線性方程組求解等。 相比於NumPy的linalg模塊,SciPy的linalg模塊包含更多的高級功能,並且在處理一些特定的數值計算問題時,可能會表現出更好的性能。 1. ...
  • 深入理解CAS 什麼是CAS 為什麼要學CAS:大廠你必須深入研究底層!有所突破! java層面的cas >compareAndSet compareAndSet(int expectedValue, int newValue) 期望並更新,達到期望值就更新、否則就不更新! package org. ...
  • 思想 DAO(Data Access Object)數據訪問對象,是我們在做結構化資料庫訪問的時候傳輸的對象,通過這個對象我們可以與資料庫中的表建立映射關係 DTO(Data Transfer Object)是我們在與前端進行數據交換時傳遞的對象 為什麼需要設置這這兩種對象呢? 為了數據安全 如果我 ...
  • 前言: 繼上篇:Taurus .Net Core 微服務開源框架:Admin 插件【4-3】 - 配置管理 - Mvc【Plugin-MicroService 微服務】 本篇繼續介紹下一個內容: 系統配置節點:Mvc - Plugin - CORS 跨域界面: 界面如下: 跨域功能相關配置說明如下: ...
  • 前言 由於業務需要,需要多台雲伺服器,但是公有雲的帶寬價格不菲,所以不可能給所有的雲伺服器都配上公網IP,一方面是成本的問題,另一方面也是公網安全的問題。 所以通過其它的方式使用無公網的雲伺服器來來實現對外資源的訪問。 一、操作步驟 至少需要有一臺具有公網IP的雲伺服器! 1、開啟ECS的路由轉發功 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 眾所周知,vue路由模式常見的有 history 和 hash 模式,但其實還有一種方式-abstract模式(瞭解一哈~) 別急,本文我們將重點逐步瞭解: 路由 + 幾種路由模式 + 使用場景 + 思考 + freestyle 路由概念 ...
  • 架構目標 高可用性 整體系統可用性最低99.9%,目標99.99%。全年故障時間整個系統不超過500分鐘,單個系統故障不超過50分鐘。 高可擴展性 系統架構簡單清晰,應用系統間耦合低,容易水平擴展,業務功能增改方便快捷。 低成本 增加服務的重用性,提高開發效率,降低人力成本; 最終一致性 服務設計能 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 微服務架構已經成為搭建高效、可擴展系統的關鍵技術之一,然而,現有許多微服務框架往往過於複雜,使得我們普通開發者難以快速上手並體驗到微服務帶了的便利。為瞭解決這一問題,於是作者精心打造了一款最接地氣的 .NET 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...