Log4Net 日志组件通用代码

matese-fields-233175-unsplash.jpg | center | 827x550

概述

C# 领域最知名的日志组件非 Log4Net 莫属。一直以来都是现用现查手册,虽然浪费不了多少时间,但是这种习惯终归不好。于是,搜集了一份封装比较完善的代码,具体实现了:可零配置,动态指定文件名和文件路径,按日期和大小自动分割文件。

代码详情

1. 创建配置文件类

初始化Logger时首先读取配置项中的同名Appender,如果存在使用配置参数,如果不存在则使用默认配置。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestProject.Log4net
{
    public class ReadParamAppender : log4net.Appender.AppenderSkeleton
    {
        private string _file;
        public string File
        {
            get { return this._file; }
            set { _file = value; }
        }

        private int _maxSizeRollBackups;
        public int MaxSizeRollBackups
        {
            get { return this._maxSizeRollBackups; }
            set { _maxSizeRollBackups = value; }
        }

        private bool _appendToFile = true;
        public bool AppendToFile
        {
            get { return this._appendToFile; }
            set { _appendToFile = value; }
        }

        private string _maximumFileSize;
        public string MaximumFileSize
        {
            get { return this._maximumFileSize; }
            set { _maximumFileSize = value; }
        }

        private string _layoutPattern;
        public string LayoutPattern
        {
            get { return this._layoutPattern; }
            set { _layoutPattern = value; }
        }

        private string _datePattern;
        public string DatePattern
        {
            get { return this._datePattern; }
            set { _datePattern = value; }
        }

        private string _level;
        public string Level
        {
            get { return this._level; }
            set { _level = value; }
        }

        protected override void Append(log4net.Core.LoggingEvent loggingEvent)
        {
        }
    }
}

2. 动态创建Logger工厂类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Configuration;

using log4net;
using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Repository;
using log4net.Repository.Hierarchy;

[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace TestProject.Log4net
{
    public static class CustomRollingFileLogger
    {
        private static readonly ConcurrentDictionary<string, ILog> loggerContainer = new ConcurrentDictionary<string, ILog>();

        private static readonly Dictionary<string, ReadParamAppender> appenderContainer = new Dictionary<string, ReadParamAppender>();
        private static object lockObj = new object();

        //默认配置
        private const int MAX_SIZE_ROLL_BACKUPS = 20;
        private const string LAYOUT_PATTERN = "%d [%t] %-5p %c  - %m%n";
        private const string DATE_PATTERN = "-yyyyMMdd\".txt\"";
        private const string MAXIMUM_FILE_SIZE = "256MB";
        private const string LEVEL = "debug";

        //读取配置文件并缓存
        static CustomRollingFileLogger()
        {
            IAppender[] appenders = LogManager.GetRepository().GetAppenders();
            for (int i = 0; i < appenders.Length; i++)
            {
                if (appenders[i] is ReadParamAppender)
                {
                    ReadParamAppender appender = (ReadParamAppender)appenders[i];
                    if (appender.MaxSizeRollBackups == 0)
                    {
                        appender.MaxSizeRollBackups = MAX_SIZE_ROLL_BACKUPS;
                    }
                    if (appender.Layout != null && appender.Layout is log4net.Layout.PatternLayout)
                    {
                        appender.LayoutPattern = ((log4net.Layout.PatternLayout)appender.Layout).ConversionPattern;
                    }
                    if (string.IsNullOrEmpty(appender.LayoutPattern))
                    {
                        appender.LayoutPattern = LAYOUT_PATTERN;
                    }
                    if (string.IsNullOrEmpty(appender.DatePattern))
                    {
                        appender.DatePattern = DATE_PATTERN;
                    }
                    if (string.IsNullOrEmpty(appender.MaximumFileSize))
                    {
                        appender.MaximumFileSize = MAXIMUM_FILE_SIZE;
                    }
                    if (string.IsNullOrEmpty(appender.Level))
                    {
                        appender.Level = LEVEL;
                    }
                    lock(lockObj)
                    {
                        appenderContainer[appenders[i].Name] = appender;
                    }
                }
            }
        }

        public static ILog GetCustomLogger(string loggerName, string category = null, bool additivity = false)
        {
            return loggerContainer.GetOrAdd(loggerName, delegate(string name)
            {
                RollingFileAppender newAppender = null;
                ReadParamAppender appender = null;
                if (appenderContainer.ContainsKey(loggerName))
                {
                    appender = appenderContainer[loggerName];
                    newAppender = GetNewFileApender(loggerName, string.IsNullOrEmpty(appender.File) ? GetFile(category, loggerName) : appender.File, appender.MaxSizeRollBackups,
                        appender.AppendToFile, true, appender.MaximumFileSize, RollingFileAppender.RollingMode.Composite, appender.DatePattern, appender.LayoutPattern);
                }
                else
                {
                    newAppender = GetNewFileApender(loggerName, GetFile(category, loggerName), MAX_SIZE_ROLL_BACKUPS, true, true, MAXIMUM_FILE_SIZE, RollingFileAppender.RollingMode.Composite, 
                        DATE_PATTERN, LAYOUT_PATTERN);
                }
                log4net.Repository.Hierarchy.Hierarchy repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
                Logger logger = repository.LoggerFactory.CreateLogger(repository, loggerName);
                logger.Hierarchy = repository;
                logger.Parent = repository.Root;
                logger.Level = GetLoggerLevel(appender == null ? LEVEL : appender.Level);
                logger.Additivity = additivity;
                logger.AddAppender(newAppender);
                logger.Repository.Configured = true;
                return new LogImpl(logger);
            });
        }

        //如果没有指定文件路径则在运行路径下建立 Log\{loggerName}.txt
        private static string GetFile(string category, string loggerName)
        {
            if (string.IsNullOrEmpty(category))
            {
                return string.Format(@"Log\{0}.txt", loggerName);
            }
            else
            {
                return string.Format(@"Log\{0}\{1}.txt", category, loggerName);
            }
        }

        private static Level GetLoggerLevel(string level)
        {
            if (!string.IsNullOrEmpty(level))
            {
                switch (level.ToLower().Trim())
                {
                    case "debug":
                        return Level.Debug;

                    case "info":
                        return Level.Info;

                    case "warn":
                        return Level.Warn;

                    case "error":
                        return Level.Error;

                    case "fatal":
                        return Level.Fatal;
                }
            }
            return Level.Debug;
        }

        private static RollingFileAppender GetNewFileApender(string appenderName, string file, int maxSizeRollBackups, bool appendToFile = true, bool staticLogFileName = false, string maximumFileSize = "5MB", RollingFileAppender.RollingMode rollingMode = RollingFileAppender.RollingMode.Composite, string datePattern = "yyyyMMdd\".txt\"", string layoutPattern = "%d [%t] %-5p %c  - %m%n")
        {
            RollingFileAppender appender = new RollingFileAppender
            {
                LockingModel = new FileAppender.MinimalLock(),
                Name = appenderName,
                File = file,
                AppendToFile = appendToFile,
                MaxSizeRollBackups = maxSizeRollBackups,
                MaximumFileSize = maximumFileSize,
                StaticLogFileName = staticLogFileName,
                RollingStyle = rollingMode,
                DatePattern = datePattern
            };
            PatternLayout layout = new PatternLayout(layoutPattern);
            appender.Layout = layout;
            layout.ActivateOptions();
            appender.ActivateOptions();
            return appender;
        }
    }
}

使用方法

1. 零配置使用

Log logger = CustomRollingFileLogger.GetCustomLogger("pay", "10001");  
logger.Debug("debug message");  

将在运行目录下生成 Log\10001\pay.txt

按日期分割日志文件,如果文件超过256M,也进行分割。

2. 使用配置文件

修改App.config/Web.config,添加或修改项:

<configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
  <log4net>
    <appender name="pay" type="Rainstorm.Log4net.ReadParamAppender">
      <param name="file" value="D:\\log\\pay.txt"/> <!--文件路径 -->
      <param name="appendToFile" value="true"/> <!--如果同名文件已存在,是否在文件中追加日志 -->
      <param name="maxSizeRollBackups" value="100"/> <!--在发生文件分割时,最多保留的历史文件个数 -->
      <param name="maximumFileSize" value="2MB"/> <!--设置按文件大小分割的阈值 -->
      <param name="datePattern" value="yyyyMMdd'.txt'"/> <!--按日期分割时文件重命名规则 -->
      <param name="level" value="debug"/> <!--logger输出等级 -->
      <layout type="log4net.Layout.PatternLayout"> <!--日志输出格式 -->
        <conversionPattern value="%d - %m%n"/> <!--文件路径 -->
      </layout>
    </appender>
    <root>
      <level value="INFO"/>
      <appender-ref ref="pay"/>
    </root>
  </log4net>

调用方式:

ILog logger = CustomRollingFileLogger.GetCustomLogger("pay");
logger.Debug("debug message");

相关链接

C#语言的特殊语法

委托 delegate

  1. 委托(delegate)是对函数原型的包装,相当于函数指针,是引用类型
    • ① 委托的声明:public delegate double MyDelegate(double x);
    • ② 委托的实例化:MyDelegate d2=new MyDelegate(obj.myMethod);
    • ③ 委托的调用:委托变量名(参数列表),如 d2(8.9)
  2. 委托的合并,又称多播 MultiCastDelegate
    • ① 一个委托实例中可以“包含”多个函数
    • ② 调用委托,就是调用其中多个函数
    • ③ 多个函数间的先后顺序是没有意义的,返回值也就没有太多意义
    • ④ 运算符 +-、+=、-=,动态地增减其中的函数,提高程序的灵活性

事件 event

  1. 事件(event)相当于回调函数
    • ① 事件的声明 public event 委托名 事件名;
    • ② 事件的注册于移除:事件名 += 或 -= (在事件所在类的外面,只能用以上两个运算符)
    • ③ 事件的发生:事件名(参数列表),相当于回调所注册的函数
  2. 用户界面中的事件实例:按钮点击事件
this.button1.Click += new System.EventHandler(this.button1_Click);

private void button1_Click(object sender,EventArgs e)
{
    this.label1.Text =DateTime.Now.ToString();
}
  1. 定义及使用事件的6步曲

    • ① 声明事件参数类:class xxxEventArgs{}
    • ② 声明委托:delegate void xxxEventHandler(obj,args)
    • ③ 定义事件:public event 类型 名称
    • ④ 发生事件:事件名(参数)
    • ⑤ 定义一个方法:void 方法名(obj,args)
    • ⑥ 注册事件:xxx.事件 += new 委托(方法名)
  2. 泛型(Generic)

List<Book> books = new List<Book>();
Book book = books[0];

  1. 匿名方法
    • ① 声明方法:delegate(参数){方法体}
    • ② 可以当成一个匿名方法:new Thread(new ThreadStart(delegate(){...}));
    • ③ 可以被隐式转换为一个兼容的委托类型:new Thread(delegate(){...});
  2. Lambda 表达式,相当于匿名方法的简写
    • 省略 delegate ,甚至省略参数类型
    • 直接用(参数)=>{语句或表达式}
    • 示例:
      • button1.Click += (sender,e)=>{...}
      • new Thread(()=>{...}).Start();
      • PlotFun(x=x*x, 0, 100);
  3. Linq (Language Integrated Query)

常见形式:

from c in customers where c.Age>10 orderby c.Name select new {c.Name, c.Phone}

使用示例:

using System;
using System.Linq;

class Program
{
    public static void Main(string[] args)
    {

        int[] arr = new int[] { 8, 5, 89, 3, 56, 4, 1, 58 };

        var m = from n in arr where n < 5 orderby n select n*n;

        foreach (var n in m)
        {
            Console.WriteLine(n);
        }
        Console.Read();
    }
}

总结:匿名函数使用 delegate;Lambda 表达式使用 => ;Linq 使用 from,where,select

C#语言概述

  1. C#之父:Anders Hejlsberg
  2. C#历程图示:

C#语言概述

  1. 架构、语言、工具:

C#语言概述

  1. 统一的编程API:NET Framework类库

C#语言概述

  1. 公共语言运行时(CLR,Common Language Runtime),相当于Java中的虚拟机。设计目标:简化开发、简化应用程序部署、基类库、支持多种语言。
  2. C#程序编译和执行的过程图示:

C#语言概述

  1. exe文件中的内容:IL指令(Intermediate Language)、元信息
  2. 反编译工具:ildasm.exe
  3. 文件扩展名:

    程序文件 .cs
    工程文件 .csproj
    解决方案 .sln

  4. 面向对象程序核心概念:

对象:

属性(property):
如:label1.Textthis.BackColor
方法(method):
如:xxxx.SetBounds(x,y,w,h);MessageBox.Show("hello");
时间(event):
如:Click
this.button1.Click += new System.EventHandler(this.button1_Click);

  1. 使用导入

using System.Windows.Forms;

命名空间

namespace xxxxx{...}

继承

public class Form1 : System.Windows.Forms.Form

自动生成的代码

对象的生成(new),事件的注册(+=)

程序示例:

using System;
class HelloWorld {
    //C#调用 Main() 作为程序的开始
    public static void Main(){
        Console.WriteLine("Hello World.");
    }
}

C#程序设计的方法论:面向对象

  1. 现实中的事物抽象为类(class),基本要素:字段(field)也即变量;方法(method)也即函数。
  2. 构造方法(constructor):主要作用是完成对象的初始化工作。

– ① 构造方法的方法名与类名相同;
– ② 构造函数没有返回类型,也不能写 void。
– ③ 如果用户没有自定义任何构造方法,则系统会自动产生一个 public Person(){}

  1. 对象的创建:构造方法不能显示地直接调用,而是使用 new 来调用。
  2. this的使用:

– ① 指这个对象本身
– ② 常用于访问这个对象的字段及方法(VS 会智能提示)
– ③ 用于区分字段与局部变量
– ④ 用于构造方法调用另一个构造方法,如 public Person():this(0,""){}

  1. 属性(property)的书写:
private string _name;

public string Name
{
    get
    {
        return _name;
    }
    set
    {
        _name = value;
    }
}

可简写为:

public string Name{set;get;}
  1. 属性与字段的比较:

– ① 属性实际上是方法;
– ② 可以只读或只写:只有 get 或 set
– ③ 可以进行有效性检查:if …
– ④ 可以计算得到的数据
– ⑤ 可以定义抽象属性

  1. 索引器(Indexer)
修饰符 类型名 this[参数列表]
{
    set
    {
    }
    get
    {
    }
}
  1. 属性与索引的比较:
属性 索引器
通过名称标识 通过参数列表进行标识
通过简单名称来访问 通过[ ]运算符来访问
可以用 static 修饰 不能用 static 修饰
属性的 get 访问器没有参数 索引的 get 访问器具有与索引相同的参数列表
属性的 set 访问器包含隐式 value 参数 除了 value 参数外,索引的 set 访问器还具有与索引相同的参数列表
可以继承 可以继承
  1. 使用继承(inheritance)
  • 继承窗体示例:public class Form1:System.Windows.Forms.Form
  • 子类 subclass、父类 baseclass
  • C#中采用单继承
  • 所有的类都是通过直接或间接地继承 object(即 System.Object)得到的。如:class SubClass:BaseClass{}
  • 子类自动地从父类那里继承所有的字段、方法、属性、索引器等成员作为自己的成员
  • 除了继承父类的成员外,子类还可以添加新的成员,隐藏或修改父类的成员。
  1. 继承中与父类同名的方法处理:

– ① 定义同名、但参数列表(签名)与父类不同的方法,这称为对父类方法的重载(Overloading)
– ② 定义同名且参数列表也与父类相同的方法,这称为新增加一种方法,用 new 表示
– ③ 定义同名且参数列表也与父类相同的方法,而且父类的方法用了 abstract 或 virtual 进行了修饰,子类的同名方法用了 override 进行了修饰,这称为虚方法的覆盖(Overriding)

  1. 常用运算符:
    as 运算符

    • 示例:Student s3 = p1 as Student;

– as 只能针对引用型变量
– 如果不能转换,as 运算不会引起异常,只是值为 null

② **is 运算符**

– 示例:if(p is Person)
– 判断一个对象是不是某个类(及其子类)的实例

③ **typeof() 运算符**

– 示例:Type t = typeof(变量);Type t = typeof(类名);
– 获得其运行时的类型

  1. 访问控制符列表
    C#程序设计的方法论:面向对象
  2. static 控制符详解:
    • static 的字段、方法、属性是属于整个类的
    • static 方法中,不能访问实例变量
    • 调用 static 方法时,直接用类名访问
    • static 变量可以用来表示“全局变量”
    • 类名使用 static 来修饰,其内字段、方法全为 static
    • static 构造方法只会调用一次,但其调用时间是不确定的
  3. const 相当于静态常量,只能用于基本类型及 string。如 Math.PI;readonly 相当于不可改量,只能修饰字段,在构造方法中赋值或者在声明时就赋值,只能赋一次值。
  4. 其他类:
    • sealed 类不可继承,如 String、Console、Math、Convert、Graphics、Font等
    • abstract 类不可实例化(new),是抽象的,如 Array、RandomNumberGenerator
  5. 接口(interface)相当于抽象类,一个抽象成员的集合。如 ICloneable、IComparacle、IConvertible、IDisposable、IFormattable、IEnumerable
  6. 定义一个接口
public interface IStringList
{
    void Add(string s);
    int Count{get;}
    string this[int index]{get;set;}
}

public abstract这两个关键词不加。

  1. 实现接口
class 类名:[父类,]接口,接口,...,接口
{
    ...
}
  1. 显示接口成员实现:
    • 方法名前写接口名:void IWindow.Close(){...}
    • 调用时,只能用接口调用:((IWindow)f).Close();
    • 在不同接口的方法相同时,能消除歧义
  2. 结构(struct)实现方式:
struct 结构名 [: 接口名]
{
    ...
}

结构是隐式 sealed ;因此它们不能被继承。

  1. 使用 struct 注意列表:
    • struct 是值类型
    • 结构不能包含无参数构造方法
    • 每个字段在定义时,不能给初始值
    • 构造方法中,必须对每个字段进行赋值
    • 实例化时,使用 new,值类型变量在赋值时,实行的是字段的 copy
  2. 枚举(enum)声明及使用:
enum MyColor
{
    Red,
    Green = 1,
    Blue = 2
}
MyColor c = MyColor.Red;
  1. 面向对象(Object Oriented)的三大特点:
    • 继承 inheritance
      • 子类继承父类的成员、还可增加、调用、隐藏
      • 提高软件模块的可重用性和可扩充性
    • 封装 encapsulation
      • 使用接口,而不关心具体的类
      • 使用属性,而将字段设为 private
    • 多态 polymorphism
      • 相同的方法,不同的参数
      • 自动调用子类相应的方法

C#程序处理流程:顺序、分支与循环

 

  1. 变量命名规则:
  • 不能是C#关键字。
  • 由字母、数字、下划线构成。
  • 第一个字符必须是字母或下划线。
  • 不要太长,一般不超过31个字符为宜。
  • 变量名最好不要与库函数名、类名相同。
  1. C#数据类型分为:值类型(Value Type)和引用类型(Reference Type);值类型存储在栈中,引用类型栈中仅存储引用变量,对象存储在堆中。

详细可划分:

C#程序处理流程:顺序、分支与循环

①、 整数类型:
有符号 sbyte short int long
无符号 byte ushort uint ulong
字符类型 char,如:'a' (使用单引号)
②、 实数类型:
float 如 3.14F
double 如3.14D(默认为 double 类型,即 D 省略)

  • C# 采用 Unicode 编码,每个字符占两个字节,可是使用十六进制编码形式表示字符:char c1 = '\u0061'
  • 当有不同种类混合运算时:int -> long -> float -> double,所有的 byte,short,char 等转换为 int

③、 十进制类型:
Decimal 如 120.5M
④、 布尔类型:
bool 如 true false(必须为小写)
⑤、 字符串类型:

  • 是引用类型,字符串常量会有特殊处理
  • 使用双引号表示,如 "abcd1234"
  • @符号内不用\转义,可以换行。如 @"abcd\rqee"

⑥、推断类型:
在编译时确定,如:var a=1+2;

⑦、Nullable 类型:
如:int? a = 32;
查看是否有值:if(a.HasValue)...

⑧、Dynamic 类型:
由DLR支持,如:dynamic x = new Cell();
编译时不检查,运行时才确定, 主要用于与 COM 组件或其他语言交互

  1. 等价关系

– int 相当于 System.Int32
– double 相当于 System.Double
– bool 相当于 System.Boolean
– string 相当于 System.String(如果使用 using System,则 string 相当于 String)

  1. 转义符:

\uxxxx 1 到 4 位十六进制数所表示的字符
\' 单引号字符
\" 双引号字符
\\ 反斜杠字符
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格

  1. 运算符
    算术运算符 +, -, *, /, %, ++, —
    > – + 还可用于字符串连接,如 string s="hello"+"world!";
    > – + 运算符两侧的操作数只要有一个是字符串类型,系统自动将另一个操作数转换成字符串进行连接,如 string s="hello,"+300;输出:hello,300

关系运算符 >, <, >=, <=, ==, !=
逻辑运算符 !, &, |, ^, &&, ||

  • 短路逻辑运算符 && 第一个操作数为假则不判断第二个操作数
  • || 第一个操作数为真则不判断第二个操作数

位运算符 &, |, ^, ~, >>, <<
赋值运算符 =, +=, -=, *=, /=
条件运算符 z?a:b

  1. 运算符优先级与结合性

C#程序处理流程:顺序、分支与循环

  1. 跳转语句:
    break 语句 结束当前循环
    continue 语句 进入下一次循环
    goto 语句 跳转到某个语句标号
    try{}catch{}语句 异常的捕获
  2. 声明数组时不能指定其长度;C#中多维数组不必须是规则矩阵形式