1、介绍

  Logging组件是微软实现的日志记录组件包括控制台(Console)、调试(Debug)、事件日志(EventLog)和TraceSource,但是没有实现最常用用的文件记录日志功能(可以用其他第三方的如NLog、Log4Net。之前写过NLog使用的文章)。

2、默认配置

  新建.Net Core Web Api项目,添加下面代码。

    [Route("api/[controller]")]
public class ValuesController : Controller
{
ILogger<ValuesController> logger;
     //构造函数注入Logger
public ValuesController(ILogger<ValuesController> logger)
{
this.logger = logger;
}
[HttpGet]
public IEnumerable<string> Get()
{
logger.LogWarning("Warning");
return new string[] { "value1", "value2" };
}
}

运行结果如下:

我刚开始接触的时候,我就有一个疑问我根本没有配置关于Logger的任何代码,仅仅写了注入,为什么会起作用呢?最后我发现其实是在Program类中使用了微软默认的配置。

public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)//在这里使用了默认配置
.UseStartup<Startup>()
.Build();
}

下面为CreateDefaultBuilder方法的部分源码,整个源码在 https://github.com/aspnet/MetaPackages,可以看出在使用模板创建项目的时候,默认添加了控制台和调试日志组件,并从appsettings.json中读取配置。

                builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"));
})
.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;
            //加载appsettings.json文件 使用模板创建的项目,会生成一个配置文件,配置文件中包含Logging的配置项
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
            .......
})
.ConfigureLogging((hostingContext, logging) =>
{ 
            //从appsettings.json中获取Logging的配置
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
            //添加控制台输出
logging.AddConsole();
            //添加调试输出
logging.AddDebug();
})

3、建立自己的Logging配置

  首先修改Program类

public class Program
{
public static void Main(string[] args)
{
//指定配置文件路径
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())//设置基础路径
.AddJsonFile($"appsettings.json", true, true)//加载配置文件
.AddJsonFile($"appsettings.{EnvironmentName.Development}.json", true, true)
.Build(); var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(config)//使用配置
.UseUrls(config["AppSettings:Url"])//从配置中读取 程序监听的端口号
.UseEnvironment(EnvironmentName.Development)//如果加载了多个环境配置,可以设置使用哪个配置 一般有测试环境、正式环境

              //.ConfigureLogging((hostingCotext, logging) => //第一种配置方法 直接在webHostBuilder建立时配置 不需要修改下面的Startup代码
              //{
              //    logging.AddConfiguration(hostingCotext.Configuration.GetSection("Logging"));
              //    logging.AddConsole();
              //})

                        .Build();
host.Run();
}
}

修改Startup类如下面,此类的执行顺序为 Startup构造函数 > ConfigureServices > Configure

public class Startup
{
public IConfiguration Configuration { get; private set; }
public IHostingEnvironment HostingEnvironment { get; private set; }
//在构造函数中注入 IHostingEnvironment和IConfiguration,配置已经在Program中设置了,注入后就可以获取配置文件的数据
public Startup(IHostingEnvironment env, IConfiguration config)
{
HostingEnvironment = env;
Configuration = config;
}
public void ConfigureServices(IServiceCollection services)
{

        services.AddMvc();

        //第二种配置 也可以这样加上日志功能,不用下面的注入
        //services.AddLogging(builder => 
        //{
          // builder.AddConfiguration(Configuration.GetSection("Logging"))
          //        .AddConsole();
        //});

        }
     //注入ILoggerFactory
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
       //第三种配置 注入ILogggerFactory,然后配置参数
//添加控制台输出
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
       //添加调试输出
loggerFactory.AddDebug();
app.UseMvc();
}
}

这种结构就比较清晰明了。

4、Logging源码解析

  三种配置其实都是为了注入日志相关的服务,但是调用的方法稍有不同。现在我们以第二种配置来详细看看其注入过程。首先调用AddLogging方法,其实现源码如下:
 public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
services.AddOptions();//这里会注入最基础的5个服务 option相关服务只要是跟配置文件相关,通过Option服务获取相关配置文件参数参数 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;
}

接着会调用AddConfiguration

  public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
{
builder.AddConfiguration();
       //下面为AddConfiguration的实现

        public static void AddConfiguration(this ILoggingBuilder builder)
        {
          builder.Services.TryAddSingleton<ILoggerProviderConfigurationFactory, LoggerProviderConfigurationFactory>();
          builder.Services.TryAddSingleton(typeof(ILoggerProviderConfiguration<>), typeof(LoggerProviderConfiguration<>));
        }

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

下面来看打印日志的具体实现:

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)        
{
       var loggers = Loggers;
List<Exception> exceptions = null;
       //loggers为LoggerInformation数组,如果你在Startup中添加了Console、Deubg日志功能了,那loggers数组值有2个,就是它俩。
foreach (var loggerInfo in loggers)
{  //循环遍历每一种日志打印,如果满足些日子的条件,才执行打印log方法。比如某一个日志等级为Info,
          //但是Console配置的最低打印等级为Warning,Debug配置的最低打印等级为Debug
          //则Console中不会打印,Debug中会被打印
if (!loggerInfo.IsEnabled(logLevel))
{
continue;
}
try
{
            //每一种类型的日志,对应的打印方法不同。执行对应的打印方法
loggerInfo.Logger.Log(logLevel, eventId, state, exception, formatter);
}
catch (Exception ex)
{
if (exceptions == null)
{
exceptions = new List<Exception>();
} exceptions.Add(ex);
}
}
    }

下面具体看一下Console的打印实现:

首先ConsoleLogger实现了ILogger的Log方法,并在方法中调用WriteMessage方法

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
       //代码太多 我就省略一些判空代码
var message = formatter(state, exception); if (!string.IsNullOrEmpty(message) || exception != null)
{
WriteMessage(logLevel, Name, eventId.Id, message, exception);
}
} public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
{
       .......
if (logBuilder.Length > )
{
var hasLevel = !string.IsNullOrEmpty(logLevelString);
//这里是主要的代码实现,可以看到,并没有写日志的代码,而是将日志打入到一个BlockingCollection<LogMessageEntry>队列中
         //这里需要指出 BlockingCollection是线程安全的集合,多个线程同时访问,不会发生数据混乱。
_queueProcessor.EnqueueMessage(new LogMessageEntry()
{
Message = logBuilder.ToString(),
MessageColor = DefaultConsoleColor,
LevelString = hasLevel ? logLevelString : null,
LevelBackground = hasLevel ? logLevelColors.Background : null,
LevelForeground = hasLevel ? logLevelColors.Foreground : null
});
}
       ......
}
 下面看日志被放入队列后的具体实现: 

public class ConsoleLoggerProcessor : IDisposable
{
    private const int _maxQueuedMessages = 1024;

    private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
    private readonly Thread _outputThread;

   public IConsole Console;

        public ConsoleLoggerProcessor()
{
//在构造函数中启动一个线程,执行ProcessLogQueue方法
       //从下面ProcessLogQueue方法可以看出,是循环遍历集合,将集合中的数据打印
_outputThread = new Thread(ProcessLogQueue)
{
IsBackground = true,
Name = "Console logger queue processing thread"
public virtual void EnqueueMessage(LogMessageEntry message)
{
if (!_messageQueue.IsAddingCompleted)
{
try
{
_messageQueue.Add(message);
return;
}
catch (InvalidOperationException) { }
} WriteMessage(message);
} internal virtual void WriteMessage(LogMessageEntry message)
{
if (message.LevelString != null)
{
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
} Console.Write(message.Message, message.MessageColor, message.MessageColor);
Console.Flush();
} private void ProcessLogQueue()
{
    
try
        {
         
 //GetConsumingEnumerable()方法比较特殊,当集合中没有值时,会阻塞自己,一但有值了,直到集合中又有元素继续遍历
          foreach (var message in _messageQueue.GetConsumingEnumerable())
{
WriteMessage(message);
}
}
catch
{
try
{
_messageQueue.CompleteAdding();
}
catch { }
}
}
  }
 

.Net Core中的日志组件(Logging)的更多相关文章

  1. 玩转ASP.NET Core中的日志组件

    简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...

  2. .NET Core 中的日志与分布式链路追踪

    目录 .NET Core 中的日志与分布式链路追踪 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory IL ...

  3. (14)ASP.NET Core 中的日志记录

    1.前言 ASP.NET Core支持适用于各种内置和第三方日志记录提供应用程序的日志记录API.本文介绍了如何将日志记录API与内置提供应用程序一起使用. 2.添加日志提供程序 日志记录提供应用程序 ...

  4. .NET Core中的验证组件FluentValidation的实战分享

    今天有人问我能不能出一篇FluentValidation的教程,刚好今天在实现我们的.NET Core实战项目之CMS的修改密码部分的功能中有用到FluentValidation,所以就以修改用户密码 ...

  5. Asp.Net Core中利用Seq组件展示结构化日志功能

    在一次.Net Core小项目的开发中,掌握的不够深入,对日志记录并没有好好利用,以至于一出现异常问题,都得跑动服务器上查看,那时一度怀疑自己肯定没学好,不然这一块日志不可能需要自己扒服务器日志来查看 ...

  6. ASP.NET Core 中的日志记录

    目录 内置日志的使用 使用Nlog 集成ELK 参考 内置日志的使用 Logger 是 asp .net core 的内置 service,所以我们就不需要在ConfigureService里面注册了 ...

  7. java中的日志组件-log4j

    1.为什么使用日志组件 Log4J是Apache的一个开放源代码项目,它是一个日志操作包,通过使用Log4J,可以指定日志信息输出的目的地,如控制台.文件.CUI组件.NT的事件记录器:还可以控制每一 ...

  8. Python中的日志管理Logging模块

    1.基本的用法 import logging logging.debug('This is debug message') logging.info('This is info message') l ...

  9. .NET Core中的数据保护组件

    原文地址: PREVENTING INSECURE OBJECT REFERENCES IN ASP.NET CORE 2.0 作者: Tahir Naushad 背景介绍 在 OWASP(开放式 W ...

随机推荐

  1. error while loading shared libraries的解決方法

    我是在启动nginx的时候报这个错误,搜索这个错误时发现这篇文章,非本人(小渡博客)原创. 原文地址:http://blog.csdn.net/dumeifang/article/details/29 ...

  2. 单色三角形(hdu-5072

    单色三角形模型:空间里有n个点,任意三点不共线.每两个点之间都用红色或者黑色线段链接.如果一个三角形的三条边同色,责成这个三角形是单色三角形.对于给定的红色线段列表,找出单色三角形的个数. 分析:对于 ...

  3. wind量化交易

    https://www.joinquant.com/study?f=home&m=memu https://www.v2ex.com/member/mushroomqiu https://sa ...

  4. s5 Docker的持久化存储和数据共享

    数据库容器的数据如何才能不会丢失?Docker的持久化存储技术.Docker的数据共享技术能极大提高开发人员的开发效率,边写代码,边看运行结果. 数据持久化之Data Volume Docker持久化 ...

  5. Unity3D连接本地或局域网MySQL数据库

    准备工作: 1.打开 Unity3D 安装目录,到这个路径下 Editor > Data > Mono > lib > mono > 2.0 拷贝出下图的五个动态链接库, ...

  6. 美团codeM预赛A轮 倒水

    [编程题] 倒水 时间限制:1秒 空间限制:32768K 有一个大水缸,里面水的温度为T单位,体积为C升.另有n杯水(假设每个杯子的容量是无限的),每杯水的温度为t[i]单位,体积为c[i]升. 现在 ...

  7. Android studio 中的TabWidget

    1.TabWidget 的 layout文件 <?xml version="1.0" encoding="utf-8"?> <TabHost ...

  8. koa中返回404并且刷新后才正常的解决方案

    概述 这几天学习koa2,有一些心得,记录下来,供以后开发时参考,相信对其他人也有用. 起因 这几天学习koa2,写的代码执行时有一个奇怪的bug:明明能够返回数据,却有时正常返回数据,有时偏偏给你返 ...

  9. 国外青少年最喜爱的聊天 app,竟然是 Google Docs

    简评: 这还真不是标题党,Google Docs 的协作中内置了实时聊天的功能,也可以进行 comments,颇有种现代「传纸条」的既视感.其实国内的石墨文档.腾讯文档等协作工具也有类似的效果,本文很 ...

  10. Spark基础脚本入门实践2:基础开发

    1.最基本的Map用法 val data = Array(1, 2, 3, 4, 5)val distData = sc.parallelize(data)val result = distData. ...