整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

系统日志收集之初探rsyslog

系统日志收集之初探rsyslog

统内核和许多程序会产生各种错误信息、警告信息和其他的提示信息,这些信息对用户了解系统的运行状态是非常有用的,所以需要把它们保存到对应的日志文件中,以便后续进行分析和监控系统或软件的状态。 Linux 系统拥有非常灵活和强大的日志功能,几乎可以保存所有的操作记录,并可以从中检索出我们需要的信息。完成这个工作的守护进程就是 rsyslog。

介绍

谈及 rsyslog[1],就不可避免的涉及另外两个软件 syslog[2]syslog-ng[3]。这三款软件设计的目标是一样的,就是解决系统和程序的日志收集问题。每一个项目都在试图提高前者的稳定性和功能性。

syslog

syslog 由 Eric Allman 在 1980 年代开发,是 Sendmail 项目的一部分。因为它的易用性被很多类 Unix 系统使用,成为其标准的日志记录解决方案。在这个过程中,它演变成一种协议,成为在互联网协议(TCP/IP)的网络中传递记录档消息的标准。

syslog 协议属于一种主从式协议:syslog 发送端会发送出一个小的文字消息(小于 1024 位组)到 syslog 接收端。接收端通常名为 syslogd、syslog daemon 或 syslog 服务器。系统日志消息可以被以 UDP 协议或 TCP 协议来发送。这些资料是以明码类型被发送。不过由于 SSL 加密外套(例如 Stunnel、sslio 或 sslwrap 等)并非 syslog 协议本身的一部分,因此可以被用来透过SSL/TLS 方式提供一层加密。

syslog-ng

syslog-ng 是 syslog NextGeneration 的简写。该项目发起于 1998 年并基于 syslog 协议开发。作为 syslog 的下一代产品,功能肯定比 syslog 强大的多,如高性能,可靠的传输,支持多平台,高可靠性,众多的用户群体,强大的日志过滤及排序,事件标签和关联性,支持最新的IETF标准等。

rsyslog

rsyslog 是 the rocket-fast system for log processing 的简写。该项目始于 2004 年,当时 rsyslog 的主要作者 Rainer Gerhards 决定编写一个新的强大 syslog 守护程序来与 syslog-ng 竞争。它实现了基本的 syslog 协议,并扩展了基于内容的过滤功能,丰富了过滤能力,处理脱机输出的队列操作,支持不同模块的输出,配置选项更加灵活,并添加了使用 TCP 进行传输的功能。

由于 rsyslog 的高性能,出色的安全性和模块化设计,它不仅作为常规的系统日志收集工具,还能够接受各种来源的输入,将其转换,然后将结果输出到不同的目的地。

它的优势有如下:

? 高性能(使用 C 编写,运用多线程)

? 支持 TCP, SSL, TLS, RELP

? 支持多种输出(MySQL, PostgreSQL等)

? 支持对系统日志的过滤

? 灵活配置多种输出

正因为如此,它是众多类 Unix 系统和 GNU/Linux 发行版系统日志采集的首选。

syslog 协议

既然三款软件都基于 syslog 协议,那就先来介绍一下 syslog 协议。相关 RFC 文件有 2001 年发行的RFC 3164[4](The BSD syslog Protocol), 2009 年发行的 RFC 5424[5](The Syslog Protocol), RFC 5425[6](Transport Layer Security Mapping for Syslog),RFC 5426[7](Transmission of Syslog Messages over UDP)。其中RFC 3164 已经被 RFC 5424 废除,所以下面介绍的以 RFC 5424 为准。

三层模型

Syslog 协议使用三层结构,第一层是消息层,指要传输的信息;第二层是应用层,主要用于消息的生成,解析,路由和存储,代表有发送者,中继器和接受者。第三层是传输层,主要用于发送和接收网络上的信息,代表有发送设备和接收设备。 具体的层次结构见下图(这块的理解可以参考 OSI 7层模型或 TCP/IP 4层模型):

部署场景

syslog 协议遵循以下的原则:

? 协议没有信息确认机制 消息从发送者发送到接受者的 UDP 514 端口,不需要接收方应答。

? 发送者和中继器可以将相同的消息发送给多个接收者和中继器

? 发送者,中继器和接收者可以部署在同一个系统上。

按照上述原则,有如下图的部署场景。

消息格式

syslog 协议定义了消息格式,由三部分组成:消息头 HEADER ,结构化数据 STRUCTURED-DATA 和消息 MSG(可选)。其中消息头又包含优先级(PRIority),版本号(VERSION),时间戳(TIMESTAMP),主机名(HOSTNAME), 应用名(APP-NAME),进程标识(PROCID)和消息标识(MSGID)。

其中优先级由设备(Facility)和严重性(Severity)共同决定。PRI=Facility * 8 + Severity。 设备的可选值有以下24个:

代号

设备(Facility)

注释

0

kernel messages

内核相关

1

user-level messages

用户相关(默认)

2

mail system

邮件相关

3

system daemons

系统守护进程相关

4

security/authorization messages (note 1)

登陆授权相关

5

messages generated internally by syslogd

syslogd相关

6

line printer subsystem

打印相关

7

network news subsystem

新闻相关

8

UUCP subsystem

unix到unix的cp相关

9

clock daemon (note 2)

任务计划相关

10

security/authorization messages (note 1)

登陆授权相关

11

FTP daemon

FTP相关

12

NTP subsystem


13

log audit (note 1)

登陆授权相关

14

log alert (note 1)

登陆授权相关

15

clock daemon (note 2)

任务计划相关

16

local use 0 (local0)

用户自定义0

17

local use 1 (local1)

用户自定义1

18

local use 2 (local2)

用户自定义2

19

local use 3 (local3)

用户自定义3

20

local use 4 (local4)

用户自定义4

21

local use 5 (local5)

用户自定义5

22

local use 6 (local6)

用户自定义6

23

local use 7 (local7)

用户自定义7

严重性的可选值有以下8个,这也是Facility * 8的原因:

代号

严重性(Severity)

注释

0

Emergency: system is unusable

崩溃级别

1

Alert: action must be taken immediately

报警级别

2

Critical: critical conditions

危急级别

3

Error: error conditions

错误级别

4

Warning: warning conditions

警告级别

5

Notice: normal but significant condition

提示级别

6

Informational: informational messages

消息级别

7

Debug: debug-level messages

调试级别

其它注意点

? 协议的实现必须支持基于 TLS 的传输,应该支持基于 UDP 的传输

? 所有接收设备必须能够结构长度不超过480个八位字节的消息,应该接收长度最大为2048个八位字节的消息,可以接收超过2048个八位字节的消息(可以截断或丢弃)。

rsyslog 使用

鉴于 rsyslog 已经是众多类 Unix 系统和 GNU/Linux 发行版系统日志采集的首选,所以这里重点介绍一下 rsyslog 使用。

配置文件

一般 rsyslog 的配置文件在 /etc/rsyslog.conf, 其由 3 个部分组成:模块(MODULES),全局设置(GLOBAL DRICTIVES)和规则(RULE)。这里以 Centos 中 rsyslog 配置为例。

# rsyslog configuration file

# For more information see /usr/share/doc/rsyslog-*/rsyslog_conf.html
# If you experience problems, see http://www.rsyslog.com/doc/troubleshoot.html

#### 模块MODULES ####

# The imjournal module bellow is now used as a message source instead of imuxsock.
$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
$ModLoad imjournal # provides access to the systemd journal
#$ModLoad imklog # reads kernel messages (the same are read from journald)
#$ModLoad immark  # provides --MARK-- message capability

# Provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514


#### 全局设置GLOBAL DIRECTIVES ####

# Where to place auxiliary files
$WorkDirectory /var/lib/rsyslog

# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

# File syncing capability is disabled by default. This feature is usually not required,
# not useful and an extreme performance hit
#$ActionFileEnableSync on

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf

# Turn off message reception via local log socket;
# local messages are retrieved through imjournal now.
$OmitLocalLogging on

# File to store the position in the journal
$IMJournalStateFile imjournal.state


#### 规则RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
*.info;mail.none;authpriv.none;cron.none                /var/log/messages

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog


# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 :omusrmsg:*

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log


# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#$ActionQueueFileName fwdRule1 # unique name prefix for spool files
#$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
#$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
#$ActionQueueType LinkedList   # run asynchronously
#$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
#*.* @@remote-host:514
# ### end of the forwarding rule ###

每次修改配置文件后可以通过以下操作来判断配置文件是否合理并重启 rsyslogd 服务。

# 验证配置文件 /etc/syslog.conf 是否合理
rsyslogd -f /etc/rsyslog.conf -N1
# 重启 rsyslog 服务
systemctl restart rsyslog

基本验证

一般系统都会提供 logger 命令行,可以使用其向系统输入日志。

# -i 记录进程id
# -t 标识记录的tag
# -p 指定消息的设备信息和日志等级,默认user.info
logger -i -t 'hjy_test' -p facility.level 'message'

实战

网上有一篇博文[8]是介绍将 rsyslog 的日志输出到 mysql 中,当然强大的 rsyslog 不仅支持输出到 mysql 中,基本覆盖了所有的主流存储软件。 不过这里介绍的实战是利用 rsyslog 的用户自定义设备来实现推荐系统的用户行为收集。大体的思路是推荐请求或数据上报请求通过 nginx 将请求转发到多台 online 服务中的一个。 该服务处理完请求后会将推荐的数据或打点上报的数据通过 SyslogHandler 汇总到目标服务器上进行推荐系统用户行为的统一处理。具体架构见下图:

优势如下:

? 速度快,稳定性高,性能好(支持百万QPS,压测到十万级别[9])

? rsyslogd 一般服务器自带,不需要安装,只需简单配置

? 客户端实现简单,比如 Python 中 logger 就有 SyslogHandler 来向 rsyslogd 发送日志。

参考文献

1.rsyslog官网[10]

2.rsyslog源码[11]

3.维基百科syslog[12]

4.维基百科rsyslog[13]

5.维基百科syslog-ng[14]

6.三种syslog比较[15]

7.rfc3164[16]

8.rfc5424[17]

9.rfc5425[18]

10.rfc5426[19]

11.rsyslog配置[20]

12.记录rsyslog日志到mysql[21]

13.rsyslog 的 TCP 转发性能测试[22]

References

[1] rsyslog: https://en.wikipedia.org/wiki/Rsyslog
[2] syslog:
https://zh.wikipedia.org/wiki/Syslog
[3] syslog-ng:
https://en.wikipedia.org/wiki/Syslog-ng
[4] RFC 3164:
https://tools.ietf.org/html/rfc3164
[5] RFC 5424:
https://tools.ietf.org/html/rfc5424
[6] RFC 5425:
https://tools.ietf.org/html/rfc5425
[7] RFC 5426:
https://tools.ietf.org/html/rfc5426
[8] 博文:
https://www.codenong.com/cs105581064/
[9] 压测到十万级别:
http://chenlinux.com/2015/02/12/rsyslog-forwarder-testing/

NET 中的日志使用技巧

Serilog

Serilog 是 .NET 社区中使用最广泛的日志框架,所以笔者使用一个小节单独讲解使用方法。

示例项目在 Demo2.Console 中。

创建一个控制台程序,引入两个包:

Serilog.Sinks.Console
Serilog.Sinks.File

除此之外,还有 Serilog.Sinks.ElasticsearchSerilog.Sinks.RabbitMQ 等。Serilog 提供了用于将日志事件以各种格式写入存储的接收器。下面列出的许多接收器都是由更广泛的 Serilog 社区开发和支持的;https://github.com/serilog/serilog/wiki/Provided-Sinks

可以直接使用代码配置 Serilog:

private static Serilog.ILogger GetLogger()
{
const string LogTemplate="{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}";
var logger=new LoggerConfiguration()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.Enrich.FromLogContext()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.WriteTo.Console(outputTemplate: LogTemplate)
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day, outputTemplate: LogTemplate)
.CreateLogger();
return logger;
}

如果想从配置文件中加载,添加 Serilog.Settings.Configuration:

private static Serilog.ILogger GetJsonLogger()
{
IConfiguration configuration=new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile(path: "serilog.json", optional: true, reloadOnChange: true)
.Build();
if (configuration==)
{
throw new ArgumentException($"未能找到 serilog.json 日志配置文件");
}
var logger=new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
return logger;
}

serilog.json 配置文件示例:

{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Debug"
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"outputTemplate": "{SourceContext} {Scope} {Timestamp:HH:mm} [{Level}] {Message:lj} {Properties:j} {NewLine}{Exception}"
}
}
]
}
}

依赖注入 Serilog。

引入 Serilog.Extensions.Logging 包。

private static Microsoft.Extensions.Logging.ILogger InjectLogger()
{
var logger=GetJsonLogger();
var ioc=new ServiceCollection();
ioc.AddLogging(builder=> builder.AddSerilog(logger: logger, dispose: true));
var loggerProvider=ioc.BuildServiceProvider().GetRequiredService<ILoggerProvider>();
return loggerProvider.CreateLogger("Program");
}

最后,使用不同方式配置 Serilog 日志,然后启动程序打印日志。

static void Main()
{
var log1=GetLogger();
log1.Debug("溪源More、痴者工良");
var log2=GetJsonLogger();
log2.Debug("溪源More、痴者工良");
var log3=InjectLogger();
log3.LogDebug("溪源More、痴者工良");
}
20:50 [Debug] 溪源More、痴者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}
20:50 [Debug] 溪源More、痴者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}
20:50 [Debug] 溪源More、痴者工良 {"MachineName": "WIN-KQDULADM5LA", "ThreadId": 1}

在 ASP.NET Core 中使用日志

示例项目在 Demo2.Api 中。

新建一个 ASP.NET Core API 新项目,引入 Serilog.AspNetCore 包。

在 Program 中添加代码注入 Serilog 。

var builder=WebApplication.CreateBuilder(args);

Log.Logger=new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.CreateLogger();
builder.Host.UseSerilog(Log.Logger);
//builder.Host.UseSerilog();

将前面示例中的 serilog.json 文件内容复制到 appsettings.json 中。

启动程序后,尝试访问 API 接口,会打印示例如下的日志:

Microsoft.AspNetCore.Hosting.Diagnostics 20:32 [Information] Request finished HTTP/1.1 GET http://localhost:5148/WeatherForecast - - - 200 - application/json;+charset=utf-8 1029.4319ms {"ElapsedMilliseconds": 1029.4319, "StatusCode": 200, "ContentType": "application/json; charset=utf-8", "ContentLength": , "Protocol": "HTTP/1.1", "Method": "GET", "Scheme": "http", "Host": "localhost:5148", "PathBase": "", "Path": "/WeatherForecast", "QueryString": "", "EventId": {"Id": 2}, "RequestId": "0HMOONQO5ONKU:00000003", "RequestPath": "/WeatherForecast", "ConnectionId": "0HMOONQO5ONKU"}

如果需要为请求上下文添加一些属性信息,可以添加一个中间件,示例如下:

app.UseSerilogRequestLogging(options=>
{
options.EnrichDiagnosticContext=(diagnosticContext, httpContext)=>
{
diagnosticContext.Set("TraceId", httpContext.TraceIdentifier);
};
});
 HTTP GET /WeatherForecast responded 200 in 181.9992 ms {"TraceId": "0HMSD1OUG2DHG:00000003" ... ...

对请求上下文添加属性信息,比如当前请求的用户信息,在本次请求作用域中使用日志打印信息时,日志会包含这些上下文信息,这对于分析日志还有帮助,可以很容易分析日志中那些条目是同一个上下文。在微服务场景下,会使用 ElasticSearch 等日志存储引擎查询分析日志,如果在日志中添加了相关的上下文属性,那么在分析日志时可以通过对应的属性查询出来,分析日志时可以帮助排除故障。

如果需要打印 http 的请求和响应日志,我们可以使用 ASP.NET Core 自带的 HttpLoggingMiddleware 中间件。

首先注入请求日志拦截服务。

builder.Services.AddHttpLogging(logging=>
{
logging.LoggingFields=HttpLoggingFields.All;
// 避免打印大量的请求和响应内容,只打印 4kb
logging.RequestBodyLogLimit=4096;
logging.ResponseBodyLogLimit=4096;
});

通过组合 HttpLoggingFields 枚举,可以配置中间件打印 Request、Query、HttpMethod、Header、Response 等信息。

可以将HttpLogging 中间件放在 Swagger、Static 之后,这样的话可以避免打印哪些用处不大的请求,只保留 API 请求相关的日志。

app.UseHttpLogging();

HttpLoggingMiddleware 中的日志模式是以 Information 级别打印的,在项目上线之后,如果每个请求都被打印信息的话,会降低系统性能,因此我们可以在配置文件中覆盖配置,避免打印普通的日志。

"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

上下文属性和作用域

示例项目在 Demo2.ScopeLog 中。

日志范围注意事项
Microsoft.Extensions.Logging.Abstractions 提供 BeginScopeAPI,可用于添加任意属性以记录特定代码区域内的事件。

解释其作用

API 有两种形式:

IDisposable BeginScope<TState>(TState state)
IDisposable BeginScope(this ILogger logger, string messageFormat, params object[] args)

使用如下的模板:

{SourceContext} {Timestamp:HH:mm} [{Level}] (ThreadId:{ThreadId}) {Message}{NewLine}{Exception} {Scope}

使用示例:

 static void Main()
{
var logger=GetLogger();
using (logger.BeginScope("Checking mail"))
{
// Scope is "Checking mail"
logger.LogInformation("Opening SMTP connection");

using (logger.BeginScope("Downloading messages"))
{
// Scope is "Checking mail" -> "Downloading messages"
logger.LogError("Connection interrupted");
}
}
}

而在 Serilog 中,除了支持上述接口外,还通过 LogContext 提供了在日志中注入上下文属性的方法。其作用是添加属性之后,使得在其作用域之内打印日志时,日志会携带这些上下文属性信息。

 using (LogContext.PushProperty("Test", 1))
{
// Process request; all logged events will carry `RequestId`
Log.Information("{Test} Adding {Item} to cart {CartId}", 1,1);
}

嵌套复杂一些:

using (LogContext.PushProperty("A", 1))
{
log.Information("Carries property A=1");

using (LogContext.PushProperty("A", 2))
using (LogContext.PushProperty("B", 1))
{
log.Information("Carries A=2 and B=1");
}

log.Information("Carries property A=1, again");
}

当需要设置大量属性时,下面的方式会比较麻烦;

using (LogContext.PushProperty("Test1", 1))
using (LogContext.PushProperty("Test2", 2))
{
}

例如在 ASP.NET Core 中间件中,我们可以批量添加:

 public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var enrichers=new List<ILogEventEnricher>();
if (!string.IsOrEmpty(correlationId))
{
enrichers.Add(new PropertyEnricher(_options.EnricherPropertyNames.CorrelationId, correlationId));
}

using (LogContext.Push(enrichers.ToArray()))
{
await next(context);
}
}

在业务系统中,可以通过在中间件获取 Token 中的用户信息,然后注入到日志上下文中,这样打印出来的日志,会携带用户信息。

非侵入式日志

非侵入式的日志有多种方法,比如 ASP.NET Core 中间件管道,或者使用 AOP 框架。

这里可以使用笔者开源的 CZGL.AOP 框架,Nuget 中可以搜索到。

示例项目在 Demo2.AopLog 中。

有一个类型,我们需要在执行 SayHello 之前和之后打印日志,将参数和返回值记录下来。

 public class Hello
{
public virtual string SayHello(string content)
{
var str=$"Hello,{content}";
return str;
}
}

编写统一的切入代码,这些代码将在函数被调用时执行。

Before 会在被代理的方法执行前或被代理的属性调用时生效,你可以通过 AspectContext 上下文,获取、修改传递的参数。

After 在方法执行后或属性调用时生效,你可以通过上下文获取、修改返回值。

public class LogAttribute : ActionAttribute
{
public override void Before(AspectContext context)
{
Console.WriteLine($"{context.MethodInfo.Name} 函数被执行前");
foreach (var item in context.MethodValues)
Console.WriteLine(item.ToString());
}

public override object After(AspectContext context)
{
Console.WriteLine($"{context.MethodInfo.Name} 函数被执行后");
Console.WriteLine(context.MethodResult.ToString());
return context.MethodResult;
}
}

改造 Hello 类,代码如下:

[Interceptor]
public class Hello
{
[Log]
public virtual string SayHello(string content)
{
var str=$"Hello,{content}";
return str;
}
}

然后创建代理类型:

 static void Main(string[] args)
{
Hello hello=AopInterceptor.CreateProxyOfClass<Hello>();
hello.SayHello("any one");
Console.Read();
}

启动程序,会输出:

SayHello 函数被执行前
any one
SayHello 函数被执行后
Hello,any one

你完全不需要担心 AOP 框架会给你的程序带来性能问题,因为 CZGL.AOP 框架采用 EMIT 编写,并且自带缓存,当一个类型被代理过,之后无需重复生成。

CZGL.AOP 可以通过 .NET Core 自带的依赖注入框架和 Autofac 结合使用,自动代理 CI 容器中的服务。这样不需要 AopInterceptor.CreateProxyOfClass 手动调用代理接口。

CZGL.AOP 代码是开源的,可以参考笔者另一篇博文:

https://www.cnblogs.com/whuanle/p/13160139.html

. Log4Net简介

Log4net是从Java中的Log4j迁移过来的一个.Net版的开源日志框架,它的功能很强大,可以将日志分为不同的等级,以不同的格式输出到不同的存储介质中,比如:数据库、txt文件、内存缓冲区、邮件、控制台、ANSI终端、远程接收端等等,我们这里主要介绍最常用的两种:txt文件和数据库。

(PS:其它的存储介质详见 http://logging.apache.org/log4net/release/config-examples.html)

Log4net将日志分为五个级别,分别是: FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息),每个级别都对应着一组重载方法进行调用。

官网地址:http://logging.apache.org/log4net/index.html

Nuget地址:https://www.nuget.org/packages/log4net/

Nuget安装:Install-Package log4net

最新版本:2.0.8 (2018-08-09)

本节主要围绕两个主要的存储介质:【txt文件】和【SQLServer数据库】展开,涵盖的知识点有:

①. 基本的使用步骤。

②. 初始化关联配置文件的几种形式。

③. 代码调用详解。

④. 配置文件详解。

⑤. 简单的封装和在MVC框架中的调用。

二. 基本使用步骤

我们先以控制台程序为例,简单介绍Log4net存储日志到txt文本文档中,后面在做代码的详解。

1. 新建01-SimpleDemo控制台程序,通过指令 【Install-Package log4net】安装相应程序集。

2. 在默认配置文件中App.config(B/S程序则为web.config)中进行配置,主要分两块:

A. 在<configuration></configuration>节点下新增节点下新增(要在其最顶部):

<configSections>

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />

</configSections>

B. 在<configuration></configuration>根节点下,配置log4net的核心配置代码, 主要节点如下:

<log4net> <appender> </appender> <root></root> </log4net>

详细代码如下:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3 <!-- 1. 添加log4net的节点声明配置代码-->
 4 <configSections>
 5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 6 </configSections>
 7 <!--2. log4net的核心配置代码-->
 8 <log4net> 
 9 <!--把日志信息输出到以日期命名的文件里-->
10 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
11 <!--文件夹的位置-->
12 <file value="D:\MyLog1\" />
13 <appendToFile value="true" />
14 <!--动态生成文件名-->
15 <param name="StaticLogFileName" value="false" />
16 <!--以日期命名-->
17 <param name="DatePattern" value="yyyyMMdd".log"" />
18 <rollingStyle value="Date" />
19 <!--日志在日志文件中的布局方式-->
20 <layout type="log4net.Layout.PatternLayout">
21 <conversionPattern value="%newline %n记录时间:%date %n线程ID:[%thread] %n日志级别: %-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n"/>
22 </layout>
23 <!--使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件 -->
24 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
25 </appender> 
26 <root>
27 <level value="ALL"></level>
28 <appender-ref ref="RollingFileAppender"></appender-ref>
29 </root> 
30 </log4net>
31 <startup>
32 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
33 </startup>
34 </configuration>

3. 代码调用

1 log4net.Config.XmlConfigurator.Configure();
2 ILog log=LogManager.GetLogger("test");
3 log.Debug("调试信息");

4. 运行结果

截止此处,日志保存成功。

三. 初始化配置文件

 前面提到在默认配置文件中App.config(B/S程序则为web.config)中进行配置,可以通过代码 log4net.Config.XmlConfigurator.Configure(); 来初始化配置,或者还可以通过 [assembly: log4net.Config.XmlConfigurator()] 反射的形式进行初始化配置,二者可以达到同样的效果,代表了两种初始化配置文件的形式。

PS: [assembly: log4net.Config.XmlConfigurator()] 可以加在 当前使用文件的 namespace上作用于当前文件,或者加在Properties/AssemblyInfo.cs中,则该项目全局都无须再初始化了。

在实际项目中,默认的配置文件里可能包含很多框架的信息,这个时候把 log4net的配置代码再放入进去,就会显得有点杂乱,或者有些“奇葩”的人把默认配置文件改名了,这个时候使用上述默认的两种方式就不好用了,那么这种情况怎么处理呢?

这里重点介绍 通过 log4net.Config.XmlConfigurator.Configure(); 来关联配置文件。

情况一: 使用默认配置文件的情况

1. 代码配置:log4net.Config.XmlConfigurator.Configure();

2. 反射配置:[assembly: log4net.Config.XmlConfigurator()]

(这里只是举例,很少有修改默认配置文件名称的)

1. 代码配置: 首先将App1.config文件的属性中的“生成操作”改为“ 嵌入的资源”,然后通过以下代码进行配置。

1 Assembly assembly=Assembly.GetExecutingAssembly();
2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.App1.config");
3 log4net.Config.XmlConfigurator.Configure(xml);

注:代码中的 _01_SimpleDemo 为命名空间名。

2. 反射配置:[assembly: log4net.Config.XmlConfigurator(ConfigFile="log4net.xml")]

  注:用这种方式属性中的:复制到输出目录需要改为:始终复制,生成操作不需要配置,使用默认:无 即可

情况三:新建单独xml文件,进行log4net的配置 (推荐采用这种方式,和原配置文件区分开,单独配置方便,处理方式和情况二是一致的)

1. 代码配置:首先将log4net.xml文件的属性中的“生成操作”改为“ 嵌入的资源”,然后通过以下代码进行配置。

1 Assembly assembly=Assembly.GetExecutingAssembly();
2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.log4net.xml");
3 log4net.Config.XmlConfigurator.Configure(xml);

注:代码中的 _01_SimpleDemo 为命名空间名。

情况四:无论是修改默认配置文件的名称为 或者 新建单独的xml作为配置文件 → 可以通过绝对路径的方式进行处理 【不推荐这种方式】

1. 直接写绝对路径 (注意这种方式【不需要】配置文件属性为 “嵌入的资源”)

1 log4net.Config.XmlConfigurator.Configure(new FileInfo(@"D:\06-我的开发之路\DotNet体系\05-DotNet框架篇\03-Log4net详解\Code\01-SimpleDemo\log4net.xml"));

2 通过代码获取绝对路径 (注意这种方式【不需要】配置文件属性的“生成操作”改为 “嵌入的资源”,但需要改为“始终复制”,确保输出到bin文件下)

1 string assemblyFilePath=Assembly.GetExecutingAssembly().Location;
2 string assemblyDirPath=Path.GetDirectoryName(assemblyFilePath);
3 string configFilePath=assemblyDirPath + " //log4net.xml";
4 log4net.Config.XmlConfigurator.Configure(new FileInfo(configFilePath));

PS:B/S程序下通过 log4net.Config.XmlConfigurator.Configure(new FileInfo(Server.MapPath("~") + @"/log4net.xml")); 来配置。

四. 代码调用详解

Log4net允许多个ILog对象同时存在,通过代码:ILog log=LogManager.GetLogger("xxx"); 来创建。

A: 日志级别由高到低分别为:FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息),另外还有 OFF和 ALL 。

几点说明:OFF表示所有信息都不写入,ALL表示所有信息都写入,我们也可以通过:<root><level value="WARN" ></ level ></root>这样配置,表示WARN级别以及高于WARN以上的级别才会被写入日志

B: 写入日志的方法有:Debug、Error、Fatal、Info、Warn五个方法,每个方法都有两个重载,如下图:

分享在使用配置文件为log4net.xml的情况下的调用代码:

 1 Assembly assembly=Assembly.GetExecutingAssembly();
 2 var xml=assembly.GetManifestResourceStream("_01_SimpleDemo.log4net.xml");
 3 log4net.Config.XmlConfigurator.Configure(xml);
 4 ILog log=LogManager.GetLogger("test");
 5 log.Debug("调试信息");
 6 log.Info("一般信息");
 7 log.Warn("警告");
 8 try
 9 {
10 int.Parse("ddd");
11 }
12 catch (Exception ex)
13 {
14 log.Error("一般错误", ex);
15 }
16 log.Fatal("致命错误");

五. 配置文件详解

Log4net的配置文件主要分为两大部分:分别是 【自定义配置节点】和 【核心代码配置】,自定义配置节点代码固定,如下图,核心代码配置主要位于:<log4net></log4net>节点中,里面包括<appender></appender>节点配置日日志输出途径 和 <root></root>节点,用于设置记录日志的级别和启用哪些输出途径。

几点说明:

1. 自定义配置节点 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> 代码固定,直接复制即可。

2. <root></root> 节点主要用来: 配置日志的的输出级别和加载日志的输出途径。

A: level中的value值表示该值及其以上的日志级别才会输出,日志级别包括:OFF > FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息) > ALL ,比如:

    <level value="INFO"></level> 表示只有INFO及其以上的日志级别才会被保存。

PS:OFF表示所有信息都不写入,ALL表示所有信息都写入。

B: <appender-ref></appender-ref>标签用于加载日志的输出途径代码,通过ref和appender标签的中name属性相关联,比如:

    <appender-ref ref="RollingFileAppender"></appender-ref> 表示开启txt文档保存日志的方式。

3. <appender></appender>节点,用来配置日志的输出途径的,本节主要介绍了输出到 【txt文本文档】中 和 【数据库】。

 A:分享一下数据库的表结构,详细配置见下面的代码分享,需要注意字段类型相匹配,并且要显式指定其长度。

B:关于txt文本文档的命名,可以存放到一个文件夹里,也可以按照时间来区分文件夹,并且命名可以 动态+指定命名的方式。

 C:关于日志文件大小的说明和文件个数的说明,主要需要三个节点配合使用(实际开发中,如果一个txt特别大,打开的时候会非常的慢,卡顿,所以该步骤有必要配置一下)。

PS:首先要配置 RollingStyle 节点为Size模式或者Composite模式,然后配置 maximumFileSize 节点设置每个文件的大小,最后配置 MaxSizeRollBackups 节点,设置日志文件的个数。超出大小后在所有文件名后自动增加正整数重新命名,数字最大的最早写入。

用下面的代码简单测试一下:

1 <param name="RollingStyle" value="Composite" />
2 <param name="maximumFileSize" value="10KB" />
3 <param name="MaxSizeRollBackups" value="5" />
  

 
详细代码如下:
 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3 <!-- 一. 添加log4net的自定义配置节点-->
 4 <configSections>
 5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 6 </configSections>
 7 <!--二. log4net的核心配置代码-->
 8 <log4net>
 9 <!--(一) 配置日志的输出途径-->
 10 <!--1. 输出途径(一) 将日志以回滚文件的形式写到文件中-->
 11 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
 12 <!--1.1 文件夹的位置(也可以写相对路径)-->
 13 <param name="File" value="D:\MyLog1\" />
 14 <!--相对路径 C/S程序生成在Debug目录下-->
 15 <!--<param name="File" value="/Logs/" />-->
 16 <!--1.2 是否追加到文件-->
 17 <param name="AppendToFile" value="true" />
 18 <!--1.3 使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件 -->
 19 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
 20 <!--1.4 配置Unicode编码-->
 21 <Encoding value="UTF-8" />
 22 <!--1.5 是否只写到一个文件里-->
 23 <param name="StaticLogFileName" value="false" />
 24 <!--1.6 配置按照何种方式产生多个日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
 25 <param name="RollingStyle" value="Composite" />
 26 <!--1.7 介绍多种日志的的命名和存放在磁盘的形式-->
 27 <!--1.7.1 在根目录下直接以日期命名txt文件 注意"的位置,去空格 -->
 28 <param name="DatePattern" value="yyyy-MM-dd".log"" />
 29 <!--1.7.2 在根目录下按日期产生文件夹,文件名固定 test.log -->
 30 <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />-->
 31 <!--1.7.3 在根目录下按日期产生文件夹,这是按日期产生文件夹,并在文件名前也加上日期 -->
 32 <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />-->
 33 <!--1.7.4 在根目录下按日期产生文件夹,这再形成下一级固定的文件夹 -->
 34 <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />-->
 35 <!--1.8 配置每个日志的大小。【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】可用的单位:KB|MB|GB。不要使用小数,否则会一直写入当前日志,
 36 超出大小后在所有文件名后自动增加正整数重新命名,数字最大的最早写入。-->
 37 <param name="maximumFileSize" value="10MB" />
 38 <!--1.9 最多产生的日志文件个数,超过则保留最新的n个 将value的值设置-1,则不限文件个数 【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】
 39 与1.8中maximumFileSize文件大小是配合使用的-->
 40 <param name="MaxSizeRollBackups" value="5" />
 41 <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定义布局-->
 42 <layout type="log4net.Layout.PatternLayout">
 43 <conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n%newline"/>
 44 </layout>
 45 </appender>
 46 
 47 <!--2. 输出途径(二) 记录日志到数据库-->
 48 <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
 49 <!--2.1 设置缓冲区大小,只有日志记录超设定值才会一块写入到数据库-->
 50 <param name="BufferSize" value="1" />
 51 <!--2.2 引用-->
 52 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
 53 <!--2.3 数据库连接字符串-->
 54 <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
 55 <!--2.4 SQL语句插入到指定表-->
 56 <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
 57 <!--2.5 数据库字段匹配-->
 58 <!-- 线程号-->
 59 <parameter>
 60 <parameterName value="@threadId" />
 61 <dbType value="String" />
 62 <size value="100" />
 63 <layout type="log4net.Layout.PatternLayout">
 64 <conversionPattern value="%thread" />
 65 </layout>
 66 </parameter>
 67 <!--日志级别-->
 68 <parameter>
 69 <parameterName value="@log_level" />
 70 <dbType value="String" />
 71 <size value="100" />
 72 <layout type="log4net.Layout.PatternLayout">
 73 <conversionPattern value="%level" />
 74 </layout>
 75 </parameter>
 76 <!--日志记录类名称-->
 77 <parameter>
 78 <parameterName value="@log_name" />
 79 <dbType value="String" />
 80 <size value="100" />
 81 <layout type="log4net.Layout.PatternLayout">
 82 <conversionPattern value="%logger" />
 83 </layout>
 84 </parameter>
 85 <!--日志信息-->
 86 <parameter>
 87 <parameterName value="@log_msg" />
 88 <dbType value="String" />
 89 <size value="5000" />
 90 <layout type="log4net.Layout.PatternLayout">
 91 <conversionPattern value="%message" />
 92 </layout>
 93 </parameter>
 94 <!--异常信息 指的是如Infor 方法的第二个参数的值-->
 95 <parameter>
 96 <parameterName value="@log_exception" />
 97 <dbType value="String" />
 98 <size value="2000" />
 99 <layout type="log4net.Layout.ExceptionLayout" />
100 </parameter>
101 <!-- 日志记录时间-->
102 <parameter>
103 <parameterName value="@log_time" />
104 <dbType value="DateTime" />
105 <layout type="log4net.Layout.RawTimeStampLayout" />
106 </parameter>
107 </appender>
108 <!--(二). 配置日志的的输出级别和加载日志的输出途径-->
109 <root>
110 <!--1. level中的value值表示该值及其以上的日志级别才会输出-->
111 <!--OFF > FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息) > ALL -->
112 <!--OFF表示所有信息都不写入,ALL表示所有信息都写入-->
113 <level value="ALL"></level>
114 <!--2. append-ref标签表示要加载前面的日志输出途径代码 通过ref和appender标签的中name属性相关联-->
115 <appender-ref ref="RollingFileAppender"></appender-ref>
116 <appender-ref ref="AdoNetAppender"></appender-ref>
117 </root>
118 </log4net>
119 
120 </configuration>

六. 简单的封装及完整代码分享

这里模拟在系统框架中对Log4net进行简单的封装,然后在MVC框架中调用,并分享全部代码。

 步骤一:新建Ypf.Utils类库,作为工具类库,引入log4net程序集,并将前面用到的log4net.xml 复制进来,改属性为嵌入资源,然后新建LogUtils类(不要起名为LogHelp),对Log4net的方法进行简单的封装,主要包括:初始化代码、ILog实例创建、五类日志级别的封装。

特别注意:这种封装会带来一个问题,会导致输出的文件中出错类永远显示的为LogUtils这个封装类,这里采用StackTrace类进行迂回处理一下,就可以定位到具体的出错位置了,如下图:

log4net.xml文件代码如下:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3 <!-- 一. 添加log4net的自定义配置节点-->
 4 <configSections>
 5 <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 6 </configSections>
 7 <!--二. log4net的核心配置代码-->
 8 <log4net>
 9 <!--(一) 配置日志的输出途径-->
 10 <!--1. 输出途径(一) 将日志以回滚文件的形式写到文件中-->
 11 <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
 12 <!--1.1 文件夹的位置(也可以写相对路径)-->
 13 <param name="File" value="D:\MyLog1\" />
 14 <!--相对路径-->
 15 <!--<param name="File" value="Logs/" />-->
 16 <!--1.2 是否追加到文件-->
 17 <param name="AppendToFile" value="true" />
 18 <!--1.3 使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件 -->
 19 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
 20 <!--1.4 配置Unicode编码-->
 21 <Encoding value="UTF-8" />
 22 <!--1.5 是否只写到一个文件里-->
 23 <param name="StaticLogFileName" value="false" />
 24 <!--1.6 配置按照何种方式产生多个日志文件 (Date:日期、Size:文件大小、Composite:日期和文件大小的混合方式)-->
 25 <param name="RollingStyle" value="Composite" />
 26 <!--1.7 介绍多种日志的的命名和存放在磁盘的形式-->
 27 <!--1.7.1 在根目录下直接以日期命名txt文件 注意"的位置,去空格 -->
 28 <param name="DatePattern" value="yyyy-MM-dd".log"" />
 29 <!--1.7.2 在根目录下按日期产生文件夹,文件名固定 test.log -->
 30 <!--<param name="DatePattern" value="yyyy-MM-dd/"test.log"" />-->
 31 <!--1.7.3 在根目录下按日期产生文件夹,这是按日期产生文件夹,并在文件名前也加上日期 -->
 32 <!--<param name="DatePattern" value="yyyyMMdd/yyyyMMdd"-test.log"" />-->
 33 <!--1.7.4 在根目录下按日期产生文件夹,这再形成下一级固定的文件夹 -->
 34 <!--<param name="DatePattern" value="yyyyMMdd/"OrderInfor/test.log"" />-->
 35 <!--1.8 配置每个日志的大小。【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】可用的单位:KB|MB|GB。不要使用小数,否则会一直写入当前日志,
 36 超出大小后在所有文件名后自动增加正整数重新命名,数字最大的最早写入。-->
 37 <param name="maximumFileSize" value="10MB" />
 38 <!--1.9 最多产生的日志文件个数,超过则保留最新的n个 将value的值设置-1,则不限文件个数 【只在1.6 RollingStyle 选择混合方式与文件大小方式下才起作用!!!】
 39 与1.8中maximumFileSize文件大小是配合使用的-->
 40 <param name="MaxSizeRollBackups" value="5" />
 41 <!--1.10 配置文件文件的布局格式,使用PatternLayout,自定义布局-->
 42 <layout type="log4net.Layout.PatternLayout">
 43 <conversionPattern value="记录时间:%date %n线程ID:[%thread] %n日志级别:%-5level %n出错类:%logger property: [%property{NDC}] - %n错误描述:%message%newline %n%newline"/>
 44 </layout>
 45 </appender>
 46 
 47 <!--2. 输出途径(二) 记录日志到数据库-->
 48 <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
 49 <!--2.1 设置缓冲区大小,只有日志记录超设定值才会一块写入到数据库-->
 50 <param name="BufferSize" value="1" />
 51 <!--2.2 引用-->
 52 <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
 53 <!--2.3 数据库连接字符串-->
 54 <connectionString value="data source=localhost;initial catalog=LogDB;integrated security=false;persist security info=True;User ID=sa;Password=123456" />
 55 <!--2.4 SQL语句插入到指定表-->
 56 <commandText value="INSERT INTO LogInfor ([threadId],[log_level],[log_name],[log_msg],[log_exception],[log_time]) VALUES (@threadId, @log_level, @log_name, @log_msg, @log_exception,@log_time)" />
 57 <!--2.5 数据库字段匹配-->
 58 <!-- 线程号-->
 59 <parameter>
 60 <parameterName value="@threadId" />
 61 <dbType value="String" />
 62 <size value="100" />
 63 <layout type="log4net.Layout.PatternLayout">
 64 <conversionPattern value="%thread" />
 65 </layout>
 66 </parameter>
 67 <!--日志级别-->
 68 <parameter>
 69 <parameterName value="@log_level" />
 70 <dbType value="String" />
 71 <size value="100" />
 72 <layout type="log4net.Layout.PatternLayout">
 73 <conversionPattern value="%level" />
 74 </layout>
 75 </parameter>
 76 <!--日志记录类名称-->
 77 <parameter>
 78 <parameterName value="@log_name" />
 79 <dbType value="String" />
 80 <size value="100" />
 81 <layout type="log4net.Layout.PatternLayout">
 82 <conversionPattern value="%logger" />
 83 </layout>
 84 </parameter>
 85 <!--日志信息-->
 86 <parameter>
 87 <parameterName value="@log_msg" />
 88 <dbType value="String" />
 89 <size value="5000" />
 90 <layout type="log4net.Layout.PatternLayout">
 91 <conversionPattern value="%message" />
 92 </layout>
 93 </parameter>
 94 <!--异常信息 指的是如Infor 方法的第二个参数的值-->
 95 <parameter>
 96 <parameterName value="@log_exception" />
 97 <dbType value="String" />
 98 <size value="2000" />
 99 <layout type="log4net.Layout.ExceptionLayout" />
100 </parameter>
101 <!-- 日志记录时间-->
102 <parameter>
103 <parameterName value="@log_time" />
104 <dbType value="DateTime" />
105 <layout type="log4net.Layout.RawTimeStampLayout" />
106 </parameter>
107 </appender>
108 <!--(二). 配置日志的的输出级别和加载日志的输出途径-->
109 <root>
110 <!--1. level中的value值表示该值及其以上的日志级别才会输出-->
111 <!--OFF > FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息) > ALL -->
112 <!--OFF表示所有信息都不写入,ALL表示所有信息都写入-->
113 <level value="ALL"></level>
114 <!--2. append-ref标签表示要加载前面的日志输出途径代码 通过ref和appender标签的中name属性相关联-->
115 <appender-ref ref="RollingFileAppender"></appender-ref>
116 <appender-ref ref="AdoNetAppender"></appender-ref>
117 </root>
118 </log4net>
119 
120 </configuration>

LogUtils类代码如下

 1 using log4net;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Diagnostics;
 5 using System.Linq;
 6 using System.Reflection;
 7 using System.Text;
 8 using System.Threading.Tasks;
 9 
 10 namespace Ypf.Utils
 11 {
 12 public class LogUtils
 13 {
 14 //可以声明多个日志对象
 15 public static ILog log=LogManager.GetLogger(typeof(LogUtils));
 16 
 17 #region 01-初始化Log4net的配置
 18 /// <summary>
 19 /// 初始化Log4net的配置
 20 /// xml文件一定要改为嵌入的资源
 21 /// </summary>
 22 public static void InitLog4Net()
 23 {
 24 Assembly assembly=Assembly.GetExecutingAssembly();
 25 var xml=assembly.GetManifestResourceStream("Ypf.Utils.log4net.xml");
 26 log4net.Config.XmlConfigurator.Configure(xml);
 27 }
 28 #endregion
 29 
 30 
 31 
 32 
 33 /************************* 五种不同日志级别 *******************************/
 34 //FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息)
 35 
 36 /// <summary>
 37 /// 将调试的信息输出,可以定位到具体的位置(解决高层封装带来的问题)
 38 /// </summary>
 39 /// <returns></returns>
 40 private static string getDebugInfo()
 41 {
 42 StackTrace trace=new StackTrace(true);
 43 return trace.ToString();
 44 }
 45 
 46 #region 01-DEBUG(调试信息)
 47 /// <summary>
 48 /// Debug
 49 /// </summary>
 50 /// <param name="msg">日志信息</param>
 51 public static void Debug(string msg)
 52 {
 53 log.Debug(getDebugInfo() + msg);
 54 }
 55 /// <summary>
 56 /// Debug
 57 /// </summary>
 58 /// <param name="msg">日志信息</param>
 59 /// <param name="exception">错误信息</param>
 60 public static void Debug(string msg, Exception exception)
 61 {
 62 log.Debug(getDebugInfo() + msg, exception);
 63 }
 64 
 65 #endregion
 66 
 67 #region 02-INFO(一般信息)
 68 /// <summary>
 69 /// Info
 70 /// </summary>
 71 /// <param name="msg">日志信息</param>
 72 public static void Info(string msg)
 73 {
 74 log.Info(getDebugInfo() + msg);
 75 }
 76 /// <summary>
 77 /// Info
 78 /// </summary>
 79 /// <param name="msg">日志信息</param>
 80 /// <param name="exception">错误信息</param>
 81 public static void Info(string msg, Exception exception)
 82 {
 83 log.Info(getDebugInfo() + msg, exception);
 84 }
 85 #endregion
 86 
 87 #region 03-WARN(警告)
 88 /// <summary>
 89 /// Warn
 90 /// </summary>
 91 /// <param name="msg">日志信息</param>
 92 public static void Warn(string msg)
 93 {
 94 log.Warn(getDebugInfo() + msg);
 95 }
 96 /// <summary>
 97 /// Warn
 98 /// </summary>
 99 /// <param name="msg">日志信息</param>
100 /// <param name="exception">错误信息</param>
101 public static void Warn(string msg, Exception exception)
102 {
103 log.Warn(getDebugInfo() + msg, exception);
104 }
105 #endregion
106 
107 #region 04-ERROR(一般错误)
108 /// <summary>
109 /// Error
110 /// </summary>
111 /// <param name="msg">日志信息</param>
112 public static void Error(string msg)
113 {
114 log.Error(getDebugInfo() + msg);
115 }
116 /// <summary>
117 /// Error
118 /// </summary>
119 /// <param name="msg">日志信息</param>
120 /// <param name="exception">错误信息</param>
121 public static void Error(string msg, Exception exception)
122 {
123 log.Error(getDebugInfo() + msg, exception);
124 }
125 #endregion
126 
127 #region 05-FATAL(致命错误)
128 /// <summary>
129 /// Fatal
130 /// </summary>
131 /// <param name="msg">日志信息</param>
132 public static void Fatal(string msg)
133 {
134 log.Fatal(getDebugInfo() + msg);
135 }
136 /// <summary>
137 /// Fatal
138 /// </summary>
139 /// <param name="msg">日志信息</param>
140 /// <param name="exception">错误信息</param>
141 public static void Fatal(string msg, Exception exception)
142 {
143 log.Fatal(getDebugInfo() + msg, exception);
144 }
145 
146 #endregion
147 
148 
149 
150 }
151 }

步骤二:新建名Ypf.MVC的MVC5框架,添加对Ypf.Utils类库的引用,在Global.asax全局文件中添加对 对Log4net进行初始化。

然后就可以愉快的进行调用测试了哦。

 1 /// <summary>
 2 /// 测试log4net
 3 /// 首先需要再Global中初始化log4net
 4 /// </summary>
 5 /// <returns></returns>
 6 public ActionResult Index()
 7 {
 8 //FATAL(致命错误) > ERROR(一般错误) > WARN(警告) > INFO(一般信息) > DEBUG(调试信息)
 9 LogUtils.Debug("出错了");
10 try
11 {
12 int.Parse("ddf");
13 }
14 catch (Exception ex)
15 {
16 LogUtils.Debug("出错了",ex);
17 }
18 
19 LogUtils.Info("出错了");
20 try
21 {
22 int.Parse("ddf");
23 }
24 catch (Exception ex)
25 {
26 LogUtils.Info("出错了", ex);
27 }
28 
29 LogUtils.Warn("出错了");
30 try
31 {
32 int.Parse("ddf");
33 }
34 catch (Exception ex)
35 {
36 LogUtils.Warn("出错了", ex);
37 }
38 
39 LogUtils.Error("出错了");
40 try
41 {
42 int.Parse("ddf");
43 }
44 catch (Exception ex)
45 {
46 LogUtils.Error("出错了", ex);
47 }
48 
49 LogUtils.Fatal("出错了");
50 try
51 {
52 int.Parse("ddf");
53 }
54 catch (Exception ex)
55 {
56 LogUtils.Fatal("出错了", ex);
57 }
58 
59 return View();
60 }

七. 补充:分文件存放

在前面的介绍中,忽略了一种情况,各种类型的日志(操作日志也好,错误日志也好)都存放在一个txt文档里,在实际开发中很不方便,在这里介绍一种利用Log4net过滤器实现不同日志分文件夹存放的功能。

几种过滤器,可以用来过滤掉Appender中的内容:

DenyAllFilter: 阻止所有的日志事件被记录

LevelMatchFilter: 只有指定等级的日志事件才被记录

LevelRangeFilter :日志等级在指定范围内的事件才被记录

LoggerMatchFilter: 与Logger名称匹配,才记录

PropertyFilter: 消息匹配指定的属性值时才被记录

StringMathFilter: 消息匹配指定的字符串才被记录

分文件夹存放的核心所在:

①. 配置文件的调整:利用LoggerMatchFilter和DenyAllFilter过滤器实现分文件存放。

②:声明ILog对象的时候,需要与LoggerMatchFilter过滤器中的Value值相对应,才能保证该ILog对象记录的日志存放到该Appender节点对应的路径下。

③. 高层调用:

分享一下log4net的配置文件和LogUtils的封装文件。