前言

什么是结构化呢? 结构化,就是将原本没有规律的东西进行有规律话。

就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某种规律得到最佳的效果。

同样日志结构话,能够让我们得到一些好处。如果说容易检索,容易分析,总的来说就是让我们的日志更加有规律。

如果我们的日志结构化了,那么可以使用elasticsearch 这样的框架进行二次整理,再借助一些分析工具。

我们就能做到可视化分析系统的运行情况,做到日志告警、上下文关联、实现追踪系统集成,同样也易于检索相关信息。

说了这么多,其实对于程序员,就是为了节约排查错误的时间,然后在做系统稳定化方案的时候有依据,我们是讲道理的,做事要有依据,不能张口就来。

正文

这里结构化,借助需要借助serilog,引用serilog.aspnetcore。

开源地址如下:

https://github.com/serilog/serilog-aspnetcore

这里介绍一下简单的接入方式:

在CreateHostBuilder 中加入UseSerilog:

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

然后在Startup的ConfigureServices的加入:

services.AddLogSeriLog(Configuration);

这个AddLogSeriLog 是我写的扩展:

public static class SeriLogExtension
{
public static Serilog.ILogger AddLogSeriLog(this IServiceCollection services, IConfiguration configuration)
{
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration)
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.WriteTo.Console(new RenderedCompactJsonFormatter())
.WriteTo.File(formatter: new CompactJsonFormatter(), "logs\\test.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
return Log.Logger;
}
}

输出:

这时候发现我们的输出都json化了。

同时在根目录下的logs目录下输出日志文件:

但是我们这样发现,日志太多了,我们需要过滤掉一些日志,那么我们可以配置:

"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Error",
"System": "Information"
}
}
}

测试代码:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{
_logger.LogInformation("Hello Word");
return 1;
}

结果:

因为上面配置了Microsoft 为Error级别的,故而系统打印的日志只有Error基本才会输出。

这里面简单看一下Serilog 的原理,因为https://www.cnblogs.com/aoximin/p/14854519.html 已经介绍了日志系统的基本结果,故而直接往下续:

查看UseSerilog:

public static IHostBuilder UseSerilog(
this IHostBuilder builder,
ILogger logger = null,
bool dispose = false,
LoggerProviderCollection providers = null)
{
if (builder == null)
throw new ArgumentNullException(nameof (builder));
builder.ConfigureServices((Action<HostBuilderContext, IServiceCollection>) ((_, collection) =>
{
if (providers != null)
ServiceCollectionServiceExtensions.AddSingleton<ILoggerFactory>(collection, (Func<IServiceProvider, M0>) (services =>
{
SerilogLoggerFactory serilogLoggerFactory = new SerilogLoggerFactory(logger, dispose, providers);
foreach (ILoggerProvider service in (IEnumerable<ILoggerProvider>) ServiceProviderServiceExtensions.GetServices<ILoggerProvider>(services))
serilogLoggerFactory.AddProvider(service);
return (ILoggerFactory) serilogLoggerFactory;
}));
else
ServiceCollectionServiceExtensions.AddSingleton<ILoggerFactory>(collection, (Func<IServiceProvider, M0>) (services => (ILoggerFactory) new SerilogLoggerFactory(logger, dispose, (LoggerProviderCollection) null)));
SerilogHostBuilderExtensions.ConfigureServices(collection, logger);
}));
return builder;
}

这里我简单梳理一下日志系统的基本结构,首选是ILoggerFactory, 里面会有一个CreateLogger,创建一个ILogger。那么这个ILogger的作用是什么呢?

是用来统一接口的。 比如说我们有文件日志,有控制台日志,有很多方式输出日志。那么就要有一个管理的来统一接口。

而每一种打印方式,继承ILoggerProvider,比如ConsoleProvider继承ILoggerProvider有一个CreateLogger方法,这个CreateLog才是创建具体的实现类,比如说ConsoleLogger,ConsoleLogger又继承ILogger。

从而ILogger的另一个实现类Logger实现代理,管理其他继承ILogger的实现类。

可能我这样说有点模糊,那么请看完上面链接,应该就会明白我所以表达的意思。

那么回到上文中。

ServiceCollectionServiceExtensions.AddSingleton(collection, (Func<IServiceProvider, M0>) (services => (ILoggerFactory) new SerilogLoggerFactory(logger, dispose, (LoggerProviderCollection)

将我们的ILoggerFactory 替换成了SerilogLoggerFactory,官方是LoggerFactory。

那么看下SerilogLoggerFactory:

public SerilogLoggerFactory(
Serilog.ILogger logger = null,
bool dispose = false,
LoggerProviderCollection providerCollection = null)
{
this._provider = new SerilogLoggerProvider(logger, dispose);
this._providerCollection = providerCollection;
} /// <summary>Disposes the provider.</summary>
public void Dispose()
{
this._provider.Dispose();
} /// <summary>
/// Creates a new <see cref="T:Microsoft.Extensions.Logging.ILogger" /> instance.
/// </summary>
/// <param name="categoryName">The category name for messages produced by the logger.</param>
/// <returns>
/// The <see cref="T:Microsoft.Extensions.Logging.ILogger" />.
/// </returns>
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
{
return this._provider.CreateLogger(categoryName);
} /// <summary>
/// Adds an <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" /> to the logging system.
/// </summary>
/// <param name="provider">The <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" />.</param>
public void AddProvider(ILoggerProvider provider)
{
if (provider == null)
throw new ArgumentNullException(nameof (provider));
if (this._providerCollection != null)
this._providerCollection.AddProvider(provider);
else
SelfLog.WriteLine("Ignoring added logger provider {0}", (object) provider, (object) null, (object) null);
}
}

那么继续查看CreateLogger,SerilogLoggerProvider的CreateLogger 及其相关:

private readonly Serilog.ILogger _logger;
public SerilogLoggerProvider(Serilog.ILogger logger = null, bool dispose = false)
{
if (logger != null)
this._logger = logger.ForContext((IEnumerable<ILogEventEnricher>) new SerilogLoggerProvider[1]
{
this
});
if (!dispose)
return;
if (logger != null)
this._dispose = (Action) (() =>
{
if (!(logger is IDisposable disposable))
return;
disposable.Dispose();
});
else
this._dispose = new Action(Log.CloseAndFlush);
}
public Microsoft.Extensions.Logging.ILogger CreateLogger(string name)
{
return (Microsoft.Extensions.Logging.ILogger) new SerilogLogger(this, this._logger, name);
}

而SerilogLogger 就是具体来管理全部继承ILogger具体的实现的类。 那么是否是和官方一样,其他实现类都是继承自ILogger的呢?答案不是,他们都继承自ILogEventSink,下面会继续提及,暂时不要关心这个。

同时要忘记官方每个实现具体打印的类继承ILogger,它的设计思想还是一样的,只是换了一个继承接口。

SerilogLogger 里面继承Microsoft.Extensions.Logging.ILogger,实现具体的打印的。

看下SerilogLogger 的具体的部分,主要看log方法,因为这个实现打印的,这下面不用看太多,只需要看到其实里面是调用_logger的方法就行。

internal class SerilogLogger : Microsoft.Extensions.Logging.ILogger
{
private static readonly MessageTemplateParser MessageTemplateParser = new MessageTemplateParser();
private static readonly LogEventProperty[] LowEventIdValues = Enumerable.Range(0, 48).Select<int, LogEventProperty>((Func<int, LogEventProperty>) (n => new LogEventProperty("Id", (LogEventPropertyValue) new ScalarValue((object) n)))).ToArray<LogEventProperty>();
private readonly SerilogLoggerProvider _provider;
private readonly Serilog.ILogger _logger; public SerilogLogger(SerilogLoggerProvider provider, Serilog.ILogger logger = null, string name = null)
{
SerilogLoggerProvider serilogLoggerProvider = provider;
if (serilogLoggerProvider == null)
throw new ArgumentNullException(nameof (provider));
this._provider = serilogLoggerProvider;
this._logger = logger;
Serilog.ILogger logger1 = this._logger;
if (logger1 == null)
logger1 = Serilog.Log.Logger.ForContext((IEnumerable<ILogEventEnricher>) new SerilogLoggerProvider[1]
{
provider
});
this._logger = logger1;
if (name == null)
return;
this._logger = this._logger.ForContext("SourceContext", (object) name, false);
} public bool IsEnabled(LogLevel logLevel)
{
return this._logger.IsEnabled(LevelConvert.ToSerilogLevel(logLevel));
} public IDisposable BeginScope<TState>(TState state)
{
return this._provider.BeginScope<TState>(state);
} public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
LogEventLevel serilogLevel = LevelConvert.ToSerilogLevel(logLevel);
if (!this._logger.IsEnabled(serilogLevel))
return;
Serilog.ILogger logger = this._logger;
string str1 = (string) null;
List<LogEventProperty> logEventPropertyList = new List<LogEventProperty>();
if (state is IEnumerable<KeyValuePair<string, object>> keyValuePairs)
{
foreach (KeyValuePair<string, object> keyValuePair in keyValuePairs)
{
if (keyValuePair.Key == "{OriginalFormat}" && keyValuePair.Value is string str2)
str1 = str2;
else if (keyValuePair.Key.StartsWith("@"))
{
LogEventProperty property;
if (logger.BindProperty(keyValuePair.Key.Substring(1), keyValuePair.Value, true, out property))
logEventPropertyList.Add(property);
}
else
{
LogEventProperty property;
if (logger.BindProperty(keyValuePair.Key, keyValuePair.Value, false, out property))
logEventPropertyList.Add(property);
}
}
Type type = state.GetType();
TypeInfo typeInfo = type.GetTypeInfo();
if (str1 == null && !typeInfo.IsGenericType)
{
str1 = "{" + type.Name + ":l}";
LogEventProperty property;
if (logger.BindProperty(type.Name, SerilogLogger.AsLoggableValue<TState>(state, formatter), false, out property))
logEventPropertyList.Add(property);
}
}
if (str1 == null)
{
string propertyName = (string) null;
if ((object) state != null)
{
propertyName = "State";
str1 = "{State:l}";
}
else if (formatter != null)
{
propertyName = "Message";
str1 = "{Message:l}";
}
LogEventProperty property;
if (propertyName != null && logger.BindProperty(propertyName, SerilogLogger.AsLoggableValue<TState>(state, formatter), false, out property))
logEventPropertyList.Add(property);
}
if (eventId.Id != 0 || eventId.Name != null)
logEventPropertyList.Add(SerilogLogger.CreateEventIdProperty(eventId));
MessageTemplate messageTemplate = SerilogLogger.MessageTemplateParser.Parse(str1 ?? "");
LogEvent logEvent = new LogEvent(DateTimeOffset.Now, serilogLevel, exception, messageTemplate, (IEnumerable<LogEventProperty>) logEventPropertyList);
logger.Write(logEvent);
}
}

那么SerilogLogger 的_logger 到底是什么呢?我们可以看到其实这个_logger 是SerilogLoggerFactory到SerilogLoggerProvider到SerilogLogger一层一层传进去的,最后调用ForContext生成,上面可以看到具体各个的实例化函数。

如果我们不传的话,那么会默认使用 Serilog.Log.Logger,在SerilogLogger的实例化函数中这样写道:

Serilog.ILogger logger1 = this._logger;
if (logger1 == null)
logger1 = Serilog.Log.Logger.ForContext((IEnumerable<ILogEventEnricher>) new SerilogLoggerProvider[1]
{
provider
});
this._logger = logger1;

而在UseSerilog中logger参数的参数介绍中也写道: The Serilog logger; if not supplied, the static will be used.

之所以重点谈论这个_logger 这个数据,是为了引出Serilog.Log.Logger这个东西,是不是感觉特别眼熟?我们在Startup的ConfigureServices中写道:

services.AddLogSeriLog(Configuration);

AddLogSeriLog 为:

public static Serilog.ILogger AddLogSeriLog(this IServiceCollection services, IConfiguration configuration)
{
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration)
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.WriteTo.Console(new RenderedCompactJsonFormatter())
.WriteTo.File(formatter: new CompactJsonFormatter(), "logs\\test.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
return Log.Logger;
}

这里我们就生成了具体打印实现类的管理类,是代理模式哈。大体就是各个不同的实现类继承ILogEventSink,然后SafeAggregateSink也继承自ILogEventSink,SafeAggregateSink里面有个属性readonly ILogEventSink[] _sinks,然后调用Serilog的 Logger

类调用write方法会调用SafeAggregateSink的Emit。

SafeAggregateSink类如下,主要看下Emit方法:

class SafeAggregateSink : ILogEventSink
{
readonly ILogEventSink[] _sinks; public SafeAggregateSink(IEnumerable<ILogEventSink> sinks)
{
if (sinks == null) throw new ArgumentNullException(nameof(sinks));
_sinks = sinks.ToArray();
} public void Emit(LogEvent logEvent)
{
foreach (var sink in _sinks)
{
try
{
// 调用不同的Log打印的实现类 如文件、控制台 等
sink.Emit(logEvent);
}
catch (Exception ex)
{
SelfLog.WriteLine("Caught exception while emitting to sink {0}: {1}", sink, ex);
}
}
}
}

具体就不细写了,里面都是一些格式匹配的,根据我们上面的分析,其实我们应该知道先看CreateLogger这个函数哈,然后去看Console这个函数。如对格式化感兴趣可

以去看下哈,里面又套了一层代理模式的娃。

上述只是个人整理,如有错误,望请指出,谢谢。

下一次 中间件。 前面其实写个中间件的一些基本概念,介绍其实中间件就是职责链模式,所以下一节主要整理一下具体实践。

重新整理 .net core 实践篇—————日志系统之结构化[十八]的更多相关文章

  1. 重新整理 .net core 实践篇—————日志系统之作用域[十七]

    前言 前面介绍了服务与日志之间的配置,那么我们服务会遇到下面的场景会被遇到一些打log的问题. 前面我提及到我们的log,其实是在一个队列里面,而我们的请求是在并发的,多个用户同时发送请求这个时候我们 ...

  2. 重新整理 .net core 实践篇—————日志系统之战地记者[十五]

    前言 本节开始整理日志相关的东西.先整理一下日志的基本原理. 正文 首先介绍一下包: Microsoft.Extengsion.Logging.Abstrations 这个是接口包. Microsof ...

  3. 重新整理 .net core 实践篇—————日志系统之服务与日志之间[十六]

    前言 前文介绍了一些基本思路,那么这里介绍一下,服务如何与配置文件配合. 正文 服务: public interface ISelfService { void ShowLog(); } public ...

  4. 重新整理 .net core 实践篇—————配置系统之强类型配置[十]

    前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...

  5. 重新整理 .net core 实践篇————配置系统之盟约[五]

    前言 在asp .net core 中我们会看到一个appsettings.json 文件,它就是我们在服务中的各种配置,是至关重要的一部门. 不管是官方自带的服务,还是我们自己编写的服务都是用它来实 ...

  6. 重新整理 .net core 实践篇—————配置系统之军令状[七](配置文件)

    前言 介绍一下配置系统中的配置文件,很多服务的配置都写在配置文件中,也是配置系统的大头. 正文 在asp .net core 提供了下面几种配置文件格式的读取方式. Microsoft.extensi ...

  7. 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)

    前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...

  8. 重新整理 .net core 实践篇————配置系统——军令(命令行)[六]

    前言 前文已经基本写了一下配置文件系统的一些基本原理.本文介绍一下命令行导入配置系统. 正文 要使用的话,引入Microsoft.extensions.Configuration.commandLin ...

  9. 重新整理 .net core 实践篇—————配置系统之简单配置中心[十一]

    前言 市面上已经有很多配置中心集成工具了,故此不会去实践某个框架. 下面链接是apollo 官网的教程,实在太详细了,本文介绍一下扩展数据源,和简单翻翻阅一下apollo 关键部分. apollo 服 ...

随机推荐

  1. Mybatis-Plus03 代码自动生成器

    先看完Mybatis-Plus01和Mybatis-Plus02再看Mybatis-Plus03 AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerato ...

  2. 初始化mysql报错bin/mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory

    原因:缺少libaio.so.1 解决办法:安装即可 yum install -y libaio

  3. Linux安装Redis报错`cc:命令未找到`

    缺少gcc和gcc-c++的编译环境,安装即可. 可以联网情况下使用命令 yum install gcc yum install gcc-c++ 然后清理原来的残余文件 make distclean ...

  4. 在局域网内知道计算机的名字查找计算机的IP

    第一步 nbtstat -a 计算机名字 第二步 nbtstat -c 可以看到计算机地址

  5. 【近取 key】Alpha 阶段任务分配

    项目 内容 这个作业属于哪个课程 2021春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 alpha阶段初始任务分配 我在这个课程的目标是 进一步提升工程化开发能力,积累团队协作经验,熟悉 ...

  6. JVM内存溢出后服务还能运行吗

    文章开篇问一个问题吧,一个java程序,如果其中一个线程发生了OOM,那进程中的其他线程还能运行吗? 接下来做实验,看看JVM的六种OOM之后程序还能不能访问. 在这里我用的是一个springboot ...

  7. Java虚拟机栈和PC寄存器

    PC Register介绍 JVM中的程序计数寄存器(Program Counter Register)中,Register 的命名源于CPU的寄存器,寄存器存储指令相关的现场信息.CPU只有把数据装 ...

  8. queryset惰性与缓存

    https://blog.csdn.net/zhu6201976/article/details/83550461

  9. [笔记] 《c++ primer》书店程序 Chapter 1

    书店程序是<c++ primer>中重要的实例,涉及大部分重要知识点,但代码分散阅读不便,下面按照章节顺序总结 Sales_item.h #ifndef SALESITEM_H // we ...

  10. DNS和BIND

    https://www.jianshu.com/p/296b2c7ea76f DNS和BIND 毛利卷卷发关注 0.482018.07.25 10:33:44字数 4,919阅读 4,909 DNS ...