在 .NET Core Logging中使用 Trace和TraceSource

本文介紹了在.NET Core中如何在組件設計中使用Trace和TraceSource。

在以下方面會提供一些幫助:

1.你已經為.NET Framework和.NET Core / .NET Standard構建了組件,而且你更喜歡保留Trace和TraceSource。

2.你有使用跟蹤和TraceSource的第三方組件。

3.你正在將一個複雜的.NET Framework應用程式遷移到.NET Core,並且目前還不想更改跟蹤和日誌記錄設計。

4.你將保持跟蹤和日誌記錄的分離和易用。

5.可部署的程式不會託管在Linux中。

目標讀者是那些在.NET Framework編程方面有豐富經驗的程式設計師,這裡討論的知識是 .NET Core和.NET Framework 。

在.NET生態系統中使用特定的技術堆棧會產生一些困惑,因為有很多選擇,比如應該使用哪種類型的運行時?在這篇文章中,我們將試圖把這些要點都說清楚。

在.NET Core中,默認的跟蹤和日誌被升級為 ILogger<T>,相應的日誌記錄器對象被通過 .NET Core的依賴注入實例化。ILogger<T> 可以與System.Diagnostics.TraceSource比較。ILoggerProvider對象可以與System.Diagnostics.TraceListener比較。

在.NET Core上為一個複雜的應用程式編程期間,我發現關於在.NET Core中使用Trace和TraceSource的文章很少,而幾乎所有的文章和示例都是關於 ILogger<T>的。我可以通過Google找到如何使用ILogger並立即注入Program,Startup和控制器,而我一直在尋找例子使ILogger組件遠離項目。

Logger

接下來的程式碼示例包含多個項目,每個項目代表一個簡單的場景和一個技術解決方案。

日誌記錄不包含在.NET Core運行時和啟動邏輯中,也不包含在控制台應用程式的腳手架程式碼中。為了使用日誌,Microsoft.Extensions.Logging是必不可少的。

但是,僅此包不足以將日誌記錄到控制台。相反,使用Microsoft.Extensions.Logging.Console:

這個package包括Microsoft.Extensions.Logging。此外,通常你不會在程式碼中切換編碼,而是在配置文件中,通常在.NET Core中,使用的是JSON配置文件,因此你需要Microsoft.Extensions.Configuration.Json。

LoggerFactory

程式碼示例:ConsoleAppLoggerFactory

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace ConsoleAppLoggerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", false, true)
                                .Build();

            using var loggerFactory = LoggerFactory.Create(
                builder =>
                    {
                        builder.AddConfiguration(configuration.GetSection("Logging"));
                        builder.AddConsole();
                    }
            );

            var logger = loggerFactory.CreateLogger<Program>();
            logger.LogInformation("1111logger information"); //settings in appsettings.json filters this out
            logger.LogWarning("2222logger warning");

            var fooLogger = loggerFactory.CreateLogger<FooService>();
            IFooService fooService = new FooService(fooLogger);
            fooService.DoWork();
        }
    }

    public interface IFooService
    {
        void DoWork();
    }

    public class FooService : IFooService
    {
        private readonly ILogger logger;

        public FooService(ILogger<FooService> logger)
        {
            this.logger = logger;
        }

        public void DoWork()
        {
            logger.LogInformation("3333Doing work.");
            logger.LogWarning("4444Something warning");
            logger.LogCritical("5555Something critical");
        }
    }
}

appsettings.json

{
    "Logging": {
        "Console": {
            "disableColors": false
        },

        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Information",
            "ConsoleAppLoggerDemo.FooService": "Warning",
            "ConsoleAppLoggerDemo.Program": "warning"
        }
    }
}

這對於非常簡單的場景來說已經足夠好了,但是,在複雜的應用程式中,你可能想要利用.net Core中內置的依賴注入,如下所述。

向ServiceCollection注入Logger

程式碼示例:ConsoleAppAddLogging

在ServiceCollection中構建並註冊了ILoggerFactory的一個實例,因此該工廠隨後通過以下兩種方式創建 ILogger<Program>:

serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>();

或者

serviceProvider.GetService<ILogger<Program>>();

當FooService通過依賴注入實例化時,定義的ILogger<FooService>被實例化和注入。

using Microsoft.Extensions.Logging;
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
​
namespace ConsoleAppLoggerDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World! from console");
​
            var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json", false, true)
                                .Build();
​
​
            using var serviceProvider = new ServiceCollection()
                .AddSingleton<IFooService, FooService>()
                .AddLogging(builder =>
                {
                    builder.AddConfiguration(configuration.GetSection("Logging"));
                    builder.AddConsole();
                })
                .BuildServiceProvider();
​
​
            ILogger<Program> logger = serviceProvider.GetService<ILogger<Program>>();
            //logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>(); // Factory first. This works too.
​
            IFooService fooService = serviceProvider.GetService<IFooService>();
​
​
            logger.LogInformation("1111logger information");
            logger.LogWarning("2222logger warning");
​
            fooService.DoWork();
        }
​
​
    }
​
    public interface IFooService
    ...
}

Trace 和 TraceSource

System.Diagnostics.Trace 和 System.Diagnostics.TraceSource是為分離跟蹤和日誌而設計的,通過附加的跟蹤監聽器實現日誌記錄。

程式碼示例:ConsoleAppTraceListener

. net Framework中許多TraceListener派生類在. net Core上都是可用的,但是像IisTraceListener這樣的東西在. net Core上是不可用的。

在. net Framework中,應用程式可以初始化Trace和TraceSource,並在執行應用程式程式碼的第一行之前,通過app.config實例化並載入跟蹤偵聽器。

在.net Core中,你仍然可以使用各種跟蹤監聽器,比如ConsoleTraceListener,但是,因為. net Core默認情況下不會載入配置文件,而且內置配置不會關心跟蹤監聽器。

using (var listener = new TextWriterTraceListener("c:\\temp\\mylog.txt"))
using (var consoleListener = new ConsoleTraceListener())
{
    Trace.Listeners.Add(listener);
    Trace.Listeners.Add(consoleListener);
}

因此,你必須在應用程式啟動程式碼中實例化跟蹤偵聽器並初始化Trace和TraceSources對象。還不算太壞,不過事情會繼續發展,越來越多的第三方組件可能會使用ILogger<T>介面用於跟蹤和日誌記錄。考慮到各種因素和權衡,最好在TraceSource和ILogger<T>之間建立一座橋樑,這樣使用Trace和TraceSource的遺留組件可以向ILogger<T>發送跟蹤消息。

LoggerTraceListener

這是一個輔助TraceListener,可以將Trace和TraceSource連接到ILogger。

程式碼示例:ConsoleAppTrace

由於Trace和TraceSource只有偵聽器與日誌記錄介面,因此這裡有LoggerTraceListener來偵聽跟蹤並寫入ILogger<T>,它最終將跟蹤發送給記錄器提供程式。

public class LoggerTraceListener : TraceListener
{
    private readonly ILogger logger;
​
    public LoggerTraceListener(ILogger logger)
    {
        this.logger = logger;
    }
​
    public override void Write(string message)
    {
        logger.LogInformation(message);
    }
​
    public override void WriteLine(string message)
    {
        logger.LogInformation(message);
    }
​
    public override void WriteLine(string message, string category)
    {
        logger.LogInformation(category + ": " + message);
    }
​
    public override void TraceEvent
           (TraceEventCache eventCache, string source, TraceEventType eventType, int id)
    {
        switch (eventType)
        {
            case TraceEventType.Critical:
                logger.LogCritical(id, source);
                break;
            case TraceEventType.Error:
                logger.LogError(id, source);
                break;
            case TraceEventType.Warning:
                logger.LogWarning(id, source);
                break;
            case TraceEventType.Information:
                logger.LogInformation(id, source);
                break;
            case TraceEventType.Verbose:
                logger.LogTrace(id, source);
                break;
            case TraceEventType.Start:
                logger.LogInformation(id, "Start: " + source);
                break;
            case TraceEventType.Stop:
                logger.LogInformation(id, "Stop: " + source);
                break;
            case TraceEventType.Suspend:
                logger.LogInformation(id, "Suspend: " + source);
                break;
            case TraceEventType.Resume:
                logger.LogInformation(id, "Resume: " + source);
                break;
            case TraceEventType.Transfer:
                logger.LogInformation(id, "Transfer: " + source);
                break;
            default:
                throw new InvalidOperationException("Impossible");
        }
}

 Startup:

using var serviceProvider = new ServiceCollection()
        .AddSingleton<IFooService, FooService>()
        .AddLogging(builder =>
        {
            builder.AddConfiguration(configuration.GetSection("Logging"));
            builder.AddConsole();
        })
        .BuildServiceProvider();
​
ILogger<Program> logger = serviceProvider.GetService<ILogger<Program>>();
IFooService fooService = serviceProvider.GetService<IFooService>();
logger.LogInformation("1111logger information");
logger.LogWarning("2222logger warning");
fooService.DoWork();
using (var listener = new LoggerTraceListener(logger))
{
    System.Diagnostics.Trace.Listeners.Add(listener);
    TraceSources.Instance.InitLoggerTraceListener(listener);
    TraceLover.DoSomething();
    TraceSourceLover.DoSomething();
}

現在,Trace和TraceSource可以使用什麼日誌媒體是由連接到ILogger的日誌程式提供程式決定的。

程式碼示例:ConsoleappSeriLog

微軟已經在.net Core中開發了一些具體的日誌提供程式,這可能是基於業務願景和設計。

有相當多的第三方日誌提供商:

· NLog

· Log4net

· Serilog

我認為Serilog 是最好的。

var configuration = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", false, true)
        .Build();
​
Serilog.Log.Logger = new Serilog.LoggerConfiguration()
        .Enrich.FromLogContext()
        .ReadFrom.Configuration(configuration)
        .CreateLogger();
​
using var serviceProvider = new ServiceCollection()
    .AddLogging(builder => builder.AddSerilog())
    .AddSingleton<IFooService, FooService>()
    .BuildServiceProvider();
​
var logger = serviceProvider.GetService<ILogger<Program>>();
var fooService = serviceProvider.GetService<IFooService>();
​
try
{
    Log.Information("Starting up");
    logger.LogInformation("1111logger information");
    logger.LogWarning("2222logger warning");
​
    fooService.DoWork();
​
    using (var listener = new LoggerTraceListener(logger))
    {
        System.Diagnostics.Trace.Listeners.Add(listener);
        TraceSources.Instance.InitLoggerTraceListener(listener);
​
        TraceLover.DoSomething();
        TraceSourceLover.DoSomething();
    }
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application start-up failed");
}
finally
{
    Log.CloseAndFlush();
}

appsettings.json:

{
    "TraceSource": {
        "WebApi": {
            "SourceLevels": "Information"
        },
​
        "HouseKeeping": { "SourceLevels": "Warning" },
​
        "DbAudit": {
            "SourceLevels": "Warning"
        }
    },
​
    "Serilog": {
        "MinimumLevel": {
            "Default": "Debug",
            "Override": {
                "Microsoft": "Information",
                "System": "Warning",
                "ConsoleAppLoggerDemo.FooService": "Warning",
                "ConsoleAppLoggerDemo.Program": "Warning"
            }
        },
​
        "WriteTo": [
            {
                "Name": "Console"
            },
​
            {
                "Name": "File",
                "Args": {
                    "path": "%PROGRAMDATA%/my/logs/CloudPosApi_Test.log",
                    "outputTemplate": "{Timestamp:MM-dd HH:mm:ss.fff zzz} 
                           [{Level}] {ThreadId} {Message}{NewLine}{Exception}",
                    "rollingInterval": "Day"
                }
            }
        ]
    }
}

在.NET Framework中,運行時將載入app.config並在執行應用程式程式碼的第一行之前將設置應用於一些內置組件。一些其他組件,如SMTPClient和System.Diagnostics組件默認情況下將讀取app.config。

在.NET Core上,在程式碼中或通過載入配置文件進行配置是應用程式程式設計師的職責。

 

歡迎關注我的公眾號——碼農譯站,如果你有喜歡的外文技術文章,可以通過公眾號留言推薦給我。

原文鏈接://www.codeproject.com/Articles/5255953/Use-Trace-and-TraceSource-in-NET-Core-Logging