Log4net trong C# Và Tầm Quan Trọng Của Việc Tạo Log - Nhật Ký Làm Việc


LOG4NET là một thư viện mã nguồn mở cho phép chúng ta tạo ra một hoặc nhiều tập tin log, kiểu log cũng như nội dung log một cách linh hoạt và thuận tiện. Ngoài ra, Log4net còn có thể thay đổi trạng thái log lúc ứng dụng đang chạy mà không cần ngừng chương trình. Bên cạnh đó, khi sử dụng log4net sẽ không ảnh hưởng đáng kể đến performance của ứng dụng và điều quan trọng của một thư viện nguồn mở đó là chúng ta có thể tùy ý phát triển theo nhu cầu của cá nhân hoặc tập thể miễn là phù hợp giấy phép mã nguồn mở.




Bài tiếp theo...


Để hiểu được tầm quan trọng của việc log, chúng ta có một tình huống như sau; Cty chúng tôi đang phát triển một ứng dụng và đã đến kỳ hạn phải triển khai cho người dùng, dĩ nhiên do dự án có độ trễ vì thế chúng tôi không thể đảm bảo rằng các tính năng cũng như toàn bộ hệ thống của chúng tôi hoạt động tốt, ngoài ra có một số lỗi mà chỉ khi được triển khai thực tế mới phát hiện. Vậy người dùng gặp lỗi khi sử dụng, mà vì khoản cách, vì bảo mật nên chúng tôi khổng thể tiếp cận khách hàng hoặc cài Visual trên máy của họ để DEBUG chưa kể nếu bạn chạy MultiThread thì rất khó Debug - chỉ mơ hồ biết thông tin về Bug thông qua một người không phải kỹ thuật viên thì phải mất khá lâu để cải thiện được tình hình. Thế thì việc tạo Log trong quá trình người dùng sử dụng thật sự là một giải pháp tốt - những con số biết nói. Tôi chỉ việc yêu cầu người dùng gửi cho chúng tôi file Log và họ sẽ sớm có bản vá lỗi cho vấn của họ.


Để sử dụng được Log4net các bạn cần chú ý đến các vấn đề sau đây:



* Tập tin cấu hình Log4net
Măc định tập tin này chính là tập tin cấu hình của một ứng dụng dạng [myApp].exe.config hay Web.config cho Asp.NET bạn cũng có thể tạo một file cấu hình khác

  • Log ra những dạng nào: ConsoleAppender, FileAppender, RollingFileAppender, AdoNetAppender...

  • Các file log có thuộc tính gì: file, appendToFile, maximumFileSize, ...

  • Các thông tin cho một dòng log: layout, thread, date, level, ...


* Cách thức log
Là cách để thực hiện việc nhận cấu hình và tiến hành tạo log

  • Các cấu hình log4net được lưu ở đâu

  • Log những gì và level của log ra sao



[caption id="" align="aligncenter" width="640"]phạm tuân một đoạn log[/caption]

I> Cấu trúc một file cấu hình log4net


Một tập tin cấu hình của log4net có cấu trúc đơn giản sẽ như dưới, trong đó phần quan trọng là các thẻ <appender> và thẻ <root>




[code language="xml"]
<!-- Đây là thư mục gốc của tập tin cấu hình của bạn -->
<configuration>
<!-- Phần này nên giữ nguyên -->
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>

<!-- Nội dung cấu hình -->
<log4net> <!-- Level 1 -->
<!-- Định nghĩa Các appender-->
<appender> <!-- Level 2 -->
<layout> <!-- Level 3 -->
<conversionPattern /> <!-- Level 4 -->
</layout>
<filter> <!-- Level 3 -->
</filter>
</appender>

<!-- Sử dụng các appender-->
<root> <!-- Level 2 -->
<level /> <!-- Level 3 -->
<appender-ref /> <!-- Level 3 -->
</root>

</configuration>
[/code]

Thẻ <appender>


Thẻ này quy định các thức thể hiện(lưu trữ) các dòng log, trong đó có 4 cách cơ bản thể hiện: ConsoleAppender, FileAppender, RollingFileAppender, AdoNetAppender, ... Cách hay dùng nhất là RollingFileAppender. Hãy để ý thẻ <layout>, xem bảng cuối bài để hiểu các thuộc tính.


RollingFileAppender: Tạo ra tập tin log, khi tập tin này quá giới hạn thì nó sẽ bị đổi tên và tạo tập tin mới để log, vậy chúng ta sẽ có nhiều tập tin log.




[code language="xml"]
<!--Log to file collection-->
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="mylogfile.txt" />
<pre> <appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>
[/code]

ConsoleAppender: log ra màng hình, chỉ hiển thị khi ứng dụng có của sổ Console




[code language="xml"]
<!--Log to Console window-->
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{ABSOLUTE} [%thread] %level %logger - %message%newline"/>
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
[/code]

FileAppender: tạo ra duy nhất một file log




[code language="xml"]
<!--Log to file-->
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="mylogfile.log" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
</appender>
[/code]

AdoNetAppender: tạo ra các dòng log và lưu vào Database




[code language="xml"]
<!--Log to Database-->
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="100" />
<connectionType value="System.Data.SqlClient.SqlConnection,
System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="data source=[database server];
initial catalog=[database name];integrated security=false;
persist security info=True;User ID=[user];Password=[password]" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
[Message],[Exception]) VALUES (@log_date, @thread, @log_level,
@logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</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="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
</appender>
[/code]

Trong đó ý nghĩa các thẻ con của thẻ appender như sau:


Layout




[code language="xml"]
<!-- Đây là thẻ mô tả cách hiển thị một dòng log -->
<layout type="log4net.Layout.PatternLayout">
<!-- Thẻ này là định dạng của một dòng log, ý nghĩa các ký tự xin tham khảo bảng cuối trang-->
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/>
</layout>
[/code]

Filter




[code language="xml"]
<!-- Thẻ này dùng để lọc log-->
<!-- Start-->
<!-- Chỉ log nếu dòng log có chữ test, chú ý rằng StringMatchFilter luôn đi kèm DenyAllFilter-->
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
<!-- End-->

<!-- Chỉ log trong một giới hạn level-->
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>

<!-- Start-->
<!-- Chỉ log level ERROR, chú ý rằng LevelMatchFilterluôn đi kèm DenyAllFilter-->
<filter type="log4net.Filter.LevelMatchFilter">
<levelToMatch value="ERROR"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
<!-- End-->

<!-- Dùng chung với hai trường hợp kể trên-->
<filter type="log4net.Filter.DenyAllFilter"/>
[/code]

Xem chi tiết về filter tại bài config nâng cao

Level


Chúng ta có bảy level sau với mức cao nhất ở trên cùng của danh sách, khi bạn chọn một level để log thì các level từ đó trở lên đều được log:
-> OFF - không có gì được log
-> FATAL
-> ERROR
-> WARN
-> INFO
-> DEBUG
-> ALL - mọi thứ sẽ được log




[caption id="" align="aligncenter" width="451"]tuanpham Quan hệ 6 cấp độ log ( - OFF)[/caption]

Thẻ <Root>


Là thẻ quyết định level nào sẽ được log và các dạng log sẽ được sử dụng




[code language="xml"]
<root>
<level value="INFO"/> <!-- Chỉ log từ level INFO trở lên-->
<appender-ref ref="FileAppender"/> <!-- dùng FileAppender-->
<appender-ref ref="ConsoleAppender"/><!-- dùng ConsoleAppender-->
</root>
[/code]

 II> Cách thức log


Khi đã hoàn tất việc tạo file config, chúng ta tiến hành chọn thời điểm và nội dung log phù hợp trong Code-behind, chúng ta có 4 bước như sau


B1-> Tải thư viện log4net.dll và add vào Project của bạn, nhớ Reference. Bạn có thể tải dll cả mã nguồn ở đây còn tôi thì tôi thích dùng Nuget với command sau




[code language="csharp"]
Install-Package log4net
[/code]

B2-> Hãy khai báo một biến static readonly toàn cục cho tất cả các class cần log, một ví dụ tổng quát bên dưới




[code language="csharp"]
class Program
{
//Coppy dòng code này cho tất cả các class
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

static void Main(string[] args)
{
}
}
[/code]

B3-> Chỉ ra tập tin cấu hình của log4net, tôi có hai cách làm việc này
C1) Dán một trong hai dòng code sau vào dòng dưới cùng của tập tin AssemblyInfo.cs (tìm trong project của bạn)




[code language="csharp"]
//Dùng dòng này nếu tập tin cấu hình năm ở một nơi nào đó hoặc không phải tập tin cấu hình của ứng dụng
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "UsingLog4Net.xml", Watch = true)]

//Dùng dòng code này nếu tập tin cấu hình log4net chính là tập tin cấu hình của ứng dụng
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
[/code]

C2) Chỉ rõ tập tin cấu hình ở thân hàm mà bạn nghĩ hàm này sẽ được gọi đầu tiên khi ứng dụng được khởi động hoặc ít ra là trước khi tiến hành log bất kỳ thứ gì. Tôi nghĩ đó là hàm Main cho một ứng dụng Console




[code language="csharp"]
class Program
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

static void Main(string[] args)
{
//Chỉ được dùng một trong hai dòng code sau
//Dòng này khi dùng một tập tin cấu hình bất kỳ
XmlConfigurator.Configure(new FileInfo("UsingLog4Net.xml"));
//Dòng này khi dùng tập tin cấu hình của ứng dụng
            XmlConfigurator.Configure();

Console.ReadLine();
}
}
[/code]

B4-> Công việc còn lại là log nơi nào bạn nghĩ là cần thiết, tùy thuộc vào ý đồ mà chọn level phù hợp




[code language="csharp"]
class Program
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

static void Main(string[] args)
{
XmlConfigurator.Configure(new FileInfo("UsingLog4Net.xml"));

log.Info("Application started");

try
{
int value = int.Parse("Tuân");
}
catch (Exception e)
{
log.ErrorFormat("Error: {0}",e.Message);
}

Console.ReadLine();
}
}
[/code]

Trên đây là tất cả những gì cở bản nhất để tiến hành tạo log cho một ứng dụng C#, mời bạn đọc tải về ví dụ tại đây. Ngoài ra bạn cũng có thể tham khảo AppDomain Logging và  log4Net and SQLite


Dưới đây là ý nghĩa các giá trị thông tin cho một dòng log được cấu hình trong layout, tôi xin phép trích nguyên văn để các bạn dùng cho chính xác:



Conversion Patterns


As I mentioned above, the conversion pattern entry is used for the pattern layout to tell the appender how to store the information. There are many different keywords that can be used in these patterns, as well as string literals. Here I will specify what I think are the most useful and important ones. The full list can be found in the log4net documentation.




  • %date - Outputs the date using the local time zone information. This date can be formatted using the curly braces and a layout pattern such as %date{MMMM dd, yyyy HH:mm:ss, fff} to output the value of "January 01, 2011 14:15:43, 767". However, it is suggested that you use one of the log4net date formatters (ABSOLUTE, DATE, or ISO8601) since they offer better performance.

  • %utcdate - This is the same as the %date modifier, but it outputs in universal time. The modifiers for date/time all work the same way.

  • %exception - If an exception is passed in, it will be entered and a new line will be placed after the exception. If no exception is passed in, this entry will be ignored and no new line will be put in. This is usually placed at the end of a log entry, and usually a new line is placed before the exception as well.

  • %level - This is the level you specified for the event (DEBUG, INFO, WARN, etc.).

  • %message - This is the message you passed into the log event.

  • %newline - This is a new line entry. Based upon the platform you are using the application on, this will be translated into the appropriate new line character(s). This is the preferred method to enter a new line and it has no performance problems compared to the platform-specific operators.

  • %timestamp - This is the number of milliseconds since the start of the application.

  • %thread - This will give you the name of the thread that the entry was made on (or the number if the thread is not named).


Beyond these are a few more that can be very useful but should be used with caution. They have negative performance implications and should be used with caution. The list includes:




  • %identity - This is the user name of the current user using the Principal.Identity.Name method.

  • %location - Especially useful if you are running in Debug mode, this tells you where the log method was called (line number, method, etc.). However, the amount of information will decrease as you operate in Release mode depending on what the system can access from the compiled code.

  • %line - This is the line number of the code entry (see the note above on the location issues).

  • %method - This is the method that calls the log entry (see the note above on the location issues).

  • %username - This outputs the value of the WindowsIdentity property.


You may notice that some config files have letters instead of names. These have been depreciated in favor of whole word entries like I have specified above. Also, while I won't cover it in depth here, note that each of these entries can be formatted to fit a certain width. Spaces can be added (to either side) and values can be truncated in order to fit inside of fixed-width columns. The basic syntax is to place a numeric value or values between the % sign and the name. Here are the modifiers:




  • X - Specifies the minimum number of characters. Anything that has fewer characters will have spaces placed on the left of the value to equal 20 characters including the message. For example, %10messagewill give you " hi".

  • -X - Same as above, only the spaces will be placed on the right. For example, %-10message will give you "hi ".

  • .X - Specifies the maximum number of characters. The important thing to note is that this will truncate the beginning of the string, not the end. For example, %.10message will give me "rror entry" if the string passed in was "Error entry".


You can put all of this together with something like this: "%10.20message", which would specify that if the message isn't ten characters long, put spaces on the left to fill it out to ten characters, but if the message is more than 20 characters long, cut off the beginning to make it only 20 characters.


Chúc các bạn thành công!
PHẠM TUÂN

3 nhận xét

Write nhận xét
Vương Thiên Ân
AUTHOR
January 4, 2015 at 5:04 AM delete

Bài viết khá đầy đủ. Nhưng mình nghĩ bạn nên tập trung kỹ vào phần kiến trúc của log hơn là hướng dẫn sử dụng chi tiết. Để từ đó có thể áp dụng cho nhiêu ngôn ngữ lập trình khác nhau.
Nếu có thể bạn nên bổ sung thêm phần: khi nào nên dùng level log nào. Vì mình thấy trong quá trình sử dụng hay bị nhầm lẫn phần này.

Reply
avatar
Phạm Tuân
AUTHOR
January 4, 2015 at 7:59 AM delete

Cảm ơn ý kiến đóng góp của bạn, sẽ sớm có bài viết chuyên sâu cho phần bạn yêu cầu.
Phần level mình không nói rõ nên dùng vào trường hợp nào vì mình nghĩ đó là ý đồ mỗi người khác nhau sẽ dùng khác nhau

Reply
avatar
March 3, 2015 at 3:01 PM delete

[…] chúng ta thêm vào file config một thẻ appender với các thông số cở bản như bài trước và để tiến hành log cho một namspace ta dùng thẻ logger. Format config như […]

Reply
avatar