Blog chia sẽ kinh nghiệm lập trình và Technical VN. Nơi mà các bạn có thể học tập và tham khảo về những công nghệ mới nhất hiện nay về .NET

Hiển thị các bài đăng có nhãn log4net. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn log4net. Hiển thị tất cả bài đăng

Thứ Ba, 3 tháng 3, 2015


Hôm nay tôi sẽ hướng dẫn các bạn một số kỹ thuật với log4net, 3 kỹ thuật chúng ta cần nắm sau bài này là "Làm sao để tạo các file log riêng cho từng namspace", "Làm sao để các file log tự động được ghi vào thư mục có tên tương ứng với ngày hiện tại" và "làm sao để lọc thông tin cho một file log - nâng cao". Mời các bạn cùng theo dõi.




Bài trước...



I> Tạo file riêng cho từng namspace


Để tạo một tập tin/kiểu log 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ư sau




[code language="xml"]
<!-- name của appender là tên bất kỳ-->
<appender name="rootTrace">
<!-- param -->
</appender>
<appender name="MyCompany">
<!-- param -->
</appender>
<appender name="MyCompany.MyCompany1">
<!-- param -->
</appender>
<appender name="MyCompany.MyCompany2">
<!-- param -->
</appender>

<root>
<level value="ALL"/>
<appender-ref ref="rootTrace"/>
</root>

<!-- name của logger chính là namespace muốn lọc-->
<logger name="MyCompany">
<level value="WARN" />
<!-- ref đến một appender-->
<appender-ref ref="MyCompany" />
</logger>
<logger name="MyCompany.MyCompany1">
<level value="WARN" />
<appender-ref ref="MyCompany.MyCompany1" />
</logger>
<logger name="MyCompany.MyCompany2">
<level value="DEBUG" />
<appender-ref ref="MyCompany.MyCompany2" />
</logger>
[/code]

Ví dụ: Chúng ta sẽ có một file log  với tên "myLog.log", chứa toàn bộ log sinh ra từ namespace "Namespace.subNamespace" và loglevel = "DEBUG"




[code language="xml"]
<!-- config appender với type = RollingFileAppender-->
<appender name="myApenName" type="log4net.Appender.RollingFileAppender">
<file value="..\log\myLog.log"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maximumFileSize value="25MB"/>
<maxSizeRollBackups value="100"/>
<staticLogFileName value="true"/>
<countDirection value="0"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy/MM/dd HH:mm:ss.fff} %-5level [%-4thread] %logger - %message%newline" />
</layout>
</appender>

<logger name="Namespace.subNamespace">
<level value="DEBUG" />
<!-- refer đến appender bạn muốn-->
<appender-ref ref="myApenName" />
</logger>

<root>
<level value="INFO" />
<!-- <appender-ref ref="myApenName"/> -->
</root>
[/code]

Lưu ý rằng: chúng ta không nên refer đến appender "myApenName" trong thẻ root vì thế này đồng nghĩa với việc log tất cả các namespace



 II> Cách tạo các file log trong một thư mục được đặt tên theo ngày


Cụ thể là nếu hôm nay ngày 03/03/2015 thì chúng ta sẽ có thư mục mang tên "03-03-2015", thư mục này chứa tất cả các file log được tạo ra đúng ngày hôm nay.
Xem ví dụ sau đây,




[code language="xml"]
<appender name="Alllog" type="log4net.Appender.RollingFileAppender">
<!-- (1) -->
<param name="File" value="../Log/" />
<param name="DatePattern" value='yyyy-MM-dd"/Alllog.log"' />
<!-- (2) -->
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="Composite" />
<param name="AppendToFile" value="true" />
<param name="MaximumFileSize" value="5MB" />
<param name="MaxSizeRollBackups" value="50" />
<encoding value="utf-8" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy/MM/dd HH:mm:ss.fff} %-5level [%-4thread] %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
[/code]

Các bạn chú ý hai thẻ mà tôi đặc giữa hai số (1) và (2); Dòng đầu chúng ta dùng dấu '/' thay vì '\' như thông thường; Dòng hai là đường dẫn lưu file log - đường dẫn này có một thư mục mà tên được lấy từ ngày hiện tại với format là "yyyy-MM-dd".



III> Các kỹ thuật filter nâng cao trong log4net


Như chúng ta cũng biết ở bài trước, LogLevel cũng là một filter để chúng ta có thể tùy chọn nội dung log phù hợp, kèm theo đó trong mỗi Appender có các filter riêng. Hôm nay, chúng ta sẽ cùng tìm hiểu thêm về filter trong mỗi Appender .



Nhắc lại: bài trước nói về thẻ filter tôi nhắc đến ba loại như sau: StringMatchFilter, LevelRangeFilter, LevelMatchFilter, DenyAllFilter; Hôm nay chúng ta có thêm một loại nữa là "LoggerMatchFilter"


1) Cách tạo Whitelisting by logger - danh sách các namespace được phép ghi log


Format như sau




[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamspace.comm" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
[/code]

Bộ lọc LoggerMatchFilter mặc định giá trị của acceptOnMatch là True, tức là bộ lọc này là bộ lọc cho phép(các giá trị thõa điều kiện của bộ lọc này thì sẽ được ghi ra file). Ví dụ rên đây sẽ chỉ ghi ra log cho các nội dung log thuộc namespace là "myNamspace.comm", vì tất cả các log khác bị cấm (DenyAllFilter)



2) Cách tạo Blacklisting by logger - danh sách các namespace bị cấm ghi log


Format như sau




[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="LoggerDeny" />
<acceptOnMatch value="false" />
</filter>
[/code]

Tất cả các log đều được ghi trừ log phát sinh tại namespace "LoggerDeny", chúng ta làm điều này bằng cách set giá trị cho thẻ acceptOnMatch là False



3) Cách tạo bộ LoggerMatchFilter gộp


* Format của việc gộp các LoggerMatchFilter




[code language="xml"]
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="Noisy.Namespace.But.Important" />
</filter>
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="Noisy.Namespace" />
<acceptOnMatch value="false" />
</filter>
[/code]

Ví dụ trên cho ta một file log chứa các log của namespace "Noisy.Namespace.But.Important" và các log của namespace "Noisy.Namespace" bị cấm ghi -> Song thực tế chúng ta không mấy khi dùng kiểu gộp này.


* Format của việc gộp LoggerMatchFilter với LevelRangeFilter hoặc StringMatchFilter




[code language="xml"]
<appender name="AllLog" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamespace" />
<acceptOnMatch value="false" />
</filter>
<filter type="log4net.Filter.LevelRangeFilter">
<acceptOnMatch value="true" />
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>

<!-- param...-->
</appender>

<appender name="AllLog" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.LoggerMatchFilter">
<loggerToMatch value="myNamespace" />
</filter>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />

<!-- param...-->
</appender>
[/code]

Ví dụ này ghi tất cả các log có level từ INFO->FATAL và trừ log của namespace "myNamespace" hoặc ghi tất cả các log của namespace "myNamespace" và các log có chứa "test".
Chú ý rằng: nếu trong bộ gộp các thẻ filter có kiểu LoggerMatchFilter chúng ta nên để thẻ filter này lên trên các thẻ filter khác.


Chúc các bạn thành công,


Phạm Tuân

Thứ Năm, 1 tháng 1, 2015


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.