当前位置: 移动技术网 > IT编程>开发语言>.net > 详解.Net Core中的日志组件(Logging)

详解.Net Core中的日志组件(Logging)

2018年08月08日  | 移动技术网IT编程  | 我要评论

项城征婚,湖南女子学院招生网,哈士奇俱乐部

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 > 0)
      {
        var haslevel = !string.isnullorempty(loglevelstring);
        //这里是主要的代码实现,可以看到,并没有写日志的代码,而是将日志打入到一个blockingcollection<logmessageentry>队列中
        _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 { }
      }
    }
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网