一、为什么需要监控WCF服务
咱们先聊聊为什么要给Windows服务托管的WCF加日志和监控。想象一下,你开发了一个重要的订单处理服务,部署在生产环境后突然挂了,客户投诉电话打爆了,但你却一脸懵逼不知道问题出在哪。这时候如果有完善的日志记录和监控,就能快速定位问题,说不定还能在客户发现前就修复好。
WCF服务作为Windows服务运行时,最大的痛点就是"黑盒效应"——它默默在后台运行,不出问题没人注意,一出问题就是大问题。通过添加日志记录,我们可以:
- 实时掌握服务运行状态
- 快速定位异常和性能瓶颈
- 分析历史运行趋势
- 为容量规划提供数据支持
二、日志记录方案选型
给WCF服务加日志,常见的有几种方案:
- 使用System.Diagnostics的TraceSource
- 集成第三方日志框架如NLog、log4net
- 自定义文本文件日志
- 直接写入数据库
我个人最推荐的是NLog,原因很简单:
- 配置灵活,支持多种输出目标
- 性能优秀,异步日志不会阻塞主线程
- 丰富的过滤和格式化功能
- 社区活跃,文档齐全
下面我们就以NLog为例,演示如何为WCF服务添加完善的日志记录。
三、实战:集成NLog到WCF服务
3.1 基础环境准备
首先确保你的项目是.NET Framework 4.5+(WCF经典托管方式),然后通过NuGet安装NLog:
Install-Package NLog
Install-Package NLog.Config
3.2 配置NLog
在项目中添加NLog.config文件(记得设置"复制到输出目录"为"始终复制"),下面是一个典型配置:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Info"
internalLogFile="c:\temp\nlog-internal.log">
<targets>
<!-- 控制台输出,调试时使用 -->
<target name="console" xsi:type="Console" layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
<!-- 文件日志,按天分割 -->
<target name="logfile" xsi:type="File"
fileName="c:\logs\wcfservice.${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}${exception:format=ToString}"
archiveEvery="Day"
maxArchiveFiles="30" />
<!-- 错误日志单独记录 -->
<target name="errorfile" xsi:type="File"
fileName="c:\logs\wcfservice.error.${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}${exception:format=ToString}"
archiveEvery="Day"
maxArchiveFiles="30" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="console" />
<logger name="*" minlevel="Info" writeTo="logfile" />
<logger name="*" minlevel="Error" writeTo="errorfile" />
</rules>
</nlog>
3.3 在WCF服务中使用NLog
在服务实现类中添加日志记录:
using NLog;
public class OrderService : IOrderService
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public bool SubmitOrder(Order order)
{
try
{
Logger.Info($"开始处理订单,订单号:{order.OrderId}");
// 业务逻辑处理
if (string.IsNullOrEmpty(order.CustomerName))
{
Logger.Warn("客户名称为空的订单");
throw new ArgumentException("客户名称不能为空");
}
// 模拟处理耗时
Stopwatch sw = Stopwatch.StartNew();
Thread.Sleep(100); // 业务处理
sw.Stop();
Logger.Info($"订单处理完成,耗时:{sw.ElapsedMilliseconds}ms");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, $"订单处理失败,订单号:{order.OrderId}");
throw; // 向上抛出异常
}
}
}
3.4 添加服务生命周期日志
在Windows服务中,我们还需要记录服务的启动、停止等事件:
public partial class OrderWindowsService : ServiceBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private ServiceHost _serviceHost;
public OrderWindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Logger.Info("订单服务正在启动...");
try
{
_serviceHost = new ServiceHost(typeof(OrderService));
_serviceHost.Open();
Logger.Info("订单服务启动成功");
}
catch (Exception ex)
{
Logger.Fatal(ex, "订单服务启动失败");
throw;
}
}
protected override void OnStop()
{
Logger.Info("订单服务正在停止...");
try
{
_serviceHost?.Close();
Logger.Info("订单服务已停止");
}
catch (Exception ex)
{
Logger.Error(ex, "订单服务停止时发生异常");
}
}
}
四、进阶:添加性能监控
除了基本的日志记录,我们还可以添加性能计数器来监控服务的运行状态:
4.1 安装性能计数器
在服务安装时创建性能计数器:
public class OrderServiceInstaller : Installer
{
public OrderServiceInstaller()
{
// 服务安装配置
var processInstaller = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalSystem
};
var serviceInstaller = new ServiceInstaller
{
ServiceName = "OrderService",
DisplayName = "订单处理服务",
Description = "处理系统订单的核心服务",
StartType = ServiceStartMode.Automatic
};
Installers.Add(processInstaller);
Installers.Add(serviceInstaller);
// 创建性能计数器
if (!PerformanceCounterCategory.Exists("OrderService"))
{
var counters = new CounterCreationDataCollection
{
new CounterCreationData
{
CounterName = "OrdersProcessed",
CounterHelp = "Total number of orders processed",
CounterType = PerformanceCounterType.NumberOfItems32
},
new CounterCreationData
{
CounterName = "OrdersFailed",
CounterHelp = "Total number of failed orders",
CounterType = PerformanceCounterType.NumberOfItems32
},
new CounterCreationData
{
CounterName = "AvgProcessingTime",
CounterHelp = "Average order processing time in ms",
CounterType = PerformanceCounterType.AverageTimer32
}
};
PerformanceCounterCategory.Create(
"OrderService",
"Order service performance counters",
PerformanceCounterCategoryType.SingleInstance,
counters);
}
}
}
4.2 在服务中使用性能计数器
修改之前的OrderService类:
public class OrderService : IOrderService
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly PerformanceCounter OrdersProcessedCounter;
private static readonly PerformanceCounter OrdersFailedCounter;
private static readonly PerformanceCounter AvgProcessingTimeCounter;
static OrderService()
{
OrdersProcessedCounter = new PerformanceCounter(
"OrderService", "OrdersProcessed", false);
OrdersFailedCounter = new PerformanceCounter(
"OrderService", "OrdersFailed", false);
AvgProcessingTimeCounter = new PerformanceCounter(
"OrderService", "AvgProcessingTime", false);
}
public bool SubmitOrder(Order order)
{
Stopwatch sw = Stopwatch.StartNew();
try
{
Logger.Info($"开始处理订单,订单号:{order.OrderId}");
// 业务逻辑处理...
sw.Stop();
OrdersProcessedCounter.Increment();
AvgProcessingTimeCounter.IncrementBy(sw.ElapsedTicks);
Logger.Info($"订单处理完成,耗时:{sw.ElapsedMilliseconds}ms");
return true;
}
catch (Exception ex)
{
sw.Stop();
OrdersFailedCounter.Increment();
Logger.Error(ex, $"订单处理失败,订单号:{order.OrderId}");
throw;
}
}
}
五、异常处理与健康检查
5.1 全局异常处理
在WCF中,我们可以通过IErrorHandler接口实现全局异常处理:
public class GlobalErrorHandler : IErrorHandler
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public bool HandleError(Exception error)
{
Logger.Error(error, "全局捕获的WCF异常");
return false; // 表示异常已处理
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// 将异常转换为FaultException
var faultException = new FaultException(
$"服务端错误: {error.Message}");
var messageFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultException.Action);
}
}
// 在服务行为中应用
public class ErrorServiceBehavior : IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(new GlobalErrorHandler());
}
}
// 其他方法留空实现
public void AddBindingParameters(...) { }
public void Validate(...) { }
}
5.2 健康检查端点
添加一个简单的健康检查接口:
[ServiceContract]
public interface IHealthCheckService
{
[OperationContract]
ServiceStatus GetServiceStatus();
}
public class HealthCheckService : IHealthCheckService
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public ServiceStatus GetServiceStatus()
{
try
{
// 这里可以添加各种健康检查逻辑
var status = new ServiceStatus
{
IsHealthy = true,
Uptime = DateTime.Now - Process.GetCurrentProcess().StartTime,
MemoryUsage = Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024 + "MB",
LastError = LoggerFactory.GetLastError()
};
Logger.Debug($"健康检查通过:{JsonConvert.SerializeObject(status)}");
return status;
}
catch (Exception ex)
{
Logger.Error(ex, "健康检查失败");
throw;
}
}
}
六、日志分析与监控
有了完善的日志记录后,我们还需要考虑如何有效利用这些日志数据:
- 日志集中管理:使用ELK(Elasticsearch+Logstash+Kibana)或Splunk等工具集中管理多台服务器的日志
- 实时告警:对ERROR级别日志设置邮件或短信告警
- 性能分析:分析平均处理时间、吞吐量等指标
- 趋势预测:基于历史数据预测未来负载
这里给出一个简单的日志分析示例,使用PowerShell分析日志文件:
# 分析过去7天的错误日志
$errorLogs = Get-Content "C:\logs\wcfservice.error.*.log" |
Where-Object { $_ -match "ERROR" } |
Select-Object -First 1000
# 按异常类型分组统计
$errorStats = $errorLogs |
ForEach-Object {
if ($_ -match "Exception: (.*?)\|") {
$matches[1]
}
} |
Group-Object |
Sort-Object Count -Descending |
Select-Object Name, Count
# 输出统计结果
$errorStats | Format-Table -AutoSize
# 找出最常出现的错误
$topError = $errorStats[0].Name
Write-Host "最常见的错误是: $topError"
七、注意事项与最佳实践
在实施WCF服务日志监控时,需要注意以下几点:
日志级别合理使用:
- TRACE:最详细的调试信息
- DEBUG:调试关键点
- INFO:重要业务流程
- WARN:可预期的异常情况
- ERROR:需要干预的错误
- FATAL:导致服务崩溃的错误
性能考虑:
- 使用异步日志记录避免阻塞主线程
- 控制日志量,避免记录过多不必要信息
- 定期归档和清理旧日志
安全考虑:
- 不要在日志中记录敏感信息(密码、信用卡号等)
- 设置适当的日志文件访问权限
- 考虑日志数据的加密存储
监控策略:
- 设置合理的监控阈值
- 避免告警风暴(太多无关紧要的告警)
- 建立分级响应机制
八、总结
通过本文的介绍,我们全面了解了如何为Windows服务托管的WCF服务添加日志记录和监控功能。从基础的NLog集成,到性能计数器、全局异常处理、健康检查等高级功能,再到日志分析和监控策略,形成了一套完整的解决方案。
实际项目中,可以根据具体需求选择合适的组件和方案。比如:
- 小型系统:NLog文件日志+简单监控即可
- 中型系统:增加ELK集中日志管理
- 大型分布式系统:考虑完整的APM解决方案(如Azure Application Insights)
记住,好的日志监控系统就像给服务装上了黑匣子,不仅能帮助快速定位问题,还能为系统优化提供数据支持。希望本文能帮助你在WCF服务监控方面少走弯路,构建出更加健壮可靠的服务系统。
评论