日志组件是NewLife系列组件最早最基础,同时也是流血流泪最多的一个模块,它的底蕴定能感动每一个用户!
没有日志的应用系统是不完整的。系统遇到啥问题,翻日志看看当时上下文,实在分析不出问题,修改代码再打几个日志……这是每一个程序员的日常写照。
不少同学喜欢调试程序,但是听过“薛定谔的猫”这个故事的人不多。有时候程序跑起来没问题,调试就有问题;有时候跑起来有问题,调试就没有问题。就像是薛定谔的猫,测不准原理,调试观察本身干涉了程序运行。这个时候就需要看日志。
单片机嵌入式设备、安卓移动应用、Linux嵌入式应用,虽然都可以在线调试,但也会有许多不方便的地方。如果能够配合使用日志,将会事半功倍。
Nuget包:NewLife.Core
源码:https://github.com/NewLifeX/X/tree/master/NewLife.Core/Log
视频:https://www.bilibili.com/video/BV1Y3411t7co
视频:https://www.bilibili.com/video/BV1De41157g1
最佳实践
所有类型.NET应用,引用 NewLife.Core 包后,在入口Main函数最开头写入:
XTrace.UseConsole();
它将同时在控制台和日志文件(Log目录)中输出 NewLife 组件的所有日志内容。
Get Started
新建控制台项目 ConsoleApp1 ,放到 D:\Test,其它目录也可以
从Nuget引用 NewLife.Core ,安装最新版本。
打开Program.cs的Main函数,写入一下代码
XTrace.UseConsole(); XTrace.WriteLine("Hello NewLife!");
点击XTrace左边图标,可选“using NewLife.Log;”自动添加命名空间引用。
点击上方绿色启动三角符号,或者按下F5,启动应用程序。
可以看到打开一个控制台窗口,并输入一行日志
13:15:06.270 1 N - Hello NewLife!
这就是我们前面代码输出的内容:XTrace.WriteLine("Hello NewLife!");
这也是最简单最常见的日志用法:XTrace.WriteLine
XTrace是静态跟踪类,WriteLine等日志输出方法,本质上是调用实现了ILog接口的XTrace.Log。
日志接口ILog
ILog是日志输出标准接口
/// <summary>写日志</summary> /// <param name="level">日志级别</param> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Write(LogLevel level, String format, params Object?[] args); /// <summary>调试日志</summary> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Debug(String format, params Object?[] args); /// <summary>信息日志</summary> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Info(String format, params Object?[] args); /// <summary>警告日志</summary> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Warn(String format, params Object?[] args); /// <summary>错误日志</summary> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Error(String format, params Object?[] args); /// <summary>严重错误日志</summary> /// <param name="format">格式化字符串</param> /// <param name="args">格式化参数</param> void Fatal(String format, params Object?[] args); /// <summary>是否启用日志</summary> Boolean Enable { get; set; } /// <summary>日志等级,只输出大于等于该级别的日志,默认Info</summary> LogLevel Level { get; set; }
Write核心方法,很少使用,一般都使用 Info/Debug/Warn/Error/Fatal 等接口,最终都是调用Write,只是日志等级不同。
日志对象可以独立控制是否启用,以及日志等级Level。
已有日志等级:
/// <summary>日志等级</summary> public enum LogLevel : System.Byte { /// <summary>打开所有日志记录</summary> All = 0, /// <summary>最低调试。细粒度信息事件对调试应用程序非常有帮助</summary> Debug, /// <summary>普通消息。在粗粒度级别上突出强调应用程序的运行过程</summary> Info, /// <summary>警告</summary> Warn, /// <summary>错误</summary> Error, /// <summary>严重错误</summary> Fatal, /// <summary>关闭所有日志记录</summary> Off = 0xFF }
文件日志
文本文件日志是最重要的日志,也是XTrace.Log的默认实现。
文本文件日志是把日志逐行输出到文本文件中,每天一个文件。
如果想要独立存储某个模块的日志,可以实例化一个专属的TextFileLog对象。推荐使用Create创建。
/// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary> /// <param name="path">日志目录或日志文件路径</param> /// <param name="fileFormat"></param> /// <returns></returns> public static TextFileLog Create(String path, String fileFormat = null); /// <summary>每个目录的日志实例应该只有一个,所以采用静态创建</summary> /// <param name="path">日志目录或日志文件路径</param> /// <returns></returns> public static TextFileLog CreateFile(String path);
文件日志内部有队列、延迟关闭等复杂逻辑,强烈建议使用单例,Create可确保单例实现。
文本文件日志主要特性:
- 每天一个文件,例如 2021_06_25.log
- 每个文件最大10M,(可在core.config中配置LogFileMaxBytes),超过后产生新的日志文件,例如 2021_06_25_2.log
- 日志目录为Log子目录,(可配置LogPath)
- 日志目录中最多只保存最新200个日志文件,(可配置LogFileBackups,网络安全法要求至少保存6个月日志)
- 日志等级,可配置LogLevel,默认Info,大于等于该等级才输出日志到文件
- 日志文件格式,可配置LogFileFormat,默认 {0:yyyy_MM_dd}.log,也可以按照日志等级区分目录,例如 {1}/{0:yyyy_MM_dd}.log
- 日志写入使用队列实现,避免影响应用层性能
- 如果连续5秒没有日志写入,则自动关闭日志文件句柄,此时用户可以根据需要移动或删除日志文件
这里的10M和200个,最大日志占用2G,可以有效的避免异常信息写爆磁盘!(说多都是泪……)
来看看上面Demo的日志文件
#Software: ConsoleApp1 #ProcessID: 38144 x64 #AppDomain: ConsoleApp1 #FileName: D:\Test\ConsoleApp1\ConsoleApp1\bin\Debug\net5.0\ConsoleApp1.exe #BaseDirectory: D:\Test\ConsoleApp1\ConsoleApp1\bin\Debug\net5.0\ #TempPath: C:\Users\Stone\AppData\Local\Temp\ #CommandLine: D:\Test\ConsoleApp1\ConsoleApp1\bin\Debug\net5.0\ConsoleApp1.dll #ApplicationType: Console #CLR: 5.0.7, .NET 5.0.7 #OS: Microsoft Windows NT 10.0.19042.0, X6/Stone #CPU: 8 #GC: IsServerGC=False, LatencyMode=Interactive #ThreadPool: Min=32/32, Max=32767/1000, Available=32766/1000 #Date: 2021-06-25 #字段: 时间 线程ID 线程池Y/网页W/普通N/定时T 线程名/任务ID 消息内容 #Fields: Time ThreadID Kind Name Message 13:42:42.463 1 N - NewLife.Core v8.10.2021.0604 Build 2021-06-07 09:05:06 .NETCoreApp,Version=v5.0 13:42:42.465 1 N - X组件核心库 ©2002-2021 NewLife 13:42:42.466 1 N - ConsoleApp1 v1.0.0 Build 2000-01-01 .NETCoreApp,Version=v5.0 13:42:42.466 1 N - ConsoleApp1 13:42:42.466 1 N - Hello NewLife!
可以看到,日志文件非常完善,还有一个很完整的日志头。
文本日志文件格式,参考了多款微软产品,头部井号#隔开的行是注释行,用于说明情况。
通过日志头,可以了解到一下信息:
- 软件名ConsoleApp1,取自进程名
- 进程Id,是否64位进程
- 应用程序域,这个现在没有太多意义了
- 执行文件名,全路径
- 基准目录,全路径。这个非常重要,很多人的控制台程序正常,改为系统服务以后异常,很大可能性就是因为这个基准目录变成了操作系统目录。
- 临时目录,X组件有时候需要使用临时目录
- 命令行参数
- 应用类型,.NETCore里面基本上都是Console了,有点难以区分WinForm和Web
- .NET版本,CLR版本
- 操作系统、CPU等
- GC设置
- 线程池设置值。
- 日志字段格式介绍
字段格式:
- 时间,时分秒和毫秒。这里的毫秒特别重要,可以精确知道各个操作执行耗时情况
- 线程Id,如果每个日志所属线程不加以区分,在多线程环境下就会一团糟
- 线程类型,线程池Y、网页W、普通N、定时T
- 线程名/任务Id
- 日志内容
由此可见,日志组件特别适用于分析多线程问题。
设计如此详尽的日志头,主要为了能够准确记录程序的执行上下文环境,特别对于客户端应用来说尤为重要!
控制台日志
前面例程中,Main函数开头有一行 XTrace.UseConsole() ,意思是使用控制台日志。如果没有这一行,XTrace.WriteLine默认只会写入文本文件日志。加上后,同时写文件和控制台。
控制台日志ConsoleLog没有日志头,其它跟文本文件日志一样,输出时间、线程信息和日志内容。
控制台日志多了个彩色显示,不同线程以不同颜色区分,便于快速区分同一个线程的日志。受制于控制台颜色样本不足,某些情况下不同线程可能使用相同的颜色。内置10种颜色,线程Id对颜色个数取余。
除了WinForm和早期ASP.Net,否则一般都使用 XTrace.UseControle() 把日志同步输出到控制台。对应用性能有严格要求时,可以注释这一行关闭控制台日志。
切记!!!控制台日志不宜过多,否则会严重影响应用性能,因为控制台数据内部带有锁需要排队。
控件日志
对于WinForm应用来说,希望能够把日志输出到某个富文本框之中。控件日志TextControlLog为此而生!
XTrace中有扩展方法 UseWinFormControl :
/// <summary>在WinForm控件上输出日志,主要考虑非UI线程操作</summary> /// <remarks>不是常用功能,为了避免干扰常用功能,保持UseWinForm开头</remarks> /// <param name="control">要绑定日志输出的WinForm控件</param> /// <param name="useFileLog">是否同时使用文件日志,默认使用</param> /// <param name="maxLines">最大行数</param> public static void UseWinFormControl(this Control control, Boolean useFileLog = true, Int32 maxLines = 1000)
在码神工具 NewLife.XCoder 的网络调试工具中,FrmMain_Load 有以下代码:
txtReceive.UseWinFormControl();
这里的txtReceive是富文本框RichTextBox,负责数据接收区,同时用于显示日志。
因此,UseWinFormControl 用于把日志重定向到富文本框,第二参数useFileLog指定继续写文件日志。
网络日志
NewLife.Core 组件也支持Android和iOS开发(基于Xamarin),由于设备上的日志很不方便实时查看,因而设计了网络日志。NetworkLog通过UDP协议把日志发送到局域网某个目标地址。
配置文件core.config中指定NetworkLog配置项,例如 udp://10.0.0.3:514 ,也可以使用广播地址 udp://255.255.255.255:514 。然后在开发机 10.0.0.3 上开一个码神工具,网络工具监听UDP514端口即可接收日志。
嵌入式Linux应用开发也可以使用同样办法。
网络日志降低了我们对移动应用和嵌入式应用的调试依赖。
星尘日志中心
网络日志还有一个隐藏技能,core.config 的 NetworkLog 填写http地址时,支持星尘日志中心。例如 http://star.newlifex.com:6600/log。
网络日志内部使用队列缓冲数据,按批发送到星尘服务端,服务端分表分库存储。