翻译 - ASP.NET Core 基本知识 - 日志(Logging)

翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0

 本主题描述了ASP.NET Core 应用程序中应用到的 .NET 中的日志。关于 .NET 中日志的详细信息,查看 Logging in .NET。更多关于 Blazor 应用程序日志,查看 ASP.NET Core Blazor logging

日志提供器

日志提供器存储日志,除了控制台提供器是用来显示日志的。例如,Azure 应用程序 Insights 提供器在 Azure Application Insights 中存储日志。

默认的 ASP.NET Core web 应用程序模板:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

上面的代码展示了使用 ASP.NET Core web 应用程序模板创建的 Program 类。接下来的几个部分提供了基于 ASP.NET Core web 应用程序模板的示例,它们都使用了通用主机(Generic Host)。无主机的控制台应用程序(Non-host console apps)在本文档的之后讨论。

为了覆盖 Host.CreateDefaultBuilder 添加的默认的日志提供器集合,需要调用 ClearProviders 并且添加需求的日志提供器。例如下面的代码:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging(logging =>
        {
            logging.ClearProviders();
            logging.AddConsole();
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

更多的日志提供器,查看:

创建日志

创建日志可以使用从依赖注入(dependency injection (DI))中获取  ILogger<TCategoryName> 对象。

下面的例子:

  • 创建了一个类型为 ILogger<AboutModel> 的 logger,使用了一个完全限定的类型名称 AboutModel 的类型
  • 调用 LogInformation 以 Information 级别输出日志。日志级别表明了日志事件的严重程度
public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Levels 和 categories 之后会在本文档的有更详细的解释。

关于 Blazor 的信息,查看 ASP.NET Core Blazor logging

Create logs in Main and Startup 展示了如何在 Main 和 Startup 中创建日志。

配置日志

日志的配置通常由 appsettings.{Environment}.json 文件的 Logging 部分提供。下面的 appsettings.Development.json 文件是由 ASP.NET Core web 应用程序模板生成的:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

在上面的 JSON 中:

  • 指定了 "Default","Microsoft" 和 “Microsoft.Hosting.Lifetime” 类别
  • "Microsoft" 类别会应用到所有以 "Microsoft" 开头的类别中。例如,这里的设置会应用到 "Microsoft.AspNetCore.Routing.EndpointMiddleware" 类别
  • "Microsoft" 类别日志的级别是 Warning 以及更高级别
  • "Microsoft.Hosting.Lifetime" 类别要比 "Microsoft" 更加具体,所以 "Micriosoft.Hosting.Lifttime" 类别的日志等级是 "Information" 以及更高
  • 没有指定具体的日志提供器,因此 LogLevel 会应用到除了 Windows EventLog 之外的所有使能的日志提供器

Logging 属性可以有日志等级(LogLevel)和日志提供器属性。LogLevel 为选定的类别指定了最小的日志等级(level)。在上面的 JSON 中,指定了 Information 和 Warning 日志等级。LogLevel 表明了日志的严重程度,范围是 0 - 6。

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 和 None = 6。

当指定一个日志级别 (LogLevel) 时,为消息开启的日志的级别处在指定的级别以及更高。在前面的 JSON 中,默认的类别会以 Information 以及更高级别记录日志。例如,Information,Warning,Error 以及 Critical 消息会被记录。更多信息,查看日志级别(Log levels)。

一个提供器属性可以指定一个 LogLevel 属性。一个提供器下面的 LogLevel 指定该提供器日志记录的级别,并且会覆盖无提供器日志设置。考虑下面的 appsettings.json 文件:

{
  "Logging": {
    "LogLevel": { // All providers, LogLevel applies to all the enabled providers.
      "Default": "Error", // Default logging, Error and higher.
      "Microsoft": "Warning" // All Microsoft* categories, Warning and higher.
    },
    "Debug": { // Debug provider.
      "LogLevel": {
        "Default": "Information", // Overrides preceding LogLevel:Default setting.
        "Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category.
      }
    },
    "EventSource": { // EventSource provider
      "LogLevel": {
        "Default": "Warning" // All categories of EventSource provider.
      }
    }
  }
}

Logging.{providername}.logLevel 中的设置覆盖了 Logging.LogLevel 中的设置。在上面的 JSON 中,Debug 提供器的默认日志记录级别被设置为 Information:

Logging:Debug:LogLevel:Default:Information

上面的设置为每一个 Logging:Debug: 类别指定了 Information 日志级别,除了 Micriosoft:Hosting。当一个特定的分类被列出来的时候,特定分类的设置会覆盖默认分类的设置。在上面的 JSON 中,Logging:Debug:LogLevel 分类中的 "Microsoft:Hosting" 和 "Default" 覆盖了 Logging:LogLevel 中的设置。

最小的日志级别可以被指定为以下任意一个:

  • 指定的提供器:例如,Logging:EventSource:LogLevel:Default:Information
  • 指定的类别:例如,Logging:LogLevel:Microsoft:Warning
  • 所有的提供器和所有的类别:Logging:LogLevel:Default:Warning

 任何在最小级别之下的记录不会是:

  • 传递给提供器的
  • 记录或者显示出来的

如果要废止所有的日志记录,指定 LogLevel.None。LogLevel.None 的值是 6,拥有比 LogLevel.Critical(5) 更高的级别。

如果一个提供器支持 log scopes,IncludeScopes 用来指明它们是否开启。更多信息,查看 log scopes

下面的 appsettings.json 文件包含了默认开启的所有的提供器:

{
  "Logging": {
    "LogLevel": { // No provider, LogLevel applies to all the enabled providers.
      "Default": "Error",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Warning"
    },
    "Debug": { // Debug provider.
      "LogLevel": {
        "Default": "Information" // Overrides preceding LogLevel:Default setting.
      }
    },
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
        "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
        "Microsoft.AspNetCore.Mvc.Razor": "Error",
        "Default": "Information"
      }
    },
    "EventSource": {
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "EventLog": {
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "AzureAppServicesFile": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "AzureAppServicesBlob": {
      "IncludeScopes": true,
      "LogLevel": {
        "Microsoft": "Information"
      }
    },
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}

在上面的例子中:

  • 类别和级别都不是推荐的值。这个例子是用来展示所有默认提供器的
  • Logging.{providername}.LogLevel 中的设置会覆盖 Logging.LogLevel 中的设置。例如,Debug.LogLevel.Default 的级别会覆盖 LogLevel.Default 的级别
  • 每一个默认提供器都使用了别名。每一个提供器定义了一个别名,可以在配置中替代完全限定的类型名称。内置的提供器别名是:
    Console
    Debug
    EventSource
    AzureAppServicesFile
    AzureAppServicesBlob
    ApplicationInsights

通过命令行,环境变量和其它配置设置日志级别

日志级别可以通过任何的配置提供器(configuration providers)设置。

冒号 (:) 分隔符对于分层的环境变量键值上并不是在所有的平台上都能正常工作的。双下划线 (__):

  • 所有平台都支持。例如,冒号(:)分隔符不被 Bash(Bash) 支持,但是双下划线(__)受支持
  • 自动的会被一个冒号 (:) 替换

下面的命令:

  • 在 Windows 上设置环境键值 Logging:LogLevel:Microsoft 的值为 Information
  • 当使用 ASP.NET Core web 应用程序模板创建应用程序时用来测试设置。 dotnet run 命令必须在项目目录中在使用 set 之后运行
set Logging__LogLevel__Microsoft=Information
dotnet run

上面的环境设置:

  • 仅仅设置在它们被设置的命令窗口启动的进程中起作用
  • 不会被使用 Visual Studio 启动的浏览器读取到

下面的 setx 命令也可以在 Windows 上设置环境键和值。不像 set,setx 设置是持久的。 /M 开关在系统环境中设置变量。如果没有使用 /M,将会设置为用户环境变量。

setx Logging__LogLevel__Microsoft=Information /M

在  Azure App Service 中,在 Settings > Configuration 页面,选择 New application setting。Azure App Service 应用程序设置:

  • 加密存储并且通过加密信道传播
  • 暴露为环境变量

更多信息,查看 Azure Apps: Override app configuration using the Azure Portal

更多关于使用环境变量设置 ASP.NET Core 配置的信息,查看环境变量(environment variables)。关于使用其它配置源,包括命令行,Azure Key Vault,Azure App Congfiguration,其它文件格式,以及更多的信息,查看 ASP.NET Core 中的配置(Configuration in ASP.NET Core)。

筛选规则是如何应用的

当一个 ILogger<TCategoryName> 对象被创建时,ILoggerFactory  会选择一个单一规则提供器应用到这个日志对象。所有由 ILogger 实例写入的消息都会基于选择的规则过滤。每个提供器和类别对的具体规则是从变量规则中选择的。

当为一个给定的类别创建一个 ILoger 对象时,下面的算法会被用于每一个提供器:

  • 选择所有匹配提供器或者它的别名的规则。如果没有匹配的项,使用一个空的提供器选择所有的规则
  • 根据上一步骤的结果,选择最长匹配类别前缀的规则。如果没有匹配的项,选择所有没有指定类别的规则
  • 如果多个规则被选中,选取最后一个
  • 如果没有规则被选中,使用 MinimumLevel

dotnet run 和 Visual Studio 的日志输出

使用 default logging providers 创建的日志会被输出:

  • 在 Visual Studio 中
    在调试是,在 Debug output 窗口中输出
    在 ASP.NET Core Web 服务器窗口中
  • 当使用 dotnet run 运行应用程序时,在控制台窗口中输出

开头为 "Microsoft" 类别的日志是从 ASP.NET Core 框架代码中输出的日志。ASP.NET Core 和应用程序代码使用相同的日志 API 和 提供器。

日志类别

当一个 ILogger 对象被创建时,会指定一个类别。类别会包含在 ILogger 实例创建的每一条日志消息。类别字符串可以是任意的,但是一般约定使用类名。例如,在一个控制器中,名称可能是 "TodoApi.Controllers.TodoController"。ASP.NET Core web 应用程序会使用 ILogger<T> 自动获取类型 T 的一个使用完整限定类型名作为类别的 ILogger 实例:

public class PrivacyModel : PageModel
{
    private readonly ILogger<PrivacyModel> _logger;

    public PrivacyModel(ILogger<PrivacyModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        _logger.LogInformation("GET Pages.PrivacyModel called.");
    }
}

如果要显式指定类别,可以调用 ILoggerFactory.CreateLogger:

public class ContactModel : PageModel
{
    private readonly ILogger _logger;

    public ContactModel(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("MyCategory");
    }

    public void OnGet()
    {
        _logger.LogInformation("GET Pages.ContactModel called.");
    }

当在多个方法中使用一个固定名称调用 CreateLogger 会非常有用,可以通过类别来组织事件。

ILogger<T> 和使用 T 类型的完整限定类型名是等价的。

日志等级

下面的表格列出了日志等级(LogLevel)的值,约定的 Log{LogLevel} 扩展方法和推荐的用法:

LogLevel Value Method Description
Trace 0 LogTrace 包含最详细的信息。这些信息可能包含敏感的应用程序数据。默认的这些信息是被关闭掉的,不应该在生产环境中打开。
Debug 1 LogDebug 用来调试和开发。由于信息量大,在生产环境中应该谨慎使用。
Information 2 LogInformation 跟踪应用程序的一般运行流。可能拥有长期使用价值。
Warning 3 LogWarning 用于非正常或者异常事件。一般包括那些不会引起应用程序失败的错误或者条件。
Error 4 LogError 用于不能处理的错误和异常。这些信息表明在当前操作或者请求中的失败,并不是应用程序范围的失败。
Critical 5 LogCritical 需要立即关注的失败。例如,丢失数据的情况,超出硬盘空间的错误。
None 6   指定不应该输出任何信息的日志类别。

在上面的表格中, LogLevel 以严重程度从低到高的顺序列出。

Log 方法的第一个参数是 LogLevel,指定日志的严重程度。大多数的开发者会调用 Log{LogLevel} 扩展方法,而不是调用 Log(LogLevel, ...)。Log{Level} 扩展方法会调用 Log 方法,并指定 LogLevel。例如,下面两个日志调用功能是相同的并且生成相同的日志:

[HttpGet]
public IActionResult Test1(int id)
{
    var routeInfo = ControllerContext.ToCtxString(id);

    _logger.Log(LogLevel.Information, MyLogEvents.TestItem, routeInfo);
    _logger.LogInformation(MyLogEvents.TestItem, routeInfo);

    return ControllerContext.MyDisplayRouteInfo();
}

MyLogEvents.TestItem 是事件的 ID。MyLogEvents 是示例程序的一部分,被输出到 Log event ID 对应的部分。

MyDisplayRouteInfo and ToCtxString 由 NuGet 包 Rick.Docs.Samples.RouteInfo 提供。这个方法显示 Controller 路由信息。

下面的代码创建 Information 和 Warning 日志信息:

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

在前面的代码中,第一个 Log{LogLevel} 的参数,MyLogEvents.GetItem 是 Log event ID。第二个参数是一个带有由方法剩余参数提供参数值的占位符。方法参数在该文档之后的消息模板(message template)部分会解释。

调用合适的 Log{LogLevel} 方法控制写入到特定存储媒介的多少。例如:

  • 生产环境
    Trace 或者 Information 级别的日志会生成大量的详细的日志信息。为了控制花费和不超出数据存储限制,使用 Trace 和 Information 级别的消息记录大量的信息到低花费的数据存储中。考虑限制 Trace 和 Information 到指定的类别。
    Warning 到 Critical 级别的日志应该生成少量的信息
    花费和存储限制通常不用担心
    较少的日志允许在数据存储上有更灵活的选择
  • 开发环境
    设置为 Warning
    当解决困难时,添加 Trace 或者 Information 信息。为了限制输出,设置 Trace 和 Information 仅限于在调查研究的类别。

ASP.NET Core 为框架事件输出日志。例如,考虑日志输出:

  • 使用 ASP.NET Core 模板创建的 Razor Pages 应用程序
  • 日志设置为 Logging:Console:LogLevel:Microsoft:Information
  • 导航到隐私页面
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 GET https://localhost:5001/Privacy
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint '/Privacy'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
      Route matched with {page = "/Privacy"}. Executing page /Privacy
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[101]
      Executing handler method DefaultRP.Pages.PrivacyModel.OnGet - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[102]
      Executed handler method OnGet, returned result .
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
      Executing an implicit handler method - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
      Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
      Executed page /Privacy in 74.5188ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint '/Privacy'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 149.3023ms 200 text/html; charset=utf-8

下面的 JSON 设置了 Logging:Console:LegLevel:Microsoft:Information:

{
  "Logging": {      // Default, all providers.
    "LogLevel": {
      "Microsoft": "Warning"
    },
    "Console": { // Console provider.
      "LogLevel": {
        "Microsoft": "Information"
      }
    }
  }
}

Log event ID

每一个日志都可以指定一个 event ID,示例应用程序使用 MyLogEvents 类定义了 event IDs:

public class MyLogEvents
{
    public const int GenerateItems = 1000;
    public const int ListItems     = 1001;
    public const int GetItem       = 1002;
    public const int InsertItem    = 1003;
    public const int UpdateItem    = 1004;
    public const int DeleteItem    = 1005;

    public const int TestItem      = 3000;

    public const int GetItemNotFound    = 4000;
    public const int UpdateItemNotFound = 4001;
}
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

一个 event ID 关联一组事件。例如,所有和在页面上显示一列项目相关的日志可能都是 1001。

日志提供器可能把 event ID 存储在 ID 字段中,在日志信息中,或者不存储。Debug 提供器不会显示 event IDs。Console 提供器会在类别之后的方括号中显示 event IDs:

info: TodoApi.Controllers.TodoItemsController[1002]
      Getting item 1
warn: TodoApi.Controllers.TodoItemsController[4000]
      Get(1) NOT FOUND

日志信息模板

每一个 log API 都使用一个信息模板。信息模板可以包含为参数提供的占位符。站位符使用的是名称,而不是数字:

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id);
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

决定哪个参数用于日志信息站位符的值的是参数的顺序,而不是占位符的名称。在下面的代码中,参数名称和信息模板占位符的顺序是不相同的:

string apples = 1;
string pears = 2;
string bananas = 3;

_logger.LogInformation("Parameters: {pears}, {bananas}, {apples}", apples, pears, bananas);

然而,参数被赋值到占位符的顺序是:apples,pears,bananas。日志消息反映了参数的顺序:

Parameters: 1, 2, 3

这种方式允许日志提供器实现 semantic or structured logging。参数本身被传递给日志系统,而不仅仅是格式化的信息模板。这使得日志提供器存储参数的值为字段。例如,考虑下面的日志方法:

_logger.LogInformation("Getting item {Id} at {RequestTime}", id, DateTime.Now);

例如,当输出日志到 Azure Table Storage 时:

  • 每一个 Azure Table 实体都有 ID 和 RequestTime 属性
  • 带有属性的 Tables 简化了日志数据的查询。例如,一个查询可以查找在一个特定 RequestTime 范围内的所有日志,而不用从文本信息中解析出时间

异常日志

Logger 方法有带有异常参数的重载方法:

[HttpGet("{id}")]
public IActionResult TestExp(int id)
{
    var routeInfo = ControllerContext.ToCtxString(id);
    _logger.LogInformation(MyLogEvents.TestItem, routeInfo);

    try
    {
        if (id == 3)
        {
            throw new Exception("Test exception");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(MyLogEvents.GetItemNotFound, ex, "TestExp({Id})", id);
        return NotFound();
    }

    return ControllerContext.MyDisplayRouteInfo();
}

MyDisplayRouteInfo and ToCtxString 由 NuGet 包 Rick.Docs.Samples.RouteInfo 提供。这个方法用来显示 Controller 路由信息。

异常日志是提供器特有的。

默认日志级别

如果默认日志级别没有设置,默认日志级别的值是 Information。

例如,考虑下面的 web 应用程序:

  • 使用 ASP.NET Core web 应用程序模板创建
  • 删除或者重命名 appsettings.json 和 appsettings.Development.json 文件

设置好上面的步骤,导航到 privacy 或者 home 页面时,会产生很多带有 Micriosoft 类别名称的 Trace,Debug,和 Information 信息。

下面的代码会设置默认的日志级别,当默认的日志级别没有在配置中设置的时候:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

通常的,日志的级别应该在配置中指定,而不是在代码中指定。

Filter function

对于那些没有通过配置或者代码复制规则的所有的提供器和类别,都会调用一个 filter function:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
            {
                logging.AddFilter((provider, category, logLevel) =>
                {
                    if (provider.Contains("ConsoleLoggerProvider")
                        && category.Contains("Controller")
                        && logLevel >= LogLevel.Information)
                    {
                        return true;
                    }
                    else if (provider.Contains("ConsoleLoggerProvider")
                        && category.Contains("Microsoft")
                        && logLevel >= LogLevel.Information)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                });
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

上面的代码在类别包含了 Controller 或者 Microsoft 并且日志级别是 information 或者更高的时候会输出显示控制台日志。

一般的,日志级别应该在配置中指定而不是在代码中。

ASP.NET Core 和 EF Core 类别

下面的表格包含了 ASP.NET Core 和 Entity Framework Core 用到的一些类别,并且添加了关于日志的备注:

Category Notes
Microsoft.AspNetCore 一般的 ASP.NET Core 调试信息
Microsoft.AspNetCore.DataProtection 哪些键被考虑,发现和使用
Microsoft.AspNetCore.HostFiltering 主机允许的
Microsoft.AspNetCore.Hosting HTTP 请求花费了多长时间和它们是什么时候开始的。哪些主机启动的程序集被加载了。
Microsoft.AspNetCore.Routing 路由匹配信息
Microsoft.AspNetCore.Server 连接启动,停止和保持在线响应。HTTPS 证书信息
Microsoft.AspNetCore.StaticFiles 已送达的文件
Microsoft.AspNetCore.Mvc MVC 和 Razor 调试。模型绑定,执行过滤,视图编译,方法选择
Microsoft.EntityFrameworkCore 通常的 Enfity Framework Core 调试。数据库活动和配置,更改检测和迁移

在控制台窗口中浏览更多的类别,appsettings.Development.json 按照以下设置:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Trace",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Log scopes

一个 scope 可以把一些逻辑操作分为一组。这样的分组可以被用于附加一些相同的数据到分组中的每一个日志。例如,每一个日志都作为包含一个事务 ID 的处理事务的一部分被创建。

一个 scope:

以下提供器支持 scope:

 在 using 块中通过包装调用 logger 来使用 scope:

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    TodoItem todoItem;

    using (_logger.BeginScope("using block message"))
    {
        _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

        todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            _logger.LogWarning(MyLogEvents.GetItemNotFound, 
                "Get({Id}) NOT FOUND", id);
            return NotFound();
        }
    }

    return ItemToDTO(todoItem);
}

内置的日志提供器

ASP.NET Core 包含以下日志提供器作为共享框架的一部分:

  • Console
  • Debug
  • EventSource
  • EventLog

下面的日志提供器由 Microsoft 提供维护,但是并不作为共享框架的一部分。它们必须作为额外的 nuget 包安装。

ASP.NET Core 不包含把日志写入文件的日志提供器。如果要把日志写入文件,考虑使用第三方的日志提供器(third-party logging provider)。

关于 stdout 和 使用 ASP.NET Core 模块调试日志,查看 Troubleshoot ASP.NET Core on Azure App Service and IIS 和 ASP.NET Core Module

Console

Console 提供器输出日志到控制台。更多关于在开发中浏览控制台日志的信息,查看 Logging output from dotnet run and Visual Studio

Debug

Debug 通过使用 System.Diagnostics.Debug 类输出日志。调用 System.Diagostics.Debug.WriteLine 写入到 Debug 提供器。

在 Linux 上,Debug 提供器日志的位置是依赖于发行版的,可能是以下之一:

  • /var/log/message
  • /var/log/syslog

Event Source

EventSource 提供器使用名称 Microsoft-Extensions-Logging 写入到一个跨平台的 event source。在 Windows 上,提供器使用 ETW

dotnet 追踪工具

dotnet-trace 工具是一个跨平台的 CLI 全局工具,它能够使得 .NET Core 追踪运行的进程。工具使用 LoggingEventSource 收集 Microsoft.Extensions.Logging.EventSource 提供器数据。

查看 dotnet-trace 关于安装的信息。

  1. 使用 dotnet run 命令运行应用程序
  2. 确定 .NET Core 应用程序进程标识符(PID)
    dotnet trace ps

    查找和应用程序程序集相同名称进程的 PID。

  3. 指定 dotnet trace 命令
    一般命令语法:
    dotnet trace collect -p {PID} 
        --providers Microsoft-Extensions-Logging:{Keyword}:{Provider Level}
            :FilterSpecs=\"
                {Logger Category 1}:{Category Level 1};
                {Logger Category 2}:{Category Level 2};
                ...
                {Logger Category N}:{Category Level N}\"

    当使用 PowerShell 命令行 shell 时,使用单引号引用 --providers 的值:

    dotnet trace collect -p {PID} 
        --providers 'Microsoft-Extensions-Logging:{Keyword}:{Provider Level}
            :FilterSpecs=\"
                {Logger Category 1}:{Category Level 1};
                {Logger Category 2}:{Category Level 2};
                ...
                {Logger Category N}:{Category Level N}\"'
    在非 Windows 平台,添加 -f speedscope 选项改变输出追踪文件的格式为 speedscope.
    下面的表格中定义了关键字:
    Keyword Description
    1 关于 LoggingEventSource 的日志元数据事件。不要从 ILoger 记录事件。
    2 当 ILogger.log() 被调用时,打开 Message 事件。用编程的方式(不是格式化)提供信息。
    4 当 ILogger.log() 被调用时,打开 FormatMessage 事件。提供格式化的信息的字符串版本。
    8 当 ILogger.log() 被调用时,打开 MessageJson 事件。提供一个表示参数的 JSON。

    下面的表格列出了提供器的级别:
    Provider Level Description
    0 LogAlways
    1 Critical
    2 Error
    3 Warning
    4 Information
    5 Verbose

    解析类别级别可以是一个字符串或者数字:
    Category named value Numeric value
    Trace 0
    Debug 1
    Information 2
    Warning 3
    Error 4
    Critical 5

    提供器级别和类别级别:
    - 顺序是相反的
    - 字符串常量并不是全部相同的
    如果没有 FilterSpecs 没有指定,EventSourceLogger 的实现就会试图转换提供器级别到类别级别,并且会应用到所有类别:
    Provider level Category Level
    Verbose(5) Debug(1)
    Informational(4) Information(2)
    Warning(3) Warning(3)
    Error(2) Error(4)
    Critical(1) Critical(5)

    如果指定了 FilterSpecs,列表中任意的类别会使用对应的编码的级别,所有其它的类别都被过滤掉。
    下面的例子有以下假设:
    - 应用程序正在运行,并且调用了 logger.LogDebug("123456")
    - 进程 ID (PID)通过 set PID=12345 设置,12345 是实际的 PID。
    考虑以下命令:
    dotnet trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:5

     上面的命令:
    - 不会捕捉调试信息,因为类别的级别 5 是 Critical
    - 提供了一个 FilterSpecs
    下面的命令会捕捉调试信息,因为类别的级别被指定为 1 Debug

    dotnet trace collect -p %PID%  --providers Microsoft-Extensions-Logging:4:5:\"FilterSpecs=*:1\"

    {Logger Category} 和 {Category Level} 的 FilterSpecs 实体代表了额外的日志过滤条件。使用 ; 字符分隔 FilterSpecs 实体。
    示例使用了一个 Windows 命令行:

    dotnet trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:4\"

    上面的命令:
    - Event Source logger 生成为错误 (2) 生成了格式化的字符串(4)
    - Microsoft.AspNetCore.Hosting 以 Informational 日志级别记录日志

  4. 使用按下 Enter 键或者 Ctrl + C 停止 dotnet trace 工具
    trace 使用名称 trace.nettrace 保存在 dotnet trace 命令行执行的文件夹中
  5. 使用 Perfview 打开 trace。打开 trace.nettrace 文件浏览 trace events。

如果应用程序没有使用 CreateDefaultBuilder 创建主机,可以添加 Event Source provider 到应用程序日志配置中。

更多信息,查看:

Prefview

使用 PerfView utility 搜集和浏览日志。也有其它用来浏览 ETW 日志的工具,但是 Prefview 提供了处理 ASP.NET Core ETW 事件的最佳体验。

为了配置用来搜集该提供器事件日志的 PrefView,可以添加字符串 *Microsoft-Extensions-Logging 到 Additional Providers 列表。不要漏掉字符串开头的 *。

Windows EventLog 

EventLog 提供器发送日志输出到 Windows Event Log。不像其它提供器一样,EventLog 提供器不会继承默认的 non-provider 设置。如果 EventLog 日志设置没有被指定,默认就是  default to LogLevel.Warning

要设置级别低于 LogLevel.Warning,可以显式设置日志级别。下面的例子设置 Event Log 默认日志级别为 LogLevel.Information (LogLevel.Information):

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

AddEventLog overloads 可以在 EventLogSettings 中传递。如果 null 或者没有指定,下面的默认设置会被使用:

  • LogName: "Application"
  • SourceName: ".NET Runtime"
  • MachineName: 使用本机名称

下面的代码更改 SourceName 有默认的值 ".NET Runtime" 到 MyLogs:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
            {
                logging.AddEventLog(eventLogSettings =>
                {
                    eventLogSettings.SourceName = "MyLogs"; 
                });
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Azure App Service

 Microsoft.Extensions.Logging.AzureAppServices 提供了一个包用来写入日志到 Azure App Servirce 应用程序文件系统的文本文件和 Azure Storage account 中的 blob storage

提供器的包不包含在共享框架中,要使用这个提供器,可以添加提供器包到项目中。

使用 AzureFileLoggerOptions 和 AzureBlobLoggerOptions 配置提供器设置,就像下面示例展示的一样:

public class Scopes
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging(logging => logging.AddAzureWebAppDiagnostics())
                .ConfigureServices(serviceCollection => serviceCollection
                    .Configure<AzureFileLoggerOptions>(options =>
                    {
                        options.FileName = "azure-diagnostics-";
                        options.FileSizeLimit = 50 * 1024;
                        options.RetainedFileCountLimit = 5;
                    })
                    .Configure<AzureBlobLoggerOptions>(options =>
                    {
                        options.BlobName = "log.txt";
                    }))
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

当部署到 Azure App Service 时,应用程序使用 Azure portal 中的  App Service 页面的 App Service logs 部分中的设置。当下面的设置更新时,更改会立即生效而不用重启或者重新部署应用程序:

  • Application Logging (Filesystem)
  • Application Logging (Blob)

日志文件的默认位置是 D:\home\LogFiles\Application 文件夹总,默认的文件名称是 diagnostics-yyyymmdd.txt。默认文件大小限制是 10M,保留的最大的文件数量是 2。默认的 blob 名称是 {app-name}{timestamp}/yyyy/mm/dd/hh/{guid}-applicationLog.txg。

提供器只有在项目运行在 Azure 环境中时才会记录日志。

Azure 日志流

Azure 日志流支持实时浏览日志活动状态:

  • 应用程序服务器
  • web 服务器
  • 失败请求追踪

配置 Azure 日志流:

  • 从应用程序 portal 页面导航到 App Service 日志页面
  • 设置 Application Logging (文件系统)打开
  • 选择日志级别。设置仅仅会应用到 Azure log 流

导航到 Log Stream 页面浏览日志。使用 ILogger 接口记录日志。

Azure Application Insights

 Microsoft.Extensions.Logging.ApplicationInsights 提供记录日志到 Azure Application Insights 的包。Application Insights 是一个监控一个 web 应用程序和提供查询以及分析临时数据的服务。如果你使用这个提供器,你可以使用 Application Insights 工具获取和分析你的日志。

日志提供器作为 Microsoft.ApplicationInsights.AspNetCore 的一个依赖包含在里面,这个包提供了所有的可在在 ASP.NET Core 中使用的遥测技术。如果使用了这个包,你不用再去安装提供器的包。

 Microsoft.ApplicationInsights.Web 包适用于 ASP.NET Core 4.x,不适用于 ASP.NET Core。

更多信息,查看一下资源:

第三方日志提供器

第三方 ASP.NET Core 日志框架:

一些第三方可以执行 semantic logging, also known as structured logging

使用第三方框架和使用内置提供器的用法是相似的:

  1. 添加一个 NuGet 包到你的项目
  2. 调用由日志框架提供的 ILoggerFactory 扩展方法

更多信息,查看对应的提供器文档。第三方日志提供器不受 Microsoft 支持。

无主机(Non-host)控制台应用程序

关于在 non-web 控制台应用程序中如何使用 Generic Host 的示例,查看 Background Tasks sample app (Background tasks with hosted services in ASP.NET Core) 的 Program.cs 文件。

日志提供器

在一个 non-host 控制台应用程序中,调用提供器的 Add{provider name} 扩展方法创建一个 LoggerFactory:

class Program
{
    static void Main(string[] args)
    {
        using var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder
                .AddFilter("Microsoft", LogLevel.Warning)
                .AddFilter("System", LogLevel.Warning)
                .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
                .AddConsole()
                .AddEventLog();
        });
        ILogger logger = loggerFactory.CreateLogger<Program>();
        logger.LogInformation("Example log message");
    }
}

创建日志

要创建日志对象,使用 ILogger<TCategoryName> 对象。使用 LoggerFactory 创建一个 ILogger。

下面的示例使用 LoggingConsoleApp.Program 作为类别创建一个 logger:

class Program
{
    static void Main(string[] args)
    {
        using var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder
                .AddFilter("Microsoft", LogLevel.Warning)
                .AddFilter("System", LogLevel.Warning)
                .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
                .AddConsole()
                .AddEventLog();
        });
        ILogger logger = loggerFactory.CreateLogger<Program>();
        logger.LogInformation("Example log message");
    }
}

在下面的示例中,logger 使用 Information 作为几倍被用于创建日志。Log 级别表明了日志事件的严重程度。

class Program
{
    static void Main(string[] args)
    {
        using var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder
                .AddFilter("Microsoft", LogLevel.Warning)
                .AddFilter("System", LogLevel.Warning)
                .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
                .AddConsole()
                .AddEventLog();
        });
        ILogger logger = loggerFactory.CreateLogger<Program>();
        logger.LogInformation("Example log message");
    }
}

Levels 和 categories 在本文档中有更详细的解释。

在主机构造过程中输出日志(Log during host construction)

在主机构造过程中输出日志不被直接支持。然而,一个隔离的 logger 可以使用。在下面的示例中,在 CreateHostBuilder 中使用到一个  Serilog logger 记录日志。AddSerilog 在 Log.Logger 中使用静态配置被指定:

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var builtConfig = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddCommandLine(args)
            .Build();

        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .WriteTo.File(builtConfig["Logging:FilePath"])
            .CreateLogger();

        try
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureServices((context, services) =>
                {
                    services.AddRazorPages();
                })
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config.AddConfiguration(builtConfig);
                })
                .ConfigureLogging(logging =>
                {   
                    logging.AddSerilog();
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host builder error");

            throw;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

配置一个依赖于 ILogger 的服务

因为一个隔离的 DI 容器为 Web Host 创建,早期版本的 ASP.NET Core 支持构造方法注入一个 logger 到 Startup 中。关于为什么只有一个 Generic Host 的容器会被创建,查看 breaking change announcement

要配置一个依赖于 ILogger<T> 的服务,使用构造方法注入或者提供一个工厂方法。工厂方法仅仅在没有其他选择的时候推荐使用。例如,考虑一个需要由 DI 提供的 ILogger<T> 实例:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddRazorPages();

    services.AddSingleton<IMyService>((container) =>
    {
        var logger = container.GetRequiredService<ILogger<MyService>>();
        return new MyService() { Logger = logger };
    });
}

上面的代码是一个方法(Func),在 DI 容器需要构造一个 MyService 的实例的时候第一次运行。你可以通过这个方法获取任意注册的服务。

在 Main 中创建 logs

下面的代码在主机创建后从 DI 中获取一个 ILogger 实例在 Main 中输出日志:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();

    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("Host created.");

    host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

在 Startup 中创建 logs

下面的代码在 Startup.Configure 中输出日志:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
                      ILogger<Startup> logger)
{
    if (env.IsDevelopment())
    {
        logger.LogInformation("In Development.");
        app.UseDeveloperExceptionPage();
    }
    else
    {
        logger.LogInformation("Not Development.");
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapRazorPages();
    });
}

在 DI 容器设置完成之前,在 Startup.ConfigureServices 方法中输出日志是不支持的:

  • Logger 注入到 Startup 构造方法是不支持的
  • Logger 注入到 Stratup.ConfigureServices 方法签名中也是不受支持的

存在这个限制的原因是日志依赖于 DI 和配置,反过来又取决于 DI。DI 容器直到 ConfigureServices 完成时才会设置好。

更多关于配置一个依赖于 ILogger<T> 的服务或者构造方法注入一个 logger 到 Startup 在早期版本受支持的信息,查看 Configure a service that depends on ILogger

没有异步的 logger 方法

日志执行的很快,以至于不值得异步代码的性能牺牲。如果一个日志数据存储慢了,不要直接写入。考虑一开始把日志信息写入到一个更快的存储中,然后移动他们到慢一些的存储中。例如,当记录日志到 SQL Server 中时,不要直接在一个 Log 方法中写入,由于 Log 方法是同步的。相反的,可以同步的添加日志到内存队列中,然后一个后台工作把信息拉出队列,异步的把数据推送到 SQL Server 中。更多信息,查看 Github 问题 this

在应用程序运行时更改日志级别

日志 API 不包含在应用程序运行时更改日志级别的情景。然而,一个配置提供器支持重新读取配置,这些会关于日志的配置会立即生效。例如,File Configuration Provider,默认的会重新加载配置。如果要在应用程序运行时配置在代码中改变,应用程序可以调用 IConfigurationRoot.Reload 更新应用程序的日志配置。

ILogger 和 ILoggerFactory

ILogger<TCategoryName> 和 ILoggerFactory 接口和实现包含在 .NET Core SDK 中。它们在下面的 NuGet 包中也可以使用:

在代码中应用过滤规则

优先的设置日志过滤规则的方法是使用 Configuration

下面的例子展示了如何在代码中注册过滤规则:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
               logging.AddFilter("System", LogLevel.Debug)
                  .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information)
                  .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace))
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

logging.AddFilter("System", LogLevel.Debug) 指定了 System 类别和日志级别 Debug。这个过滤会应用到所有的提供器上,因为没有指定特定的提供器。

AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) 指定了:

  • Debug 日志提供器
  • Information 日志级别以及更高的级别
  • 所有的开头为 "Microsoft" 的类别

创建一个自定义的 logger

要创建一个自定义的 logger,查看  Implement a custom logging provider in .NET

更多资源

 

posted @ 2021-04-21 21:49  sims  阅读(368)  评论(0编辑  收藏  举报