zoukankan      html  css  js  c++  java
  • Chapter 20: Diagnostics


    WHAT'S IN THIS CHAPTER?
    n Code contracts
    n Tracing
    n Event logging
    n Performance monitoring
    WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
    The wrox.com code downloads for this chapter are found at www.wrox.com/go/procsharp on the Download Code tab. The code for this
    chapter is divided into the following major examples:
    n Code Contracts
    n Tracing Demo
    n Tracing Demo with EventLog
    n EventSource Sample
    n Event Log
    n Event Log Reader
    n Performance Counter
    DIAGNOSTICS OVERVIEW
    This chapter explains how to get real-time information about your running application in order to identify any issues that it might have during
    production or to monitor resource usage to ensure that higher user loads can be accommodated. This is where the namespace
    System.Diagnostics comes into play. This namespace offers classes for tracing, event logging, performance counts, and code
    contracts.
    One way to deal with errors in your application, of course, is by throwing exceptions. However, an application might not fail with an exception,
    but still not behave as expected. The application might be running well on most systems but have a problem on a few. On the live system, you
    can change the log behavior by changing a configuration value and get detailed live information about what's going on in the application. This
    can be done with tracing.
    If there are problems with applications, the system administrator needs to be informed. The Event Viewer is a commonly used tool that not only
    the system administrator should be aware of. With the Event Viewer, you can both interactively monitor problems with applications and be
    informed about specific events that happen by adding subscriptions. The event-logging mechanism enables you to write information about the
    application.
    To analyze resources needed by applications, you can monitor applications with specified time intervals, e.g. get counts every 5 minutes. This
    way you can have data for 24 hours or a week without filling terabytes, and can plan for a different application distribution or the extension of
    system resources. The Performance Monitor can be used to get these data. You can write live data from your application by using
    performance counts.
    Design by contract is another feature offered by the .NET Framework. A method signature defines the type of parameters. It doesn't give you
    any information about the values that you can pass to the method. This is a feature of design by contract. Using classes from the namespace
    System.Diagnostics.Contracts you can define preconditions, postconditions, and invariants. These contracts can be checked during
    runtime but also with a static contract analyzer. This chapter explains these facilities and demonstrates how you can use them from your
    applications.
    CODE CONTRACTS
    Design by contract is an idea from the Eiffel programming language that defines preconditions, postconditions, and invariants. .NET includes
    classes for static and runtime checks of code within the namespace System.Diagnostics.Contracts that can be used by all .NET
    languages. With this functionality you can define preconditions, postconditions, and invariants within a method. The preconditions specify what
    requirements the parameters must fulfill, the postconditions define the requirements on returned data, and the invariants define the
    requirements of variables within the method itself.
    Contract information can be compiled both into the debug code and the release code. It is also possible to define a separate contract
    assembly, and many checks can be made statically without running the application. You can also define contracts on interfaces that cause the
    implementations of the interface to fulfill the contracts. Contract tools can rewrite the assembly to inject contract checks within the code for
    runtime checks, check the contracts during compile time, and add contract information to the generated XML documentation.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 2 / 29
    Figure 20-1 shows the project properties for the code contracts in Visual Studio 2013. Here, you can define what level of runtime checking
    should be done, indicate whether assert dialogs should be opened on contract failures, and configure static checking. Setting the Perform
    Runtime Contract Checking to Full defines the symbol CONTRACTS_FULL. Because many of the contract methods are annotated with the
    attribute [Conditional("CONTRACTS_FULL")], all runtime checks are performed with this setting only.
    Code contracts are defined with the Contract class. All contract requirements that you define in a method, whether they are preconditions or
    postconditions, must be placed at the beginning of the method. You can also assign a global event handler to the event ContractFailed
    that is invoked for every failed contract during runtime. Invoking SetHandled with the e parameter of type ContractFailedEventArgs
    stops the standard behavior of failures that would throw an exception (code file CodeContractSamples/Program.cs):
    Figure 20-1
    Contract.ContractFailed += (sender, e) =>
    {
    Console.fdhWriteLine(e.Message);
    e.SetHandled();
    };
    Preconditions
    Preconditions check the parameters that are passed to a method. With the Contract class, preconditions are defined with the Requires
    method. With the Requires method, a Boolean value must be passed, and an optional message string with the second parameter that is
    shown when the condition does not succeed. The following example requires that the argument min be less than or equal to the argument
    max:
    static void MinMax(int min, int max)
    {
    Contract.Requires(min <= max);
    //…
    }
    Using the generic variant of the Requires method enables specifying an exception type that should be invoked in case the condition is not
    fulfilled. The following contract throws an ArgumentNullException if the argument o is null. The exception is not thrown if an event handler
    sets the ContractFailed event to handled. Also, if the Assert on Contract Failure setting is configured, Trace.Assert is used to stop
    the program instead of throwing the exception defined:
    Note To work with code contracts you can use classes available with .NET 4.0 in the namespace
    System.Diagnostics.Contracts. However, no tool is included with Visual Studio 2013. You need to install the NuGet
    package Code Contracts Editor Extensions from Microsoft Research.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 3 / 29
    static void Preconditions(object o)
    {
    Contract.Requires<ArgumentNullException>(o != null,
    "Preconditions, o may not be null");
    //…
    Requires<TException> is not annotated with the attribute [Conditional("CONTRACTS_FULL")]; nor does it have a condition on
    the DEBUG symbol, so this runtime check is done in any case. Requires<TException> throws the defined exception if the condition is
    not fulfilled.
    For checking collections that are used as arguments, the Contract class offers Exists and ForAll methods. ForAll checks every item
    in the collection if the condition succeeds. In the example, it checks whether every item in the collection has a value smaller than 12. With the
    Exists method, it checks whether any one element in the collection meets the condition:
    static void ArrayTest(int[] data)
    {
    Contract.Requires(Contract.ForAll(data, i => i < 12));
    Both the methods Exists and ForAll have an overload whereby you can pass two integers, fromInclusive and toExclusive,
    instead of IEnumerable<T>. A range from the numbers (excluding toExclusive) is passed to the predicate defined with the third
    parameter. Exists and ForAll can be used with preconditions, postconditions, and invariants.
    Postconditions
    Postconditions define guarantees about shared data and return values after the method has completed. Although they define some
    guarantees on return values, they must be written at the beginning of a method; all contract requirements must be at the beginning of the
    method.
    Ensures and EnsuresOnThrow<TException> are postconditions. The following contract ensures that the variable sharedState is
    less than 6 at the end of the method (the value can change in between):
    private static int sharedState = 5;
    static void Postcondition()
    {
    Contract.Ensures(sharedState < 6);
    sharedState = 9;
    Console.WriteLine("change sharedState invariant {0}", sharedState);
    sharedState = 3;
    Console.WriteLine("before returning change it to a valid value {0}",
    sharedState);
    }
    With EnsuresOnThrow<TException>, it is guaranteed that a shared state meets a condition if a specified exception is thrown.
    To guarantee a return value, the special value Result<T> can be used with an Ensures contract. In the next example, the result is of type
    int as is also defined with the generic type T for the Result method. The Ensures contract guarantees that the return value is less than
    6:
    static int ReturnValue()
    {
    Contract.Ensures(Contract.Result<int>() < 6);
    return 3;
    }
    You can also compare a current value to an old value. This is done with the OldValue<T> method, which returns the original value on method
    entry for the variable passed. The following contract ensures that the result returned from the method is larger than the old value received from
    the argument x:
    static int ReturnLargerThanInput(int x)
    {
    Contract.Ensures(Contract.Result<int>() > Contract.OldValue<int>(x));
    return x + 3;
    }
    If a method returns values with the out modifier instead of just with the return statement, conditions can be defined with ValueAtReturn.
    The following contract defines that the x variable must be larger than 5 and smaller than 20 on return, and with the y variable module 5 must
    equal 0 on return:
    static void OutParameters(out int x, out int y)
    {
    Contract.Ensures(Contract.ValueAtReturn<int>(out x) > 5 &&
    Contract.ValueAtReturn<int>(out x) < 20);
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 4 / 29
    Contract.Ensures(Contract.ValueAtReturn<int>(out y) % 5 == 0);
    x = 8;
    y = 10;
    }
    Invariants
    Invariants define contracts for variables during the object's lifetime. Contract.Requires defines input requirements of a method, and
    Contract.Ensures defines requirements on method end. Contract.Invariant defines conditions that must succeed during the
    whole lifetime of an object.
    The following code snippet shows an invariant check of the member variable x, which must be larger than 5. With the initialization of x, x is
    initialized to 10, which fulfills the contract. The call to Contract.Invariant can only be placed within a method that has the
    ContractInvariantMethod attribute applied. This method can be public or private, can have any name (the name ObjectInvariant
    is suggested), and can only contain contract invariant checks:
    private int x = 10;
    [ContractInvariantMethod]
    private void ObjectInvariant()
    {
    Contract.Invariant(x > 5);
    }
    Invariant verification is always done at the end of public methods. In the next example, the method Invariant assigns 3 to the variable x,
    which results in a contract failure at the end of this method:
    public void Invariant()
    {
    x = 3;
    Console.WriteLine("invariant value: {0}", x);
    // contract failure at the end of the method
    }
    Purity
    You can use custom methods within contract methods, but these methods must be pure. Pure means that the method doesn't change any
    visible state of the object.
    You can mark methods and types as pure by applying the Pure attribute. get accessors of properties are assumed to be pure by default.
    With the current version of the code contract tools, purity is not enforced.
    Contracts for Interfaces
    With interfaces you can define methods, properties, and events that a class derived from the interface must implement. With the interface
    declaration you cannot define how the interface must be implemented, but now this is possible using code contracts.
    In the following example, the interface IPerson defines FirstName, LastName, and Age properties, and the method ChangeName.
    What's special about this interface is the attribute ContractClass. This attribute is applied to the interface IPerson and defines that the
    PersonContract class is used as the code contract for the interface (code file CodeContractsSamples/IPerson.cs):
    [ContractClass(typeof(PersonContract))]
    public interface IPerson
    {
    string FirstName { get; set; }
    string LastName { get; set; }
    int Age { get; set; }
    void ChangeName(string firstName, string lastName);
    }
    The class PersonContract implements the interface IPerson and defines code contracts for all the members. This is defined with the
    get accessors of the properties but can also be defined with all methods that are not allowed to change state. The FirstName and
    LastName get accessors also define that the result must be a string with Contract.Result. The get accessor of the Age property
    defines a postcondition, ensuring that the returned value is between 0 and 120. The set accessor of the FirstName and LastName
    properties requires that the value passed is not null. The set accessor of the Age property defines a precondition that the passed value is
    between 0 and 120 (code file CodeContractSamples/PersonContract.cs):
    [ContractClassFor(typeof(IPerson))]
    public abstract class PersonContract : IPerson
    {
    string IPerson.FirstName
    {
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 5 / 29
    get { return Contract.Result<String>(); }
    set { Contract.Requires(value != null); }
    }
    string IPerson.LastName
    {
    get { return Contract.Result<String>(); }
    set { Contract.Requires(value != null); }
    }
    int IPerson.Age
    {
    get
    {
    Contract.Ensures(Contract.Result<int>() >= 0 &&
    Contract.Result<int>() < 121);
    return Contract.Result<int>();
    }
    set
    {
    Contract.Requires(value >= 0 && value < 121);
    }
    }
    void IPerson.ChangeName(string firstName, string lastName)
    {
    Contract.Requires(firstName != null);
    Contract.Requires(lastName != null);
    }
    }
    Now a class implementing the IPerson interface must fulfill all the contract requirements. The class Person is a simple implementation of
    the interface that fulfills the contract (code file CodeContractsSamples/Person.cs):
    public class Person : IPerson
    {
    public Person(string firstName, string lastName)
    {
    this.FirstName = firstName;
    this.LastName = lastName;
    }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public int Age { get; set; }
    public void ChangeName(string firstName, string lastName)
    {
    this.FirstName = firstName;
    this.LastName = lastName;
    }
    }
    When using the class Person, the contract must also be fulfilled. For example, assigning null to a property is not allowed:
    var p = new Person { FirstName = "Tom", LastName = null };
    // contract error
    Nor is it allowed to assign an invalid value to the Age property:
    var p = new Person { FirstName = "Tom", LastName = "Turbo" };
    p.Age = 133; // contract error
    Abbreviations
    A new feature of .NET 4.5 and code contracts are abbreviations. If some contracts are required repeatedly, a reuse mechanism is available. A
    method that contains multiple contracts can be attributed with the ContractAbbreviator attribute, and thus it can be used within other
    methods requiring this contract:
    [ContractAbbreviator]
    private static void CheckCollectionContract(int[] data)
    {
    Contract.Requires<ArgumentNullException>(data != null);
    Contract.Requires(Contract.ForAll(data, x => x < 12));
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 6 / 29
    }
    Now the method CheckCollectionContract can be used within a method, checking the parameter for a non-null value, and checking
    every value of the collection to be smaller than 12:
    private static void Abbrevations(int[] data)
    {
    CheckCollectionContract(data);
    }
    Contracts and Legacy Code
    With a lot of legacy code, arguments are often checked with if statements and throw an exception if a condition is not fulfilled. With code
    contracts, it is not necessary to rewrite the verification; just add one line of code:
    static void PrecondtionsWithLegacyCode(object o)
    {
    if (o == null) throw new ArgumentNullException("o");
    Contract.EndContractBlock();
    The EndContractBlock defines that the preceding code should be handled as a contract. If other contract statements are used as well, the
    EndContractBlock is not necessary.
    TRACING
    Tracing enables you to see informational messages about the running application. To get information about a running application, you can start
    the application in the debugger. During debugging, you can walk through the application step by step and set breakpoints at specific lines and
    when you reach specific conditions. The problem with debugging is that a program with release code can behave differently from a program
    with debug code. For example, while the program is stopping at a breakpoint, other threads of the application are suspended as well. Also,
    with a release build, the compiler-generated output is optimized and, thus, different effects can occur. With optimized release code, garbage
    collection is much more aggressive than with debug code. The order of calls within a method can be changed, and some methods can be
    removed completely and be called in-place. There is a need to have runtime information from the release build of a program as well. Trace
    messages are written with both debug and release code.
    A scenario showing how tracing helps is described here. After an application is deployed, it runs on one system without problems, while on
    another system intermittent problems occur. When you enable verbose tracing, the system with the problems gives you detailed information
    about what's happening inside the application. The system that is running without problems has tracing configured just for error messages
    redirected to the Windows event log system. Critical errors are seen by the system administrator. The overhead of tracing is very small
    because you configure a trace level only when needed.
    The tracing architecture has four major parts:
    n Source — The originator of the trace information. You use the source to send trace messages.
    n Switch — Defines the level of information to log. For example, you can request just error information or detailed verbose information.
    n Listeners — Trace listeners define the location to which the trace messages should be written.
    n Filters — Listeners can have filters attached. The filter defines what trace messages should be written by the listener. This way, you can
    have different listeners for the same source that write different levels of information.
    Figure 20-2 shows a Visual Studio class diagram illustrating the major classes for tracing and how they are connected. The TraceSource
    uses a switch to define what information to log. It has a TraceListenerCollection associated with it, to which trace messages are
    forwarded. The collection consists of TraceListener objects, and every listener has a TraceFilter connected.
    Note When using assemblies with legacy code, with the code contracts configuration the assembly mode must be set to Custom
    Parameter Validation.
    Note Several .NET technologies make use of trace sources, which you just need to enable to see what's going on. For example, WPF
    defines, among others, sources such as System.Windows.Data, System.Windows.RoutedEvent,
    System.Windows.Markup, and System.Windows.Media.Animation. However, with WPF, you need to enable tracing
    not only by configuring listeners but also by setting within the registry key
    HKEY_CURRENT_USERSoftwareMicrosoftTracingWPF a new DWORD named ManagedTracing and the value 1 —
    or turn it on programmatically.
    Classes from the System.Net namespace use the trace source System.Net; WCF uses the trace sources
    System.ServiceModel and System.ServiceModel.MessageLogging. WCF tracing is discussed in Chapter 43,
    "Windows Communication Foundation."
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 7 / 29
    Figure 20-2
    Trace Sources
    You can write trace messages with the TraceSource class. Tracing requires the Trace flag of the compiler settings. With a Visual Studio
    project, the Trace flag is set by default with debug and release builds, but you can change it through the Build properties of the project.
    To write trace messages, you need to create a new TraceSource instance. In the constructor, the name of the trace source is defined. The
    method TraceInformation writes an informational message to the trace output. Instead of just writing informational messages, the
    TraceEvent method requires an enumeration value of type TraceEventType to define the type of the trace message.
    TraceEventType.Error specifies the message as an error message. You can define it with a trace switch to see only error messages.
    The second argument of the TraceEvent method requires an identifier. The ID can be used within the application itself. For example, you
    can use id 1 for entering a method and id 2 for exiting a method. The method TraceEvent is overloaded, so the TraceEventType
    and the ID are the only required parameters. Using the third parameter of an overloaded method, you can pass the message written to the
    trace. TraceEvent also supports passing a format string with any number of parameters, in the same way as
    Console.WriteLine.TraceInformation does nothing more than invoke TraceEvent with an identifier of 0. TraceInformation
    is just a simplified version of TraceEvent. With the TraceData method, you can pass any object — for example, an exception instance —
    instead of a message.
    To ensure that data is written by the listeners and does not stay in memory, you need to do a Flush. If the source is no longer needed, you
    can invoke the Close method, which closes all listeners associated with the trace source. Close does a Flush as well (code file
    TracingDemo/Program.cs):
    public class Program
    {
    internal static TraceSource trace =
    new TraceSource("Wrox.ProCSharp.Instrumentation");
    static void TraceSourceDemo1()
    {
    trace.TraceInformation("Info message");
    trace.TraceEvent(TraceEventType.Error, 3, "Error message");
    trace.TraceData(TraceEventType.Information, 2, "data1", 4, 5);
    trace.Close();
    }
    Note The TraceSource class is more difficult to use compared to the Trace class when writing trace messages, but it provides more
    options.
    Note You can use different trace sources within your application. It makes sense to define different sources for different libraries, so that
    you can enable different trace levels for different parts of your application. To use a trace source, you need to know its name. A
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 8 / 29
    The TraceEventType enumeration that is passed as an argument to the TraceEvent method defines the following levels to indicate the
    severity of the problem: Verbose, Information, Warning, Error, and Critical.Critical defines a fatal error or application
    crash; Error defines a recoverable error. Trace messages at the Verbose level provide detailed debugging information.
    TraceEventType also defines action levels Start, Stop, Suspend, and Resume, which define timely events inside a logical
    operation. As the code is written now, it does not display any trace message because the switch associated with the trace source is turned off.
    Trace Switches
    To enable or disable trace messages, you can configure a trace switch. Trace switches are classes derived from the abstract base class
    Switch. Derived classes are BooleanSwitch, TraceSwitch, and SourceSwitch. The class BooleanSwitch can be turned on
    and off, and the other two classes provide a range level. One range is defined by the SourceLevels enumeration. To configure trace
    switches, you must know the values associated with the SourceLevels enumeration. SourceLevels defines the values Off, Error,
    Warning, Info, and Verbose.
    You can associate a trace switch programmatically by setting the Switch property of the TraceSource. In the following example, the
    associated switch is of type SourceSwitch, has the name Wrox.ProCSharp.Diagnostics, and has the level Verbose:
    internal static SourceSwitch traceSwitch =
    new SourceSwitch("Wrox.ProCSharp.Diagnostics")
    { Level = SourceLevels.Verbose };
    internal static TraceSource trace =
    new TraceSource("Wrox.ProCSharp.Diagnostics")
    { Switch = traceSwitch };
    Setting the level to Verbose means that all trace messages should be written. If you set the value to Error, only error messages are
    displayed. Setting the value to Information means that error, warning, and info messages are shown. By writing the trace messages once
    more, you can see the messages while running the debugger in the Output window.
    Usually, you would want to change the switch level not by recompiling the application, but instead by changing the configuration. The trace
    source can be configured in the application configuration file. Tracing is configured within the <system.diagnostics> element. The trace
    source is defined with the <source> element as a child element of <sources>. The name of the source in the configuration file must exactly
    match the name of the source in the program code. In the next example, the trace source has a switch of type
    System.Diagnostics.SourceSwitch associated with the name MySourceSwitch. The switch itself is defined within the
    <switches> section, and the level of the switch is set to verbose (config file TracingDemo/App.config):
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <system.diagnostics>
    <sources>
    <source name="Wrox.ProCSharp.Diagnostics" switchName="MySourceSwitch"
    switchType="System.Diagnostics.SourceSwitch" />
    </sources>
    <switches>
    <add name="MySourceSwitch" value="Verbose"/>
    </switches>
    </system.diagnostics>
    </configuration>
    Now you can change the trace level just by changing the configuration file; there's no need to recompile the code. After the configuration file is
    changed, you must restart the application.
    Trace Listeners
    By default, trace information is written to the Output window of the Visual Studio debugger; but by changing the application's configuration, you
    can redirect the trace output to different locations.
    Where the tracing results should be written to is defined by trace listeners. A trace listener is derived from the abstract base class
    TraceListener. .NET includes several trace listeners to write the trace events to different targets. For file-based trace listeners, the base
    class TextWriterTraceListener is used, along with the derived classes XmlWriterTraceListener to write to XML files and
    DelimitedListTraceListener to write to delimited files. Writing to the event log is done with either the EventLogTraceListener
    or the EventProviderTraceListener. The latter uses the event file format available since Windows Vista. You can also combine web
    tracing with System.Diagnostics tracing and use the WebPageTraceListener to write System.Diagnostics tracing to the web
    trace file, trace.axd.
    .NET Framework delivers many listeners to which trace information can be written; but if the provided listeners don't fulfill your requirements,
    you can create a custom listener by deriving a class from the base class TraceListener. With a custom listener, you can, for example,
    write trace information to a web service, write messages to your mobile phone, and so on. It's not usually desirable to receive hundreds of
    messages on your phone, however, and with verbose tracing this can become really expensive.
    common naming convention is to use the same name as the assembly name.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 9 / 29
    You can configure a trace listener programmatically by creating a listener object and assigning it to the Listeners property of the
    TraceSource class. However, usually it is more interesting to just change a configuration to define a different listener.
    You can configure listeners as child elements of the <source> element. With the listener, you define the type of the listener class and use
    initializeData to specify where the output of the listener should go. The following configuration defines the
    XmlWriterTraceListener to write to the file demotrace.xml, and the DelimitedListTraceListener to write to the file
    demotrace.txt (config file TracingDemo/App.config):
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <system.diagnostics>
    <sources>
    <source name="Wrox.ProCSharp.Diagnostics" switchName="MySourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">
    <listeners>
    <add name="xmlListener"
    type="System.Diagnostics.XmlWriterTraceListener"
    traceOutputOptions="None"
    initializeData="c:/logs/mytrace.xml" />
    <add name="delimitedListener" delimiter=":"
    type="System.Diagnostics.DelimitedListTraceListener"
    traceOutputOptions="DateTime, ProcessId"
    initializeData="c:/logs/mytrace.txt" />
    </listeners>
    </source>
    </sources>
    <switches>
    <add name="MySourceSwitch" value="Verbose"/>
    </switches>
    </system.diagnostics>
    </configuration>
    With the listener, you can also specify what additional information should be written to the trace log. This information is specified with the
    traceOutputOptions XML attribute and is defined by the TraceOptions enumeration. The enumeration defines Callstack,
    DateTime, LogicalOperationStack, ProcessId, ThreadId, and None. You can add this comma-separated information to the
    traceOutputOptions XML attribute, as shown with the delimited trace listener.
    The delimited file output from the DelimitedListTraceListener, including the process ID and date/time, is shown here:
    "Wrox.ProCSharp.Diagnostics":Start:0:"Main started"::7724:""::
    "2012-05-11T14:31:50.8677211Z"::
    "Wrox.ProCSharp.Diagnostics":Information:0:"Info message"::7724:"Main"::
    "2012-05-11T14:31:50.8797132Z"::
    "Wrox.ProCSharp.Diagnostics":Error:3:"Error message"::7724:"Main"::
    "2012-05-11T14:31:50.8817119Z"::
    "Wrox.ProCSharp.Diagnostics":Information:2::"data1","4","5":7724:"Main"::
    "2012-05-11T14:31:50.8817119Z"::
    The XML output from the XmlWriterTraceListener always contains the name of the computer, the process ID, the thread ID, the
    message, the time created, the source, and the activity ID. Other fields, such as the call stack, logical operation stack, and timestamp, vary
    according to the trace output options.
    If a listener should be used by multiple trace sources, you can add the listener configuration to the element <sharedListeners>, which is
    independent of the trace source. The name of the listener that is configured with a shared listener must be referenced from the listeners of the
    trace source:
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <system.diagnostics>
    <sources>
    <source name="Wrox.ProCSharp.Diagnostics" switchName="MySourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">
    <listeners>
    <add name="xmlListener"
    type="System.Diagnostics.XmlWriterTraceListener"
    traceOutputOptions="None"
    initializeData="c:/logs/mytrace.xml" />
    <add name="delimitedListener" />
    Note You can use the XmlDocument, XPathNavigator, and XElement classes to analyze the content from the XML file. These
    classes are covered in Chapter 34, "Manipulating XML."
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 10 / 29
    </listeners>
    </source>
    </sources>
    <sharedListeners>
    <add name="delimitedListener" delimiter=":"
    type="System.Diagnostics.DelimitedListTraceListener"
    traceOutputOptions="DateTime, ProcessId"
    initializeData="c:/logs/mytrace.txt" />
    </sharedListeners>
    <switches>
    <add name="MySourceSwitch" value="Verbose"/>
    </switches>
    </system.diagnostics>
    </configuration>
    Filters
    Every listener has a Filter property that defines whether the listener should write the trace message. For example, multiple listeners can be
    used with the same trace source. One of the listeners writes verbose messages to a log file, and another listener writes error messages to the
    event log. Before a listener writes a trace message, it invokes the ShouldTrace method of the associated filter object to determine whether
    the trace message should be written.
    A filter is a class that is derived from the abstract base class TraceFilter. .NET offers two filter implementations: SourceFilter and
    EventTypeFilter. With the source filter, you can specify that trace messages are to be written only from specific sources. The event type
    filter is an extension of the switch functionality. With a switch, it is possible to define, according to the trace severity level, whether the event
    source should forward the trace message to the listeners. If the trace message is forwarded, the listener can then use the filter to determine
    whether the message should be written.
    The changed configuration now defines that the delimited listener should write trace messages only if the severity level is of type warning or
    higher, because of the defined EventTypeFilter. The XML listener specifies a SourceFilter and accepts trace messages only from
    the source Wrox.ProCSharp.Tracing. If you have a large number of sources defined to write trace messages to the same listener, you
    can change the configuration for the listener to concentrate on trace messages from a specific source:
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <system.diagnostics>
    <sources>
    <source name="Wrox.ProCSharp.Tracing" switchName="MySourceSwitch"
    switchType="System.Diagnostics.SourceSwitch">xs
    <listeners>
    <add name="xmlListener" />
    <add name="delimitedListener" />
    </listeners>
    </source>
    </sources>
    <sharedListeners>
    <add name="delimitedListener" delimiter=":"
    type="System.Diagnostics.DelimitedListTraceListener"
    traceOutputOptions="DateTime, ProcessId"
    initializeData="c:/logs/mytrace.txt">
    <filter type="System.Diagnostics.EventTypeFilter"
    initializeData="Warning" />
    </add>
    <add name="xmlListener"
    type="System.Diagnostics.XmlWriterTraceListener"
    traceOutputOptions="None"
    initializeData="c:/logs/mytrace.xml">
    <filter type="System.Diagnostics.SourceFilter"
    initializeData="Wrox.ProCSharp.Diagnostics" />
    </add>
    </sharedListeners>
    <switches>
    <add name="MySourceSwitch" value="Verbose"/>
    </switches>
    </system.diagnostics>
    </configuration>
    The tracing architecture can be extended. Just as you can write a custom listener derived from the base class TraceListener, you can
    create a custom filter derived from TraceFilter. With that capability, you can create a filter that specifies writing trace messages
    depending, for example, on the time, on an exception that occurred lately, or on the weather.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 11 / 29
    Correlation
    With trace logs, you can see the relationship of different methods in several ways. To see the call stack of the trace events, a configuration only
    needs to track the call stack with the XML listener. You can also define a logical call stack that can be shown in the log messages; and you can
    define activities to map trace messages.
    To show the call stack and the logical call stack with the trace messages, the XmlWriterTraceListener can be configured to the
    corresponding traceOuputOptions. The MSDN documentation (http://msdn.microsoft.com/en-us/library/System.Diagnostics.
    XmlWriterTraceListener(v=vs.110).aspx) provides details about all the other options you can configure for tracing with this listener.
    <sharedListeners>
    <add name="xmlListener" type="System.Diagnostics.XmlWriterTraceListener"
    traceOutputOptions="LogicalOperationStack, Callstack"
    initializeData="c:/logs/mytrace.xml">
    </add>
    </sharedListeners>
    So you can see the correlation with trace logs, in the Main method a new activity ID is assigned to the CorrelationManager by setting
    the ActivityID property. Events of type TraceEventType.Start and TraceEventType.Stop are done at the beginning and end of
    the Main method. In addition, a logical operation named "Main" is started and stopped with the StartLogicalOperation and
    StopLogicalOperation methods:
    static void Main()
    {
    // start a new activity
    if (Trace.CorrelationManager.ActivityId == Guid.Empty)
    {
    Guid newGuid = Guid.NewGuid();
    Trace.CorrelationManager.ActivityId = newGuid;
    }
    trace.TraceEvent(TraceEventType.Start, 0, "Main started");
    // start a logical operation
    Trace.CorrelationManager.StartLogicalOperation("Main");
    TraceSourceDemo1();
    StartActivityA();
    Trace.CorrelationManager.StopLogicalOperation();
    Thread.Sleep(3000);
    trace.TraceEvent(TraceEventType.Stop, 0, "Main stopped");
    }
    The method StartActivityA that is called from within the Main method creates a new activity by setting the ActivityId of the
    CorrelationManager to a new GUID. Before the activity stops, the ActivityId of the CorrelationManager is reset to the previous
    value. This method invokes the Foo method and creates a new task with the Task.Factory.StartNew method. This task is created so
    that you can see how different threads are displayed in a trace viewer:
    private static void StartActivityA()
    {
    Guid oldGuid = Trace.CorrelationManager.ActivityId;
    Guid newActivityId = Guid.NewGuid();
    Trace.CorrelationManager.ActivityId = newActivityId;
    Trace.CorrelationManager.StartLogicalOperation("StartActivityA");
    trace.TraceEvent(TraceEventType.Verbose, 0,
    "starting Foo in StartNewActivity");
    Foo();
    trace.TraceEvent(TraceEventType.Verbose, 0,
    "starting a new task");
    Task.Run(() => WorkForATask());
    Trace.CorrelationManager.StopLogicalOperation();
    Trace.CorrelationManager.ActivityId = oldGuid;
    }
    The Foo method that is started from within the StartActivityA method starts a new logical operation. The logical operation Foo is
    Note Tasks are explained in Chapter 21, "Tasks, Threads, and Synchronization."
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 12 / 29
    started within the StartActivityA logical operation:
    private static void Foo()
    {
    Trace.CorrelationManager.StartLogicalOperation("Foo operation");
    trace.TraceEvent(TraceEventType.Verbose, 0, "running Foo");
    Trace.CorrelationManager.StopLogicalOperation();
    }
    The task that is created from within the StartActivityA method runs the method WorkForATask. Here, only simple trace events with
    start and stop information, and verbose information, are written to the trace:
    private static void WorkForATask()
    {
    trace.TraceEvent(TraceEventType.Start, 0, "WorkForATask started");
    trace.TraceEvent(TraceEventType.Verbose, 0, "running WorkForATask");
    trace.TraceEvent(TraceEventType.Stop, 0, "WorkForATask completed");
    }
    To analyze the trace information, the tool Service Trace Viewer, svctraceviewer.exe, can be started. This tool is mainly used to analyze
    WCF traces, but you can also use it to analyze any trace that is written with the XmlWriterTraceListener. Figure 20-3 shows the Activity
    tab of Service Trace Viewer, with each activity displayed on the left, and the events displayed on the right. When you select an event you can
    choose to display either the complete message in XML or a formatted view. The latter displays basic information, application data, the logical
    operation stack, and the call stack in a nicely formatted manner.
    Figure 20-4 shows the Graph tab of the dialog. Using this view, different processes or threads can be selected for display in separate swim
    lanes. As a new thread is created with the Task class, a second swim lane appears by selecting the thread view.
    Figure 20-3
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 13 / 29
    Figure 20-4
    Tracing with ETW
    A fast way to do tracing is by using Event Tracing for Windows (ETW). ETW is used by Windows for tracing, event logging, and performance
    counts. To write traces with ETW, the EventProviderTraceListener can be configured as a listener, as shown in the following snippet.
    The type attribute is used to find the class dynamically. The class name is specified with the strong name of the assembly together with the
    class name. With the initializeData attribute, a GUID needs to be specified to uniquely identify your listener. You can create a GUID by
    using the command-line tool uuidgen or the graphical tool guidgen:
    <sharedListeners>
    <add name="etwListener"
    type="System.Diagnostics.Eventing.EventProviderTraceListener,
    System.Core, Version=4.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    initializeData="{8ADA630A-F1CD-48BD-89F7-02CE2E7B9625}"/>
    After changing the configuration, before you run the program once more to write traces using ETW, you need to start a trace session by using
    the logman command. The start option starts a new session to log. The -p option defines the name of the provider; here the GUID is used
    to identify the provider. The -o option defines the output file, and the -ets option sends the command directly to the event trace system
    without scheduling:
    logman start mysession -p {8ADA630A-F1CD-48BD-89F7-02CE2E7B9625}
    -o mytrace.etl -ets
    After running the application, the trace session can be stopped with the stop command:
    logman stop mysession -ets
    The log file is in a binary format. To get a readable representation, the utility tracerpt can be used. With this tool it's possible to extract
    CSV, XML, and EVTX formats, as specified with the -of option:
    tracerpt mytrace.etl -o mytrace.xml -of XML
    Using EventSource
    EventSource is a new class for tracing, available since .NET 4.5. This class gives a new way to do tracing and is fully based on ETW. This
    class and types that can be used with EventSource are defined within the System.Diagnostics.Tracing namespace.
    A simple way to use EventSource is to create a class that derives from the EventSource type and defining methods to write trace
    information by calling the WriteEvent method of the base class. The class MyProjectEventSource defines strongly typed members
    Note The command-line tools logman and tracerpt are included with the Windows operating system.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 14 / 29
    like Startup and CallService that invoke the method WriteEvent of the base class (code file
    EventSourceSample/MyProjectEventSource.cs):
    public class MyProjectEventSource : EventSource
    {
    private MyProjectEventSource()
    {
    }
    public static MyProjectEventSource Log = new MyProjectEventSource();
    public void Startup()
    {
    base.WriteEvent(1);
    }
    public void CallService(string url)
    {
    base.WriteEvent(2, url);
    }
    public void ServiceError(string message, int error)
    {
    base.WriteEvent(3, message, error);
    }
    }
    In a simple scenario where just information messages should be written, nothing more is necessary. Besides passing an event ID to the trace
    log, the WriteEvent method has 14 overloads that allow passing message strings, int, and long values, and any number of objects.
    With this implementation, the members of the MyProjectEventSource type can be used to write trace messages as shown in the
    Program class (code file EventSourceSample/Program.cs). The Main method makes a trace log calling the Startup method,
    invokes the NetworkRequestSample method to create a trace log via the CallService method, and makes a trace log in case of an
    error:
    class Program
    {
    static void Main()
    {
    MyProjectEventSource.Log.Startup();
    NetworkRequestSample();
    Console.ReadLine();
    }
    private static async void NetworkRequestSample()
    {
    try
    {
    var client = new HttpClient();
    string url = "http://www.cninnovaton.com";
    MyProjectEventSource.Log.CallService(url);
    string result = await client.GetStringAsync(url);
    Console.WriteLine("Complete……………‥");
    }
    catch (Exception ex)
    {
    MyProjectEventSource.Log.ServiceError(ex.Message, ex.HResult);
    }
    }
    }
    Trace messages can be accessed out-of-process using the logman utility (as discussed earlier in the "Tracing with ETW" section). You can
    also use the PerfView utility to read trace messages. You can download PerfView from the Microsoft Download Center at
    http://www.microsoft.com/downloads.
    For accessing trace messages in-process, you can use the EventListener base class. You just need to create a class that derives from
    the EventListener class and overrides the OnEventWritten method. With this method, trace messages are passed to the parameter
    of type EventWrittenEventArgs. Thet sample implementation sends information about the event, including the payload, which is the
    additional data passed to the WriteEvent method of the EventSource (code file EventSourceSample/MyListener.cs):
    class MyListener : EventListener
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 15 / 29
    {
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
    Console.WriteLine("created {0} {1}", eventSource.Name, eventSource.Guid);
    }
    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
    Console.WriteLine("event id: {0} source: {1}", eventData.EventId,
    eventData.EventSource.Name);
    foreach (var payload in eventData.Payload)
    {
    Console.WriteLine(" {0}", payload);
    }
    }
    }
    The listener is activated in the Main method of the Program class. Event sources can be accessed calling the static method GetSources
    of the EventSource class:
    IEnumerable<EventSource> eventSources = EventSource.GetSources();
    InitListener(eventSources);
    The InitListener method invokes the EnableEvents method of the custom listener and passes every event source. The sample code
    registers to listen to log every message setting EventLevel.LogAlways. You can also specify just to write information messages, which
    also include errors, or errors only. Specifying this level is very similar to the trace source switch discussed earlier in the "Trace Switches"
    section:
    private static void InitListener(IEnumerable<EventSource> sources)
    {
    listener = new MyListener();
    foreach (var source in sources)
    {
    listener.EnableEvents(source, EventLevel.LogAlways);
    }
    }
    Advanced Tracing with EventSource
    For many applications, using the EventSource as described in the previous section is all that you need. However, you also have more
    control of tracing as shown in the code file EventSourceSampleMyAdvancedProjectEventSource.cs.
    By default, the name of the event source is the same as the name of the class, but you can change the name and the unique identifier by
    applying the EventSource attribute. Every event trace method can be accompanied by the Event attribute. Here you can define the ID of
    the event, an opcode, the trace level, custom keywords, and tasks. The listener can filter out trace messages based on keywords. The
    keywords are specified by setting single bits in a flag-style enumeration:
    [EventSource(Name="EventSourceSample", Guid="45FFF0E2-7198-4E4F-9FC3-DF6934680096")]
    class MyAdvancedProjectEventSource : EventSource
    {
    public class Keywords
    {
    public const EventKeywords Network = (EventKeywords)1;
    public const EventKeywords Database = (EventKeywords)2;
    public const EventKeywords Diagnostics = (EventKeywords)4;
    public const EventKeywords Performance = (EventKeywords)8;
    }
    public class Tasks
    {
    public const EventTask CreateMenus = (EventTask)1;
    public const EventTask QueryMenus = (EventTask)2;
    }
    private MyAdvancedProjectEventSource()
    {
    }
    public static MyAdvancedProjectEventSource Log = new MyAdvancedProjectEventSource();
    [Event(1, Opcode=EventOpcode.Start, Level=EventLevel.Verbose)]
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 16 / 29
    public void Startup()
    {
    base.WriteEvent(1);
    }
    [Event(2, Opcode=EventOpcode.Info, Keywords=Keywords.Network,
    Level=EventLevel.Verbose, Message="{0}")]
    public void CallService(string url)
    {
    base.WriteEvent(2);
    }
    [Event(3, Opcode=EventOpcode.Info, Keywords=Keywords.Network,
    Level=EventLevel.Error, Message="{0} error: {1}")]
    public void ServiceError(string message, int error)
    {
    base.WriteEvent(3);
    }
    [Event(4, Opcode=EventOpcode.Info, Task=Tasks.CreateMenus,
    Level=EventLevel.Verbose, Keywords=Keywords.Network)]
    public void SomeTask()
    {
    base.WriteEvent(4);
    }
    }
    With the new detailed usage of the event definition, the listener needs to specify the keywords that should be logged besides the log level:
    listener.EnableEvents(source, EventLevel.Verbose, (EventKeywords)15L);
    EVENT LOGGING
    System administrators use the Event Viewer to get critical messages about the health of the system and applications, and informational
    messages. You should write error messages from your application to the event log so that the information can be read with the Event Viewer.
    Trace messages can be written to the event log if you configure the EventLogTraceListener class. The EventLogTraceListener
    has an EventLog object associated with it to write the event log entries. You can also use the EventLog class directly to write and read
    event logs.
    In this section, you explore the following:
    n Event-logging architecture
    n Classes for event logging from the System.Diagnostics namespace
    n Adding event logging to services and other application types
    n Creating an event log listener with the EnableRaisingEvents property of the EventLog class
    n Using a resource file to define messages
    Figure 20-5 shows an example of a log entry resulting from a failed access with Distributed COM.
    For custom event logging, you can use classes from the System.Diagnostics namespace.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 17 / 29
    Figure 20-5
    Event-logging Architecture
    Event log information is stored in several log files. The most important ones are application, security, and system. Looking at the registry
    configuration of the event log service, you will notice several entries under
    HKEY_LOCAL_MACHINESystemCurrentControlSetServicesEventlog with configurations pointing to the specific files. The
    system log file is used from the system and device drivers. Applications and services write to the application log. The security log is a readonly
    log for applications. The auditing feature of the operating system uses the security log. Every application can also create a custom
    category and write event log entries there, such as Media Center.
    You can read these events by using the Event Viewer administrative tool. To open it directly from the Server Explorer of Visual Studio, rightclick
    the Event Logs item and select the Launch Event Viewer entry from the context menu. The Event Viewer dialog is shown in Figure 20-6.
    Figure 20-6
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 18 / 29
    The event log contains the following information:
    n Type — The main types are Information, Warning, or Error. Information is an infrequently used type that denotes a successful operation;
    Warning denotes a problem that is not immediately significant; and Error denotes a major problem. Additional types are FailureAudit
    and SuccessAudit, but these types are used only for the security log.
    n Date — Date and Time show the day and time that the event occurred.
    n Source — The Source is the name of the software that logs the event. The source for the application log is configured in the following
    registry key:
    HKEY_LOCAL_MACHINESystemCurrentControlSetServicesEventlog
    Application[ApplicationName]
    Within this key, the value EventMessageFile is configured to point to a resource DLL that holds error messages:
    n Event ID — The event identifier specifies a particular event message.
    n Category — A category can be defined so that event logs can be filtered when using the Event Viewer. Categories can be defined
    according to an event source.
    Event-logging Classes
    For writing event logs, two different Windows APIs exist. One API, available since Windows Vista, is wrapped by the classes in the
    namespace System.Diagnostics.Eventing. The other wrapper classes are in the System.Diagnostics namespace.
    The System.Diagnostics namespace has the following classes for event logging.
    The heart of event logging is in the EventLog class. The members of this class are explained in the following table.
    Creating an Event Source
    Before writing events, you must create an event source. You can use either the CreateEventSource method of the EventLog class or the
    Note This book covers event logs using the System.Diagnostics namespace. The other event logs from the
    System.Diagnostics.Eventing namespace don't have strong support for .NET, require several command-line tools, and
    unsafe C# code.
    CLASS DESCRIPTION
    EventLog With the EventLog class, you can read and write entries in the event log, and establish applications as event sources.
    EventLogEntry The EventLogEntry class represents a single entry in the event log. With the
    EventLogEntryCollection, you can iterate through EventLogEntry items.
    EventLogInstaller The EventLogInstaller class is the installer for an EventLog component. EventLogInstaller
    calls EventLog.CreateEventSource to create an event source.
    EventLogTraceListener With the help of the EventLogTraceListener, traces can be written to the event log. This class implements
    the abstract class TraceListener.
    EVENTLOG MEMBER DESCRIPTION
    Entries With the Entries property, you can read event logs. Entries returns an EventLogEntryCollection that
    contains EventLogEntry objects holding information about the events. There is no need to invoke a Read method. The
    collection is filled as soon as you access this property.
    Log Specifies the log for reading or writing event logs.
    LogDisplayName A read-only property that returns the display name of the log.
    MachineName Specifies the system on which to read or write log entries.
    Source Specifies the source of the event entries to write.
    CreateEventSource() Creates a new event source and a new log file.
    DeleteEventSource() Invoke this to get rid of an event source.
    SourceExists() Using this element, you can verify whether the source already exists before creating an event source.
    WriteEntry()
    WriteEvent()
    Write event log entries with either the WriteEntry or WriteEvent method. WriteEntry is simpler, because
    you just need to pass a string. WriteEvent is more flexible, because you can use message files that are independent
    of the application and that support localization.
    Clear() Removes all entries from an event log.
    Delete() Deletes a complete event log.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 19 / 29
    class EventLogInstaller. Because you need administrative privileges when creating an event source, an installation program is best for
    defining the new source.
    The following example verifies that an event log source named EventLogDemoApp already exists. If it doesn't exist, then an object of type
    EventSourceCreationData is instantiated that defines the source name EventLogDemoApp and the log name ProCSharpLog.
    Here, all events of this source are written to the ProCSharpLog event log. The default is the application log:
    string logName = "ProCSharpLog";
    string sourceName = "EventLogDemoApp";
    if (!EventLog.SourceExists(sourceName))
    {
    var eventSourceData = new EventSourceCreationData(sourceName, logName);
    EventLog.CreateEventSource(eventSourceData);
    }
    The name of the event source is an identifier of the application that writes the events. For the system administrator reading the log, the
    information helps to identify the event log entries in order to map them to application categories. Examples of names for event log sources are
    LoadPerf for the Performance Monitor, MSSQLSERVER for Microsoft SQL Server, MsiInstaller for the Windows Installer, Winlogon,
    Tcpip, Time-Service, and so on.
    Setting the name "Application" for the event log writes event log entries to the application log. You can also create your own log by specifying a
    different application log name. Log files are located in the directory <windows>System32WinEvtLogs.
    With the EventSourceCreationData class, you can also specify several more characteristics for the event log, as described in the
    following table.
    Writing Event Logs
    For writing event log entries, you can use the WriteEntry or WriteEvent methods of the EventLog class. The EventLog class has
    both a static and an instance method WriteEntry. The static method WriteEntry requires a parameter of the source. The source can
    also be set with the constructor of the EventLog class. In the following example, the log name, the local machine, and the event source name
    are defined in the constructor. Next, three event log entries are written with the message as the first parameter of the WriteEntry method.
    WriteEntry is overloaded. The second parameter you can assign is an enumeration of type EventLogEntryType. With
    EventLogEntryType, you can define the severity of the event log entry. Possible values are Information, Warning, and Error;
    and for auditing, SuccessAudit and FailureAudit. Depending on the type, different icons are shown in the Event Viewer. With the third
    parameter, you can specify an application-specific event ID that can be used by the application itself. In addition, you can pass applicationspecific
    binary data and a category.
    using (var log = new EventLog(logName, ".", sourceName))
    {
    log.WriteEntry("Message 1");
    log.WriteEntry("Message 2", EventLogEntryType.Warning);
    log.WriteEntry("Message 3", EventLogEntryType.Information, 33);
    }
    Resource Files
    Instead of defining the messages for the event log in the C# code and passing it to the WriteEntry method, you can create a message
    resource file, define messages in the resource file, and pass message identifiers to the WriteEvent method. Resource files also support
    localization.
    EVENTSOURCECREATIONDATA DESCRIPTION
    Source Gets or sets the name of the event source.
    LogName Defines the log where event log entries are written. The default is the application log.
    MachineName Defines the system to read or write log entries.
    CategoryResourceFile Defines a resource file for categories. Categories enable easier filtering of event log entries within a single source.
    CategoryCount Defines the number of categories in the category resource file.
    MessageResourceFile Instead of specifying that the message should be written to the event log in the program that writes the events, messages
    can be defined in a resource file that is assigned to the MessageResourceFile property. Messages from the
    resource file are localizable.
    ParameterResourceFile Messages in a resource file can have parameters. The parameters can be replaced by strings defined in a resource file
    that is assigned to the ParameterResourceFile property.
    Note Message resource files are native resource files that have nothing in common with .NET resource files. .NET resource files are
    covered in Chapter 28, "Localization."
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 20 / 29
    A message file is a text file with the mc file extension. The syntax that this file uses to define messages is very strict. The sample
    file EventLogMessages.mc contains four categories followed by event messages. Every message has an ID that can be used
    by the application writing event entries. Parameters that can be passed from the application are defined with % syntax in the
    message text (resource file EventLogDemo/EventLogDemoMessages.mc):
    ; // EventLogDemoMessages.mc
    ; // ********************************************************
    ; // — Event categories -
    ; // Categories must be numbered consecutively starting at 1.
    ; // ********************************************************
    MessageId=0x1
    Severity=Success
    SymbolicName=INSTALL_CATEGORY
    Language=English
    Installation
    .
    MessageId=0x2
    Severity=Success
    SymbolicName=DATA_CATEGORY
    Language=English
    Database Query
    .
    MessageId=0x3
    Severity=Success
    SymbolicName=UPDATE_CATEGORY
    Language=English
    Data Update
    .
    MessageId=0x4
    Severity=Success
    SymbolicName=NETWORK_CATEGORY
    Language=English
    Network Communication
    .
    ; // — Event messages -
    ; // *********************************
    MessageId = 1000
    Severity = Success
    Facility = Application
    SymbolicName = MSG_CONNECT_1000
    Language=English
    Connection successful.
    .
    MessageId = 1001
    Severity = Error
    Facility = Application
    SymbolicName = MSG_CONNECT_FAILED_1001
    Language=English
    Could not connect to server %1.
    .
    MessageId = 1002
    Severity = Error
    Facility = Application
    SymbolicName = MSG_DB_UPDATE_1002
    Language=English
    Database update failed.
    .
    MessageId = 1003
    Severity = Success
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 21 / 29
    Use the Messages Compiler, mc.exe, to create a binary message file. The following command compiles the source file containing the
    messages to a messages file with the .bin extension and the file Messages.rc, which contains a reference to the binary message file:
    mc -s EventLogDemoMessages.mc
    Next, you must use the Resource Compiler, rc.exe. The following command creates the resource file EventLogDemoMessages.RES:
    rc EventLogDemoMessages.rc
    With the linker, you can bind the binary message file EventLogDemoMessages.RES to a native DLL:
    link /DLL /SUBSYSTEM:WINDOWS /NOENTRY /MACHINE:x86 EventLogDemoMessages.RES
    Now, you can register an event source that defines the resource files as shown in the following code. First, a check is done to determine
    whether the event source named EventLogDemoApp exists. If the event log must be created because it does not exist, the next check
    verifies that the resource file is available. Some samples in the MSDN documentation demonstrate writing the message file to the
    <windows>system32 directory, but you shouldn't do that. Copy the message DLL to a program-specific directory that you can get with the
    SpecialFolder enumeration value ProgramFiles. If you need to share the messages file among multiple applications, you can put it into
    Environment.SpecialFolder.CommonProgramFiles.
    If the file exists, a new object of type EventSourceCreationData is instantiated. In the constructor, the name of the source and the name
    of the log are defined. You use the properties CategoryResourceFile, MessageResourceFile, and ParameterResourceFile
    to define a reference to the resource file. After the event source is created, you can find the information on the resource files in the registry with
    the event source. The method CreateEventSource registers the new event source and log file. Finally, the method
    RegisterDisplayName from the EventLog class specifies the name of the log as it is displayed in the Event Viewer. The ID 5001 is
    taken from the message file (code file EventLogDemo/Program.cs):
    string logName = "ProCSharpLog";
    string sourceName = "EventLogDemoApp";
    string resourceFile = Environment.GetFolderPath(
    Environment.SpecialFolder.ProgramFiles) +
    @"procsharpEventLogDemoMessages.dll";
    if (!EventLog.SourceExists(sourceName))
    {
    if (!File.Exists(resourceFile))
    {
    Console.WriteLine("Message resource file does not exist");
    return;
    }
    Facility = Application
    SymbolicName = APP_UPDATE
    Language=English
    Application %%5002 updated.
    .
    ; // — Event log display name -
    ; // ********************************************************
    MessageId = 5001
    Severity = Success
    Facility = Application
    SymbolicName = EVENT_LOG_DISPLAY_NAME_MSGID
    Language=English
    Professional C# Sample Event Log
    .
    ; // — Event message parameters -
    ; // Language independent insertion strings
    ; // ********************************************************
    MessageId = 5002
    Severity = Success
    Facility = Application
    SymbolicName = EVENT_LOG_SERVICE_NAME_MSGID
    Language=English
    EventLogDemo.EXE
    .
    For the exact syntax of message files, check the MSDN documentation for Message Text Files
    (http://msdn.microsoft.com/library/windows/desktop/dd996906. aspx).
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 22 / 29
    var eventSource = new EventSourceCreationData(sourceName, logName);
    eventSource.CategoryResourceFile = resourceFile;
    eventSource.CategoryCount = 4;
    eventSource.MessageResourceFile = resourceFile;
    eventSource.ParameterResourceFile = resourceFile;
    EventLog.CreateEventSource(eventSource);
    }
    else
    {
    logName = EventLog.LogNameFromSourceName(sourceName, ".");
    }
    var evLog = new EventLog(logName, ".", sourceName);
    evLog.RegisterDisplayName(resourceFile, 5001);
    Now you can use the WriteEvent method instead of WriteEntry to write the event log entry. WriteEvent requires an object of type
    EventInstance as a parameter. With the EventInstance, you can assign the message ID, the category, and the severity of type
    EventLogEntryType. In addition to the EventInstance parameter, WriteEvent accepts parameters for messages that have
    parameters and binary data in the form of a byte array:
    using (var log = new EventLog(logName, ".", sourceName))
    {
    var info1 = new EventInstance(1000, 4,
    EventLogEntryType.Information);
    log.WriteEvent(info1);
    var info2 = new EventInstance(1001, 4,
    EventLogEntryType.Error);
    log.WriteEvent(info2, "avalon");
    var info3 = new EventInstance(1002, 3,
    EventLogEntryType.Error);
    byte[] additionalInfo = { 1, 2, 3 };
    log.WriteEvent(info3, additionalInfo);
    }
    You can read the event log entries with the Event Viewer.
    PERFORMANCE MONITORING
    Performance monitoring can be used to get information about the normal behavior of applications, to compare ongoing system behavior with
    previously established norms, and to observe changes and trends, particularly in applications running on the server. When you have a scenario
    of more and more users accessing the application, before the first user complains about a performance issue, the system administrator can
    already act and increase resources where needed. The Performance Monitor (PerfMon) is a great tool to see all the performance counts for
    acting early. As a developer, this tool also helps a lot to understand the running application and its foundation technologies.
    Microsoft Windows has many performance objects, such as System, Memory, Objects, Process, Processor, Thread,
    Cache, and so on. Each of these objects has many counts to monitor. For example, with the Process object, the user time, handle count,
    page faults, thread count, and so on can be monitored for all processes or for specific process instances. The .NET Framework and several
    applications, such as SQL Server, also add application-specific objects.
    Performance-monitoring Classes
    The System.Diagnostics namespace provides the following classes for performance monitoring:
    n PerformanceCounter — Can be used both to monitor counts and to write counts. New performance categories can also be created
    with this class.
    n PerformanceCounterCategory — Enables you to step through all existing categories, as well as create new ones. You can
    programmatically obtain all the counters in a category.
    n PerformanceCounterInstaller — Used for the installation of performance counters. Its use is similar to that of the
    Note To delete a previously created event source, you can use EventLog.DeleteEventSource(sourceName). To delete a log,
    you can invoke EventLog.Delete(logName).
    Note For the message identifiers, define a class with const values, which provide a more meaningful name for the identifiers in the
    application.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 23 / 29
    EventLogInstaller discussed previously.
    Performance Counter Builder
    The sample application PerformanceCounterDemo is a simple Windows application with just two buttons to demonstrate writing
    performance counts. The handler of one button registers a performance counter category; the handler of the other button writes a performance
    counter value. In a similar way to the sample application, you can add performance counters to a Windows Service (see Chapter 27,
    "Windows Services"), to a network application (see Chapter 26, "Networking"), or to any other application from which you would like to receive
    live counts.
    Using Visual Studio, you can create a new performance counter category by selecting Performance Counters in Server Explorer and then
    selecting Create New Category from the context menu. This launches the Performance Counter Builder (see Figure 20-7). Set the name of the
    performance counter category to Wrox Performance Counters. The following table shows all performance counters of the sample application.
    Figure 20-7
    Performance Counter Builder writes the configuration to the performance database. This can also be done dynamically by using the Create
    method of the PerformanceCounterCategory class in the System.Diagnostics namespace. An installer for other systems can
    easily be added later using Visual Studio.
    The following code snippet shows how a performance category can be added programmatically. With the tool from Visual Studio, you can only
    create a global performance category that doesn't have different values for different processes of running applications. Creating a
    performance category programmatically enables you to monitor performance counts from different applications, which is done here.
    First, a const for the category name is defined, as well as SortedList<TKey, TValue>, which contains the names of the performance
    counts (code file PerformanceCounterDemo/MainWindow.xaml.cs):
    private const string perfomanceCounterCategoryName =
    "Wrox Performance Counters";
    private SortedList<string, Tuple<string, string>> perfCountNames;
    The list of the perfCountNames variable is filled in within the method InitializePerformanceCountNames. The value of the sorted
    Note In order to create a performance counter category with Visual Studio, Visual Studio must be started in elevated mode.
    PERFORMANCE COUNTER DESCRIPTION TYPE
    # of button clicks Total # of button clicks NumberOfItems32
    # of button clicks/sec # of button clicks per second RateOfCountsPerSecond32
    # of mouse move events Total # of mouse move events NumberOfItems32
    # of mouse move events/sec # of mouse move events per second RateOfCountsPerSecond32
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 24 / 29
    list is defined as Tuple<string, string> to define both the name and the description of the performance counter:
    private void InitializePerfomanceCountNames()
    {
    perfCountNames = new SortedList<string, Tuple<string, string>>();
    perfCountNames.Add("clickCount", Tuple.Create("# of button Clicks",
    "Total # of button clicks"));
    perfCountNames.Add("clickSec", Tuple.Create("# of button clicks/sec",
    "# of mouse button clicks in one second"));
    perfCountNames.Add("mouseCount", Tuple.Create("# of mouse move events",
    "Total # of mouse move events"));
    perfCountNames.Add("mouseSec", Tuple.Create("# of mouse move events/sec",
    "# of mouse move events in one second"));
    }
    The performance counter category is created next, in the method OnRegisterCounts. After a check to verify that the category does not
    already exist, the array CounterCreationData is created, which is filled with the types and names of the performance counts. Next,
    PerformanceCounterCategory.Create creates the new category. PerformanceCounterCategoryType.MultiInstance
    defines that the counts are not global, but rather that different values for different instances can exist:
    private void OnRegisterCounts(object sender, RoutedEventArgs e)
    {
    if (!PerformanceCounterCategory.Exists(
    perfomanceCounterCategoryName))
    {
    var counterCreationData = new CounterCreationData[4];
    counterCreationData[0] = new CounterCreationData
    {
    CounterName = perfCountNames["clickCount"].Item1,
    CounterType = PerformanceCounterType.NumberOfItems32,
    CounterHelp = perfCountNames["clickCount"].Item2
    };
    counterCreationData[1] = new CounterCreationData
    {
    CounterName = perfCountNames["clickSec"].Item1,
    CounterType = PerformanceCounterType.RateOfCountsPerSecond32,
    CounterHelp = perfCountNames["clickSec"].Item2,
    };
    counterCreationData[2] = new CounterCreationData
    {
    CounterName = perfCountNames["mouseCount"].Item1,
    CounterType = PerformanceCounterType.NumberOfItems32,
    CounterHelp = perfCountNames["mouseCount"].Item2,
    };
    counterCreationData[3] = new CounterCreationData
    {
    CounterName = perfCountNames["mouseSec"].Item1,
    CounterType = PerformanceCounterType.RateOfCountsPerSecond32,
    CounterHelp = perfCountNames["mouseSec"].Item2,
    };
    var counters = new CounterCreationDataCollection(counterCreationData);
    var category = PerformanceCounterCategory.Create(
    perfomanceCounterCategoryName,
    "Sample Counters for Professional C#",
    PerformanceCounterCategoryType.MultiInstance,
    counters);
    MessageBox.Show(String.Format("category {0} successfully created",
    category.CategoryName));
    }
    Adding PerformanceCounter Components
    With Windows Forms or Windows Service applications, you can add PerformanceCounter components from the toolbox or from Server
    Explorer by dragging and dropping to the designer surface.
    With WPF applications that's not possible. However, it's not a lot of work to define the performance counters manually, as this is done with the
    method InitializePerformanceCounters. In the following example, the CategoryName for all performance counts is set from the
    const string performance-CounterCategoryName; the CounterName is set from the sorted list. Because the application writes
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 25 / 29
    performance counts, the ReadOnly property must be set to false. When writing an application that only reads performance counts for
    display purposes, you can use the default value of the ReadOnly property, which is true. The InstanceName of the
    PerformanceCounter object is set to an application name. If the counters are configured to be global counts, then InstanceName may
    not be set:
    private PerformanceCounter performanceCounterButtonClicks;
    private PerformanceCounter performanceCounterButtonClicksPerSec;
    private PerformanceCounter performanceCounterMouseMoveEvents;
    private PerformanceCounter performanceCounterMouseMoveEventsPerSec;
    private void InitializePerformanceCounters()
    {
    performanceCounterButtonClicks = new PerformanceCounter
    {
    CategoryName = perfomanceCounterCategoryName,
    CounterName = perfCountNames["clickCount"].Item1,
    ReadOnly = false,
    MachineName = ".",
    InstanceLifetime = PerformanceCounterInstanceLifetime.Process,
    InstanceName = this.instanceName
    };
    performanceCounterButtonClicksPerSec = new PerformanceCounter
    {
    CategoryName = perfomanceCounterCategoryName,
    CounterName = perfCountNames["clickSec"].Item1,
    ReadOnly = false,
    MachineName = ".",
    InstanceLifetime = PerformanceCounterInstanceLifetime.Process,
    InstanceName = this.instanceName
    };
    performanceCounterMouseMoveEvents = new PerformanceCounter
    {
    CategoryName = perfomanceCounterCategoryName,
    CounterName = perfCountNames["mouseCount"].Item1,
    ReadOnly = false,
    MachineName = ".",
    InstanceLifetime = PerformanceCounterInstanceLifetime.Process,
    InstanceName = this.instanceName
    };
    performanceCounterMouseMoveEventsPerSec = new PerformanceCounter
    {
    CategoryName = perfomanceCounterCategoryName,
    CounterName = perfCountNames["mouseSec"].Item1,
    ReadOnly = false,
    MachineName = ".",
    InstanceLifetime = PerformanceCounterInstanceLifetime.Process,
    InstanceName = this.instanceName
    };
    }
    To calculate the performance values, you need to add the fields clickCountPerSec and mouseMoveCountPerSec:
    public partial class MainWindow : Window
    {
    // Performance monitoring counter values
    private int clickCountPerSec = 0;
    private int mouseMoveCountPerSec = 0;
    Add an event handler to the Click event of the button, add an event handler to the MouseMove event of the button, and add the following
    code to the handlers:
    private void OnButtonClick(object sender, RoutedEventArgs e)
    {
    this.performanceCounterButtonClicks.Increment();
    this.clickCountPerSec++;
    }
    private void OnMouseMove(object sender, MouseEventArgs e)
    {
    this.performanceCounterMouseMoveEvents.Increment();
    this.mouseMoveCountPerSec++;
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 26 / 29
    }
    The Increment method of the PerformanceCounter object increments the counter by one. If you need to increment the counter by more
    than one — for example, to add information about a byte count sent or received — you can use the IncrementBy method. For the
    performance counts that show the value in seconds, just the two variables, clickCountPerSec and mouseMovePerSec, are
    incremented.
    To show updated values every second, add a DispatcherTimer to the members of the MainWindow:
    private DispatcherTimer timer;
    This timer is configured and started in the constructor. The DispatcherTimer class is a timer from the namespace
    System.Windows.Threading. For applications other than WPF, you can use other timers as discussed in Chapter 21. The code that is
    invoked by the timer is defined with an anonymous method:
    public MainWindow()
    {
    InitializeComponent();
    InitializePerfomanceCountNames();
    InitializePerformanceCounts();
    if (PerformanceCounterCategory.Exists(perfomanceCounterCategoryName))
    {
    buttonCount.IsEnabled = true;
    timer = new DispatcherTimer(TimeSpan.FromSeconds(1),
    DispatcherPriority.Background,
    delegate
    {
    this.performanceCounterButtonClicksPerSec.RawValue =
    this.clickCountPerSec;
    this.clickCountPerSec = 0;
    this.performanceCounterMouseMoveEventsPerSec.RawValue =
    this.mouseMoveCountPerSec;
    this.mouseMoveCountPerSec = 0;
    },
    Dispatcher.CurrentDispatcher);
    timer.Start();
    }
    }
    perfmon.exe
    Now you can monitor the application. You can start Performance Monitor from the Administrative Tools applet in the control panel. Within
    Performance Monitor, click the + button in the toolbar; there, you can add performance counts. Wrox Performance Counters shows up as a
    performance object. All the counters that have been configured appear in the Available counters list, as shown in Figure 20-8.
    After you have added the counters to the performance monitor, you can view the actual values of the service over time (see Figure 20-9). Using
    this performance tool, you can also create log files to analyze the performance data later.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 27 / 29
    Figure 20-8
    Figure 20-9
    SUMMARY
    In this chapter, you have looked at tracing and logging facilities that can help you find intermittent problems in your applications. You should
    plan early, building these features into your applications, as this will help you avoid many troubleshooting problems later.
    With tracing, you can write debugging messages to an application that can also be used for the final product delivered. If there are problems,
    you can turn tracing on by changing configuration values, and find the issues.
    Event logging provides the system administrator with information that can help identify some of the critical issues with the application.
    Performance monitoring helps in analyzing the load from applications and enables proactive planning for resources that might be required.
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 28 / 29
    Professional C# 5.0 and .NET 4.5.1
    Reprinted for Avanade/11990, Avanade John Wiley & Sons (US), John Wiley & Sons, Inc. (c) 2014, Copying Prohibited
    Page 29 / 29

  • 相关阅读:
    29-赫夫曼树
    28-线索化二叉树
    27-顺序存储二叉树
    26-二叉树的遍历查找和删除
    25-二叉树的概念
    24-逻辑结构分析
    23-哈希表
    22-查找算法
    21-堆排序
    Mui-列表/table-view
  • 原文地址:https://www.cnblogs.com/hellohongfu/p/4269959.html
Copyright © 2011-2022 走看看