在工作中,有个WPF项目已经完成且已交付使用,但是在二开时新增一个需求:在把日志输出到日志文件功能不变的情况下,再把ErrorWarnInfo级别的日志输出到日志查看界面。

在接到这个需求时,一开始我想的是自己封装一个日志工具,再修改需要记录日志的代码。但后来想到这是一个中大型的项目,每个日志记录的地方都要修改。工作量太大且会有很多遗漏的地方。所以在博客园等网站上寻求帮助,但是没找到符合的解决方法。后来在翻看Nlog的日志调用相关代码时发现可以自定义一个TargetWithLayout,实现自己业务需求。

记录一下实现的过程

一、先创建一个CallbackTarget类,继承于NLogTargetWithLayout,里面增加一个LogCallback的委托,用于处理记录日志后的相关功能。具体代码如下

[Target("CallbackTarget")]
public class CallbackTarget : TargetWithLayout
{
    public static Action<LogEventInfo> LogCallback { get; set; }
    public static View.LogInfo FrmLogInfo { get; set; }

    protected override void Write(LogEventInfo logEvent)
    {
        // 记录日志
        base.Write(logEvent);
        // 执行某个方法
        LogCallback?.Invoke(logEvent);
    }
}

二、在项目启动类APP中添加处理CallbackTarget自定义的规则

/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
    public App()
    {
        // 加载注册NLog
        AddNLogRules();
    }

    /// <summary>
    /// 添加日志规则
    /// </summary>
    private void AddNLogRules()
    {
        LogManager.LoadConfiguration("NLog.config");

        // 这里可以添加自定义规则
        var config = LogManager.Configuration;

        // 创建自定义目标
        var customTarget = new CallbackTarget
        {
            Layout = "${longdate} ${level} ${message}"
        };

        config.AddRule(LogLevel.Info, LogLevel.Error, customTarget);

        // 应用新的配置
        LogManager.ReconfigExistingLoggers();
    }
}

三,在日志显示界面使用

public class LogInfoViewModel
{
    public View.LogInfo frmLogInfo;

    public LogInfoViewModel()
    {
        CallbackTarget.LogCallback = (logEvent) =>
        {
            if (string.IsNullOrWhiteSpace(logEvent?.Level?.Name))
            {
                OrtherLog(logEvent.Message);
                return;
            }
            switch (logEvent.Level.Name)
            {
                case "Info":
                    {
                        var msg = $"{logEvent.TimeStamp:yyyy-MM-dd HH:mm:ss.fff} - 信息:{logEvent.Message}";
                        InfoLog(msg);
                    }
                    break;
                case "Warn":
                    {
                        var msg = $"{logEvent.TimeStamp:yyyy-MM-dd HH:mm:ss.fff} - 警告:{logEvent.Message}";
                        WarnLog(msg);
                    }
                    break;
                case "Error":
                    {
                        var msg = $"{logEvent.TimeStamp:yyyy-MM-dd HH:mm:ss.fff} - 错误:{logEvent.Message}";
                        ErrorLog(msg);
                    }
                    break;
                default:
                    {
                        var msg = $"{logEvent.TimeStamp:yyyy-MM-dd HH:mm:ss.fff} - 信息:{logEvent.Message}";
                        OrtherLog(msg);
                    }
                    break;
            }
        };
    }

    public void Log(string message, Color color)
    {
        Application.Current?.Dispatcher?.Invoke(() =>
        {
            Paragraph paragraph = new Paragraph()
            {
                LineHeight = 12
            };
            Run run = new Run(message);
            run.Foreground = new SolidColorBrush(color);
            paragraph.Inlines.Add(run);
            frmLogInfo.rtbLog.Document.Blocks.Add(paragraph);
        });
    }

    public void OrtherLog(string message)
    {
        Log(message, Colors.White);
    }

    public void ErrorLog(string message)
    {
        Log(message, Colors.Red);
    }

    public void WarnLog(string message)
    {
        Log(message, Colors.Orange);
    }

    public void InfoLog(string message)
    {
        Log(message, Colors.Green);
    }
}

四、把项目运行起来就能实现对应的效果
运行效果

五、结束语:
本篇文章记录的是符合当前项目的需求,只是提供一个思路。CallbackTarget类或者后面的LogCallback委托可以根据自己的需求自定义实现