5,448,104 members and growing! (14,782 online)
Email Password   helpLost your password?
Web Development » Silverlight » General     Intermediate License: The Code Project Open License (CPOL)

Clog - Client Logging, Silverlight Edition

By Daniel Vaughan

A customizable log provider system that allows you to harness your existing logging system to log client side messages to your server. Includes a Silverlight interface and Log Viewer.
C# 3.0, C#, Windows, .NET, .NET 3.5, XAML, ASP.NET, VS2008, Visual Studio, Design, Dev, QA

Posted: 22 Nov 2007
Updated: 14 Jan 2008
Views: 15,693
Bookmarked: 40 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
33 votes for this Article.
Popularity: 6.62 Rating: 4.36 out of 5
3 votes, 9.1%
1
2 votes, 6.1%
2
0 votes, 0.0%
3
2 votes, 6.1%
4
26 votes, 78.8%
5

Please visit the CodePlex Project Site for the latest releases and source code.

Clog - All your log are belong to Clog.

Contents

Introduction

So you've deployed your swanky new Silverlight application to test. But wait! There's a problem. Your testers tell you that it breaks when they click the orange button! They send you a multitude of screen shots, but to no avail! It appears unresolvable, and with deadlines looming, it's crunch time. With no hope in site, desperation sets in. You raise your fists in the air and exclaim "If only I could know what was happening on the client side!".

Enter Clog.

Ok, the previous hypothetical scenario is melodramatic, but it highlights the need for an integrated client side logging solution. Thus I decided to create Clog. Clog is a log provider system that allows you to harness your existing logging system to log client side messages to your server. It is fully customizable, can serialize and log all Exception types, and allows filtering of messages both on the client and server sides using custom or inbuilt filters, which so far consists of an IP Address Range Filter, and a Role Membership filter. In this release Clog includes an extendible Log provider system, a log4net log strategy, and a Silverlight log viewer.

Clog Overview
Figure: Consuming Clog from a web client application.

This article will discuss how Clog works, how to set up Clog on both client and server, including configuration of the Silverlight Log Viewer control, an example use of the .NET provider model, and also some more advanced topics such as the Silverlight security model.

Although this article's client side focus is primarily on a Silverlight implementation, Clog is capable of providing logging services to any .NET or web service consumer. Thus, in future articles, I intend to provide editions for WPF and ASP.NET AJAX.

Background

A solid server side logging system is the mainstay of most web based applications. With the advent of WPF, Silverlight, and a client side CLR, it is my view that we will see a shift in focus away from the traditional and primarily server based logging scenario, to cater to a more client centric environment.

While Visual Studio allows us to readily debug Silverlight and WPF, without the debugger or some means of tracing client side events, we can find ourselves left in the dark. We have no built-in mechanism for logging to e.g., an event log on a client machine, and we lack an immediate feedback mechanism that could allow us to know if our client side .NET applications are behaving correctly. Clog bridges this client-server divide. We are now able to selectively capture logging events that originate from both client and server side applications.

I recall, some years ago, my first experiences while hand coding form validation JavaScript, and trying to provide myself with client side feedback. It was, and still is, common practice to use message boxes for displaying information while scripting. It's a fairly slipshod and haphazard approach, and also potentially embarrassing if you should forget to comment out the code when you're through! Today there are one or two Ajax JavaScript client to server logging projects out there. And while they may work well with loosely typed JavaScript, they address a different need.

There are numerous logging libraries for .NET, and many of us have come to know and rely on a particular system over time; sometimes customizing it to suit our own requirements. Clog allows us to keep our existing system, by wrapping it; allowing us to perform both client and server logging in the same manner.

Consuming Clog
Figure: Local and remote clients consuming Clog.

Clog System Overview

Clog's core component is the Orpius.Logging.dll. It provides for most of the server side functionality. Silverlight logging functionality is located in Orpius.Logging.Silverlight.dll, and auxiliary to this is the optional component Orpius.Logging.Silverlight.UI.dll, which contains the LogViewer Silverlight control. The following diagram provides a conceptual overview of some of the principle types and their interdependencies.

Clog component diagram.
Figure: Clog Component Diagram.

Using Clog

To enable Clog for client side logging we complete a two stage process. First we configure the server based project to use Clog. Then we configure our client side project to use Clog.

Server Side Configuration

To enable Clog on the server side, add a reference to the Orpius.Logging assembly.

<section name="ClientLogging"
    type="Orpius.Logging.ClientLoggingConfigSection"/>

Next, create the ClientLogging config section, as in the following excerpt.

<ClientLogging defaultProvider="Log4NetProvider">
    <providers>
        <clear />
        <add name="Log4NetProvider"
            type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
            LogStrategy="Orpius.Logging.Log4NetStrategy,
            Orpius.Logging.Log4NetLogStrategy" />
        <add name="CustomProvider"
            type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
            LogStrategy="ExampleWebsite.SimpleLogStrategy, ExampleWebsite" />
    </providers>
    <filters>
        <add name="IPAddressRange"
            type="Orpius.Logging.Filters.IPAddressRangeFilter, Orpius.Logging"
            begin="127.0.0.0" end="127.0.0.10"/>
        <add name="RoleMembership"
            type="Orpius.Logging.Filters.RoleMembershipFilter, Orpius.Logging"
            roles="Developer, Administrator"/>
    </filters>
</ClientLogging>

Create a new file in your Web project called ClientLoggingService.asmx, open it and paste the following content:

<%@ WebService Language="C#" Class="Orpius.Logging.ClientLoggingService,
    Orpius.Logging" %>

The web service code is actually located in the Orpius.Logging.dll assembly. It is up to you to define your preferred logging method. For this demonstration we are using log4net. While configuring log4net is outside the scope of this article, you can view the example website download to see how it's done. Briefly, it requires adding an assembly reference to log4net.dll, adding a config section in the web.config, and then initialising log4net within your website. I do this by performing an arbitrary logging request when the application starts.
N.B. If you don't initialise log4net from your website before it is used in another assembly, it won't be configured properly.

Clog for Silverlight Configuration

To use Clog in your Silverlight project, add a reference to the Orpius.Logging.Silverlight assembly. If you also wish to use the Silverlight Log Viewer, then add a reference to the Orpius.Logging.Silverlight.UI assembly as well.

Writing to the Log from Silverlight

To use Clog on the client side, within a Silverlight application, we add a reference to the Orpius.Logging.Silverlight assembly, and we use the LogManager to provide us with an ILog by calling GetLog method like so:

static readonly ILog log = LogManager.GetLog(typeof(Page));

The typeof(Page) argument is used to determine the name of the Log in conjunction with the URL of the page itself. This gives us a fine grained control over filtering of log requests. That is, we can control logging requests not only based on a particular Silverlight custom control or page, but also on where it has been deployed. To facilitate debugging from localhost where there is a dynamic port specified in the URL, we don't include the port number in the log name.

Writing to the log flowchart
Figure: Client log writing process.

The client-side Silverlight Log dispatches log entries to the server asynchronously, as the following excerpt shows:

void WriteLogEntryAux(LogLevel logLevel, string message, Exception exception)
{
    ExceptionMemento memento = null;
    if (exception != null)
    {
        memento = CreateMemento(exception);
    }

    LogEntryData entry = new LogEntryData()
    {
        LogLevel = logLevel,
        Message = message,
        ExceptionMemento = memento,
        CodeLocation = GetLocation(),
        LogName = Name,
        ThreadName = System.Threading.Thread.CurrentThread.Name,
        ManagedThreadId =
            System.Threading.Thread.CurrentThread.ManagedThreadId,
        Url = HtmlPage.DocumentUri.ToString(),
        OccuredAt = DateTime.Now
    };

    OnLogEntrySendAttempt(new LogEventArgs(entry));

    if (ConfigurationData == null || !ConfigurationData.LogEnabled
        /* Enable enum and remove cast
         * when Silverlight supports Enum serialization. */
            || (int)entry.LogLevel < ConfigurationData.LogLevel)
    {
        return;
    }
    /* Send of the log entry to the web service. */
    LoggingService.BeginWriteEntry(entry, delegate(IAsyncResult result)
                                              {
        OnLogEntrySent(new LogEventArgs(entry));
                                              }, null);
}

Silverlight Log Viewer

Overview

The Log Viewer is a Silverlight control that can be placed on a Canvas to automatically monitor the LogManager. When an ILog instance receives a request to write a log message, the Log Viewer is able to display the log message. The following screen capture shows the Log Viewer receiving an outgoing log entry, while the Log4Net Viewer shows the result of the log entry, after it has been relayed to log4net.

Browser and Log4Net Viewer
Figure: Clog Silverlight Log Viewer with Log4Net Viewer.

Using the Log Viewer

To include the Log Viewer in your Silverlight application, add a reference to the Orpius.Logging.Silverlight.UI assembly, and add the following namespace definition to the root canvas on a page.

xmlns:Orpius="clr-namespace:Orpius.Logging.Silverlight.UI;
    assembly=ClientBin/Orpius.Logging.Silverlight.UI.dll"

Then place the LogViewer XAML element somewhere on the page.

<Log:LogViewer x:Name="LogViewer" Canvas.Top="180" Width="640" Height="300"/>

The number of rows displayed in the LogViewer is dependant upon the height of the control. For example, given that each row is hardwired to be 16 pixels tall, if the Height property of the LogViewer is set to 160, we can expect to see 10 rows. The width of the columns is also calculated using the declared Width of the control.

Inside the Log Viewer

Silverlight doesn't have many controls provided with its download yet; the viewer is constructed from the ground up.

When a Silverlight application requests the writing of a log entry, two events may be raised by the active ILog instance. The first event, WriteRequested, happens unconditionally, and if the "Log Viewer" is in OfflineMode a log entry will be displayed immediately. The second event, LogEntrySent, is raised if the log entry is sent to the server. In this case, if the "Log Viewer" is not in OfflineMode then the log entry is displayed. Sending of the log entry depends on the ILog's ClientConfigurationData LogLevel and Enabled properties, and the requested log level.

Log Viewer display process flowchart
Figure: Log Viewer processing a log entry flowchart.

Silverlight Security Model

Silverlight does not use Code Access Security (CAS). Silverlight uses the transparency model introduced in .NET 2.0. In this model there are three levels: Transparent, Safe Critical, and Critical. In the Silverlight CLR, all code is "Transparent" by default, and, therefore, so is the user code. This is the opposite of the desktop CLR, which is "Critical" by default (.NET Security Blog). Any method decorated with a SecurityCritical attribute can't be called directly by user code because transparent code is not allowed to call "Critical" code. If "Transparent" code attempts to call "Critical" code, a MethodAccessException ensues.

Transparent code cannot call Critical code directly.
Figure: Transparent code cannot call Critical code directly.

The Silverlight mscorlib contains "many" methods decorated with the SecurityCritical attribute. One such method is the System.Diagnostics.StackTrace constructor, and, unfortunately for us, this means we can't get a stack trace; preventing us from obtaining and passing on the origin of calls to the Log. It makes sense to hide stack trace information from user code, as it may reveal sensitive information. It would, however, be nice to have a "Safe Critical" method to retrieve a stack trace consisting of just user code. But I'm not holding out for that one.

To take a look at what methods are available to our user code, fire up Reflector and replace the Framework mscorlib assembly in Reflector with the Silverlight version. As we can see, much of this assembly is a no-go-zone.

StrackTrace class disassembled in Reflector
Figure: StackTrace class disassembled in Reflector.

Extending Clog

Clog Provider Model

"All your log are belong to Clog."

-D. Vaughan. (See derivation)

Clog uses ILogStrategy instances to send log entries to third-party logging systems. Included with Clog are two ILogStrategy implementations. The first is a really simple tracing strategy that serves as a basic example; the second, a log4net strategy. I hope to include more in a later release. If you happen to write one for a particular 3rd party logging system, I'd love to include it in the next release (with credit of course).

An ILogStrategyProvider is tasked with instantiating the ILogStrategy and exposing it through its LogStrategy property. Internal to the Orpius.Logging module, there is a default implementation of an ILogStrategy that should be sufficient in most cases.

Log Strategy provider class diagram
Figure: ILogStrategy and ILogStrategyProvider class diagram.

Integrating Clog with your Existing 3rd Party Logging System

To integrate Clog with an existing logging system, implement the ILogStrategy interface, and specify the type in the provider configuration using the LogStrategy attribute, as in the following example:

<add name="CustomProvider"
    type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
        LogStrategy="YourAssembly.Strategy, YourAssembly" />

The ILogStrategy interface has three members. For client side logging functionality implement the void Write(IClientLogEntry logEntry); and LogLevel GetLogLevel(string logName); methods. If you plan to only use Clog for client side logging, then you can forget about the void Write(IServerLogEntry logEntry); overload (of course you will still need a default empty implementation). If, however, you wish to use Clog as a wrapper for your existing logging system, then provide an appropriate implementation for the IServerLogEntry overload as well.

The Log Strategy determines how a log entry is written to a log. It is here that we connect our existing logging system, such as log4net, to Clog. When a log write is requested, the current Log Strategy must take the information present in the IServerLogEntry or IClientLogEntry and construct a call to the existing logging system. The LogLevel GetLogLevel(string logName); method is used to determine the threshold at which log entries are written, and it also allows us to inhibit logging on the client side if the level is higher than the requested logging level.

This release of Clog comes with a log4net strategy (Log4NetStrategy). The following excerpt shows how the class writes a log message to log4net using a specified IClientLogEntry argument.

public void Write(IClientLogEntry logEntry)
{
    ILog log = defaultLog;
    if (logEntry.LogName != null)
    {
        log = LogManager.GetLogger(logEntry.LogName);
    }

    /* Create a Log4Net event data instance,
     and populate it with our log entry information. */
    LoggingEventData data = new LoggingEventData();
    if (logEntry.ExceptionMemento != null)
    {    /* Use the exception memento to write the message
        and stack trace etc. */
        data.ExceptionString = logEntry.ExceptionMemento.ToString();
    }

    data.Level = GetLog4NetLevel(logEntry.LogLevel);
    ICodeLocation location = logEntry.CodeLocation;
    if (location != null)
    {
        data.LocationInfo = new LocationInfo
            (location.ClassName, location.MethodName,
            location.FileName, location.LineNumber.ToString());
    }

    data.LoggerName = logEntry.LogName;
    data.Message = logEntry.Message;
    data.ThreadName = logEntry.ThreadName;
    data.TimeStamp = logEntry.OccuredAt;
    //data.Properties = logEntry.Properties;

    if (string.IsNullOrEmpty(logEntry.UserName))
    {    /* Populate the UserName property using the Membership provider. */
        MembershipUser user = Membership.GetUser(true);
        if (user != null)
        {
            data.UserName = user.UserName;
        }
    }
    else
    {
        data.UserName = logEntry.UserName;
    }

    LoggingEvent loggingEvent = new LoggingEvent(data);
    log.Logger.Log(loggingEvent);
}

Filters

Clog uses server side filters to determine what log entries to discard before they are sent to the active Log Strategy. Filters are evaluated when retrieving ClientConfigurationData, and on receipt of a log write request. I have included two filters with this release. One is an IP Address range filter, which will restrict logging to an IP within a range that is defined in the provider configuration. The other is a Role Membership filter, which restricts logging to those authenticated users who have a role specified in the configuration.

Filters class diagram
Figure: Filter class diagram.

The current ILogProvider evaluates each IFilter by calling the IsValid method, as the following excerpt from the IPAddressFilter class demonstrates.

/// <summary>
/// Restricts logging based on an IP address range.
/// </summary>

class IPAddressRangeFilter : HttpRequestFilterBase
{
    uint begin;
    uint end;

    public override bool IsValid(LogEntryOrigin origin)
    {
        if (origin != LogEntryOrigin.Client || HttpRequest == null)
        {
            return true;
        }

        string addressValue = HttpRequest.UserHostAddress;
        return IsWithinRange(begin, addressValue, end);
    }

    /// <summary>
    /// Initialises the specified filter element.
    /// </summary>
    /// <param name="filtereElement">The filtere element.</param>

    public override void Init(FilterElement filtereElement)
    {
        begin = ToUInt(IPAddress.Parse(filtereElement.Begin));
        end = ToUInt(IPAddress.Parse(filtereElement.End));
    }

    static bool IsWithinRange
        (uint beginAddress, string addressValue, uint endAddress)
    {
        IPAddress address = IPAddress.Parse(addressValue);
        uint ip = ToUInt(address);
        return ip >= beginAddress && ip <= endAddress;
    }

    /// <summary>
    /// Converts and <see cref="IPAddress"/> to an unsigned int.
    /// </summary>
    /// <param name="ipAddress">The ip address to convert.</param>
    /// <returns>A <code>uint</code> representing
    /// the specified ipAddress.</returns>

    static uint ToUInt(IPAddress ipAddress)
    {
        byte[] bytes = ipAddress.GetAddressBytes();

        uint result = (uint)bytes[0] << 24;
        result += (uint)bytes[1] << 16;
        result += (uint)bytes[2] << 8;
        result += bytes[3];

        return result;
    }
}

Logging Exceptions the Clog Way

When a request to log an Exception occurs client side, an ExceptionMemento is used to encapsulate the exception information so that it can be correctly serialized and sent over the wire. You may be aware that an Exception is not immediately serializable without using a binary formatter for serialization, and this is due to the IDictionary _data field. To sidestep any serialization issues, and relieve the Clog consumer from having to worry about the serializability of his or her logged exceptions, we gather what information we can from the exception into an ExceptionMemento instance. This is then sent over HTTP to our Clog web service, where it is relayed to the active Log Strategy.

Log Entries

ILogEntry instances encapsulate the log entry information that passes through Clog, via a remote client application or local consumer, and on to the end point; the active ILogStrategy. External consumers of Clog use the LogEntryData data type, which forms the base type for all concrete ILogEntry instances, and omits various internal properties. When a LogEntry arrives at the Clog web service, a ClientLogEntry is instantiated and decorated with the IP of the incoming request. This is then sent on to the static Log class. The following class diagram shows the ILogEntry interface and its inheritors. Concrete implementations of the various ILogEntry interfaces are internal to the Orpius.Logging project.

Log Entry class diagram
Figure: Log Entry class diagram.

IServerLogEntry instances represent server side log entries, and are used when requests to write to the log originate within the same application, and most likely within the same appdomain, as the Orpius.Logging.dll component.

IClientLogEntry instances, on the other hand, represent client side log entries. These are used when requests to write to the log originate from a remote client, such as a Silverlight Clog Log.

Points of Interest

JSON Serialization

Communicating with web services from Silverlight is one of its great features. Though, at this early stage, Silverlight Alpha 1.1 does have some limitations. The first is that there is no support for enum deserialization in Silverlight. In our ClientConfigurationData class, we have a LogLevel property. This property was initially, you guessed it, a LogLevel enum type. But as I quickly discovered, Silverlight was unable to deserialize the value, and I had to resort to changing its data type to int. Mind you, serialization (not deserialization) of enums on the client (Silverlight) side works fine. We can happily send enum values to a web service, as we do with our LogEntryData. The second gotcha was revealed when, by decorating a class with the XmlElement attributes, I was unable to add a web reference from our Silverlight Logging project. For now, my best advice is to stick with simple data container classes when exposing them from web services that you intend to consume from Silverlight.

Silverlight 1.1 Alpha and Web Services

You may notice in our ClogLoggingService.cs that we have a return type of string on our WriteEntry method. The reason for this is that I was receiving Exceptions when calling the method when the return type was null. Changing the return type to a string fixed the problem. My attempt to use a "decimal" also failed as Silverlight 1.1 doesn't support serialization of the decimal type yet. I had intended the return type to be a log id, but on further consideration this made little sense as most 3rd party logging systems don't expose that information directly anyway. Of course this is open to customization, and I will happily listen to any requests to do it differently.

ApplicationUnhandledException Doesn't Fire

I had intended to handle the Silverlight WebApplication.Current.ApplicationUnhandledException in the included example Silverlight application, where I would log the Exception using Clog. Unfortunately though, the event isn't raised properly. You can find some more information about this on the Silverlight Forum.

Future Enhancements

  • Use custom Exception to expose protected data to the ExceptionMemento
  • Add filter by log etc. to Log Viewer
  • Provide more Unit Tests for core logging functionality

Conclusion

This article discussed the implementation of Clog; a client server logging provider system. It showed how to set it up, including configuration of the Silverlight Log Viewer control, an example use of the .NET provider model. The article also touched on some more advanced topics such as the Silverlight security model. I intend to release "Clog WPF edition" in the coming weeks. Although Clog is still just a prototype, I believe it shows a lot of promise for becoming quite a useful tool.

I hope you find this project useful. If so, then you may like to rate it and/or leave feedback below.

References

History

  • November 2007
    • First release
  • January 2008
    • Added a config attribute to disable the use of ASP.NET Membership by Clog.
    • Integrated Silverlight and WPF Editions into the same download.
    • Improved the multithreaded logging capability to prevent exceptions due to logging calls being made from non STA threads.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Daniel Vaughan


Mvp
Daniel has been developing software professionally for eight years, and is based in Canberra Australia, and Prague in the Czech Republic.

Daniel is currently seeking expressions of interest from prospective employers in either Geneva/Lausanne (CH) or Prague (CZ).


Daniel's Blog


Occupation: Software Developer (Senior)
Location: Australia Australia

Other popular Silverlight articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 18 of 18 (Total in Forum: 18) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralI'm having trouble...memberDavid Moody11:24 29 Aug '08  
GeneralRe: I'm having trouble...mvpDaniel Vaughan17:15 29 Aug '08  
GeneralRe: I'm having trouble...memberDavid Moody4:45 2 Sep '08  
GeneralRe: I'm having trouble...mvpDaniel Vaughan6:07 2 Sep '08  
GeneralRe: I'm having trouble...memberDavid Moody6:30 2 Sep '08  
GeneralRe: I'm having trouble...mvpDaniel Vaughan17:10 2 Sep '08  
GeneralRe: I'm having trouble...memberDavid Moody9:29 3 Sep '08  
GeneralDiagraming toolmemberMichal Brylka6:38 26 Jan '08  
GeneralRe: Diagraming toolmvpDaniel Vaughan13:29 29 Feb '08  
GeneralExcellentmemberSacha Barber23:34 22 Nov '07  
GeneralRe: ExcellentmemberDaniel Vaughan0:51 23 Nov '07  
GeneralRe: ExcellentmemberSacha Barber2:30 23 Nov '07  
GeneralRe: ExcellentmemberDaniel Vaughan3:02 23 Nov '07  
GeneralRe: ExcellentmemberSacha Barber3:14 23 Nov '07  
GeneralWhy cant we have more articles like this?memberleppie23:25 22 Nov '07  
GeneralRe: Why cant we have more articles like this?memberDaniel Vaughan1:02 23 Nov '07