一、为什么需要监控WCF服务

咱们先聊聊为什么要给Windows服务托管的WCF加日志和监控。想象一下,你开发了一个重要的订单处理服务,部署在生产环境后突然挂了,客户投诉电话打爆了,但你却一脸懵逼不知道问题出在哪。这时候如果有完善的日志记录和监控,就能快速定位问题,说不定还能在客户发现前就修复好。

WCF服务作为Windows服务运行时,最大的痛点就是"黑盒效应"——它默默在后台运行,不出问题没人注意,一出问题就是大问题。通过添加日志记录,我们可以:

  1. 实时掌握服务运行状态
  2. 快速定位异常和性能瓶颈
  3. 分析历史运行趋势
  4. 为容量规划提供数据支持

二、日志记录方案选型

给WCF服务加日志,常见的有几种方案:

  1. 使用System.Diagnostics的TraceSource
  2. 集成第三方日志框架如NLog、log4net
  3. 自定义文本文件日志
  4. 直接写入数据库

我个人最推荐的是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;
        }
    }
}

六、日志分析与监控

有了完善的日志记录后,我们还需要考虑如何有效利用这些日志数据:

  1. 日志集中管理:使用ELK(Elasticsearch+Logstash+Kibana)或Splunk等工具集中管理多台服务器的日志
  2. 实时告警:对ERROR级别日志设置邮件或短信告警
  3. 性能分析:分析平均处理时间、吞吐量等指标
  4. 趋势预测:基于历史数据预测未来负载

这里给出一个简单的日志分析示例,使用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服务日志监控时,需要注意以下几点:

  1. 日志级别合理使用

    • TRACE:最详细的调试信息
    • DEBUG:调试关键点
    • INFO:重要业务流程
    • WARN:可预期的异常情况
    • ERROR:需要干预的错误
    • FATAL:导致服务崩溃的错误
  2. 性能考虑

    • 使用异步日志记录避免阻塞主线程
    • 控制日志量,避免记录过多不必要信息
    • 定期归档和清理旧日志
  3. 安全考虑

    • 不要在日志中记录敏感信息(密码、信用卡号等)
    • 设置适当的日志文件访问权限
    • 考虑日志数据的加密存储
  4. 监控策略

    • 设置合理的监控阈值
    • 避免告警风暴(太多无关紧要的告警)
    • 建立分级响应机制

八、总结

通过本文的介绍,我们全面了解了如何为Windows服务托管的WCF服务添加日志记录和监控功能。从基础的NLog集成,到性能计数器、全局异常处理、健康检查等高级功能,再到日志分析和监控策略,形成了一套完整的解决方案。

实际项目中,可以根据具体需求选择合适的组件和方案。比如:

  • 小型系统:NLog文件日志+简单监控即可
  • 中型系统:增加ELK集中日志管理
  • 大型分布式系统:考虑完整的APM解决方案(如Azure Application Insights)

记住,好的日志监控系统就像给服务装上了黑匣子,不仅能帮助快速定位问题,还能为系统优化提供数据支持。希望本文能帮助你在WCF服务监控方面少走弯路,构建出更加健壮可靠的服务系统。