一、前言

在项目的开发维护阶段,有时候我们关注的问题不仅仅在于功能的实现,甚至需要关注系统发布上线后遇到的问题能否及时的查找并解决。所以我们需要有一个好的解决方案来及时的定位错误的根源并做出正确及时的修复,这样才能不影响系统正常的运行状态。

这个时候我们发现,其实在asp.net core中已经内置了日志系统,并提供了各种内置和第三方日志记录提供程序的日志记录接口,在进行应用开发中,可以进行统一配置,并且利用第三方日志框架相结合,更加有效的实现日志记录。所以在这个系列中,主要是对内置日志记录系统的学习,以及后续使用第三方日志框架集成我们需要的日志系统。

二、说明

在这一篇中主要是对日志记录的配置进行说明,从开始配置日志,以及后续使用配置进行日志处理。

在新建项目成功之后,我们都会看到一个命名为appsettings.json配置,打开一看,短短的几行配置,

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

然后启动运行的时候,程序会在调试面板和控制台中分别输出显示来源如下:

在控制台中:

在调试面板中:

这里的日志配置,在系统中到底都起来什么作用?让我们来一探究竟吧!

三、开始

3.1 默认配置

我们查看源代码发现,在程序的入口点中发现,在初始化时候,通过CreateDefaultBuilder方法来实现日志记录的默认配置。

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>();
});
}

所以下面我们看一下CreateDefaultBuilder在源码中都对日志做了哪些默认配置?

        public static IHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new HostBuilder(); builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
config.AddEnvironmentVariables(prefix: "DOTNET_");
if (args != null)
{
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables(); if (args != null)
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
} logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows)
{
logging.AddEventLog();
}
})
.UseDefaultServiceProvider((context, options) =>
{
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}

通过上面这一段源码我们可以看到一个命名为ConfigureLogging的对象,我们根据命名的意思大致可以看出,这是一个配置日志的方法,继续查看ConfigureLogging源码

    public static IHostBuilder ConfigureLogging(this IHostBuilder hostBuilder, Action<HostBuilderContext, ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
}

通过IServiceCollection注册服务集合容器,将日志服务添加到这个服务容器,使用AddLogging方法实现对日志服务的注册。

    public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} services.AddOptions();
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
new DefaultLoggerLevelConfigureOptions(LogLevel.Information))); configure(new LoggingBuilder(services));
return services;
}

通过AddLogging添加到服务集合容器,先通过添加所需的配置AddOptions,通过注入的方式实现默认的ILoggerFactory,ILogger ( 这个会在后续的篇章中进行说明),再后通过LoggingBuilder完成日志对象的创建,

public interface ILoggingBuilder
{
IServiceCollection Services { get; }
}
internal class LoggingBuilder : ILoggingBuilder
{
public LoggingBuilder(IServiceCollection services)
{
Services = services;
} public IServiceCollection Services { get; }
}

对日志系统的配置,用于提供程序的接口,ILoggingBuilder后面可以对该对象进行拓展使用。

通过以上的流程CreateDefaultBuilder方法,实现对预先配置的默认值初始化,因此也发现了其中的ConfigureLogging也是其中要进行默认初始化的值,也就是系统默认的日志配置。

单独把ConfigureLogging这一块的源码拎出来再看看:

        .ConfigureLogging((hostingContext, logging) =>
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger(); if (isWindows)
{
logging.AddEventLog();
}
})

在asp.net core启动中,根据操作系统平台适应不同的服务,在windows服务中,将EventLogLoggerProvider的默认值设置为警告或者更高的级别。

AddConfiguration : 添加系统日志的全局配置。

在配置中,可以根据提供的不同类型程序来针对实现日志记录的输出方式。而这里默认实现的AddConsole()AddDebug() 和AddEventSourceLogger()分别是将日志输出到控制台、调试窗口中,以及提供写入事件源。

AddConsole : 添加控制台到工厂方法中,用来将日志记录到控制台中。

AddDebug : 添加Debug窗口到工厂方法中,用来将日志记录到窗口中。

说明:asp.net core 内置的日志接口中,实现了多种内置的日志提供器,除了上面默认实现的ConsoleDebugEventSource,还包括下面的这几个

EventLog :

TraceSource

AzureAppServicesFile

AzureAppServicesBlob

ApplicationInsights

还记得上面提到的appsettings.json配置吗?在这里,我们来看看

{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
},
"Console": {
"LogLevel": {
"Default": "Debug",
"System": "Warning"
}
}
}
}

AddConfiguration中,

logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));

获取配置文件的Logging数据,实现全局配置,

        public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
{
builder.AddConfiguration();
builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>(new LoggerFilterConfigureOptions(configuration));
builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>(new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));
builder.Services.AddSingleton(new LoggingConfiguration(configuration));
return builder;
}
internal class LoggerFilterConfigureOptions : IConfigureOptions<LoggerFilterOptions>
{
private const string LogLevelKey = "LogLevel";
private const string DefaultCategory = "Default";
private readonly IConfiguration _configuration; public LoggerFilterConfigureOptions(IConfiguration configuration)
{
_configuration = configuration;
} public void Configure(LoggerFilterOptions options)
{
LoadDefaultConfigValues(options);
} private void LoadDefaultConfigValues(LoggerFilterOptions options)
{
if (_configuration == null)
{
return;
} options.CaptureScopes = _configuration.GetValue(nameof(options.CaptureScopes), options.CaptureScopes); foreach (var configurationSection in _configuration.GetChildren())
{
if (configurationSection.Key.Equals(LogLevelKey, StringComparison.OrdinalIgnoreCase))
{
// Load global category defaults
LoadRules(options, configurationSection, null);
}
else
{
var logLevelSection = configurationSection.GetSection(LogLevelKey);
if (logLevelSection != null)
{
// Load logger specific rules
var logger = configurationSection.Key;
LoadRules(options, logLevelSection, logger);
}
}
}
}
private void LoadRules(LoggerFilterOptions options, IConfigurationSection configurationSection, string logger)
{
foreach (var section in configurationSection.AsEnumerable(true))
{
if (TryGetSwitch(section.Value, out var level))
{
var category = section.Key;
if (category.Equals(DefaultCategory, StringComparison.OrdinalIgnoreCase))
{
category = null;
}
var newRule = new LoggerFilterRule(logger, category, level, null);
options.Rules.Add(newRule);
}
}
}
}

以上是AddConfiguration实现的整体流程源码,默认注册实现LoggerFilterConfigureOptions对配置数据的读取,其中定义的 LogLevelKey = "LogLevel"DefaultCategory = "Default" 默认字符串,以此来获取默认全局配置数据。

在默认配置的文本格式appsettings.json中,Logging属性可以具有LogLevel和日志提供程序属性。Logging 下的 LogLevel 属性指定了用于记录所选类别的最低级别。在本例中, Microsoft 类别在 Information 级别记录,其他均在 Debug 级别记录。

日志级别说明:每一个日志都有指定的日志级别值,日志级别判断指示严重性或重要性。使用日志等级可以很好的过滤想要的日志,记录日志记录问题的同时,甚至为我们提供非常详细的日志信息。

LogLevel 严重性:Trace < Debug < Information < Warning < Error < Critical < None。

日志级别 常用场景
Trace = 0 记录一些对程序员调试问题有帮助的信息, 其中可能包含一些敏感信息, 所以应该避免在 生产环境中启用Trace日志,因此不应该用于生产环境。默认应禁用。
Debug = 1 记录一些在开发和调试阶段有用的短时变 量(Short-term usefulness), 所以除非为了临时排除生产环境的 故障,开发人员应该尽量避免在生产环境中启用Debug日志,默认情况下这是最详细的日志。
Information = 2 记录跟踪应用程序的一些流程, 例如,记录当前api请求的url。
Warning = 3 记录应用程序中发生出现错误或其它导致程序停止的流程异常信息。 这些信息中可能包含错误消息或者错误产生的条件, 可供后续调查,例如, 文件未找到
Error = 4 记录应用程序中某个操作产生的错误和异常信息。这些消息应该指明当前活动或操作(比如当前的 HTTP 请求),而不是应用程序范围的故障。
Critical = 5 记录一些需要立刻修复,急需被关注的问题,应当记录关键级别的日志。例如数据丢失,磁盘空间不足等。

日志级别只需要简单的通过 AddFilter 对日志的过滤级别配置一下就行了。同时也可以通过自定义在

Logging.{providername}.LogLevel 中指定了级别,则这些级别将重写 Logging.LogLevel 中设置的所有内容。(在下文自定义中说明)

由此可以看出,日志记录提供程序配置由一个或多个配置提供程序提供,如文件格式(系统自带的appsettings.json)或者通过(已安装或已创建的)自定义提供程序(下文会说明自定义方式)。

3.2 自定义配置

看完了上面实现的默认配置之后,我们也清楚了可以修改默认配置实现不同等级日志的输出,因此,我们也可以通过自定义的方式,对默认配置的修改,实现我们想要的日志记录方式。

可以通过自行选择添加提供程序来替换默认配置的提供的程序。这样就实现自定义。自定义的方式有很多,比如

3.2.1 代码添加提供程序

调用ClearProviders,清除默认之后,可添加所需的提供程序。如下:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) //可以看出在使用模板创建项目的时候,默认添加了控制台和调试日志组件,并从appsettings.json中读取配置。
.ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders(); //去掉默认添加的日志提供程序
//添加控制台输出
logging.AddConsole();
//添加调试输出
logging.AddDebug();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

由上可以发现我们可以通过在入口程序中直接对添加ConfigureLogging(在上文中源码可以看出)拓展方法来实现我们的自定义配置。

3.2.2 代码添加过滤器

过滤器AddFilter,添加过滤规则,可以为不同的日志提供者指定不同的过滤器,实现有效的自定义日志的输出。如下代码:

.ConfigureLogging(logging =>
logging.AddFilter("System", LogLevel.Debug)
.AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Trace))

添加指定了全局的过滤器,作用于所有日志提供者,示例中的第二个 AddFilter 使用类型名称来指定调试提供程序。 第一个 AddFilter 应用于全部提供程序,因为它未指定提供程序类型。

这里的AddFilter其实于之前读取配置文件信息添加配置AddConfiguration的作用相似,只是从配置文件的逻辑改成了以代码的方式实现过滤筛选,到最终也是对ConfigureOptions 的配置。

3.2.3 配置文件自定义

ASP.NET Core默认会从appSetting.json中的Logging属性读取日志的配置(当然你也可以从其他文件中读取配置),这里设置了不同的日志提供器产生的最低的日志级别,配置样例如下。

{
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
},
"LogLevel": {
"Default": "Debug"
}
}
}

此 JSON 将创建 6 条筛选规则:Debug中1 条用于调试提供程序,Console中 4 条用于控制台提供程序,最后一条LogLevel 用于所有提供程序。 创建 ILogger 对象时,为每个提供程序选择一个规则。

四、问题

虽然在这一节中只是对日志记录的配置进行了说明,但是在后续中也会对日志内部的核心运行机制进行说明介绍。所以,在这一篇中留下几个疑问

  1. 日志记录的输出可以在哪里查看?而又由什么实现决定的呢?
  2. 如何管理输出不同的日志呢?都有哪些方式呢?

以上的这些内容,会在下一篇进行介绍说明。

好了,今天的日志配置内容就说到这里了,希望能给大家在使用Core开发项目中对日志系统有进一步的认识。

五、总结

  1. 本篇主要是对net core3.1中内置的系统日志进行配置使用,不管是基于默认配置的输出方式,还是自定义形式的配置,都是为了有效的输出日志记录,便于我们查找发现问题。
  2. 关于日志配置,其实都是在对ConfigureOptions的配置,只是在形式上是直接读取配置文件或通过代码的方式实现自定义来实现日志配置。
  3. 后续会对内置的日志系统进一步说明,以及内部运行的主要核心机制。
  4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
  5. 官方源码参考资料

基于.NetCore3.1系列 —— 日志记录之日志配置揭秘的更多相关文章

  1. 基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)

    一.前言 回顾:基于.NetCore3.1系列 -- 认证授权方案之授权揭秘 (上篇) 在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构 ...

  2. 基于.NetCore3.1系列 ——认证授权方案之Swagger加锁

    一.前言 在之前的使用Swagger做Api文档中,我们已经使用Swagger进行开发接口文档,以及更加方便的使用.这一转换,让更多的接口可以以通俗易懂的方式展现给开发人员.而在后续的内容中,为了对a ...

  3. 基于.NetCore3.1系列 —— 日志记录之初识Serilog

    一.前言 对内置日志系统的整体实现进行了介绍之后,可以通过使用内置记录器来实现日志的输出路径.而在实际项目开发中,使用第三方日志框架(如: Log4Net.NLog.Loggr.Serilog.Sen ...

  4. 基于.NetCore3.1系列 —— 日志记录之自定义日志组件

    一.前言 回顾:日志记录之日志核心要素揭秘 在上一篇中,我们通过学习了解在.net core 中内置的日志记录中的几大核心要素,在日志工厂记录器(ILoggerFactory)中实现将日志记录提供器( ...

  5. Tomcat在windows服务器下,将tomcat控制台日志记录到日志文件中

    Tomcat在windows服务器下,将tomcat控制台日志记录到日志文件中 在Linux系统中,Tomcat 启动后默认将很多信息都写入到 catalina.out 文件中,我们可以通过tail  ...

  6. pm2日志记录和日志分割

    pm2日志记录和日志分割 pm2介绍 pm2是nodejs进程管理工具,现在基本是node生产服务器的标准选择,可以帮助我们实现node多进程服务,开启的多个实例自动实现负载均衡. 最重要的是保证no ...

  7. 基于.NetCore3.1系列 —— 日志记录之日志核心要素揭秘

    一.前言 在上一篇中,我们已经了解了内置系统的默认配置和自定义配置的方式,在学习了配置的基础上,我们进一步的对日志在程序中是如何使用的深入了解学习.所以在这一篇中,主要是对日志记录的核心机制进行学习说 ...

  8. ASP.NET全局错误处理和异常日志记录以及IIS配置自定义错误页面

    应用场景和使用目的 很多时候,我们在访问页面的时候,由于程序异常.系统崩溃会导致出现黄页.在通常的情况下,黄页对于我们来说,帮助是极大的,因为它可以帮助我们知道问题根源,甚至是哪一行代码出现了错误.但 ...

  9. (网页)Java日志记录框架Logback配置详解(企业级应用解决方案)(转)

    转自CSDN: 前言 Logback是现在比较流行的一个日志记录框架,它的配置比较简单学习成本相对较低,所以刚刚接触该框架的朋友不要畏惧,多花点耐心很快就能灵活应用了.本篇博文不会具体介绍Logbac ...

随机推荐

  1. 洛谷 P1220 关路灯 区间DP

    题目描述 某一村庄在一条路线上安装了 n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少).老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯. 为了 ...

  2. 大致掌握django的功能

    目录 静态文件配置 request对象方法初识 pycharm链接数据库(mysql) django链接数据库(mysql) django orm 字段的增删查改 数据的增删查改 数据的查,改,删 d ...

  3. testNG jar包启动找不到org.testng.TestNG

    主要是因为打包时依赖的jar包没有打入,网上有很多需要将对应的jar单独拷贝出来然后通过classpath引用启动,但是感觉这个就是个无底洞.拷贝了这么多个包最后还是说找不到ObjectId 启动命令 ...

  4. (三)学习了解OrchardCore笔记——灵魂中间件ModularTenantContainerMiddleware的第一行①的模块部分

    了解到了OrchardCore主要由两个中间件(ModularTenantContainerMiddleware和ModularTenantRouterMiddleware)构成,下面开始了解Modu ...

  5. Unity-ECS(一)浅谈CPU缓存命中和Unity面向数据技术栈(DOTS)--笔记

    一,缓存类型 概念:局部性. 时间局部性:当前用到的一个存储器位置,不久的将来会被用到. 空间局部性:当前用到的一个存储器位置,附近的位置会被用到. 那么在CPU的层面,这两个局部性的特性就会被Cac ...

  6. JMeter-一个接口的返回值作为输入传给其他接口

    背景: 在用JMeter写接口case,遇到一种情况,接口1查看列表接口,接口2查看详情接口,接口2需要传入接口1列表的第一条数据的id 解决方案: 首先放一下总体截图 具体步骤 1-新建一个Thre ...

  7. UML学习笔记—基本概念和初始阶段

    chpater1 1.什么是分析和设计 分析:对问题和需求的调查研究 设计:满足需求的概念上的解决方案 做正确的事(分析)和正确地做事(设计) 2.什么是Object-Oriented-Analysi ...

  8. Istio安全-认证(istio 系列七)

    Istio安全-认证 目录 Istio安全-认证 认证策略 配置 自动mutual TLS 全局启用istio的mutual TLS STRIC模式 卸载 针对单个命名空间或负载启用mutual TL ...

  9. This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary lo的解决办法

    创建存储过程时,出错信息: ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA ...

  10. ajax原生js封装

    不带注释的 function ajax(json) { json.type = json.type ? json.type : 'get'; json.async = json.async == fa ...