Contents
Introduction
My previous article, Clog - Silverlight edition,
introduced a solution to the need for an integrated approach to client side logging.
The solution was a fully customizable client side logging system, Clog,
which allows you to harness your existing logging system
to log client side messages to your server.
Clog is now capable of providing logging services
to any WCF or ASP.NET Web Service consumer.
In the previous article I focused on a Silverlight implementation.
In this, I will introduce Clog for WPF.
Clog is a log provider system, it is fully customizable, thread-safe, can serialize and log
all Exception
types, and allows filtering of messages both on the client
and server side using custom or inbuilt filters, which so far consists of
an IP Address Range Filter,
a Role Membership filter,
and three new filters including
an Environment User Name Filter,
a Machine Name Filter,
and a Time Range Filter.
In this release of Clog we have an extendible Log provider system,
a log4net log strategy,
a simple tracing log strategy,
and a new Microsoft Enterprise Library Logging Application Block log strategy.
Clog's strength lies in its ability to provide centralized logging
for different types of consumers. Not only does it allow us
to log from a browser based application, but also from
other servers in the enterprise. It allows us to leverage the features
and flexibility afforded by WCF, and we can tailor security etc.
by simply modifying the WCF config.
Clog is not a logging system as such, in that it does not provide log sinks.
What this means is that it doesn't provide any built in means to write to, for example,
a log database or a flat file.
Instead it allows you to harness your existing logging infrastructure;
not forcing you to change the way you do things.
There are plenty of logging systems out there, and no need to reinvent the wheel.
Clog supports multiple log strategies. This means that, for example, in a smart client application
we are not only able to use a ClientStrategy
to send messages back to a server, but we can
also write to a log file located on the client machine.
Figure: Consuming Clog from different client applications.
This article will discuss how to set up Clog on both client and server using a custom config section and WCF configuration,
it will touch briefly on WCF serialization, and will delve further into
how Clog works, and its new features in this release.
Background
Since the last article,
I have refactored much of Clog's core
to enable more flexibility with filtering, and less distinction between server and client logging.
The WPF implementation is more elegant, because there was
no need to build secondary modules, as was the case when targeting the Silverlight
and Desktop CLRs.
Clog System Overview
Clog's core component is the DanielVaughan.Logging.dll
. It provides
for most of the server side functionality. It is deployed on both the client-side and server-side.
A WPF client application sends log requests server-side via a WCF service.
Figure: Logging component communicating across WCF channel.
The Logging component communicates with itself across a WCF channel.
Clog then marshals the request to one or more third party logging system,
as is shown in the following diagram.
Figure: WPF edition overview.
For a more in depth explanation of Clog's core implementation,
please see the previous article.
Provision of Log Strategies
One of the things I find particularly elegant with the WPF edition
is that we are able to use the same mechanism for the provision of log strategies on both client and server.
There is no distinction made between the two environments,
we simply have a different log strategy targeting the client environment.
In the provided example, on the server side we have a log strategy to log entries to our
third party logging system, and on the client side we have a log
strategy that uses WCF to send log requests to the server.
In both cases we let the log strategies do the work.
In the following diagram we see how log requests flow,
from the client application, through to the existing logging infrastructure.
Figure: Log request flow through filters and log strategies.
When a log request occurs on the client, the client-side filters are evaluated,
and the configuration checked to ensure that the request should proceed.
If so, the request is sent to the server which, similarly, evaluates the server-side
filters and sends the request on to the log strategies.
In both situations, client and server-side, the log strategy becomes
the end point for the log request (encapsulated by the ILogEntry
instance).
Using Clog
To enable Clog for client side logging, first we configure the server based project to use Clog,
then our client side project to use Clog.
Server Side Configuration
To enable Clog on the server side, add a reference
to the DanielVaughan.Logging assembly.
If you intend to use one of the included log strategies,
either for log4net (Log4NetStrategy) or
for Microsoft Enterprise Library Logging Application Block (EnterpriseLibraryStrategy),
then add a reference
to the DanielVaughan.Logging.Log4NetLogStrategy assembly
or the DanielVaughan.Logging.EnterpriseLibraryLogStrategy assembly respectively.
<section name="Clog"
type="DanielVaughan.Logging.Configuration.ClientLoggingConfigurationSectionHandler, DanielVaughan.Logging"/>
If you would like intellisense for the Clog config section in the Visual Studio text editor,
place the Clog schema xsd, located in the Schemas directory of the download,
into C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas directory
(replacing C:\Program Files with wherever Visual Studio is installed). A Visual Studio restart is required.
Next, create the Clog config section, as in the following excerpt.
<!---->
<Clog InternalLogLevel="All" xmlns="http://danielvaughan.orpius.com/Clog/2/0/">
<LogStrategy Name="Simple" Type="ExampleWebsite.SimpleLogStrategy, ExampleWebsite">
<Filter Name="IPAddressRange" Type="DanielVaughan.Logging.Filters.IPAddressRangeFilter, DanielVaughan.Logging"
Begin="127.0.0.0" End="127.0.0.10"/>
</LogStrategy>
<LogStrategy Name="Log4Net" Type="DanielVaughan.Logging.LogStrategies.Log4NetStrategy, DanielVaughan.Logging.Log4NetLogStrategy">
<Filter Name="IPAddressRange" Type="DanielVaughan.Logging.Filters.IPAddressRangeFilter, DanielVaughan.Logging"
Begin="127.0.0.0" End="127.0.0.10"/>
<!---->
<!---->
</LogStrategy>
</Clog>
As can be seen, we are able to associate a set of filters with each log strategy.
Log entries are dispatched sequentially to each log strategy.
Create a new file in your Web project called ClogService.svc,
open it and paste the following content.
<%@ ServiceHost Language="C#" Debug="true" Service="DanielVaughan.Logging.ClogService" %>
The WCF service implementation and contracts are located in the DanielVaughan.Logging.dll assembly.
We must also configure WCF to expose our IClogService
implementation.
<!---->
<system.serviceModel>
<!---->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<behaviors>
<serviceBehaviors>
<behavior name="DanielVaughan.Logging.ClogServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"
httpHelpPageEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="DanielVaughan.Logging.ClogServiceBehavior"
name="DanielVaughan.Logging.ClogService">
<endpoint address="" binding="wsHttpBinding"
contract="DanielVaughan.Logging.IClogService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
Client Side Configuration
We enable Clog on the client side in the same manner as the server side.
Add a reference to the DanielVaughan.Logging assembly.
<section name="Clog" type="DanielVaughan.Logging.Configuration.ClientLoggingConfigurationSectionHandler, DanielVaughan.Logging"/>
Next, create the Clog config section, as in the following excerpt.
<Clog InternalLogLevel="All" xmlns="http://danielvaughan.orpius.com/Clog/2/0/">
<LogStrategy Name="Client" Type="DanielVaughan.Logging.LogStrategies.ClientStrategy, DanielVaughan.Logging">
-->
-->
<Filter Name="DenyUsers"
Type="DanielVaughan.Logging.Filters.EnvironmentUserFilter, DanielVaughan.Logging"
Users="UserName1,UserName2,UserName3,UserName4"
Action="Deny" />
-->
<Filter Name="DenyMachines"
Type="DanielVaughan.Logging.Filters.MachineFilter, DanielVaughan.Logging"
Machines="AnExampleMachineName1,AnExampleMachineName2"
Action="Deny" />
-->
-->
</LogStrategy>
</Clog>
We also need to configure WCF, and this may be customized to suit.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IClogService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true"
inactivityTimeout="00:10:00" enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2099/ClogService.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IClogService" contract="DanielVaughan.Logging.IClogService"
name="WSHttpBinding_IClogService">
</endpoint>
</client>
</system.serviceModel>
Using Interfaces as Parameter Types in WCF
In the ClogService we use interfaces as the parameter types.
The following excerpt shows the IClogService
service contract
that is exposed using WCF.
[ServiceContract(Namespace = OrganizationalConstants.ServiceContractNamespace)]
public interface IClogService
{
[OperationContract]
[ServiceKnownType(typeof(ClientInfo))]
[ServiceKnownType(typeof(ServerLogEntry))]
ClientConfigurationData GetConfiguration(IClientInfo clientInfo);
[OperationContract]
[ServiceKnownType(typeof(LogEntryData))]
void WriteEntrySilverlight(ILogEntry entryData);
[OperationContract(IsOneWay = true)]
[ServiceKnownType(typeof(LogEntryData))]
[ServiceKnownType(typeof(ServerLogEntry))]
void WriteEntry(ILogEntry entryData);
}
We see here, that to allow WCF to deserialize
to a known concrete type, in this case ClientInfo
and LogEntryData
, we decorate the methods
with the ServiceKnownType
attribute.
(ThoughtShapes 2007)
(MSDN 2007)
Also worth noting is that, you'll notice, the WriteEntry
method OperationContract
attribute has the property
IsOneWay
set to true.
This tells WCF not to issue a response for a reply message
when the method is called; making asynchronous execution unnecessary
for us.
Sacha Barber provides a very nice rundown
of the WCF service oriented
attributes.
The GetConfiguration
method now takes an IClientInfo
instance. This allows us to evaluate filters in order to determine
whether logging should be enabled for the caller. This is an enhancement
from the
previous version,
which limited filter evaluation to knowledge of
whether the call originated from a remote or local caller.
The IClientInfo
interface is intended merely as data container,
and its internal concrete implementation ClientInfo
is
the base class for LogEntry
types.
Figure: IClientInfo
interface class diagram.
I originally had to make some compromises with the class hierarchy
of the log entry types in order to maintain compatibility
with the Silverlight edition of Clog.
The internal hierarchy is deeper than I would otherwise like, and I will probably refactor it some time.
We use the Facade pattern
to simplify the presentation of the structure externally; IClientLogEntry
and IServerLogEntry
conglomerate and hide the underlying complexity.
Figure: ClientInfo
concrete implementation and inheritors' class diagram.
Unit Testing
In the past I've relied on NUnit for just about all my unit testing.
I've always found white box
unit testing and web applications to be unhappy bedfellows.
I am pleased by the unit testing functionality found in Visual Studio 2008,
especially by the ability to write white
and black
box tests for ASP.NET applications.
I was doubly pleased to learn that Microsoft has decided
to include the unit testing functionality in the Professional
version of Visual Studio 2008. I mention this because the Team System flavor
was required for unit testing in Visual Studio 2005.
Figure: Clog Logging unit test results.
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 three ILogStrategy
implementations.
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).
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.
<LogStrategy Name="CustomStrategy" Type="YourAssembly.Strategy, YourAssembly">
-->
</LogStrategy>
The following excerpt shows the ILogStrategy
interface.
We implement this interface to support other third party logging systems.
public interface ILogStrategy
{
LogLevel GetLogLevel(IClientInfo clientInfo);
void Write(IClientLogEntry logEntry);
void Write(IServerLogEntry logEntry);
}
Please see the previous article
for further information about extending Clog.
Filters
Clog uses server and now client 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.
This release includes five filters; two from the Silverlight edition, and three new ones.
IPAddressRangeFilter
Restrict or allow log requests from a single or range of IP addresses.
RoleMembershipFilter
Restrict or allow log requests from an ASP.NET Membership User with a specified role.
EnvironmentUserFilter
Restrict or allow log requests from a user based on his or her Environment.UserName value.
MachineFilter
Restrict or allow log requests from a specified set of machine names.
TimeRangeFilter
Restrict or allow log requests during time periods.
The following class diagram shows the FilterBase
class,
and the filter subclass hierarchy.
Figure: Filters class diagram.
In order to change the result of a filter evaluation we use an action
configuration value.
The Action
of a filter determines what will be done
if the filter deems itself to be valid or invalid.
<!---->
<add name="DenyUsers"
type="DanielVaughan.Logging.Filters.EnvironmentUserFilter, DanielVaughan.Logging"
users="UserName1,UserName2,UserName3,UserName4"
action="Deny"/>
In this example we prevent a log message being sent from the client
to the server (if it's a client side filter),
or written to the log (if it's a server side filter),
if the current user has an Environment.UserName that matches one
in the users attribute.
We can invert this behaviour by changing the action attribute to "Allow";
thus allowing the log request to pass through if the Environment.UserName
matches one in the list.
Debug Mode
Clog's public API catches all exceptions that occur within the Log
class.
We don't have to be concerned that Clog may cause a fault in our application.
We can, however, enable trace output and the calling of System.Diagnostics.Debug.Fail
when any logging errors occur. To do this we specify the debug
attribute of the ClientLogging configuration section like so:
<ClientLogging defaultProvider="ExampleProvider" debug="true">
...
</ClientLogging>
Visual Studio Line Tag Support
When we are debugging an application, it is convenient
to have Visual Studio jump to a source code locations for us.
Thus we can avoid finding the file and location ourselves,
by using a particular format strings targeting the Output window.
The format looks like this:
<path to source file>(<line number>): [Message Text]
(Vickery 2007)
This format is built in to Clog's CodeLocation
class.
When we call its ToString()
method we can jump to the location
automatically.
We can see an example of this in action below. Here we are tracing
log entries. When the CodeLocation
is written
to the Output window, we can have Visual Studio take us to the location
by double clicking on the text.
Figure: Visual Studio line tag support.
Points of interest
Get URL in WPF
One of the ways we can restrict logging in Clog
is by using the URL of the client. We did this with ease
in the Silverlight edition.
To achieve this in a XAML Browser Application (XBAP),
we must first check whether the application is network deployed.
The following excerpt shows how the ClientStrategy
detects whether it is an XBAP or a standalone WPF application,
and populates the relevant log entry properties accordingly.
if (ApplicationDeployment.IsNetworkDeployed)
{
Uri launchUri = ApplicationDeployment.CurrentDeployment.ActivationUri;
data.Url = launchUri.ToString();
data.LogName = GetName(logEntry.CodeLocation.ClassName, launchUri.ToString());
}
else
{
data.LogName = logEntry.CodeLocation.ClassName;
}
Acquiring an IP Address with WCF
In order to determine the IP address of the consumer of a WCF service,
we use the IncomingMessageProperties
available on the server when
a log request is received.
(Henning 2007)
(Allen 2007)
It is important to note that the RemoteEndpointMessageProperty
,
which gives us access to the IP address, is not populated unless we specify aspNetCompatibilityEnabled
in the server's WCF config.
<!---->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
The following excerpt demonstrates how to obtain the IP addresses in WCF.
MessageProperties properties = OperationContext.Current.IncomingMessageProperties;
RemoteEndpointMessageProperty endpoint = properties[RemoteEndpointMessageProperty.Name]
as RemoteEndpointMessageProperty;
return endpoint.Address;
Future enhancements
- Add non-intraday time-spans to TimeRangeFilter.
- Provide more Unit Tests for core logging functionality.
Conclusion
This article discussed how to set up Clog on both client and server
using the .NET provider model and WCF configuration,
it touched briefly on WCF serialization, raised a caveat using
the ASP.NET provider model in partial trust, and delved further into
how Clog works, and its new features in this release.
I intend to release Clog Ajax edition in the coming weeks.
I hope you find this project useful. If so, then I'd appreciate it if you would rate it
and/or leave feedback below. This will help me to make my next article better.
References
- ThoughtShapes, 2007,
Using Interfaces as Parameters (Part 1)
Retrieved 1 December 2007 from ThoughtShapes
- MSDN, 2007,
Synchronous and Asynchronous Operations
Retrieved 1 December 2007 from MSDN
- Vickery, P.S. 2001,
Adding tags support to Visual Studio
Retrieved 1 December 2007 from CodeProject.com
- Henning, P. 2007,
Client IP addresses in Orcas
Retrieved 1 December 2007 from Phil Henning's WebLog
- Allen, N. 2007,
More about Client IP Addresses
Retrieved 1 December 2007 from Nicholas Allen's Indigo Blog
History
December 2007
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.
December 2008
- Article updated to reflect new configuration format and features.