向 Service Fabric 应用程序添加日志记录Add logging to your Service Fabric application

应用程序必须提供足够的信息,以便在出现问题时对其进行取证式的调试。Your application must provide enough information to forensically debug it when problems arise. 日志记录是可以添加到 Service Fabric 应用程序的最重要功能之一。Logging is one of the most important things you can add to your Service Fabric application. 发生故障时,正确的日志记录可以为调查故障提供有效方法。When a failure occurs, good logging can give you a way to investigate failures. 通过分析日志模式,可以找到提高应用程序的性能或设计的方法。By analyzing log patterns, you may find ways to improve the performance or design of your application. 本文档演示几个不同的日志记录选项。This document demonstrates a few different logging options.

EventFlowEventFlow

EventFlow 库套件允许应用程序定义要收集哪些诊断数据,以及应将结果输出到的位置。The EventFlow library suite allows applications to define what diagnostics data to collect, and where they should be outputted to. 诊断数据可以是从性能计数器到应用程序跟踪的任何内容。Diagnostics data can be anything from performance counters to application traces. 它与应用程序在同一进程中运行,因此可以最大程度地减少通信开销。It runs in the same process as the application, so communication overhead is minimized. 有关 EventFlow 和 Service Fabric 的详细信息,请参阅使用 EventFlow 进行Azure Service Fabric 事件聚合For more information about EventFlow and Service Fabric, see Azure Service Fabric Event Aggregation with EventFlow.

使用结构化 EventSource 事件Using structured EventSource events

按用例定义消息事件可以在事件上下文中为有关事件的数据打包。Defining message events by use case allows you to package data about the event, in the context of the event. 还可以根据指定事件属性的名称或值更轻松地进行搜索和筛选。You can more easily search and filter based on the names or values of the specified event properties. 将检测输出结构化可使其变得更易于读取,但需要花费更多的精力和时间来为每个用例定义事件。Structuring the instrumentation output makes it easier to read, but requires more thought and time to define an event for each use case.

某些事件定义可在整个应用程序中共享。Some event definitions can be shared across the entire application. 例如,可在应用程序中的多个服务之间重复使用方法启动或停止事件。For example, a method start or stop event would be reused across many services within an application. 特定于域的服务(例如订单系统)可以包含 CreateOrder 事件,该事件包含自身的唯一事件。A domain-specific service, like an order system, might have a CreateOrder event, which has its own unique event. 这种方法可能会生成大量的事件,并可能需要在项目团队之间协调标识符。This approach might generate many events, and potentially require coordination of identifiers across project teams.

[EventSource(Name = "MyCompany-VotingState-VotingStateService")]
internal sealed class ServiceEventSource : EventSource
{
    public static readonly ServiceEventSource Current = new ServiceEventSource();

    // The instance constructor is private to enforce singleton semantics.
    private ServiceEventSource() : base() { }

    ...

    // The ServiceTypeRegistered event contains a unique identifier, an event attribute that defined the event, and the code implementation of the event.
    private const int ServiceTypeRegisteredEventId = 3;
    [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)]
    public void ServiceTypeRegistered(int hostProcessId, string serviceType)
    {
        WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
    }

    // The ServiceHostInitializationFailed event contains a unique identifier, an event attribute that defined the event, and the code implementation of the event.
    private const int ServiceHostInitializationFailedEventId = 4;
    [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)]
    public void ServiceHostInitializationFailed(string exception)
    {
        WriteEvent(ServiceHostInitializationFailedEventId, exception);
    }

    ...

一般情况下使用 EventSourceUsing EventSource generically

由于特定的事件可能难以定义,许多用户都会使用一组常用的参数来定义少量的事件,这些参数通常以字符串的形式输出其信息。Because defining specific events can be difficult, many people define a few events with a common set of parameters that generally output their information as a string. 因此,大部分结构化信息已丢失,使结果搜索和筛选变得更加困难。Much of the structured aspect is lost, and it's more difficult to search and filter the results. 使用此方法时,会定义一些通常对应于日志记录级别的事件。In this approach, a few events that usually correspond to the logging levels are defined. 以下代码片段定义调试和错误消息:The following snippet defines a debug and error message:

[EventSource(Name = "MyCompany-VotingState-VotingStateService")]
internal sealed class ServiceEventSource : EventSource
{
    public static readonly ServiceEventSource Current = new ServiceEventSource();

    // The Instance constructor is private, to enforce singleton semantics.
    private ServiceEventSource() : base() { }

    ...

    private const int DebugEventId = 10;
    [Event(DebugEventId, Level = EventLevel.Verbose, Message = "{0}")]
    public void Debug(string msg)
    {
        WriteEvent(DebugEventId, msg);
    }

    private const int ErrorEventId = 11;
    [Event(ErrorEventId, Level = EventLevel.Error, Message = "Error: {0} - {1}")]
    public void Error(string error, string msg)
    {
        WriteEvent(ErrorEventId, error, msg);
    }

    ...

也可以混合使用结构化检测和常规检测方法。Using a hybrid of structured and generic instrumentation also can work well. 结构化检测用于报告错误和指标。Structured instrumentation is used for reporting errors and metrics. 常规事件可用于详细日志记录,工程师在排除故障时可以使用这些日志信息。Generic events can be used for the detailed logging that is consumed by engineers for troubleshooting.

Microsoft.Extensions.LoggingMicrosoft.Extensions.Logging

ASP.NET Core 日志记录(Microsoft.Extensions.Logging NuGet 包)是一个日志记录框架,可为应用程序提供标准日志记录 API。The ASP.NET Core logging (Microsoft.Extensions.Logging NuGet package) is a logging framework that provides a standard logging API for your application. 可以将对其他日志记录后端的支持插入到 ASP.NET Core 日志记录。Support for other logging backends can be plugged into ASP.NET Core logging. 这可以为处理应用程序中的日志记录提供各种支持,而无需更改大量代码。This gives you a wide variety of support for logging in your application is processed, without having to change much code.

  1. Microsoft.Extensions.Logging NuGet 包添加到要检测的项目。Add the Microsoft.Extensions.Logging NuGet package to the project you want to instrument. 此外,还添加任何提供程序包。Also, add any provider packages. 有关详细信息,请参阅 Logging in ASP.NET Core(ASP.NET Core 中的日志记录)。For more information, see Logging in ASP.NET Core.

  2. 在服务文件中添加一条针对 Microsoft.Extensions.Loggingusing 指令。Add a using directive for Microsoft.Extensions.Logging to your service file.

  3. 在服务类中定义一个专用变量。Define a private variable within your service class.

    private ILogger _logger = null;
    
  4. 在服务类的构造函数中添加此代码:In the constructor of your service class, add this code:

    _logger = new LoggerFactory().CreateLogger<Stateless>();
    
  5. 开始检测方法中的代码。Start instrumenting your code in your methods. 下面提供了几个示例:Here are a few samples:

    _logger.LogDebug("Debug-level event from Microsoft.Logging");
    _logger.LogInformation("Informational-level event from Microsoft.Logging");
    
    // In this variant, we're adding structured properties RequestName and Duration, which have values MyRequest and the duration of the request.
    // Later in the article, we discuss why this step is useful.
    _logger.LogInformation("{RequestName} {Duration}", "MyRequest", requestDuration);
    

使用其他日志记录提供程序Using other logging providers

某些第三方提供程序(包括 SerilogNLogLoggr)可以使用上一部分所述的方法。Some third-party providers use the approach described in the preceding section, including Serilog, NLog, and Loggr. 可将其中的每个提供程序插入 ASP.NET Core 日志记录,也可以单独使用。You can plug each of these into ASP.NET Core logging, or you can use them separately. Serilog 中的某个功能可以扩充记录器发出的所有消息。Serilog has a feature that enriches all messages sent from a logger. 此功能对服务名称、类型和分区信息的输出可能很有作用。This feature can be useful to output the service name, type, and partition information. 若要在 ASP.NET Core 基础结构中使用此功能,请执行以下步骤:To use this capability in the ASP.NET Core infrastructure, do these steps:

  1. SerilogSerilog.Extensions.LoggingSerilog.Sinks.LiterateSerilog.Sinks.Observable NuGet 包添加到项目。Add the Serilog, Serilog.Extensions.Logging, Serilog.Sinks.Literate, and Serilog.Sinks.Observable NuGet packages to the project.

  2. 创建 LoggerConfiguration 和记录器实例。Create a LoggerConfiguration and the logger instance.

    Log.Logger = new LoggerConfiguration().WriteTo.LiterateConsole().CreateLogger();
    
  3. Serilog.ILogger 参数添加到服务构造函数并传递新建的记录器。Add a Serilog.ILogger argument to the service constructor, and pass the newly created logger.

    ServiceRuntime.RegisterServiceAsync("StatelessType", context => new Stateless(context, Log.Logger)).GetAwaiter().GetResult();
    
  4. 在服务构造函数中,为 ServiceTypeNameServiceNamePartitionIdInstanceId 创建属性扩充器。In the service constructor, creates property enrichers for ServiceTypeName, ServiceName, PartitionId, and InstanceId.

    public Stateless(StatelessServiceContext context, Serilog.ILogger serilog)
       : base(context)
    {
       PropertyEnricher[] properties = new PropertyEnricher[]
       {
           new PropertyEnricher("ServiceTypeName", context.ServiceTypeName),
           new PropertyEnricher("ServiceName", context.ServiceName),
           new PropertyEnricher("PartitionId", context.PartitionId),
           new PropertyEnricher("InstanceId", context.ReplicaOrInstanceId),
       };
    
       serilog.ForContext(properties);
    
       _logger = new LoggerFactory().AddSerilog(serilog.ForContext(properties)).CreateLogger<Stateless>();
    }
    
  5. 可以像在不使用 Serilog 的情况下运行 ASP.NET Core 一样检测代码。Instrument the code the same as if you were using ASP.NET Core without Serilog.

    备注

    建议不要在前面的示例中使用静态 Log.LoggerWe recommend that you don't use the static Log.Logger with the preceding example. Service Fabric 可在单个进程中托管同一服务类型的多个实例。Service Fabric can host multiple instances of the same service type within a single process. 如果使用静态 Log.Logger,属性扩充器的最后一个写入者会显示所有正在运行的实例的值。If you use the static Log.Logger, the last writer of the property enrichers will show values for all instances that are running. 这是 _logger 变量为何是服务类的专用成员变量的原因之一。This is one reason why the _logger variable is a private member variable of the service class. 另外,必须将 _logger 提供给可跨服务使用的通用代码使用。Also, you must make the _logger available to common code, which might be used across services.

后续步骤Next steps