C# 如何设计一个好用的日志库

阅读目录


回到顶部

〇、前言

相信你在实际工作期间经常遇到或听到这样的说法:

  “我现在加一下日志,等会儿你再操作下。”

  “只有在程序出问题以后才会知道打一个好的日志有多么重要。”

可见日志的记录是日常开发的必备技能。

记录日志的必要性:

  当业务比较复杂时,在关键代码附件添加合适的日志是非常重要的,这样可以出现异常后,有章可循,较快速的在不停服的情况下,定位问题并解决。特别是在项目组中,人员较多,若没有统一的日志记录规范,查找系统问题原因就更加费时费力。

记录日志的三种实现:

  1. 当业务比较简单,性能要求不高,只是单纯的记录程序的运行是否正常。此时就可以参考本文第一种实现,仅一种级别的文本记录。
  2. 当业务复杂较复杂,对性能有一定要求时,可以根据实际情况,参考本文的第二、第三种实现。
  3. 当业务非常复杂,必然运行的效率就要求比较高,如何既让程序稳定高效的运行,又能合理记录程序运行状态成为关键。高效的的日志操作可以参考本文的第三种实现。

回到顶部

一、日志的简单记录

如下,为简单的记录开发人员预输出的文本内容,其内容为自定义,输出的时间格式和固定标识需相同。

此方法的性能当然是最差的,针对同一个日志文件,需要独占访问,当同时出现多个记录需求时,会出现排队的情况,导致系统出现卡顿。当然,可以采用多目标文件的方式来提高性能表现,若业务较复杂,还是推荐使用后两种方式。

日志内容测试结果:

public static string strlock = string.Empty;static void Main(string[] args){    lock(strlock) // 在同一个日志文件操作范围添加同一个锁,避免多线程操作时因抢占资源而报错    {        WriteLogPublic.WriteLogFunStr("Program", "Main", "日志内容1");        // 实际生成的路径:C:LogsProgramMain202304log07.log        // 记录的内容:2023-04-07 11-21-31 --- 日志内容1    }}

日志类内容:

public class WriteLogPublic{    ///     /// 记录日志    ///     /// 项目名称    /// 控制器名称    /// 日志内容    public static void WriteLogFunStr(string projectname, string controllername, string strlog)    {        string sFilePath = #34;C:Logs{projectname}{controllername}{DateTime.Now.ToString("yyyyMM")}"; // 根据项目名称等创建文件夹        string sFileName = #34;log{DateTime.Now.ToString("dd")}.log";        sFileName = sFilePath + "" + sFileName; // 文件的绝对路径        if (!Directory.Exists(sFilePath)) // 验证路径是否存在            Directory.CreateDirectory(sFilePath); // 不存在则创建        FileStream fs;        StreamWriter sw;        if (File.Exists(sFileName)) // 验证文件是否存在,有则追加,无则创建            fs = new FileStream(sFileName, FileMode.Append, FileAccess.Write);        else            fs = new FileStream(sFileName, FileMode.Create, FileAccess.Write);        sw = new StreamWriter(fs);        sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + " --- " + strlog);        sw.Close();        fs.Close();    }}

回到顶部

二、通过开源库 HslCommunication 记录不同级别的日志

此方式记录日志,简单高效,可以实现不同级别日志的输出控制,日志选项的配置可以配置在程序的配置文件中,在程序启动时加载即可。

若想实现实时加载,这只能在每次写日志前初始化日志对象,这样估计就影响程序性能了。

日志内容测试结果:

static void Main(string[] args){    // 先初始化配置 HslCommunicationOper    HslCommunicationOper.HslComLogCollection("Test.ConsoleApp", "Main", 5, HslCommunication.LogNet.GenerateMode.ByEveryHour);    // HslCommunicationOper.HslComLog("Test.ConsoleApp", "Main"); // 单文件    // HslCommunicationOper.HslComLogSize("Test.ConsoleApp", "MainSize", 5); // 增加日志单文件大小配置    // HslCommunicationOper.HslComLogByDate("Test.ConsoleApp", "MainDate", TimeType.Day); // 按照日期分文件保存    HslCommunicationOper.SetMessageDegree(MessageDegree.WARN);//日志级别        // 记录日志    HslCommunicationOper.logNet.WriteDebug("调试信息");    HslCommunicationOper.logNet.WriteInfo("一般信息");     HslCommunicationOper.logNet.WriteWarn("警告信息");    HslCommunicationOper.logNet.WriteError("错误信息");    HslCommunicationOper.logNet.WriteFatal("致命信息");     HslCommunicationOper.logNet.WriteDebug("KeyWord调试信息", "调试信息");    HslCommunicationOper.logNet.WriteInfo("KeyWord一般信息", "一般信息");    HslCommunicationOper.logNet.WriteWarn("KeyWord警告信息", "警告信息");    HslCommunicationOper.logNet.WriteError("KeyWord错误信息", "错误信息");    HslCommunicationOper.logNet.WriteFatal("KeyWord致命信息", "致命信息");    HslCommunicationOper.logNet.WriteException("KeyWord-WriteException", new IndexOutOfRangeException());     HslCommunicationOper.logNet.WriteDebug("调试信息");    HslCommunicationOper.logNet.WriteInfo("一般信息");    HslCommunicationOper.logNet.WriteWarn("警告信息");    HslCommunicationOper.logNet.WriteError("错误信息");    HslCommunicationOper.logNet.WriteFatal("致命信息");}// 日志输出格式示例:    [警告] 2023-04-07 18:22:03.565 Thread:[001] 警告信息    [错误] 2023-04-07 18:22:03.605 Thread:[001] 错误信息    [致命] 2023-04-07 18:22:03.605 Thread:[001] 致命信息    [警告] 2023-04-07 18:22:03.605 Thread:[001] KeyWord警告信息 : 警告信息    [错误] 2023-04-07 18:22:03.605 Thread:[001] KeyWord错误信息 : 错误信息    [致命] 2023-04-07 18:22:03.605 Thread:[001] KeyWord致命信息 : 致命信息    [致命] 2023-04-07 18:22:03.676 Thread:[001] KeyWord-WriteException : 错误信息:Index was outside the bounds of the array.    错误源:    错误堆栈:    错误类型:System.IndexOutOfRangeException    错误方法:    /=================================================[    Exception    ]================================================/    [警告] 2023-04-07 18:22:03.676 Thread:[001] 警告信息    [错误] 2023-04-07 18:22:03.676 Thread:[001] 错误信息    [致命] 2023-04-07 18:22:03.676 Thread:[001] 致命信息

三个相关日志类:

public static class HslCommunicationOper{    public static ILogNet logNet = null;    ///     /// 日志文件根目录    ///     public static string rootpath = "C:Log";     ///     /// 单日志文件存储    ///     ///     /// 日志文件名    public static void HslComLog(string projectname, string opername)    {        logNet = new LogNetSingle(#34;{rootpath}{projectname}{opername}.txt");        logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG    }     ///     /// 限定日志文件大小    ///     ///     /// 日志上级文件夹名    /// 日志文件大小(单位:M) 1~20,默认 5    public static void HslComLogSize(string projectname, string opername, int logfilesize = 5)    {        if (logfilesize < 1 || logfilesize > 20)            logfilesize = 5;        logNet = new LogNetFileSize(#34;{rootpath}{projectname}{opername}", logfilesize * 1024 * 1024); // 单位M(5M):5 * 1024 * 1024        logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG    }     ///     /// 按照日期存储    ///     ///     /// 日志上级文件夹名    /// 传入枚举类型(TimeType),值范围:Minute、Hour、Day、Month、Season、Year    public static void HslComLogByDate(string projectname, string opername, GenerateMode generateMode = GenerateMode.ByEveryDay)    {        logNet = new LogNetDateTime(#34;{rootpath}{projectname}{opername}", generateMode); // 按每天        logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG    }     ///     /// 按照文件或日期存储    ///     ///     /// 日志上级文件夹名    /// 传入枚举类型 GenerateMode    public static void HslComLogCollection(string projectname, string opername, int filesize, GenerateMode generateMode = GenerateMode.ByEveryDay)    {        logNet = new LogNetCollection(#34;{rootpath}{projectname}{opername}", filesize * 1024 * 1024, generateMode);        logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG    }     ///     /// 单独配置日志级别    ///     /// 默认 DEBUG    public static void SetMessageDegree(MessageDegree messageDegree = MessageDegree.DEBUG)    {        switch (messageDegree)        {            case MessageDegree.DEBUG:                logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 所有等级存储                break;            case MessageDegree.INFO:                logNet.SetMessageDegree(HslMessageDegree.INFO); // 除 DEBUG 外,都存储                break;            case MessageDegree.WARN:                logNet.SetMessageDegree(HslMessageDegree.WARN); // 除 DEBUG 和 INFO 外,都存储                break;            case MessageDegree.ERROR:                logNet.SetMessageDegree(HslMessageDegree.ERROR); // 只存储 ERROR 和 FATAL                break;            case MessageDegree.FATAL:                logNet.SetMessageDegree(HslMessageDegree.FATAL); // 只存储 FATAL                break;            case MessageDegree.None:                logNet.SetMessageDegree(HslMessageDegree.None); // 不存储任何等级                break;        }    }}
public class LogNetCollection : LogPathBase, ILogNet, IDisposable{    private int fileMaxSize = 10485760; // 默认 10M    private int currentFileSize = 0;    private GenerateMode generateMode = GenerateMode.ByEveryYear;     public LogNetCollection(string filePath, int fileMaxSize = 10485760, GenerateMode generateMode = GenerateMode.ByEveryDay, int fileQuantity = -1)    {        base.filePath = filePath;        this.fileMaxSize = fileMaxSize;        this.generateMode = generateMode;        controlFileQuantity = fileQuantity;        base.LogSaveMode = LogSaveMode.FileFixedSize;        if (!string.IsNullOrEmpty(filePath) && !Directory.Exists(filePath))        {            Directory.CreateDirectory(filePath);        }    }     protected override string GetFileSaveName()    {        if (string.IsNullOrEmpty(filePath))        {            return string.Empty;        }         if (string.IsNullOrEmpty(fileName))        {            fileName = GetLastAccessFileName();        }         if (File.Exists(fileName))        {            FileInfo fileInfo = new FileInfo(fileName);            if (fileInfo.Length > fileMaxSize)            {                fileName = GetDefaultFileName();            }            else            {                currentFileSize = (int)fileInfo.Length;            }        }         return fileName;    }     private string GetLastAccessFileName()    {        string[] existLogFileNames = GetExistLogFileNames();        foreach (string result in existLogFileNames)        {            FileInfo fileInfo = new FileInfo(result);            if (fileInfo.Length < fileMaxSize) // 判断已创建的日志文件是否达到最大内存            {                currentFileSize = (int)fileInfo.Length;                return result;            }        }         return GetDefaultFileName(); // 若未创建过,通过指定方式创建    }     private string GetDefaultFileName()    {        switch (generateMode)        {            case GenerateMode.ByEveryMinute:                return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".txt");            case GenerateMode.ByEveryHour:                return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd_HH") + ".txt");            case GenerateMode.ByEveryDay:                return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd") + ".txt");            case GenerateMode.ByEveryWeek:                {                    GregorianCalendar gregorianCalendar = new GregorianCalendar();                    int weekOfYear = gregorianCalendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstDay, DayOfWeek.Monday);                    return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + "_W" + weekOfYear + ".txt");                }            case GenerateMode.ByEveryMonth:                return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyy_MM") + ".txt");            case GenerateMode.ByEverySeason:                return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + "_Q" + (DateTime.Now.Month / 3 + 1) + ".txt");            case GenerateMode.ByEveryYear:                return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + ".txt");            default:                return string.Empty;        }    }     public override string ToString()    {        return #34;LogNetFileSize[{fileMaxSize}];LogNetDateTime[{generateMode}]";    }}
/// /// 消息级别/// public enum MessageDegree{    DEBUG = 1,    INFO = 2,    WARN = 3,    ERROR = 4,    FATAL = 5,    None = 9}

 参考:C# 日志记录分级功能使用 按照日期,大小,或是单文件存储

回到顶部

三、通过开源库 NLog 实现通过配置文件配置日志选项

NLog 是一个基于 .net 平台编写的日志记录类库,我们可以使用 NLog 在应用程序中添加极为完善的跟踪调试代码。

本文将通过日志框架 Nlog 和 ConcurrentQueue 队列,实现一个高性能的日志库。

首先,为什么相中了 Nlog ?

ConcurrentQueue:表示线程安全的先进先出(FIFO)集合。所有公共成员和受保护成员 ConcurrentQueue 都是线程安全的,可以从多个线程并发使用。

1. 配置文件

对于 ASP.NET 应用程序,存在嵌入程序配置文件和单独配置文件两种方式,程序在启动时,会在应用程序主目录下依次查找:web.config(*.exe.config、*.web.config)、web.nlog(*.exe.nlog)、NLog.config

个人推荐单独文件配置,便于修改和迭代使用。

第一种方式:单独配置文件

  常用名称为 NLog.config。此时需要在根节点 nlog 加上智能感知(Intellisense)的属性配置,详见下文配置文件 XML 代码。

  1/5 targets(必须有) - 定义日志目标/输出

  其中,layout 属性的标记变量(${xxx})解析可以参考以下代码:

点击展开 查看标记释义

  2/5 rules(必须有) - 定义日志路由规则

  rules 下只有一种节点 logger(可同时配置多个),其属性释义如下:

  3/5 variables - 声明变量的值

  variable 元素定义了配置文件中需要用到的变量,一般用来表示复杂或者重复的表达式(例如文件名)。变量需要先定义后使用,否则配置文件将初始化失败。

  定义变量之后,可以通过 ${my_name} 语法来使用。

 4/5 extensions - 定义要加载的 NLog 扩展项 *.dll 文件

  extensions 节点可以添加额外的 NLog 元包或自定义功能,assembly 属性指定的被包含程序集不带后缀 .dll 。示例如下:

                                                 

  NLog 4.0 之后,与 NLog.dll 同目录下名如 NLog*.dll 的程序集(如:NLog.CustomTarget.dll)会被自动加载。

  5/5 includes - 指定当前配置文件包含多个子配置文件

  通过 ${} 语法可以使用环境变量,下例展示包含一个名为当前机器名的配置文件。

    ...        ...

  NLog 4.4.2 之后可以使用通配符 * 指定多个文件。例如:

示例配置:

<?xml version="1.0" encoding="utf-8" ?>                                

  参考:完善 .Net Core 项目 — NLog入门 (日志组件)

第二种方式:嵌入程序配置文件

  NLog 配置信息可以嵌入在 .net 应用程序自身的配置文件中,例如 *.exe.config 或者 *.web.config 中,需要使用 configSections 节点配置,如下 XML 代码,再将其他配置填入 nlog 节点即可。

  nlog 节点内的内容,参考前边‘第一种方式’。

      
......

2. 测试代码

static void Main(string[] args){    try    {        LoggerHelper._.Info(#34;完成");        LoggerHelper._.Debug(#34;Debug完成");        LoggerHelper._.Error(#34;Error完成");        throw (new Exception());    }    catch (Exception ex)    {        LoggerHelper._.Error(ex.Message);    }}// 输出日志2023-04-04 17:14:45.6651 [INFO] YOKAVerse.Net.Log.LoggerHelper.Info(Logger.cs:40) - 完成 2023-04-04 17:14:46.7303 [DEBUG] YOKAVerse.Net.Log.LoggerHelper.Debug(Logger.cs:28) - Debug完成 2023-04-04 17:14:47.2924 [ERROR] YOKAVerse.Net.Log.LoggerHelper.Error(Logger.cs:76) - Error完成 2023-04-04 17:14:49.5869 [ERROR] YOKAVerse.Net.Log.LoggerHelper.Error(Logger.cs:76) - Exception of type 'System.Exception' was thrown. 

3. 日志记录类

以下代码对 NLog 进行了封装,将日志记录先存在线程安全的队列里,以避免调用写入文件时 I/O 的耗时操作拖垮应用程序

队列有两个,一个是操作队列-concurrentQueue_operation,一个是助手队列-concurrentQueue_assistant,程序中的日志记录需求直接写入助手队列,避免影响程序频繁写入造成的系统等待。当操作队列中的记录处理完成后,再将助手队列的记录转至操作队列,继续进行比较耗时的写入操作。

当然这种方法在提高系统响应速度的同时,也存在一个弊端,就是在程序崩溃而异常退出时,可能造成积压在队列中的日志记录未全部完成落地,导致日志内容丢失。所以使用时还请权衡利弊,慎重使用。

public class LoggerHelper{    ///     /// 实例化nLog,即为获取配置文件相关信息(获取以当前正在初始化的类命名的记录器)    ///     private readonly NLog.Logger logger = LogManager.GetCurrentClassLogger();    private static LoggerHelper _obj;    ///     /// 辅助队列    ///     private static ConcurrentQueue concurrentQueue_assistant = new ConcurrentQueue();    ///     /// 操作队列    ///     private static ConcurrentQueue concurrentQueue_operation = new ConcurrentQueue();    private static string lockobj_assistant = string.Empty;    private static string lockobj_operation = string.Empty;     public static LoggerHelper LHR    {        get => _obj ?? (_obj = new LoggerHelper());        set => _obj = value;    }     public LoggerHelper()    {        InitializeTask();    }     private static LogModel logModel_init = null;    ///     /// 初始化后台线程    ///     private void InitializeTask()    {        if (logModel_init == null)        {            logModel_init = new LogModel();            Thread t = new Thread(new ThreadStart(LogOperation));            t.IsBackground = false;            t.Start();        }    }     ///     /// 记录日志    ///     private void LogOperation()    {        while (true) // 线程持续处理        {            if (concurrentQueue_assistant.Count > 0 && concurrentQueue_operation.Count == 0)            {                lock (lockobj_assistant)                {                    concurrentQueue_operation = concurrentQueue_assistant; // 将数据转至操作队列                    concurrentQueue_assistant = new ConcurrentQueue(); // 注意此处不可用 .Clear() 因为 ConcurrentQueue 为引用类型                }                LogModel logModel;                // 取出队列 concurrentQueue_operation 中待写入的日志记录,直至全部记录完成                while (concurrentQueue_operation.Count > 0 && concurrentQueue_operation.TryDequeue(out logModel))                {                    switch (logModel.type) // 日志类型分流                    {                        case NLogLevel.Trace:                            if (logModel.exobj != null)                                logger.Trace(logModel.content);                            else                                logger.Trace(logModel.content, logModel.exobj);                            break;                        case NLogLevel.Debug:                            if (logModel.exobj != null)                                logger.Debug(logModel.content);                            else                                logger.Debug(logModel.content, logModel.exobj);                            break;                        case NLogLevel.Info:                            if (logModel.exobj != null)                                logger.Info(logModel.content, logModel.exobj);                            else                                logger.Info(logModel.content);                            break;                        case NLogLevel.Error:                            if (logModel.exobj != null)                                logger.Error(logModel.content, logModel.exobj);                            else                                logger.Error(logModel.content);                            break;                        case NLogLevel.Warn:                            if (logModel.exobj != null)                                logger.Warn(logModel.content, logModel.exobj);                            else                                logger.Warn(logModel.content);                            break;                        case NLogLevel.Fatal:                            if (logModel.exobj != null)                                logger.Fatal(logModel.content, logModel.exobj);                            else                                logger.Fatal(logModel.content);                            break;                        default:                            break;                    }                }            }            else                Thread.Sleep(1000);        }    }     ///     /// 加入队列前,根据日志级别统一验证    ///     ///     public void EnqueueLogModel(LogModel logModel)    {        if ((logModel.type == NLogLevel.Trace && logger.IsTraceEnabled) || (logModel.type == NLogLevel.Debug && logger.IsDebugEnabled)            || (logModel.type == NLogLevel.Info && logger.IsInfoEnabled) || (logModel.type == NLogLevel.Warn && logger.IsWarnEnabled)            || (logModel.type == NLogLevel.Error && logger.IsErrorEnabled) || (logModel.type == NLogLevel.Fatal && logger.IsFatalEnabled))        {            lock (lockobj_assistant)            {                concurrentQueue_assistant.Enqueue(logModel);            }        }    }     ///     /// Trace,追踪,非常详细的日志,该日志等级通常仅在开发过程中被使用    ///     ///     public void Trace(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Trace, content = logcontent });    }    public void Trace(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Trace, content = logcontent, exobj = exception });    }     ///     /// Debug,调试,详尽信息次于 Trace,在生产环境中通常不启用    ///     ///     public void Debug(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Debug, content = logcontent });    }    public void Debug(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Debug, content = logcontent, exobj = exception });    }     ///     /// Info,信息,通常在生产环境中通常启用    ///     ///     public void Info(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Info, content = logcontent });    }    public void Info(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Info, content = logcontent, exobj = exception });    }     ///     /// Warn,警告,通常用于非关键问题,这些问题可以恢复,或者是暂时的故障    ///     ///     public void Warn(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Warn, content = logcontent });    }    public void Warn(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Warn, content = logcontent, exobj = exception });    }     ///     /// Error,错误,多数情况下记录Exceptions(异常)信息    ///     ///     public void Error(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Error, content = logcontent });    }    public void Error(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Error, content = logcontent, exobj = exception });    }     ///     /// Fatal,致命错误,非常严重的错误    ///     ///     public void Fatal(string logcontent)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Fatal, content = logcontent });    }    public void Fatal(string logcontent, Exception exception)    {        EnqueueLogModel(new LogModel() { type = NLogLevel.Fatal, content = logcontent, exobj = exception });    }}public class LogModel{    public NLogLevel type { get; set; }    public string content { get; set; }    public Exception exobj { get; set; }}/// /// NLog 日志等级/// public enum NLogLevel{    Trace,    Debug,    Info,    Warn,    Error,    Fatal}

  参考:C# 超高速高性能写日志 代码开源 .net core 中的那些常用的日志框架(NLog篇)

回到顶部

四、日志查看器

作为一名研发人员,高效率的日志分析是必须的,当然好的工具也是前提条件。

要想高效分析日志,有几个问题需要解决:

在日常开发使用最多的莫过于 NotePad++ 了,尽管其可以通过 “搜索-标记/标记所有-使用格式1/2/3/4/5”的操作来实现以上的前两点,但是操作较繁琐,当日志行数比较多时,也无法仅显示标记行,从而造成效率低下。

当然,对于普通的业务量不太高的日志记录,NotePad++ 足以满足使用。

下面介绍一个非常简单实用的开源日志查看工具 TextAnalysisTool.NET。

1. 下载应用程序包

下载完成后,如下图打开最新版的应用程序:

2. 分析的日志文件

按照“File -> Open”选择要打开的日志文件。

双击任意行,便会跳出“Add Filter”窗口:(Text 默认为鼠标焦点行的内容)

可以通过修改“Text Color”和“Background”来指定查询结果的文本和行底色,达到高亮显示目的。

其他选项:Description:描述;Excluding:排除,不包含;Case-sensitive:大小写敏感;Regular-expression:按照正则表达式查询。

如下图示例,查询三个语句,标志为不同的行底色效果:

若想只显示查询目标所在的行,可以如下图鼠标操作,也可使用快捷键 Ctrl+H,取消时重复操作即可。

展开阅读全文

页面更新:2024-04-27

标签:高效   日志   级别   错误   操作   简单   文件   程序   内容   信息

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top