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. Spring MVC 的国际化和本地化

    国际化: i18n 本地化: l10n java.util.Locale 类表示一个语言区域.一个 Locale 对象包含 3 个主要元件:language.country.variant java. ...

  2. my simplest kv db

    最简单的kv db 最基本的网络连接 使用STL map存储key value 作为多线程互斥的简单例子. 以后有机会逐步优化添加功能 1增加ASIO 异步通讯 2优化存储空间 传递指针 避免过多的拷 ...

  3. Minimum Increment to Make Array Unique LT945

    Given an array of integers A, a move consists of choosing any A[i], and incrementing it by 1. Return ...

  4. Minimum Domino Rotations For Equal Row LT1007

    In a row of dominoes, A[i] and B[i] represent the top and bottom halves of the i-th domino.  (A domi ...

  5. Java多线程系列2 线程常见方法介绍

    守护线程 执行一些非业务方法,比如gc.当全部都是守护线程的时候,jvm退出 线程优先级  设置线程优先级:setPriority(int priorityLevel).参数priorityLevel ...

  6. Android手机上浏览器不支持带端口号wss解决方案

    首先抄个示例过来,命名为wss-test.html,然后传到服务器: <!DOCTYPE HTML> <html> <head> <meta http-equ ...

  7. W7500P硬件TCP/IP+硬件物理层PHY+Cortex-M0处理器(48MHZ)

    W7500P 硬件TCP/IP+硬件物理层PHY+Cortex-M0处理器(48MHZ) 硬件TCP/IP+硬件物理层PHY+Cortex-M0处理器(48MHZ) 如果您发现商品信息不准确,欢迎纠错 ...

  8. turtle库的学习笔记

    (1)turtle使用pen来绘制图形 pendown()  放下画笔,移动到指定点后继续绘制 penup()   提起画笔,用于另起一个地方绘制时使用 pensize(width)   设置画笔线条 ...

  9. 2019.02.12 bzoj5294: [Bjoi2018]二进制(线段树)

    传送门 题意简述: 给出一个长度为nnn的二进制串. 你需要支持如下操作: 修改每个位置:1变0,0变1 询问对于一个区间的子二进制串有多少满足重排之后转回十进制值为333的倍数(允许前导000). ...

  10. ubuntu上vsftpd服务配置

    Ubuntu上提供两种常用的ftp服务应用:vsftpd 和 tftpd,区别如下: 1)vsftpd 支持客户端上下传文件,支持浏览器显示及下载,支持用户名密码认证,支持匿名访问,默认端口TCP:2 ...