|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Contents
IntroductionMy 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 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 our 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, no need to reinvent the wheel eh.
This article will discuss how to set up Clog on both client and server using the .NET provider model and WCF configuration, it will touch briefly on WCF serialization, raise a caveat on using the ASP.NET provider model in partial trust, and will delve further into how Clog works, and its new features in this release. BackgroundSince 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
The Logging component communicates with itself across a WCF channel. Clog then marshals the request to the third party logging system, as is shown in the following diagram.
For a more in depth explanation of Clog's core implementation, please see the previous article. Provider Model and Log StrategiesOne of the things I find particularly elegant with the WPF edition is that we are able to use the provider model 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. 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 provider model do the work. In the following diagram we see how log requests flow, from the client application, through to the existing logging infrastructure.
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 active log strategy.
In both situations, client and server-side, the log strategy becomes
the end point for the log request (encapsulated by the Using ClogTo 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 ConfigurationTo enable Clog on the server side, add a reference to the Orpius.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 Orpius.Logging.Log4NetLogStrategy assembly or the Orpius.Logging.EnterpriseLibraryLogStrategy assembly respectively. <section name="ClientLogging"
type="Orpius.Logging.Configuration.ClientLoggingConfigSection"/>
Next, create the ClientLogging config section, as in the following excerpt. <ClientLogging defaultProvider="EnterpriseLibrary" debug="true">
<providers>
<clear />
<!-- log4net strategy. -->
<add name="Log4Net"
type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
LogStrategy="Orpius.Logging.LogStrategies.Log4NetStrategy,
Orpius.Logging.Log4NetLogStrategy" />
<!-- Enterprise Library Logging strategy. -->
<add name="EnterpriseLibrary"
type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
LogStrategy="Orpius.Logging.LogStrategies.EnterpriseLibraryStrategy,
Orpius.Logging.EnterpriseLibraryLogStrategy" />
<!-- Simple tracing strategy. -->
<add name="Custom"
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"/>
<!-- Uncomment to prevent access to those users
that do now have membership of the specified roles. -->
<!--<add name="RoleMembership"
type="Orpius.Logging.Filters.RoleMembershipFilter, Orpius.Logging"
roles="Developer, Administrator"/>-->
</filters>
</ClientLogging>
Create a new file in your Web project called ClogService.svc, open it and paste the following content. <%@ ServiceHost Language="C#" Debug="true" Service="Orpius.Logging.ClogService" %>
The WCF service implementation and contracts are located in the Orpius.Logging.dll assembly.
We must also configure WCF to expose our <system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Orpius.Logging.ClogServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"
httpHelpPageEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="Orpius.Logging.ClogServiceBehavior"
name="Orpius.Logging.ClogService">
<endpoint address="" binding="wsHttpBinding"
contract="Orpius.Logging.IClogService"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
Client Side ConfigurationWe enable Clog on the client side in the same manner as the server side. Add a reference to the Orpius.Logging assembly. <section name="ClientLogging" type="Orpius.Logging.Configuration.ClientLoggingConfigSection"/>
Next, create the ClientLogging config section, as in the following excerpt. <ClientLogging defaultProvider="WpfProvider" debug="true">
<providers>
<clear />
<add name="WpfProvider"
type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
LogStrategy="Orpius.Logging.LogStrategies.ClientStrategy,
Orpius.Logging" />
</providers>
<filters>
<!--
Use the action attribute to invert the behaviour of a filter.
Action can be set to either Deny or Allow.
-->
<!-- Uncomment to restrict logging to business hours. -->
<!--<add name="TimeRange"
type="Orpius.Logging.Filters.TimeRangeFilter, Orpius.Logging"
begin="09:00" end="17:30"/>-->
<!-- Prevent users identified by Environment.UserName. -->
<add name="DenyUsers"
type="Orpius.Logging.Filters.EnvironmentUserFilter, Orpius.Logging"
users="UserName1,UserName2,UserName3,UserName4"
action="Deny"/>
<!-- Prevent machines identified by Environment.MachineName. -->
<add name="DenyMachines"
type="Orpius.Logging.Filters.MachineFilter, Orpius.Logging"
machines="AnExampleMachineName1,AnExampleMachineName2"
action="Deny"/>
</filters>
</ClientLogging>
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="ClogServiceReference.IClogService"
name="WSHttpBinding_IClogService">
<identity>
<userPrincipalName value="SOJUZ\Daniel" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Using Interfaces as Parameter Types in WCF
In the ClogService we use interfaces as the parameter types.
We were not able to do this in the ASP.NET Web Service version
that we built for the Silverlight version in the
previous article.
The following excerpt shows the [ServiceContract]
public interface IClogService
{
/// <summary>
/// Gets the configuration information
/// for the log with specified logName.
/// </summary>
/// <param name="clientInfo">Information regarding
/// the client from logging calls may be made.</param>
/// <returns>The log configuration information.</returns>
[OperationContract]
[ServiceKnownType(typeof(ClientInfo))]
ClientConfigurationData GetConfiguration(IClientInfo clientInfo);
/// <summary>
/// Writes the entry to the log
/// using the active <see cref="ILogStrategy"/>.
/// </summary>
/// <param name="entryData">The entry data to be written.</param>
[OperationContract(IsOneWay = true)]
[ServiceKnownType(typeof(LogEntryData))]
void WriteEntry(ILogEntry entryData);
}
We see here, that to allow WCF to deserialize
to a known concrete type, in this case
The
I have had to make some compromises with the class hierarchy
of the log entry types in order to maintain compatibility
with ASP.NET Web Service and the Silverlight edition of Clog.
The internal hierarchy is deeper than I would otherwise like.
We use the Facade pattern
to simplify the presentation of the structure externally;
Provider Model and Partial TrustIncluded with this release of Clog are two WPF applications. One is a standalone application; the other, a WPF Browser Application (XBAP). In order to use Clog in a partial trust environment we need to make some slight modification to the way we use the provider model.
Using the
So, for WPF Browser applications, this was a problem. But, as it turns out, it is easily overcome. try
{
ProvidersHelper.InstantiateProviders(
section.Providers, providers, typeof(ILogStrategyProvider));
partialTrust = false;
}
catch (SecurityException ex)
{ /* We're in a minimal trust environment.
* There is a demand on ProvidersHelper.InstantiateProviders
* for AspNetHostingPermissionLevel.Low that will fail
* in a partial trust WPF Browser application. */
WriteDebugTrace(string.Format(
"ProvidersHelper.InstantiateProviders threw exception."
+ " Assuming low trust internet client environment. \n{0}\n{1}",
ex.Message, ex.StackTrace));
}
In this situation we simply must get a little more manual, and take charge of building the provider ourselves. if (partialTrust)
{ /* We will assume we are in a sandbox and do things manually. */
providers = new LogStrategyProviderCollection();
ProviderSettings providerSettings = section.Providers[section.DefaultProvider];
Type type = Type.GetType(providerSettings.Type);
ProviderBase tempProvider = (ProviderBase)Activator.CreateInstance(type);
tempProvider.Initialize("ClientProvider",
new NameValueCollection() {{"LogStrategy",
typeof(ClientStrategy).AssemblyQualifiedName} });
provider = (ILogStrategyProvider)tempProvider;
providers.Add(tempProvider);
}
else
{
provider = providers[section.DefaultProvider];
}
Unit TestingIn 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.
Extending ClogClog Provider Model
Clog uses Integrating Clog with your existing 3rd Party Logging System
To integrate Clog with an existing logging system, implement the <add name="CustomProvider"
type="Orpius.Logging.LogStrategyProvider, Orpius.Logging"
LogStrategy="YourAssembly.Strategy, YourAssembly" />
The following excerpt shows the /// <summary>
/// This defines the contract for handling logging events.
/// To create a custom strategy, implement this interface
/// and define a provider for the strategy
/// in the application configuration.
/// </summary>
public interface ILogStrategy
{
/// <summary>
/// Gets the log level for the log with the specified name.
/// </summary>
/// <param name="clientInfo">Information regarding the caller.</param>
/// <returns>The threshold log level.</returns>
LogLevel GetLogLevel(IClientInfo clientInfo);
/// <summary>
/// Logs the specified client log entry.
/// <seealso cref="IServerLogEntry"/>
/// </summary>
/// <param name="logEntry">The log entry.</param>
void Write(IClientLogEntry logEntry);
/// <summary>
/// Logs the specified server log entry.
/// <seealso cref="IServerLogEntry"/>
/// </summary>
/// <param name="logEntry">The log entry.</param>
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
The following class diagram shows the
In order to change the result of a filter evaluation we use an action
configuration value.
The <!-- Prevent users identified by Environment.UserName. -->
<add name="DenyUsers"
type="Orpius.Logging.Filters.EnvironmentUserFilter, Orpius.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 <ClientLogging defaultProvider="ExampleProvider" debug="true">
...
</ClientLogging>
Visual Studio Line Tag SupportWhen 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
Points of interestGet 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 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 WCFAs of .NET 3.5 Beta 2 there was no built in way to obtain the IP address of the consumer of a WCF service. In .NET 3.5 RTM this facility has been thankfully added. Further, there is also no notion of an HttpContext within a WCF call. In fact HttpContext.Current is always null. (Henning 2007) (Allen 2007) As this project targets the Beta 2 release of the .NET framework version 3.5, some of the lines to acquire the IP address remain commented in the ClogService.cs source file. I will open this functionality up on a later release. Until then, 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
ConclusionThis 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
HistoryDecember 2007
January 2008
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||