使用NLog作为我开发的项目的日志引擎已经好几年了,前一段,某个系统需要大量的输出日志,每天大约20MB,所以打算把每天生成的日志文件压缩一下,然后只保存2个月的。
可是NLog提供的archive不提供压缩功能,所以,自己动手,丰衣足食。
0 下载源代码,准备压缩library
NLog的源代码在这里
https://github.com/jkowalski/NLog/archives/master
下载 .zip,解压缩。 我下载的版本是2.0.0.2007。
然后使用的压缩library 是DotNetZip Library
在这里下载 http://dotnetzip.codeplex.com/ ,我使用的版本是 1.9.1.8
1 扩展?
最初的想法是继承FileTarget类,写一个FileExTarget类实现压缩功能,可是尝试了一下,FileTarget没有为扩展提供函数,这种方式几乎不可能实现,所以放弃了。
2 改造
本次改造只涉及文件日志,所以目标文件只有一个:FileTarget.cs
添加下面的函数到 类的末尾
1 #if !SILVERLIGHT && !NET_CF 2 private void CompressFile(string fileSource, string fileDest) 3 { 4 5 try 6 { 7 Type type = Type.GetType("Ionic.Zip.ZipFile,Ionic.Zip"); 8 if (type != null) 9 { 10 object obj2 = Activator.CreateInstance(type); 11 Type[] types = new Type[] { typeof(string), typeof(string) }; 12 type.GetMethod("AddFile", types).Invoke(obj2, new object[] { fileSource, "" }); 13 types = new Type[] { typeof(string) }; 14 type.GetMethod("Save", types).Invoke(obj2, new object[] { fileDest }); 15 File.Delete(fileSource); 16 } 17 else 18 { 19 File.Move(fileSource, fileDest); 20 } 21 } 22 catch 23 { 24 File.Move(fileSource, fileDest); 25 } 26 } 27 28 private string ReplaceSeq(string pattern, object value) 29 { 30 int firstPart = pattern.IndexOf("{#"); 31 int lastPart = pattern.IndexOf("#}") + 2; 32 int numDigits = lastPart - firstPart - 2; 33 34 if (value is int) 35 { 36 return pattern.Substring(0, firstPart) + Convert.ToString((int)value, 10).PadLeft(numDigits, '0') + pattern.Substring(lastPart); 37 } 38 else 39 { 40 return pattern.Substring(0, firstPart) + value.ToString() + pattern.Substring(lastPart); 41 } 42 } 43 44 private DateTime GetArchiveDateTime(int value) 45 { 46 switch (this.ArchiveEvery) 47 { 48 case FileArchivePeriod.Year: 49 return DateTime.Now.AddYears(-1 * value); 50 case FileArchivePeriod.Month: 51 return DateTime.Now.AddMonths(-1 * value); 52 case FileArchivePeriod.Day: 53 return DateTime.Now.AddDays(-1 * value); 54 case FileArchivePeriod.Hour: 55 return DateTime.Now.AddHours(-1 * value); 56 case FileArchivePeriod.Minute: 57 return DateTime.Now.AddMinutes(-1 * value); 58 default: 59 return DateTime.MinValue; 60 } 61 } 62 63 private string GetDateTimeFormat() 64 { 65 switch (this.ArchiveEvery) 66 { 67 case FileArchivePeriod.Year: 68 return "yyyy"; 69 case FileArchivePeriod.Month: 70 return "yyyyMM"; 71 case FileArchivePeriod.Day: 72 return "yyyyMMdd"; 73 case FileArchivePeriod.Hour: 74 return "yyyyMMddHH"; 75 case FileArchivePeriod.Minute: 76 return "yyyyMMddHHmm"; 77 default: 78 return string.Empty; 79 } 80 } 81 82 83 private void DatetimeArchive(string fileName, string pattern) 84 { 85 string baseNamePattern = Path.GetFileName(pattern); 86 87 int firstPart = baseNamePattern.IndexOf("{#", StringComparison.Ordinal); 88 int lastPart = baseNamePattern.IndexOf("#}", StringComparison.Ordinal) + 2; 89 int trailerLength = baseNamePattern.Length - lastPart; 90 91 string fileNameMask = baseNamePattern.Substring(0, firstPart) + "*" + baseNamePattern.Substring(lastPart); 92 93 string dirName = Path.GetDirectoryName(Path.GetFullPath(pattern)); 94 DateTime archiveTime = this.GetArchiveDateTime(1); 95 DateTime checkTime = this.GetArchiveDateTime(this.MaxArchiveFiles); 96 string dateFormat = this.GetDateTimeFormat(); 97 98 var file2Delete = new List<string>(); 99 100 try 101 { 102 103 104 foreach (string s in Directory.GetFiles(dirName, fileNameMask)) 105 { 106 string baseName = Path.GetFileName(s); 107 string strFileTime = baseName.Substring(firstPart, baseName.Length - trailerLength - firstPart); 108 DateTime fileDate; 109 110 try 111 { 112 fileDate = DateTime.ParseExact(strFileTime, dateFormat, null); 113 } 114 catch (FormatException) 115 { 116 continue; 117 } 118 119 if (fileDate < checkTime) 120 { 121 file2Delete.Add(s); 122 } 123 } 124 } 125 catch (DirectoryNotFoundException) 126 { 127 Directory.CreateDirectory(dirName); 128 } 129 130 if (file2Delete.Count > 0) 131 { 132 foreach (string file in file2Delete) 133 { 134 File.Delete(file); 135 } 136 } 137 138 string newFileName = this.ReplaceSeq(pattern, archiveTime.ToString(dateFormat)); 139 this.CompressFile(fileName, newFileName); 140 } 141 #endif
然后修改DoAutoArchive函数
把 下面的代码
1 case ArchiveNumberingMode.Sequence: 2 this.SequentialArchive(fi.FullName, fileNamePattern); 3 break;
改成
case ArchiveNumberingMode.Sequence: #if !SILVERLIGHT && !NET_CF if (this.ArchiveEvery != FileArchivePeriod.None) { this.DatetimeArchive(fi.FullName, fileNamePattern); } else { #endif this.SequentialArchive(fi.FullName, fileNamePattern); #if !SILVERLIGHT && !NET_CF } #endif break;
因为本次改造不对应sliverlight和net CF版,所以把编译器开关关掉。
退到上层目录,执行build.cmd, 编译成功,改造完成。
3 使用
首先把 Ionic.Zip.dll放到NLog.dll的同样目录下,然后在Nog的target 里指定archiveEvery,archiveFileName, maxArchiveFiles 属性就可以了。
archiveEvery 是归档期间,可以是 Year, Month, Day, Hour, Minute
archiveFileName 是归档文件名的格式,基本是这样:{your log file path}/archive/log_{#}.zip
maxArchiveFiles ,归档文件数,如果超过了,会被删除。
最后典型的app.config里关于NLog的部分大约是这样的:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/> </configSections> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <target name="file" xsi:type="File" layout="[${date:format=yyyyMMdd_HHmmss}]:${message} ${exception:format=message,stacktrace,innerException:separator=
}" archiveEvery="Minute" archiveFileName ="./log/archive/log_{#}.zip" maxArchiveFiles ="6" lineEnding="CRLF" concurrentWrites="false" fileName="./log/log.txt" /> </targets> <rules> <logger name="*" minlevel="Debug" writeTo="file" /> </rules> </nlog> </configuration>
只有NLog的用法,很简单,有需要的话,回复一下,俺再给大家讲。
最后 俺修改后的FileTarget.cs 文件和编译后的dll,以及Ionic.Zip.dll在这里可以下载
本修改遵守新BSD协议。