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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...