WPF开发-日志记录及全局异常捕获

使用log4net

注意

稍大点的应用推荐这种方式。可定义参数多。

安装

Nuget安装log4net

image-20220314125020144

项目根目录添加log4net.config

设置属性

image-20211209183912018

下面两种任选一种即可。

下面两种方式主要是封装的要解决无法在日志中显示异常位置的问题。

默认(推荐)

log4net.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<!--错误日志类-->
<logger name="logerror">
<!--日志类的名字-->
<level value="ALL" />
<!--定义记录的日志级别-->
<appender-ref ref="ErrorAppender" />
<!--记录到哪个介质中去-->
</logger>
<!--信息日志类-->
<logger name="loginfo">
<level value="ALL" />
<appender-ref ref="InfoAppender" />
</logger>
<!--错误日志附加介质-->
<appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
<!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
<param name="File" value="Log\\LogError\\" />
<!--日志输出到exe程序这个相对目录下-->
<param name="AppendToFile" value="true" />
<!--输出的日志不会覆盖以前的信息-->
<param name="MaxSizeRollBackups" value="100" />
<!--备份文件的个数-->
<param name="MaxFileSize" value="10240" />
<!--当个日志文件的最大大小-->
<param name="StaticLogFileName" value="false" />
<!--是否使用静态文件名-->
<param name="DatePattern" value="yyyyMMddHHmm&quot;.htm&quot;" />
<!--日志文件名-->
<param name="RollingStyle" value="Date" />
<!--文件创建的方式,这里是以Date方式创建-->
<!--错误日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="&lt;HR COLOR=red&gt;%n异常时间:%d [%t] &lt;BR&gt;%n异常级别:%-5p &lt;BR&gt;%n异常类:%class:%line &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" />
</layout>
</appender>
<!--信息日志附加介质-->
<appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Log\\LogInfo\\" />
<param name="AppendToFile" value="true" />
<param name="MaxFileSize" value="10240" />
<param name="MaxSizeRollBackups" value="100" />
<param name="StaticLogFileName" value="false" />
<param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
<param name="RollingStyle" value="Date" />
<!--信息日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="&lt;HR COLOR=blue&gt;%n日志时间:%d [%t] &lt;BR&gt;%n日志级别:%-5p &lt;BR&gt;%n日志类:%class:%line &lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" />
</layout>
</appender>
</log4net>

其中

%class 显示包名和类名

%class{1} 只显示类名

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.IO;

using log4net;
using log4net.Config;

namespace Z.Utils.Common
{
public class ZLogHelper
{
public static readonly ILog Loginfo = LogManager.GetLogger("loginfo");
public static readonly ILog Logerror = LogManager.GetLogger("logerror");

public static void InitLog4Net()
{
FileInfo logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config");
XmlConfigurator.ConfigureAndWatch(logCfg);
}

/// <summary>
/// 停止日志记录
/// </summary>
public static void StopLog()
{
log4net.LogManager.Shutdown();
}
}
}

调用

1
2
3
ZLogHelper.Loginfo.Info("课堂启动");

ZLogHelper.Logerror.Error("异常退出:",ex);

如果在应用结束前要上传日志文件,我们就要停止日志,防止文件占用

1
ZLogHelper.StopLog();

打印示例

日志时间:2023-11-06 15:21:41,108 [1]
日志级别:INFO
日志类:SchoolClient.MyApp:35
课堂启动

注意

这里之所以没有把打印日志封装是因为:打印的类和行号是被调用的类,如果封装后,显示的都会是封装的类,那么打印类名和行号就没有意义了。

简单封装(不推荐)

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<!--错误日志类-->
<logger name="logerror">
<!--日志类的名字-->
<level value="ALL" />
<!--定义记录的日志级别-->
<appender-ref ref="ErrorAppender" />
<!--记录到哪个介质中去-->
</logger>
<!--信息日志类-->
<logger name="loginfo">
<level value="ALL" />
<appender-ref ref="InfoAppender" />
</logger>
<!--错误日志附加介质-->
<appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
<!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
<param name="File" value="Log\\LogError\\" />
<!--日志输出到exe程序这个相对目录下-->
<param name="AppendToFile" value="true" />
<!--输出的日志不会覆盖以前的信息-->
<param name="MaxSizeRollBackups" value="100" />
<!--备份文件的个数-->
<param name="MaxFileSize" value="10240" />
<!--当个日志文件的最大大小-->
<param name="StaticLogFileName" value="false" />
<!--是否使用静态文件名-->
<param name="DatePattern" value="yyyyMMddHHmm&quot;.htm&quot;" />
<!--日志文件名-->
<param name="RollingStyle" value="Date" />
<!--文件创建的方式,这里是以Date方式创建-->
<!--错误日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="&lt;HR COLOR=red&gt;%n异常时间:%d [%t] &lt;BR&gt;%n异常级别:%-5p&lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" />
</layout>
</appender>
<!--信息日志附加介质-->
<appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Log\\LogInfo\\" />
<param name="AppendToFile" value="true" />
<param name="MaxFileSize" value="10240" />
<param name="MaxSizeRollBackups" value="100" />
<param name="StaticLogFileName" value="false" />
<param name="DatePattern" value="yyyyMMdd&quot;.htm&quot;" />
<param name="RollingStyle" value="Date" />
<!--信息日志布局-->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="&lt;HR COLOR=blue&gt;%n日志时间:%d [%t] &lt;BR&gt;%n日志级别:%-5p&lt;BR&gt;%n%m &lt;BR&gt;%n &lt;HR Size=1&gt;" />
</layout>
</appender>
</log4net>

添加工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using System;
using System.IO;

using log4net;
using log4net.Config;

namespace Z.Utils.Common
{
public class ZLogHelper
{
private static readonly ILog Loginfo = LogManager.GetLogger("loginfo");
private static readonly ILog Logerror = LogManager.GetLogger("logerror");

public static void InitLog4Net()
{
var logCfg = new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config");
XmlConfigurator.ConfigureAndWatch(logCfg);
}

/// <summary>
/// 停止日志记录
/// </summary>
public static void StopLog()
{
log4net.LogManager.Shutdown();
}

public static void WriteInfoLog
(
string message,
[System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int callerLineNumber = 0
)
{
Loginfo.InfoFormat(
"日志类:{0}:{1}<br>{2}",
callerFilePath,
callerLineNumber,
message
);
}

public static void WriteErrLog
(
string message,
Exception ex,
[System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int callerLineNumber = 0
)
{
Logerror.InfoFormat(
"日志类:{0}:{1}<br>{2}<br>{3}",
callerFilePath,
callerLineNumber,
message,
ex.Message
);
}
}
}

注意

在调用记录日志前,要调用InitLog4Net方法。

因为封装了,所以自带的打印类和行数就不生效了,我们可以自己进行格式化拼接。

调用

1
2
ZLogHelper.WriteInfoLog("课堂启动");
ZLogHelper.WriteErrLog("异常退出:",ex);

打印示例

这种方式获取到的文件的路径,暂时没找到获取包名+类名的方式。

日志时间:2023-11-06 14:59:36,168 [1]
日志级别:INFO
日志类:D:\Project\csharp\xh-schoolclient\MyApp.xaml.cs:35
课堂启动

自定义日志类

注意

小应用可以使用这种方式。

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.IO;
using System.Threading;

namespace Z.Utils.Common
{
public class LogHelper
{
//读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入
private static readonly ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();

public static void LogWrite(Exception ex)
{
if (!Directory.Exists("Log"))
{
Directory.CreateDirectory("Log");
}
string logpath = @"Log\" + DateTime.Now.ToString("yyyy-MM-dd-HH") + ".html";
string log = "<div style='border: 1px solid #CCC;margin-bottom:10px;padding:10px;'>"
+ "<h3>" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "</h3>"
+ ex.Message
+ "<br>"
+ ex.InnerException
+ "<br>"
+ ex.StackTrace
+ "</div>";
try
{
//设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入
LogWriteLock.EnterWriteLock();
File.AppendAllText(logpath, log);
}
finally
{
//退出写入模式,释放资源占用
LogWriteLock.ExitWriteLock();
}
}
}
}

调用时

1
2
//记录日志
LogHelper.LogWrite(ex);

异常测试

1
LogHelper.LogWrite(new Exception("主页面启动"));

全局异常捕获

这里的示例采用的第三方库的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
ZLogHelper.InitLog4Net();
RegisterEvents();
base.OnStartup(e);
}

private void RegisterEvents()
{
//Task线程内未捕获异常处理事件
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

//UI线程未捕获异常处理事件(UI主线程)
DispatcherUnhandledException += App_DispatcherUnhandledException;

//非UI线程未捕获异常处理事件(例如自己创建的一个子线程)
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}

//Task线程内未捕获异常处理事件
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
try
{
if (e.Exception is Exception exception)
{
HandleException(exception);
}
}
catch (Exception ex)
{
HandleException(ex);
}
finally
{
e.SetObserved();
}
}

//非UI线程未捕获异常处理事件(例如自己创建的一个子线程)
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
try
{
if (e.ExceptionObject is Exception exception)
{
HandleException(exception);
}
}
catch (Exception ex)
{
HandleException(ex);
}
}

//UI线程未捕获异常处理事件(UI主线程)
private static void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
try
{
HandleException(e.Exception);
}
catch (Exception ex)
{
HandleException(ex);
}
finally
{
e.Handled = true;
}
}

//日志记录
private static void HandleException(Exception ex)
{
//记录日志
ZLogHelper.Logerror.Error(ex.Message);
Current.Shutdown();
}
}