While we are discussing output formatting, there is one other factor to consider.
Some trace listeners do not use a text formatter to format the
output they generate. This is generally because the output is in a
binary or specific format. The WMI trace listener is a typical example
that does not use a text formatter.
For such trace listeners, you can set the TraceOutputOptions property to one of a range of
values to specify the values you want to include in the output. The
Trace OutputOptions property accepts a
value from the System.Diagnostics.TraceOptions enumeration.
Valid values include CallStack, DateTime,
ProcessId, LogicalOperation Stack, Timestamp, and ThreadId. The documentation installed with
Enterprise Library, and the documentation for the System.Diagnostics
namespace on MSDN®, provide more information.
1. Filtering by Severity in a Trace Listener
1.1. Filtering by Severity in a Trace Listener
The previous example generates a third disk file that we
haven't looked at yet. We didn't forget this, but saved if for this
section because it demonstrates another feature of the trace listeners that you will often find extremely
useful. To see this, you need to view the file XmlLogFile.xml that
was generated in the C:\Temp folder by the XML trace listener we
used in the previous example. You should open it in Microsoft
Internet Explorer® (or another Web browser or text editor) to see
the structure.
You will see that the file contains only one event from the
previous example, not the three that the code in the example
generated. This is because the XML trace listener has the Filter property in its configuration set to
Error. Therefore, it will log only
events with a severity of Error or
higher. If you look back at the example code, you will see that only
the last of the three calls to the Write method specified a value for the
severity (TraceEventType.Critical in
this case), and so the default value Information was used for the other two
events.
Note:
If you get an error indicating that the XML
document created by the XML trace listener is invalid, it's
probably because you have more than one log entry in the file.
This means that it is not a valid XML document—it contains
separate event log entries added to the file each time you ran
this example. To view it as XML, you must open the file in a text
editor and add an opening and closing element (such as
<root>
and </root>) around the
content. Or, just delete it and run the example once
more.
All of the trace listeners provided with Enterprise Library
expose the Filter property, and you
can use this to limit the log entries written to the logging target to only those that are important to
you. If your code generates many information events that you use for
monitoring and debugging only under specific circumstances, you can
filter these to reduce the growth and size of the log when they are
not required.
Alternatively, (as in the example) you can use the Filter property to differentiate the
granularity of logging for different listeners in the same category.
It may be that a flat file trace listener will log all entries to an
audit log file for some particular event, but an Email trace
listener in the same category will send e-mail messages to
administrators only when an Error or Critical event occurs.
1.2. Filtering All Log Entries by Priority
As well as being able to filter log entries in individual
trace listeners based on their severity, you can set the Logging
block to filter all log entries sent to it based on their priority.
Alongside the log-enabled filter and category filter in the Filters section of the configuration , you can add a filter named
Priority Filter.
This filter has two properties that you can set: Minimum Priority and Maximum Priority. The default setting for the
priority of a log entry is -1, which
is the same as the default setting of the Minimum Priority property of the filter, and
there is no maximum priority set. Therefore, this filter will not
block any log entries. However, if you change the defaults for these
properties, only log entries with a priority between the configured
values (including the specified maximum and minimum values) will be
logged. The exception is log entries that have the default priority
of -1. These are never
filtered.
2. Creating and Using Logentry Objects
So far we have used the Write
method of the LogWriter class to
generate log entries. An alternative approach that may be useful if
you want to create log entries individually, perhaps to return them
from methods or to pass them between processes, is to generate
instances of the LogEntry class and
then write them to the configured targets afterwards.
The example, Creating and writing log entries with a
LogEntry object, demonstrates this approach. It creates two
LogEntry instances. The code first
calls the most complex constructor of the LogEntry class that accepts all of the possible
values. This includes a Dictionary of
objects with a string key (in this example, the single item Extra Information) that will be included in the
output of the trace listener and formatter. Then it writes this log
entry using an overload of the Write
method of the LogWriter that accepts a
LogEntry instance.
Next, the code creates a new empty LogEntry using the default constructor and
populates this by setting individual properties, before writing it
using the same Write method of the
LogWriter.
// Check if logging is enabled before creating log entries.
if (defaultWriter.IsLoggingEnabled())
{
// Create a Dictionary of extended properties
Dictionary<string, object> exProperties = new Dictionary<string, object>();
exProperties.Add("Extra Information", "Some Special Value");
// Create a LogEntry using the constructor parameters.
LogEntry entry1 = new LogEntry("LogEntry with category, priority, event ID, "
+ "severity, and title.", "General", 8, 9006,
TraceEventType.Error, "Logging Block Examples",
exProperties);
defaultWriter.Write(entry1);
// Create a LogEntry and populate the individual properties.
LogEntry entry2 = new LogEntry();
entry2.Categories = new string[] {"General"};
entry2.EventId = 9007;
entry2.Message = "LogEntry with individual properties specified.";
entry2.Priority = 9;
entry2.Severity = TraceEventType.Warning;
entry2.Title = "Logging Block Examples";
entry2.ExtendedProperties = exProperties;
defaultWriter.Write(entry2);
}
else
{
Console.WriteLine("Logging is disabled in the configuration.");
}
This example writes the log entries to the Windows Application
Event Log by using the General
category. If you view the events this example generates, you will see
the values set in the code above including (at the end of the list)
the extended property we specified using a Dictionary. You can see this in Figure 1.
3. Capturing Unprocessed Events and Logging Errors
The capability to route log entries through different categories
to a configured set of trace listener targets provides a very powerful
mechanism for performing a wide range of logging activities. However,
it prompts some questions. In particular, what happens if the
categories specified in a log entry don't match any in the
configuration? And what happens if there is an error when the trace
listener attempts to write the log entry to the target?
3.1. About Special Sources
In fact, the Logging block includes three special sources that
handle these situations. Each is effectively a category, and you can
add references to configured trace listeners to each one so that events arriving in
that category will be written to the target(s) you
specify.
The All Events special source
receives all events, irrespective of all other settings within the
configuration of the block. You can use this to provide an audit
trail of all events, if required. By default, it has no trace listeners configured.
The Unprocessed Category
special source receives any log entry that has a category that does
not match any configured categories. By default, this category has
no trace listeners configured.
The Logging Errors &
Warnings special source receives any log entry that causes
an error in the logging process. By default, this category contains
a reference to a trace listener that writes details of the error to
the Windows Application Event Log, though you can reconfigure this
if you wish.
3.2. An Example of Using Special Sources
The example, Using Special Sources to capture
unprocessed events or errors, demonstrates how the
Logging block reacts under these two circumstances.
The code first writes a log entry to a category named InvalidCategory, which does not exist in the
configuration. Next, it writes another log entry to a category named
CauseLoggingError that is configured
to use a Database trace listener. However, this trace listener specifies
a connection string that is invalid; it points to a database that
does not exist.
// Check if logging is enabled before creating log entries.
if (defaultWriter.IsLoggingEnabled())
{
// Create log entry to be processed by the "Unprocessed" special source.
defaultWriter.Write("Entry with category not defined in configuration.",
"InvalidCategory");
// Create log entry to be processed by the "Errors & Warnings" special source.
defaultWriter.Write("Entry that causes a logging error.", "CauseLoggingError");
}
else
{
Console.WriteLine("Logging is disabled in the configuration.");
}
You might expect that neither of these log entries would
actually make it to their target. However, the example generates the
following messages that indicate where to look for the log entries
that are generated.
Created a Log Entry with a category name not defined in the configuration.
The Log Entry will appear in the Unprocessed.log file in the C:\Temp folder.
Created a Log Entry that causes a logging error.
The Log Entry will appear in the Windows Application Event Log.
This occurs because we configured the Unprocessed Category in
the Special Sources section with a
reference to a flat file trace listener that writes log entries to a
file named Unprocessed.log. If you open this file, you will see the
log entry that was sent to the InvalidCategory category.
The example uses the default configuration for the Logging Errors & Warnings special source.
This means that the log entry that caused a logging error will be
sent to the formatted event log trace listener referenced in this
category. If you open the application event log, you will see this
log entry. The listing below shows some of the content.
Timestamp: 24/11/2009 15:14:30
Message: Tracing to LogSource 'CauseLoggingError' failed. Processing for other
sources will continue. See summary information below for more information. Should
this problem persist, stop the service and check the configuration file(s) for
possible error(s) in the configuration of the categories and sinks.
Summary for Enterprise Library Distributor Service:
======================================
-->
Message:
Timestamp: 24/11/2009 15:14:30
Message: Entry that causes a logging error.
Category: CauseLoggingError
...
...
Exception Information Details:
======================================
Exception Type: System.Data.SqlClient.SqlException
Errors: System.Data.SqlClient.SqlErrorCollection
Class: 11
LineNumber: 65536
Number: 4060
Procedure:
Server: (local)\SQLEXPRESS
State: 1
Source: .Net SqlClient Data Provider
ErrorCode: -2146232060
Message: Cannot open database "DoesNotExist" requested by the login. The login
failed.
Login failed for user 'xxxxxxx\xxx'.
...
...
StackTrace Information Details:
======================================
...
...
In addition to the log entry itself, you can see that the
event contains a wealth of information to help you to debug the
error. It contains a message indicating that a logging error
occurred, followed by the log entry itself. However, after that is a
section containing details of the exception raised by the logging
mechanism (you can see the error message generated by the SqlClient data access code), and after this
is the full stack trace.