Clog:客户端日志记录,WPF版






4.51/5 (65投票s)
一个可自定义的日志提供者系统,允许您利用现有的日志系统,通过WCF将客户端消息记录到服务器。包含WPF示例应用程序。
- 请访问 CodePlex项目站点 获取最新版本和源代码。

目录
引言
我之前的文章,Clog - Silverlight版,介绍了一种解决客户端日志记录集成方案的需求。该方案是一个完全可自定义的客户端日志记录系统Clog,它允许您利用现有的日志系统将客户端消息记录到服务器。Clog现在能够为任何WCF或ASP.NET Web服务消费者提供日志记录服务。在上一篇文章中,我专注于Silverlight实现。在这篇文章中,我将介绍WPF版Clog。
Clog是一个日志提供者系统,它是完全可自定义的,线程安全的,可以序列化和记录所有Exception
类型,并允许使用自定义或内置过滤器在客户端和服务器端过滤消息。到目前为止,它包含一个IP地址范围过滤器、一个角色成员资格过滤器,以及三个新过滤器,包括一个环境用户名过滤器、一个机器名过滤器和一个时间范围过滤器。在这个Clog发布版本中,我们有一个可扩展的日志提供者系统,一个log4net日志策略,一个简单的跟踪日志策略,以及一个新的Microsoft Enterprise Library Logging Application Block日志策略。
Clog的强大之处在于它能够为不同类型的消费者提供集中式日志记录。它不仅允许我们从基于浏览器的应用程序进行日志记录,还可以从企业中的其他服务器进行日志记录。它允许我们利用WCF提供的功能和灵活性,并且可以通过简单地修改WCF配置来定制安全性等。Clog本身并不是一个日志记录系统,因为它不提供日志接收器。这意味着它不提供任何内置的方法来写入日志数据库或平面文件。相反,它允许您利用现有的日志基础设施;不强迫您改变做事方式。市面上有大量的日志记录系统,无需重复造轮子。
Clog支持多种日志策略。这意味着,例如,在智能客户端应用程序中,我们不仅可以使用
ClientStrategy
将消息发送回服务器,还可以将日志写入客户端机器上的日志文件。
图:从不同客户端应用程序消耗Clog。
本文将讨论如何使用自定义配置节和WCF配置在客户端和服务器端设置Clog,将简要介绍WCF序列化,并深入探讨Clog的工作原理及其在此版本中的新功能。
背景
自上一篇文章以来,我对Clog的核心进行了大量重构,以提高过滤的灵活性,并减少服务器端和客户端日志记录之间的区别。WPF实现更加优雅,因为不需要构建次级模块,正如目标为Silverlight和Desktop CLR时那样。
Clog 系统概述
Clog的核心组件是DanielVaughan.Logging.dll
。它提供了大部分服务器端功能。它部署在客户端和服务器端。WPF客户端应用程序通过WCF服务将日志请求发送到服务器端。
图:通过WCF通道通信的日志组件。
日志组件通过WCF通道相互通信。然后Clog将请求编组到一个或多个第三方日志系统,如下所示。
图:WPF版概述。
有关Clog核心实现的更深入解释,请参阅上一篇文章。
日志策略提供
WPF版我发现特别优雅的一点是,我们能够使用相同的机制在客户端和服务器端提供日志策略。这两个环境之间没有区别,我们只是有一个针对客户端环境的不同日志策略。在提供的示例中,服务器端有一个将条目记录到我们第三方日志系统的日志策略,客户端有一个使用WCF将日志请求发送到服务器的日志策略。在这两种情况下,我们都让日志策略来完成工作。
在下面的图表中,我们看到日志请求如何从客户端应用程序流向现有的日志基础设施。
图:日志请求流经过滤器和日志策略。
当客户端发生日志请求时,会评估客户端过滤器,并检查配置以确保请求应该继续。如果可以,请求将被发送到服务器,服务器同样评估服务器端过滤器并将其发送到日志策略。在客户端和服务器端,日志策略都成为日志请求(由ILogEntry
实例封装)的终结点。
使用Clog
要启用Clog进行客户端日志记录,首先配置服务器端项目以使用Clog,然后配置客户端项目以使用Clog。
服务器端配置
要启用服务器端的Clog,请添加对DanielVaughan.Logging程序集的引用。如果您打算使用其中包含的日志策略之一,无论是log4net(Log4NetStrategy)还是Microsoft Enterprise Library Logging Application Block(EnterpriseLibraryStrategy),请分别添加对DanielVaughan.Logging.Log4NetLogStrategy程序集或DanielVaughan.Logging.EnterpriseLibraryLogStrategy程序集的引用。
<section name="Clog"
type="DanielVaughan.Logging.Configuration.ClientLoggingConfigurationSectionHandler, DanielVaughan.Logging"/>
如果您希望在Visual Studio文本编辑器中为Clog配置节启用智能感知,请将下载的Schemas目录中的Clog schema xsd文件放入C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas目录(将C:\Program Files替换为Visual Studio的安装路径)。需要重启Visual Studio。
接下来,创建Clog配置节,如下面的摘录所示。
<!-- InternalLogLevel is used to monitor log messages originating from Clog,
and which are written to the console. Valid values are (from less to most restrictive):
All, Debug, Info, Warn, Error, Fatal, None. -->
<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"/>
<!-- Uncomment to prevent access to those users that do now have membership of the specified roles. -->
<!-- <Filter Name="RoleMembership" type="DanielVaughan.Logging.Filters.RoleMembershipFilter, DanielVaughan.Logging"
Roles="Developer, Administrator" /> -->
</LogStrategy>
</Clog>
正如您所见,我们可以将一组过滤器与每个日志策略关联起来。日志条目按顺序分派给每个日志策略。
在您的Web项目中创建一个名为ClogService.svc的新文件,打开它并粘贴以下内容。
<%@ ServiceHost Language="C#" Debug="true" Service="DanielVaughan.Logging.ClogService" %>
WCF服务实现和契约位于DanielVaughan.Logging.dll程序集中。
我们还必须配置WCF以公开我们的IClogService
实现。
<!-- WCF Configuration. -->
<system.serviceModel>
<!-- We need to specify aspNetCompatibilityEnabled="true" in order
to retrieve the IP address etc., of the caller in our ClogService. -->
<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>
客户端配置
我们以与服务器端相同的方式在客户端启用Clog。添加对DanielVaughan.Logging程序集的引用。
<section name="Clog" type="DanielVaughan.Logging.Configuration.ClientLoggingConfigurationSectionHandler, DanielVaughan.Logging"/>
接下来,创建Clog配置节,如下面的摘录所示。
<Clog InternalLogLevel="All" xmlns="http://danielvaughan.orpius.com/Clog/2/0/">
<LogStrategy Name="Client" Type="DanielVaughan.Logging.LogStrategies.ClientStrategy, DanielVaughan.Logging">
<!--
Use the action attribute to invert the behaviour of a filter.
Action can be set to either Deny or Allow.
-->
<!-- Prevent users identified by Environment.UserName. -->
<Filter Name="DenyUsers"
Type="DanielVaughan.Logging.Filters.EnvironmentUserFilter, DanielVaughan.Logging"
Users="UserName1,UserName2,UserName3,UserName4"
Action="Deny" />
<!-- Prevent machines identified by Environment.MachineName. -->
<Filter Name="DenyMachines"
Type="DanielVaughan.Logging.Filters.MachineFilter, DanielVaughan.Logging"
Machines="AnExampleMachineName1,AnExampleMachineName2"
Action="Deny" />
<!-- Uncomment to restrict logging to business hours. -->
<!--<Filter Name="BusinessHours" Type="DanielVaughan.Logging.Filters.TimeRangeFilter, DanielVaughan.Logging"
Begin="09:00" End="17:30" />-->
</LogStrategy>
</Clog>
我们还需要配置WCF,这可以根据需要进行自定义。
<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="https://:2099/ClogService.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IClogService" contract="DanielVaughan.Logging.IClogService"
name="WSHttpBinding_IClogService">
</endpoint>
</client>
</system.serviceModel>
在WCF中使用接口作为参数类型
在ClogService中,我们使用接口作为参数类型。以下摘录显示了通过WCF公开的IClogService
服务契约。
/// <summary>
/// Provides Clog logging services
/// to remote clients.
/// </summary>
[ServiceContract(Namespace = OrganizationalConstants.ServiceContractNamespace)]
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))]
[ServiceKnownType(typeof(ServerLogEntry))]
ClientConfigurationData GetConfiguration(IClientInfo clientInfo);
/// <summary>
/// Writes the entry to the log
/// using the active <see cref="ILogStrategy"/>.
/// Does not use One Way OperationContract,
/// which is incompatible with Silverlight.
/// </summary>
/// <param name="entryData">The entry data to be written.</param>
[OperationContract]//(IsOneWay = true)]
[ServiceKnownType(typeof(LogEntryData))]
void WriteEntrySilverlight(ILogEntry entryData);
/// <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))]
[ServiceKnownType(typeof(ServerLogEntry))]
void WriteEntry(ILogEntry entryData);
}
在这里,我们看到,为了允许WCF反序列化为已知的具体类型,在本例中为ClientInfo
和LogEntryData
,我们使用ServiceKnownType
属性装饰方法。(ThoughtShapes 2007)(MSDN 2007)还值得注意的是,您会发现WriteEntry
方法的OperationContract
属性的IsOneWay
属性设置为true。这告诉WCF在调用方法时,不为回复消息发出响应;使异步执行对我们来说不是必需的。Sacha Barber对WCF面向服务的属性进行了很好的总结。
GetConfiguration
方法现在接受一个IClientInfo
实例。这允许我们评估过滤器,以确定是否为调用者启用日志记录。这是对先前版本的改进,先前版本将过滤器评估限制为仅知道调用是来自远程还是本地调用者。IClientInfo
接口仅用作数据容器,其内部具体实现ClientInfo
是LogEntry
类型的基类。
图:IClientInfo
接口类图。
我最初不得不在日志条目类型的类层次结构上做一些妥协,以保持与Clog的Silverlight版本的兼容性。内部层次结构比我想要的要深,我可能会在某个时候重构它。我们使用外观模式来简化外部结构的呈现;IClientLogEntry
和IServerLogEntry
整合并隐藏了底层复杂性。
图:ClientInfo
具体实现及其继承者的类图。
单元测试
过去,我几乎所有单元测试都依赖于NUnit。我一直发现白盒单元测试和Web应用程序不太兼容。我对Visual Studio 2008中的单元测试功能感到满意,特别是能够为ASP.NET应用程序编写白盒和黑盒测试。我更加高兴的是,微软已决定将单元测试功能包含在Visual Studio 2008的Professional版本中。我之所以提到这一点,是因为在Visual Studio 2005中进行单元测试需要Team System版本。
图:Clog日志单元测试结果。
扩展Clog
Clog 提供者模型
您所有的日志都属于Clog。
- D. Vaughan. (参见出处)
Clog使用ILogStrategy
实例将日志条目发送到第三方日志系统。Clog中包含三个ILogStrategy
实现。如果您为某个特定的第三方日志系统编写了实现,我很乐意将其包含在下一个版本中(当然会注明您的贡献)。
将Clog集成到您现有的第三方日志系统中
要将Clog集成到现有日志系统中,请实现ILogStrategy
接口,并在提供程序配置中使用LogStrategy属性指定类型,如下面的示例所示。
<LogStrategy Name="CustomStrategy" Type="YourAssembly.Strategy, YourAssembly">
<!-- Add filters here. -->
</LogStrategy>
以下摘录显示了ILogStrategy
接口。我们实现此接口以支持其他第三方日志系统。
/// <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);
}
有关扩展Clog的更多信息,请参阅上一篇文章。
过滤器。
Clog使用服务器端和现在的客户端过滤器来确定在发送到活动日志策略之前丢弃哪些日志条目。检索ClientConfigurationData
以及接收日志写入请求时都会评估过滤器。此版本包含五个过滤器;两个来自Silverlight版,三个是新的。
IPAddressRangeFilter
限制或允许来自单个或一系列IP地址的日志请求。RoleMembershipFilter
限制或允许来自具有指定角色的ASP.NET成员资格用户的日志请求。EnvironmentUserFilter
基于用户的Environment.UserName值,限制或允许用户的日志请求。MachineFilter
限制或允许来自指定机器名集合的日志请求。TimeRangeFilter
限制或允许在特定时间段内的日志请求。
下面的类图显示了FilterBase
类以及过滤器子类层次结构。
图:过滤器类图。
为了改变过滤器评估的结果,我们使用*action*配置值。过滤器的Action
决定了如果过滤器认为自己有效或无效时将执行什么操作。
<!-- Prevent users identified by Environment.UserName. -->
<add name="DenyUsers"
type="DanielVaughan.Logging.Filters.EnvironmentUserFilter, DanielVaughan.Logging"
users="UserName1,UserName2,UserName3,UserName4"
action="Deny"/>
在此示例中,如果当前用户的Environment.UserName与users属性中的某个匹配,我们将阻止将日志消息从客户端发送到服务器(如果是客户端过滤器),或写入日志(如果是服务器端过滤器)。通过将*action*属性更改为“Allow”,我们可以反转此行为;因此,如果Environment.UserName与列表中的某个匹配,则允许日志请求通过。
调试模式
Clog的公共API会捕获Log
类中发生的所有异常。我们不必担心Clog会引起应用程序的故障。但是,我们可以启用跟踪输出,并在发生任何日志记录错误时调用System.Diagnostics.Debug.Fail
。要做到这一点,我们像这样在ClientLogging配置节的debug属性中指定它。
<ClientLogging defaultProvider="ExampleProvider" debug="true">
...
</ClientLogging>
Visual Studio 行标签支持
当我们调试应用程序时,让Visual Studio跳转到源代码位置会很方便。因此,我们可以使用针对*Output*窗口的特定格式字符串来避免自己查找文件和位置。格式如下:
<源文件路径>(<行号>): [消息文本]
(Vickery 2007)
此格式是Clog的CodeLocation
类内置的。当我们调用其ToString()
方法时,可以自动跳转到该位置。我们可以在下面的示例中看到这一点。这里我们正在跟踪日志条目。当CodeLocation
写入*Output*窗口时,我们可以通过双击文本让Visual Studio跳转到该位置。
图:Visual Studio行标签支持。
关注点
在WPF中获取URL
Clog可以通过客户端的URL限制日志记录。我们在Silverlight版中轻松地做到了这一点。要在XAML浏览器应用程序(XBAP)中实现这一点,我们必须首先检查该应用程序是否是网络部署的。以下摘录显示了ClientStrategy
如何检测它是XBAP还是独立的WPF应用程序,并相应地填充相关的日志条目属性。
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;
}
使用WCF获取IP地址
为了确定WCF服务消费者的IP地址,我们使用服务器在收到日志请求时可用的IncomingMessageProperties
。(Henning 2007)(Allen 2007)重要的是要注意,RemoteEndpointMessageProperty
(它为我们提供了对IP地址的访问)不会被填充,除非我们在服务器的WCF配置中指定了aspNetCompatibilityEnabled
。
<!-- We need this to retrieve the IP address etc., of the caller in our ClogService. -->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
以下摘录演示了如何在WCF中获取IP地址。
MessageProperties properties = OperationContext.Current.IncomingMessageProperties;
RemoteEndpointMessageProperty endpoint = properties[RemoteEndpointMessageProperty.Name]
as RemoteEndpointMessageProperty;
return endpoint.Address;
未来增强功能
- 在TimeRangeFilter中添加非交易日时间跨度。
- 为核心日志功能提供更多单元测试。
结论
本文讨论了如何使用.NET提供者模型和WCF配置在客户端和服务器端设置Clog,简要介绍了WCF序列化,提出了在部分信任中使用ASP.NET提供者模型的注意事项,并深入探讨了Clog的工作原理及其在此版本中的新功能。我计划在未来几周发布Clog Ajax版。
我希望您觉得这个项目有用。如果有用,我将不胜感激您能对其进行评分和/或在下方留下反馈。这将帮助我写出更好的下一篇文章。
参考文献
- ThoughtShapes, 2007, Using Interfaces as Parameters (Part 1)
2007年12月1日检索自ThoughtShapes - MSDN, 2007, Synchronous and Asynchronous Operations
2007年12月1日检索自MSDN - Vickery, P.S. 2001, Adding tags support to Visual Studio
2007年12月1日检索自CodeProject.com - Henning, P. 2007, Client IP addresses in Orcas
2007年12月1日检索自Phil Henning's WebLog - Allen, N. 2007, More about Client IP Addresses
2007年12月1日检索自Nicholas Allen's Indigo Blog
历史
2007 年 12 月
- 首次发布。
2008年1月
- 添加了一个配置属性以禁用Clog对ASP.NET Membership的使用。
- 将Silverlight版和WPF版整合到同一个下载中。
2008年12月
- 文章已更新,以反映新的配置格式和功能。