开发人员都知道,在系统运行中要记录各种日志,自己写一个日志功能,无论是在效率还是功能扩展上来说都不是很好,目前大多用的是第三方的日志系统,其中一个非常有名,用的最多的就是log4net。下面是关于这个组件的介绍
”Log4net是基于.net开发的一款非常著名的记录日志开源组件。最早是2001年7月由NeoWorks Limited启动的项目,基本的框架源, 于另外的一个非常著名的姐妹组件-log4j。现由Apache组织开发与维护。此日志架构是可灵活扩展,且通过配置文件来设置日志的属性及输出,不同修 改代码即可实现程序的灵活跟踪。可以将日志分不同的等级,通过不同的过滤条件,以不同的样式,将日志输出到不同的媒介。可以从http://logging.apache.org/log4net/l网站下载最新版本的Log4net源码。“
目前的log4net的最新版本是1.2.13.0
下面介绍下如何在通用权限管理系统的项目中使用这个组件实现将日志输出到文件和Oracle数据库中,重点介绍下日志输出到数据库中。
首先在官网下载最新源码,目前的源码可用VS2010打开。
源码中已经实现了可以日志输出到MSSQL的功能,但我的项目目前使用的都是Oracle数据库,源码中是没有实现的,需要自己实现一下:
OracleAppender.cs源码
public class OracleAppender : BufferingAppenderSkeleton { // Fields private static readonly Type declaringType = typeof(AdoNetAppender); private string m_commandText; private CommandType m_commandType = CommandType.Text; private string m_connectionString; private string m_connectionType; private OracleCommand m_dbCommand; private OracleConnection m_dbConnection; protected ArrayList m_parameters = new ArrayList(); private bool m_reconnectOnError = false; private SecurityContext m_securityContext; protected bool m_usePreparedCommand; private bool m_useTransactions = true; // Methods public override void ActivateOptions() { base.ActivateOptions(); this.m_usePreparedCommand = (this.m_commandText != null) && (this.m_commandText.Length > 0); if (this.m_securityContext == null) { this.m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this); } this.InitializeDatabaseConnection(); this.InitializeDatabaseCommand(); } public void AddParameter(OracleAppenderParameter parameter) { this.m_parameters.Add(parameter); } protected virtual string GetLogStatement(LoggingEvent logEvent) { if (this.Layout == null) { this.ErrorHandler.Error("ADOAppender: No Layout specified."); return ""; } StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); this.Layout.Format(writer, logEvent); return writer.ToString(); } private void InitializeDatabaseCommand() { if ((this.m_dbConnection != null) && this.m_usePreparedCommand) { try { this.m_dbCommand = this.m_dbConnection.CreateCommand(); this.m_dbCommand.CommandText = this.m_commandText; this.m_dbCommand.CommandType = this.m_commandType; } catch (Exception exception) { this.ErrorHandler.Error("Could not create database command [" + this.m_commandText + "]", exception); if (this.m_dbCommand != null) { try { this.m_dbCommand.Dispose(); } catch { } this.m_dbCommand = null; } } if (this.m_dbCommand != null) { try { foreach (OracleAppenderParameter parameter in this.m_parameters) { try { parameter.Prepare(this.m_dbCommand); } catch (Exception exception2) { this.ErrorHandler.Error("Could not add database command parameter [" + parameter.ParameterName + "]", exception2); throw; } } } catch { try { this.m_dbCommand.Dispose(); } catch { } this.m_dbCommand = null; } } if (this.m_dbCommand != null) { try { this.m_dbCommand.Prepare(); } catch (Exception exception3) { this.ErrorHandler.Error("Could not prepare database command [" + this.m_commandText + "]", exception3); try { this.m_dbCommand.Dispose(); } catch { } this.m_dbCommand = null; } } } } private void InitializeDatabaseConnection() { try { this.m_dbConnection = new OracleConnection(); this.m_dbConnection.ConnectionString = this.m_connectionString; using (this.SecurityContext.Impersonate(this)) { this.m_dbConnection.Open(); } } catch (Exception exception) { this.ErrorHandler.Error("Could not open database connection [" + this.m_connectionString + "]", exception); this.m_dbConnection = null; } } protected override void OnClose() { base.OnClose(); if (this.m_dbCommand != null) { this.m_dbCommand.Dispose(); this.m_dbCommand = null; } if (this.m_dbConnection != null) { this.m_dbConnection.Close(); this.m_dbConnection = null; } } protected virtual Type ResolveConnectionType() { Type type; try { type = SystemInfo.GetTypeFromString(this.m_connectionType, true, false); } catch (Exception exception) { this.ErrorHandler.Error("Failed to load connection type [" + this.m_connectionType + "]", exception); throw; } return type; } protected override void SendBuffer(LoggingEvent[] events) { if (this.m_reconnectOnError && ((this.m_dbConnection == null) || (this.m_dbConnection.State != ConnectionState.Open))) { LogLog.Debug(declaringType, "OracleAppender: Attempting to reconnect to database. Current Connection State: " + ((this.m_dbConnection == null) ? "<null>" : this.m_dbConnection.State.ToString())); this.InitializeDatabaseConnection(); this.InitializeDatabaseCommand(); } if ((this.m_dbConnection != null) && (this.m_dbConnection.State == ConnectionState.Open)) { if (this.m_useTransactions) { OracleTransaction dbTran = null; try { dbTran = this.m_dbConnection.BeginTransaction(); this.SendBuffer(dbTran, events); dbTran.Commit(); } catch (Exception exception) { if (dbTran != null) { try { dbTran.Rollback(); } catch (Exception) { } } this.ErrorHandler.Error("Exception while writing to database", exception); } } else { this.SendBuffer(null, events); } } } protected virtual void SendBuffer(OracleTransaction dbTran, LoggingEvent[] events) { if (!this.m_usePreparedCommand) { throw new NotImplementedException(); } if (this.m_dbCommand != null) { ArrayList list = null; foreach (OracleAppenderParameter parameter in this.m_parameters) { list = new ArrayList(); OracleParameter parameter2 = this.m_dbCommand.Parameters[parameter.ParameterName]; foreach (LoggingEvent event2 in events) { object obj2 = parameter.Layout.Format(event2); if (obj2.ToString() == "(null)") { obj2 = DBNull.Value; } list.Add(obj2); } parameter2.Value = list.ToArray(); } this.m_dbCommand.ArrayBindCount = events.Length; this.m_dbCommand.ExecuteNonQuery(); } } // Properties public string CommandText { get { return this.m_commandText; } set { this.m_commandText = value; } } public CommandType CommandType { get { return this.m_commandType; } set { this.m_commandType = value; } } protected OracleConnection Connection { get { return this.m_dbConnection; } set { this.m_dbConnection = value; } } public string ConnectionString { get { return this.m_connectionString; } set { this.m_connectionString = value; } } public bool ReconnectOnError { get { return this.m_reconnectOnError; } set { this.m_reconnectOnError = value; } } public SecurityContext SecurityContext { get { return this.m_securityContext; } set { this.m_securityContext = value; } } public bool UseTransactions { get { return this.m_useTransactions; } set { this.m_useTransactions = value; } } }
OracleAppenderParameter.cs源码
public class OracleAppenderParameter { // Fields private DbType m_dbType; private bool m_inferType = true; private IRawLayout m_layout; private string m_parameterName; private byte m_precision = 0; private byte m_scale = 0; private int m_size = 0; // Methods public virtual void FormatValue(OracleCommand command, LoggingEvent loggingEvent) { OracleParameter parameter = command.Parameters[this.m_parameterName]; parameter.Value = this.Layout.Format(loggingEvent); } public virtual void Prepare(OracleCommand command) { OracleParameter param = command.CreateParameter(); param.ParameterName = this.m_parameterName; if (!this.m_inferType) { param.DbType = this.m_dbType; } if (this.m_precision != 0) { param.Precision = this.m_precision; } if (this.m_scale != 0) { param.Scale = this.m_scale; } if (this.m_size != 0) { param.Size = this.m_size; } command.Parameters.Add(param); } // Properties public DbType DbType { get { return this.m_dbType; } set { this.m_dbType = value; this.m_inferType = false; } } public IRawLayout Layout { get { return this.m_layout; } set { this.m_layout = value; } } public string ParameterName { get { return this.m_parameterName; } set { this.m_parameterName = value; } } public byte Precision { get { return this.m_precision; } set { this.m_precision = value; } } public byte Scale { get { return this.m_scale; } set { this.m_scale = value; } } public int Size { get { return this.m_size; } set { this.m_size = value; } } }
将源码重新编译,生成log4net.dll文件,下面的项目将引用这个类库文件。
为了在程序中更方便的将日志输出到文件、Email、数据库中,封装了一下日志输出功能,DotNet.Log是封装的类库
其中的DotNet.Business,DotNet.IService,DotNet.Model,DotNet.Utilities类库是通用权限系统底层的,
DotNet.Log引用了其中的DotNet.Model,主要目的是为了将用户相关信息存储到日志的表中。
其中的LogHelper.cs代码
//------------------------------------------------------------------------------------- // All Rights Reserved , Copyright (C) 2014 , ZTO , Ltd . //------------------------------------------------------------------------------------- using System; using System.Configuration; using System.IO; namespace DotNet.Log { using DotNet.Utilities; using log4net; using log4net.Appender; /// <summary> /// ZTOLog4NetHelper /// /// 修改纪录 /// /// 系统运行日志可写入到文本文件或数据库(ORACLE) /// /// 2014-10-17 版本:1.0 SongBiao 创建文件。 /// /// <author> /// <name>SongBiao</name> /// <date>2014-10-17</date> /// </author> /// </summary> public class LogHelper { /// <summary> /// 日志对象 没有在配置文件设置(没有类定义) 则通过反射 /// 方法使用root的配置 /// </summary> private static ILog log; private static LogHelper logHelper = null; /// <summary> /// 初始化 /// </summary> /// <returns></returns> public static ILog GetInstance() { logHelper = new LogHelper(null); return log; } /// <summary> /// 初始化 /// </summary> /// <param name="configPath"></param> /// <returns></returns> public static ILog GetInstance(string configPath) { logHelper = new LogHelper(configPath); return log; } /// <summary> /// 构造函数 /// </summary> /// <param name="configPath"></param> private LogHelper(string configPath) { if (!string.IsNullOrEmpty(configPath)) { log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); //log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(configPath)); //初始化Log4net log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(System.Web.HttpContext.Current.Server.MapPath(configPath))); } else { log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); } } #region 日志写入到文件 /// <summary> /// 调试日志 /// </summary> private static readonly ILog logDebugFile = DotNetLogManager.GetLogger("LogDebug"); /// <summary> /// 写入调试日志 /// </summary> /// <param name="info"></param> public static void WriteDebugLog(string info) { if (logDebugFile.IsInfoEnabled) { logDebugFile.Info(info); } } /// <summary> /// 信息日志 /// </summary> private static readonly ILog logInfoFile = DotNetLogManager.GetLogger("LogInfo"); /// <summary> /// 写入信息日志 /// </summary> /// <param name="info"></param> public static void WriteInfoLog(string info) { if (logInfoFile.IsInfoEnabled) { logInfoFile.Info(info); } } /// <summary> /// 警告日志 /// </summary> private static readonly ILog logErrorFile = DotNetLogManager.GetLogger("LogError"); /// <summary> /// 写入错误异常日志 /// </summary> /// <param name="info"></param> /// <param name="se"></param> public static void WriteErrorLog(string info, Exception se) { if (logErrorFile.IsErrorEnabled) { logErrorFile.Error(info, se); } } /// <summary> /// 警告日志 /// </summary> private static readonly ILog logWarnFile = DotNetLogManager.GetLogger("LogWarn"); /// <summary> /// 写入警告日志 /// </summary> /// <param name="info"></param> /// <param name="se"></param> public static void WriteWarnLog(string info, Exception se) { if (logWarnFile.IsErrorEnabled) { logWarnFile.Error(info, se); } } /// <summary> /// 毁灭级别日志 /// </summary> private static readonly ILog logFatalFile = DotNetLogManager.GetLogger("LogFatal"); /// <summary> /// 写入毁灭级别日志 /// </summary> /// <param name="info"></param> /// <param name="se"></param> public static void WriteFatalLog(string info, Exception se) { if (logFatalFile.IsFatalEnabled) { logFatalFile.Error(info, se); } } /// <summary> /// 非安全行为日志记录 /// </summary> private static readonly log4net.ILog logSafeFile = DotNetLogManager.GetLogger("LogSafe"); /// <summary> /// 非安全行为日志记录 /// </summary> /// <param name="info"></param> public static void WriteSafeLog(string info) { if (logSafeFile.IsInfoEnabled) { logSafeFile.Info(info); } } /// <summary> /// 缓存记录变动的日志对象 当前配置是输出到文件中 /// </summary> private static readonly ILog logCacheFile = DotNetLogManager.GetLogger("logcache"); /// <summary> /// 写入缓存变动日志 /// </summary> /// <param name="info"></param> public static void WriteCacheLog(string info) { if (logCacheFile.IsInfoEnabled) { logCacheFile.Info(info); } } #endregion #region 发送到邮箱 /// <summary> /// 发送到邮箱 /// </summary> private static readonly ILog logEmailWarn = DotNetLogManager.GetLogger("LogEmail"); /// <summary> /// 邮件通知 一般是警告类或异常类 内网可能发不了 /// </summary> /// <param name="info"></param> /// <param name="se"></param> public static void WriteEmailLog(string info, Exception se) { if (logEmailWarn.IsInfoEnabled) { logEmailWarn.Error(info, se); } } #endregion #region 日志写入到Oracle数据库表 /// <summary> /// Oracle数据库记录 /// </summary> public static IZTOLog logOracle = DotNetLogManager.GetLogger("LogOracle"); /// <summary> /// 5. 调试级别 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> public static void OracleDebug(BaseUserInfo userInfo, string title, object message, string functionName, Type type) { logOracle.Debug(userInfo, type.ToString(), functionName, title, message); } /// <summary> /// 4. 消息级别 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> public static void OracleInfo(BaseUserInfo userInfo, string title, object message, string functionName, Type type) { logOracle.Info(userInfo, type.ToString(), functionName, title, message); } /// <summary> /// 3. 警告级别 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> public static void OracleWarn(BaseUserInfo userInfo, string title, object message, string functionName, Type type) { logOracle.Warn(userInfo, type.ToString(), functionName, title, message, null); } /// <summary> /// 3. 警告级别 带异常 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> public static void OracleWarn(BaseUserInfo userInfo, string title, object message, string functionName, Type type, System.Exception ex) { logOracle.Warn(userInfo, type.ToString(), functionName, title, message, ex); } /// <summary> /// 2. 错误级别 不带异常 /// </summary> /// <param name="userInfo"></param> /// <param name="message"></param> /// <param name="functionName"></param> /// <param name="type"></param> public static void OracleError(BaseUserInfo userInfo, string title, object message, string functionName, Type type) { logOracle.Error(userInfo, type.ToString(), functionName, title, message, null); } /// <summary> /// 2. 错误级别 待异常 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="title">日志标题</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> /// <param name="Exception">异常信息</param> public static void OracleError(BaseUserInfo userInfo, string title, object message, string functionName, Type type, System.Exception ex) { logOracle.Error(userInfo, type.ToString(), functionName, title, message, ex); } /// <summary> /// 1. 毁灭级别 不带异常 /// </summary> /// <param name="userInfo"></param> /// <param name="title">日志标题</param> /// <param name="message"></param> /// <param name="functionName"></param> /// <param name="type"></param> public static void OracleFatal(BaseUserInfo userInfo, string title, object message, string functionName, Type type) { logOracle.Fatal(userInfo, type.ToString(), functionName, title, message, null); } /// <summary> /// 1. 毁灭级别 带异常 /// </summary> /// <param name="userInfo">用户信息</param> /// <param name="title">日志标题</param> /// <param name="message">日志信息</param> /// <param name="type">出错类的类名,可自定义</param> public static void OracleFatal(BaseUserInfo userInfo, string title, object message, string functionName, Type type, System.Exception ex) { logOracle.Fatal(userInfo, type.ToString(), functionName, title, message, ex); } #endregion } } /* Oracle数据库表创建 -- Create table create table SECURITY_LOG4NET ( LOG_ID NUMBER not null, LOG_DATE DATE, LOG_LEVEL VARCHAR2(255), LOG_MESSAGE VARCHAR2(4000), LOG_EXCEPTION VARCHAR2(4000), LOG_LOGGER VARCHAR2(255), LOG_SOURCE VARCHAR2(1000), USERID NUMBER, LOGTYPE VARCHAR2(255), IP VARCHAR2(20), FUNCTIONNAME VARCHAR2(255), REALNAME VARCHAR2(50), SYSTEMCODE VARCHAR2(200), LOG_TITLE VARCHAR2(60) ) tablespace USERS pctfree 10 initrans 1 maxtrans 255 storage ( initial 64K next 8K minextents 1 maxextents unlimited ); -- Add comments to the columns comment on column SECURITY_LOG4NET.LOG_ID is 'ID主键'; comment on column SECURITY_LOG4NET.LOG_DATE is '记录时间'; comment on column SECURITY_LOG4NET.LOG_LEVEL is '日志级别:调试级别:Debug,消息级别:Info,警告级别:Warn,错误级别:Error,毁灭级别:Fatal'; comment on column SECURITY_LOG4NET.LOG_MESSAGE is '操作描述'; comment on column SECURITY_LOG4NET.LOG_EXCEPTION is '异常消息'; comment on column SECURITY_LOG4NET.LOG_LOGGER is '日志记录器'; comment on column SECURITY_LOG4NET.LOG_SOURCE is '日志源'; comment on column SECURITY_LOG4NET.USERID is '用户主键ID'; comment on column SECURITY_LOG4NET.LOGTYPE is '操作类'; comment on column SECURITY_LOG4NET.IP is '操作IP'; comment on column SECURITY_LOG4NET.FUNCTIONNAME is '方法名称'; comment on column SECURITY_LOG4NET.REALNAME is '用户姓名'; comment on column SECURITY_LOG4NET.SYSTEMCODE is '子系统编号'; comment on column SECURITY_LOG4NET.LOG_TITLE is '日志标题'; -- Create/Recreate primary, unique and foreign key constraints alter table SECURITY_LOG4NET add constraint SECURITY_LOG4NET_PK primary key (LOG_ID) using index tablespace USERS pctfree 10 initrans 2 maxtrans 255 storage ( initial 64K next 1M minextents 1 maxextents unlimited ); */
项目中将日志记录到各个介质就引用这个LogHelper了。
在项目中再引用DotNet.Log,我的是一个web项目,需要设置一个配置文件。
Log4Net.config是配置日志向各种介质输出的功能,全部配置代码:
<?xml version="1.0" encoding="utf-8"?> <log4net> <root> <!--<level value="INFO" />--> <!--启用按日期分割--> <!--<appender-ref ref="LogFileAppenderByDate" />--> <!--启用按容量分割--> <!--<appender-ref ref="LogFileAppenderBySize" />--> <!--启用保存到数据库--> <!--<appender-ref ref="AdoNetAppender" />--> </root> <!--DEBUG 调试信息记录器--> <logger name="LogDebug" additivity="false"> <level value="DEBUG" /> <appender-ref ref="logger.DebugAppender" /> </logger> <appender name="logger.DebugAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="LOG//DEBUG//" /> <!--是否续写 True/false,默认为true。当文件存在时,是否在原文件上追加内容。--> <param name="AppendToFile" value="true" /> <!--单个文件大小 当RollingStyle为Composite或Size,这里设置最大文件大小(可以KB,MB,GB为单位,默认为字节)--> <param name="MaxFileSize" value="10240" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <!--当RollingStyle为Composite或Date,这里设置文件名格式--> <param name="DatePattern" value="yyyyMMdd".txt"" /> <!--创建新文件的方式,可选为Size(按文件大小),Date(按日期), Once(每启动一次创建一个文件),Composite(按日期及文件大小),默认为Composite--> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--INFO 普通信息记录器--> <logger name="LogInfo" additivity="false"> <level value="INFO" /> <appender-ref ref="logger.InfoAppender" /> </logger> <appender name="logger.InfoAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="LOG//INFO//" /> <!--是否续写 True/false,默认为true。当文件存在时,是否在原文件上追加内容。--> <param name="AppendToFile" value="true" /> <!--单个文件大小 当RollingStyle为Composite或Size,这里设置最大文件大小(可以KB,MB,GB为单位,默认为字节)--> <param name="MaxFileSize" value="10240" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <!--当RollingStyle为Composite或Date,这里设置文件名格式--> <param name="DatePattern" value="yyyyMMdd".txt"" /> <!--创建新文件的方式,可选为Size(按文件大小),Date(按日期), Once(每启动一次创建一个文件),Composite(按日期及文件大小),默认为Composite--> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--ERROR 错误记录器--> <logger name="LogError" additivity="false"> <level value="ERROR" /> <appender-ref ref="logger.ErrorAppender" additivity="false"/> </logger> <appender name="logger.ErrorAppender" type="log4net.Appender.RollingFileAppender"> <!--是否续写--> <param name="AppendToFile" value="true" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--单个文件大小--> <param name="MaxFileSize" value="10240" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <param name="File" value="Log//ERROR//" /> <param name="DatePattern" value="yyyyMMdd".txt""/> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <!--<param name="ConversionPattern" value="%n%d [%t] %-5p %c [%x] - %m%n%n%n" />--> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--WARN 警告日志记录器--> <logger name="LogWarn" additivity="false"> <level value="WARN" /> <appender-ref ref="logger.WarnAppender" additivity="false"/> </logger> <appender name="logger.WarnAppender" type="log4net.Appender.RollingFileAppender"> <!--是否续写--> <param name="AppendToFile" value="true" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--单个文件大小--> <param name="MaxFileSize" value="10240" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <param name="File" value="LOG//WARN//" /> <param name="DatePattern" value="yyyyMMdd".txt""/> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <!--<param name="ConversionPattern" value="%n%d [%t] %-5p %c [%x] - %m%n%n%n" />--> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--FATAL 毁灭级别日志记录器--> <logger name="LogFatal" additivity="false"> <level value="FATAL" /> <appender-ref ref="logger.FatalAppender" additivity="false"/> </logger> <appender name="logger.FatalAppender" type="log4net.Appender.RollingFileAppender"> <!--是否续写--> <param name="AppendToFile" value="true" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--单个文件大小--> <param name="MaxFileSize" value="10240" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <param name="File" value="LOG//FATAL//" /> <param name="DatePattern" value="yyyyMMdd".txt""/> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <!--<param name="ConversionPattern" value="%n%d [%t] %-5p %c [%x] - %m%n%n%n" />--> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--CACHE 缓存变更记录器--> <logger name="LogCache" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.CacheAppender" /> </logger> <appender name="logger.CacheAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="LOG//CACHE//" /> <!--是否续写 True/false,默认为true。当文件存在时,是否在原文件上追加内容。--> <param name="AppendToFile" value="true" /> <!--单个文件大小 当RollingStyle为Composite或Size,这里设置最大文件大小(可以KB,MB,GB为单位,默认为字节)--> <param name="MaxFileSize" value="10240" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <!--当RollingStyle为Composite或Date,这里设置文件名格式--> <param name="DatePattern" value="yyyyMMdd".txt"" /> <!--创建新文件的方式,可选为Size(按文件大小),Date(按日期), Once(每启动一次创建一个文件),Composite(按日期及文件大小),默认为Composite--> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <!--<param name="ConversionPattern" value="%n%d [%t] %-5p %c [%x] - %m%n%n%n" />--> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--非安全行为 日志记录器--> <logger name="LogSafe" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.SafeAppender" /> </logger> <appender name="logger.SafeAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="LOG//SAFE//" /> <!--是否续写 True/false,默认为true。当文件存在时,是否在原文件上追加内容。--> <param name="AppendToFile" value="true" /> <!--单个文件大小 当RollingStyle为Composite或Size,这里设置最大文件大小(可以KB,MB,GB为单位,默认为字节)--> <param name="MaxFileSize" value="10240" /> <!--备份日志数目 默认为0 可设最多保存多少个文件--> <param name="MaxSizeRollBackups" value="0" /> <!--写入的文件名 为true时,RollingStyler的date值将无效。且为true时,需要在file里指定文件名,所有日志都会记录在这个文件里。--> <param name="StaticLogFileName" value="false" /> <!--当RollingStyle为Composite或Date,这里设置文件名格式--> <param name="DatePattern" value="yyyyMMdd".txt"" /> <!--创建新文件的方式,可选为Size(按文件大小),Date(按日期), Once(每启动一次创建一个文件),Composite(按日期及文件大小),默认为Composite--> <param name="RollingStyle" value="Date" /> <!--log4net的日志输出格式--> <layout type="log4net.Layout.PatternLayout"> <param name="Header" value="◇◇◇◇◇开始◇◇◇◇◇◇" /> <param name="Footer" value="◆◆◆◆◆结束◆◆◆◆◆◆" /> <!--<param name="ConversionPattern" value="%n%d [%t] %-5p %c [%x] - %m%n%n%n" />--> <param name="ConversionPattern" value="%n时间:%d %n级别:%level %n类名:%c%n文件:%F 第%L行%n日志内容:%m%n-----------------------------------------%n" /> </layout> </appender> <!--EMAIL 发邮件 注意内网可能不能发送邮件 --> <logger name="LogEmail" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.EmaiAppender" /> </logger> <appender name="logger.EmaiAppender" type="log4net.Appender.SMTPAppender"> <authentication value="Basic"/> <subject value="【中通快递系统】运行异常状态报告" /> <to value="[email protected]" /> <from value="[email protected]" /> <smtphost value="mail.zto.cn" /> <username value="ztoexception" /> <password value="d1fbf1ff" /> <port value="25"/> <bufferSize value="2048" /> <!--超长部分是否丢弃--> <lossy value="true" /> <!-- 下面的定义, 就是 日志级别 大于 WARN 的, 才发邮件. --> <evaluator type="log4net.Core.LevelEvaluator"> <threshold value="WARN" /> </evaluator> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%property{log4net:HostName} :: %level :: %message %newlineLogger: %logger%newlineThread: %thread%newlineDate: %date%newlineNDC: %property{NDC}%newline%newline" /> </layout> </appender> <!--专门为Oracle写的日志记录器 --> <logger name="LogOracle" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.AdoNetAppender_Oracle" /> </logger> <appender name="logger.AdoNetAppender_Oracle" type="DotNet.Log.OracleAdoNetAppender"> <bufferSize value="1" /> <connectionType value="Oracle.DataAccess.Client.OracleConnection, Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" /> <ConnectionStringName value="Log4NetOracleDBConnection" /> <commandText value="INSERT INTO SECURITY_LOG4NET (LOG_ID, LOG_DATE, LOG_LEVEL, LOG_MESSAGE, LOG_EXCEPTION, LOG_LOGGER, LOG_SOURCE,USERID,LOGTYPE,IP,FUNCTIONNAME,REALNAME,LOG_TITLE,SYSTEMCODE) VALUES (SEQ_SECURITY_LOG4NET.nextval, :log_date, :log_level,:log_message, :log_exception, :logger, :source,:USERID,:LOGTYPE,:IP,:FUNCTIONNAME,:REALNAME,:LOG_TITLE,:SYSTEMCODE)" /> <!--注意自定义的字段的大小写要一致 --> <parameter> <parameterName value=":log_date" /> <dbType value="DateTime" /> <layout type="log4net.Layout.RawTimeStampLayout"> <conversionPattern value="%d{yyyy/MM/dd HH:mm:ss}" /> </layout> </parameter> <parameter> <parameterName value=":log_level" /> <dbType value="String" /> <size value="10" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%level" /> </layout> </parameter> <parameter> <parameterName value=":log_message" /> <dbType value="String" /> <size value="4000" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%m" /> </layout> </parameter> <parameter> <parameterName value=":log_exception" /> <dbType value="String" /> <size value="4000" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%exception" /> </layout> </parameter> <parameter> <parameterName value=":logger" /> <dbType value="String" /> <size value="255" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%logger" /> </layout> </parameter> <parameter> <parameterName value=":source" /> <dbType value="String" /> <size value="1000" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%file:%line" /> </layout> </parameter> <parameter> <parameterName value=":USERID" /> <dbType value="Int32" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{UserID}" /> </layout> </parameter> <parameter> <parameterName value=":LOGTYPE" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{LOGTYPE}" /> </layout> </parameter> <parameter> <parameterName value=":IP" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{IP}" /> </layout> </parameter> <parameter> <parameterName value=":FUNCTIONNAME" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{FUNCTIONNAME}" /> </layout> </parameter> <parameter> <parameterName value=":REALNAME" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{REALNAME}" /> </layout> </parameter> <parameter> <parameterName value=":LOG_TITLE" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{LOG_TITLE}" /> </layout> </parameter> <parameter> <parameterName value=":SYSTEMCODE" /> <dbType value="String" /> <layout type="DotNet.Log.CustomLayout"> <conversionPattern value="%property{SYSTEMCODE}" /> </layout> </parameter> </appender> <!--日志信息输出到页面的Trace上下文环--> <logger name="LogAspNetTrace" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.AspNetTraceAppender" /> </logger> <!--这段配置可将日志信息输出到页面的Trace上下文环境。 如果日志的级别低于WARN,会以 System.Web.TraceContext.Write方法输出; 如果级别为WARN或WARN以上则会以 System.Web.TraceContext.Warn方法输出,输出的不同颜色可以说明这一点--> <appender name ="logger.AspNetTraceAppender" type ="log4net.Appender.AspNetTraceAppender"> <layout type ="log4net.Layout.PatternLayout" > <conversionPattern value ="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/> </layout> </appender> <!--日志信息输出到控制台 高亮显示Error信息--> <logger name="LogColoredConsole" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.ColoredConsoleAppender" /> </logger> <!--ColoredConsoleAppender将日志信息输出到控制台。 默认情况下,日志信息被发送到控制台标准输出流。下面这个示例演示了如何高亮显示Error信息。--> <appender name ="logger.ColoredConsoleAppender" type ="log4net.Appender.ColoredConsoleAppender"> <mapping > <level value ="ERROR" /> <foreColor value ="White"/> <backColor value ="Red, HighIntensity"/> </mapping> <mapping> <level value ="DEBUG"/> <backColor value ="Green"/> </mapping> <layout type ="log4net.Layout.PatternLayout" > <conversionPattern value ="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/> </layout> </appender> <!--日志信息输出到控制台 高亮显示Error信息--> <logger name="LogConsole" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.ConsoleAppender" /> </logger> <!--CONSOLEAPPENDER将日志信息输出到控制台标准输出流。--> <appender name ="logger.ConsoleAppender" type ="log4net.Appender.ConsoleAppender"> <layout type ="log4net.Layout.PatternLayout" > <!--<param name ="ConversionPattern" value ="%d [%t] %-5p %c [%x] - %m%n"/>--> <conversionPattern value="[时间]:%d%n[级别]:%p%n[内容]:%m%n%n"></conversionPattern> </layout> </appender> <!--日志写入本地机器的应用程序事件日志中--> <logger name="LogEventlog" additivity="false"> <level value="ALL" /> <appender-ref ref="logger.EventLogAppender" /> </logger> <!--EventLogAppender将日志写入本地机器的应用程序事件日志中。 默认情况下,该日志的源(Source)是AppDomain.FriendlyName,也可以手动指定其它名称。--> <appender name ="logger.EventLogAppender" type ="log4net.Appender.EventLogAppender"> <layout type ="log4net.Layout.PatternLayout" > <param name ="ConversionPattern" value ="%d [%t] %-5p %c [%x] - %m%n"/> </layout> </appender> <!--FileAppender将日志信息输出到指定的日志文件。 File指定了文件名称,可以使用相对路径, 此时日志文件的位置取决于项目的类型(如控制台、Windows Forms、ASP.NET等); 也可以使用绝对路径;甚至可以使用环境变量,如<file value="${TMP}\log-file.txt" />。 AppendToFile指定是追加到还是覆盖掉已有的日志文件。还可以添加如下属性<lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> 来使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件。--> <appender name ="LogFileAppender" type ="log4net.Appender.FileAppender"> <param name ="File" value ="WebUtilClient.log"/> <param name ="AppendToFile" value ="true"/> <layout type ="log4net.Layout.PatternLayout"> <param name ="ConversionPattern" value ="%d [%t] %-5p %c [%x] - %m%n"/> </layout> </appender> <!--ForwardingAppender可以用来为一个Appender指定一组约束 在这个示例中,为ConsoleAppender添加了约束,Threshold为WARN。这意味着对于一条日志信息, 如果直接使用 ConsoleAppender,那么不论它是什么级别,总会进行输出,而如果使用这个ForwardingAppender, 则只有那些WARN或 WARN以上的日志才会发送到ConsoleAppender。--> <appender name ="ForwardingAppender" type ="log4net.Appender.ForwardingAppender"> <threshold value ="WARN" /> <appender-ref ref ="ConsoleAppender"/> </appender> <!--似乎不应该使用配置文件来配置MemoryAppender,但如果你非要这么做,看看这个示例(未验证):--> <appender name ="MemoryAppender" type ="log4net.Appender.MemoryAppender" > <onlyFixPartialEventData value ="true" /> </appender> <!--NetSendAppender向特定用户的屏幕发送消息(未验证)--> <appender name ="NetSendAppender" type ="log4net.Appender.NetSendAppender" > <threshold value ="ERROR"/> <server value ="Anders"/> <recipient value ="xym"/> <layout type ="log4net.Layout.PatternLayout"> <conversionPattern value ="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/> </layout> </appender> <!--下面这个例子描述了如何配置该Appender以向OutputDebugString API写入日志(未验证)。--> <appender name ="OutputDebugStringAppender" type ="log4net.Appender.OutputDebugStringAppender"> <layout type ="log4net.Layout.PatternLayout" > <conversionPattern value ="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/> </layout> </appender> <!--remotingAppender向特定的Sink发送日志信息(未验证):--> <appender name ="RemotingAppender" type ="log4net.Appender.RemotingAppender"> <sink value ="tcp://localhost:8085/LoggingSink"/> <lossy value ="false"/> <bufferSize value ="95"/> <onlyFixPartialEventData value ="true"/> </appender> <!--如何防止自定义的logger和root重复写入日志 logger是从root继承而来,默认的用logger写的日志会被root重复写一份, 如何防止这种情况呢,网上有人提出可以去掉root里的 appender-ref,这不是一个好的解决方法, logger只是为了单独处理一些日志,大部分的日志还是要依靠root来完成。 其实log4net提供了一种很好的解决方式,断开logger与root的继承关系, 只要在配置文件里的logger加上属性additivity="false" 就可以了。--> <!--Log4Net 日志级别 Log4net 分为如下几个级别: FATAL 毁灭级别 ERROR 错误级别 WARN 警告级别 INFO 消息级别 DEBUG 调试级别 这几种日志级别高低:FATAL> ERROR> WARN > INFO > DEBUG。 只有日志输出级别大于或等于配置的级别才能输出日志信息。 比如我的日志配置级别为INFO,那么只有log.Info(), log.Warn(), log.Error(), log.Fatal()才能输出日志信息,Debug方式就不能输出。 Log4net中还有两种特殊的配置级别: ALL - 允许所有的日志级别输出,OFF - 拒绝所有的日志级别输出。 %m[%message] : 输出的日志消息 %n : 换行 %d[%datetime] : 输出当前语句运行的时刻 %r : 输出程序从运行到执行到当前语句时消耗的毫秒数 %d : 当前语句所在的线程ID %p : 日志的当前优先级别 %c :当前日志对象的名称 %L : 输出语句所在的行号 %F :输出语句所在的文件名 %-数字 :表示该项的最小长度,如果不够,则用空格填充 --> </log4net>
在项目的web.config中需要配置的项目
其中的数据库连接是配置将日志写入到oracle数据库中,连接字符串进行了加密处理。
在Global.asax文件中的配置
可以看到, LogHelper.GetInstance("/Config/Log4Net.config")是设置日志记录的配置文件
下面的LogHelper.WriteInfoLog是将系统启动信息记录到文本文件中,LogHelper.OracleInfo是将系统启动信息记录到数据库中。因为启动时是无法获取用户信息的,因此传入了null。
再给一下global.asax.cs记录非法请求的日志功能
/// <summary> /// 在此处进行安全检测和防范 /// Application_BeginRequest /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_AcquireRequestState(object sender, EventArgs e) { HttpContext context = HttpContext.Current; string putData = string.Empty; if (Request.Cookies != null) { if (GeneralSafe.CookieData(out putData)) { ResponseWarnMessage(context, "Cookie数据有恶意字符!", putData); } } if (Request.UrlReferrer != null) { if (GeneralSafe.Referer(out putData)) { ResponseWarnMessage(context, "Referrer数据有恶意字符!", putData); } } if (Request.RequestType.ToUpper() == "POST") { if (GeneralSafe.PostData(out putData)) { ResponseWarnMessage(context, "Post数据有恶意字符!", putData); } } if (Request.RequestType.ToUpper() == "GET") { if (GeneralSafe.GetData(out putData)) { ResponseWarnMessage(context, "Get数据有恶意字符!", putData); } } } /// <summary> /// 非安全行为 输出警告信息 /// </summary> /// <param name="errorMessage"></param> /// <param name="putData"></param> private void ResponseWarnMessage(HttpContext context, string errorMessage, string putData) { //记录一下恶意攻击行为 string ipAddress = Utilities.GetIPAddress(true); BaseUserInfo userInfo = context.Session[DotNet.Business.Utilities.SessionName] as BaseUserInfo; //非安全行为同时记录到数据库和文本文件中 LogHelper.OracleWarn(userInfo, "恶意访问行为", "来自IP:" + ipAddress + "的访问存在恶意行为:" + errorMessage + "字符内容:" + putData, " private void ResponseErrorMessage(string errorMessage, string putData)", typeof(AccountController), null); LogHelper.WriteSafeLog("来自IP:" + ipAddress + "的访问存在恶意行为:" + errorMessage + "字符内容:" + putData); RouteData routeData = new RouteData(); routeData.Values.Add("controller", "Error"); routeData.Values.Add("action", "General"); routeData.Values.Add("title", "非法访问与请求提醒"); routeData.Values.Add("error", "你提交的" + errorMessage + "字符内容:" + putData); IController errorController = new ErrorController(); errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); context.Response.End(); }
以上代码可检测COOKIE,Referrer,POST,GET中传入的非法字符。页面显示效果截图如下
数据库日志记录的内容截图如下:
文本文件中记录的内容如下截图
以上就是基于log4net和通用权限管理系统底层做的一个日志记录功能,最新版本通用权限管理系统将会使用这个日志功能。
如有改进建议,欢迎大家提出。