.NET Core使用Serilog自定义日志分类

        几个月前在研究了一番Serilog的时候,就想写一篇博客记录一下这个日志框架,可惜忙过了头,给忘了。那么它的优势就不多介绍了,相信你点到这篇文章之前,已经看了不少博客,且没有头绪,哈哈😄

        废话不多说,本文不介绍Serilog的基础用法,只介绍它的一个特殊用法,就是如何使用自定义分类日志,就是把日志记录到不同的业务文件夹,由于时间久远,所以就只贴关键代码啦,懒人教程。

        首先把这俩引用加了,当然不一定是这个版本哈


var builder = WebApplication.CreateBuilder(args);
//关键代码,记得加
builder.Host.UseSerilog();

SerilogHelper.InitSerilog();

   这里使用纯代码方式,非配置文件(不会用配置文件来分类,如有大佬知道,请赐教,感谢!)

请关注Log.ForContext方法和Serilog的过滤器,它是实现将日志分类的关键所在,这一点也是在官网看到的,提供了很好的灵感。

public class SerilogHelper
    {
        #region Serilog 相关设置
        internal static string LogFilePath(string fileName) => $@"Logs/{fileName}/{DateTime.Now.Year}.{DateTime.Now.Month}/log_.log";
        internal static readonly string seriCustomProperty = "seriPos";
        internal static readonly string logOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}][{ThreadId}] {Message:lj}{NewLine}{Exception}";
        internal static readonly long? fileSize = 31457280L;
        /// <summary>
        /// 初始化serilog
        /// </summary>
        public static void InitSerilog()
        {
            /*
            WriteTo.File,可同步或异步,异步需要引用async包
            path:默认路径是程序的bin目录+path参数,当然也可以写绝对路径,只需要写入参数就可以了
            rollingInterval:创建文件的类别,可以是分钟,小时,天,月。 此参数可以让创建的log文件名 + 时间。例如log_20220202_001.log
            fileSizeLimitBytes:文件大小限制,类型long,1024=1KB,31457280=30MB
            rollOnFileSizeLimit:达到文件大小限制后,是否继续创建新文件,如log_20220202_001.log
            outputTemplate:日志模板,可以自定义
            retainedFileCountLimit:设置日志文件个数最大值,默认31,意思就是只保留最近的31个日志文件,等于null时永远保留文件
            restrictedToMinimumLevel:最小写入级别,接收器Sink的级别必须高于Logger的级别,比如Logger的默认级别为 Information,即便 Sink 重写日志级别为 LogEventLevel.Debug,也只能看到 Information
            */


            Serilog.Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Override("Default", LogEventLevel.Information)
                .MinimumLevel.Override("Microsoft", LogEventLevel.Error)
                .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
                .Enrich.FromLogContext()//记录相关上下文信息 
                .WriteTo.Console(restrictedToMinimumLevel: levelConsole, theme: Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme.Code, outputTemplate: logOutputTemplate)
                //利用过滤器对输出文件按文件夹分类
                //如果使用依赖注入ILogger记录日志,当需要记录到指定文件夹,需要传入seriPos,否则只记录到默认Default文件夹
                //比如:Log.Information("{seriPos}:Test", LogType.Order);//LogType自定义枚举
                .WriteTo.Logger(lg =>
                {
                    lg.Filter.ByIncludingOnly(e =>
                    {
                        try
                        {
                            if (e.Properties.TryGetValue(seriCustomProperty, out var value))
                            {
                                ScalarValue scalarValue = value as ScalarValue;
                                LogType arg = (LogType)scalarValue.Value;
                                if (arg != LogType.Default)
                                    return false;
                            }
                            return true;
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                    });
                    lg.WriteTo.Async(a => a.File(LogFilePath(LogType.Default.ToString()), rollingInterval: RollingInterval.Day, fileSizeLimitBytes: fileSize, rollOnFileSizeLimit: true, outputTemplate: logOutputTemplate));
                })
                .WriteToFilePath(new List<LogType> { LogType.Order, LogType.Goods })
                .CreateLogger();
        }
        #endregion

        /*****************************日志级别*****************************/
        // FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部)

        #region Info
        public static void Info(string msg, params object[] args)
        {
            Serilog.Log.Information(msg, args);
        }

        public static void Info(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Information(msg, args);
        }
        #endregion

        #region Debug
        public static void Debug(string msg, params object[] args)
        {
            Serilog.Log.Debug(msg, args);
        }

        public static void Debug(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Debug(msg, args);
        }

        public static void Debug(Exception err, string msg)
        {
            Serilog.Log.Debug(err, msg);
        }

        public static void Debug(LogType logType, Exception err, string msg)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Debug(err, msg);
        }
        #endregion

        #region Warning
        public static void Warning(string msg, params object[] args)
        {
            Serilog.Log.Warning(msg, args);
        }

        public static void Warning(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Warning(msg, args);
        }
        #endregion

        #region Error
        public static void Error(string msg, params object[] args)
        {
            Serilog.Log.Error(msg, args);
        }

        public static void Error(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(msg, args);
        }

        public static void Error(Exception err, string msg)
        {
            Serilog.Log.Error(err, msg);
        }

        public static void Error(LogType logType, Exception err, string msg)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg);
        }

        public static void Error(Exception err, string msg, params object[] args)
        {
            Serilog.Log.Error(err, msg, args);
        }

        public static void Error(LogType logType, Exception err, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg, args);
        }

        #endregion

        #region Fatal
        public static void Fatal(string msg, params object[] args)
        {
            Serilog.Log.Fatal(msg, args);
        }

        public static void Fatal(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Fatal(msg, args);
        }

        public static void Fatal(Exception err, string msg)
        {
            Serilog.Log.Fatal(err, msg);
        }

        public static void Fatal(LogType logType, Exception err, string msg)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Fatal(err, msg);
        }
        #endregion

        #region Verbose
        public static void Verbose(string msg, params object[] args)
        {
            Serilog.Log.Verbose(msg, args);
        }

        public static void Verbose(Exception err, string msg)
        {
            Serilog.Log.Verbose(err, msg);
        }
        #endregion
    }

   自定义扩展方法,用于加载不同的日志类型

public static class LoggerConfigurationExtiensions
    {
        /// <summary>
        /// 遍历集合项,将Log输出到对应LogType文件夹
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="logType">logType作为子Log文件夹名</param>
        /// <returns></returns>
        public static LoggerConfiguration WriteToFilePath(this LoggerConfiguration logger, List<LogType> logType)
        {
            logType.ForEach(
                q =>
                {
                    logger.WriteTo.Logger(lg =>
                    {
                        lg.Filter.ByIncludingOnly(Matching.WithProperty<LogType>(SerilogHelper.seriCustomProperty, p => p == q));
                        lg.WriteTo.Async(a => a.File(SerilogHelper.LogFilePath(q.ToString()), rollingInterval: RollingInterval.Day, fileSizeLimitBytes: SerilogHelper.fileSize, rollOnFileSizeLimit: true,
                            outputTemplate: SerilogHelper.logOutputTemplate));
                    });

                }
            );
            return logger;
        }
    }

  另外Serilog有个比较好玩的功能,扩展器Enricher,我们可以自由扩展一些属性,并把属性记录在日志中,像NLog的property一样,比如这里想把线程ID加到日志中。

//使用扩展器将线程ID附加到每个事件,可以在输出内容加入
    //例:outputTemplate: "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {Message}{NewLine}{Exception}")
    public class ThreadIdEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                    "ThreadId", Environment.CurrentManagedThreadId));
        }
    }

可能有人会问,为什么要定义seriCustomProperty呢,这个主要是为了兼容Microsoft.Extensions.Logging.ILogger,毕竟有些时候,我们不想去使用Helper类,而是想在Controller或者其他类中,通过依赖注入拿到ILogger对象去记录Log,比如:

public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }
}

可是Microsoft.Extensions.Logging.ILogger也只是提供基础接口,真正的实现还是Serilog,那么如何将Microsoft.Extensions.Logging.ILogger记录的Log进入Serilog的过滤器呢,那就可以通过如下方式触发:

//{seriPos}可以理解为占位符,占位符的名称正是在SerilogHelper中声明的变量(用常量会更合适)
//string seriCustomProperty = "seriPos";
Log.Information("{seriPos}:Test", LogType.Order);

//关键代码
lg.Filter.ByIncludingOnly(e =>
                    {
                        try
                        {
                            if (e.Properties.TryGetValue(seriCustomProperty, out var value))
                            {
                                ScalarValue scalarValue = value as ScalarValue;
                                LogType arg = (LogType)scalarValue.Value;
                                if (arg != LogType.Default)
                                    return false;
                            }
                            return true;
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                    });

总之,你可以以任意方式实现,Log.Information("{seriPos}:Test", LogType.Order);这种方式虽然可以实现,但用起来也确实不太舒适,所以最终我基本不用。

后来又做了一层封装,:

  public interface ISeriLogger
    {
        void LogVerbose(string msg, params object[] args);
        void LogVerbose(LogType logType, string msg, params object[] args);
        void LogInformation(string msg, params object[] args);
        void LogInformation(LogType logType, string msg, params object[] args);

        void LogDebug(string msg, params object[] args);
        void LogDebug(LogType logType, string msg, params object[] args);
        void LogDebug(Exception err, string msg);
        void LogDebug(LogType logType, Exception err, string msg);

        void LogWarning(string msg, params object[] args);
        void LogWarning(LogType logType, string msg, params object[] args);

        void LogError(string msg, params object[] args);
        void LogError(LogType logType, string msg, params object[] args);
        void LogError(Exception err, string msg);
        void LogError(LogType logType, Exception err, string msg);
        void LogError(Exception err, string msg, params object[] args);
        void LogError(LogType logType, Exception err, string msg, params object[] args);
    }

 public class SeriLogger : ISeriLogger
    {
        readonly string seriCustomProperty = SerilogHelper.seriCustomProperty;


        public void LogVerbose(string msg, params object[] args)
        {
            Serilog.Log.Verbose(msg, args);
        }

        public void LogVerbose(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Verbose(msg, args);
        }

        public void LogInformation(string msg, params object[] args)
        {
            Serilog.Log.Information(msg, args);
        }

        public void LogInformation(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Information(msg, args);
        }

        public void LogDebug(string msg, params object[] args)
        {
            Serilog.Log.Debug(msg, args);
        }

        public void LogDebug(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Debug(msg, args);
        }

        public void LogDebug(Exception err, string msg)
        {
            Serilog.Log.Debug(err, msg);
        }

        public void LogDebug(LogType logType, Exception err, string msg)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Debug(err, msg);
        }

        public void LogError(string msg, params object[] args)
        {
            Serilog.Log.Error(msg, args);
        }

        public void LogError(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(msg, args);
        }

        public void LogError(Exception err, string msg)
        {
            Serilog.Log.Error(err, msg);
        }

        public void LogError(LogType logType, Exception err, string msg)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg);
        }

        public void LogError(Exception err, string msg, params object[] args)
        {
            Serilog.Log.Error(err, msg, args);
        }

        public void LogError(LogType logType, Exception err, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Error(err, msg, args);
        }

        public void LogWarning(string msg, params object[] args)
        {
            Serilog.Log.Warning(msg, args);
        }

        public void LogWarning(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Warning(msg, args);
        }

        public void Verbose(string msg, params object[] args)
        {
            Serilog.Log.Verbose(msg, args);
        }

        public void Verbose(LogType logType, string msg, params object[] args)
        {
            Serilog.Log.ForContext(seriCustomProperty, logType).Verbose(msg, args);
        }
    }

把ISeriLogger注册到Autofac或其他容器,使用时就通过依赖注入去用即可。

总之,你可以使用SerilogHelper,也可以使用ISerilog或ILogger

public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly ISeriLogger seriLogger;

        public HomeController(ILogger<HomeController> logger, ISeriLogger seriLogger)
        {
            _logger = logger;
            this.seriLogger = seriLogger;
        }

        public IActionResult Index()
        {
            _logger.LogInformation("Hello world");//默认记录到Default文件夹
            _logger.LogInformation("{seriPos}:Hello world",LogType.Order);//记录到Order文件夹

            seriLogger.LogInformation("Hello world");//默认记录到Default文件夹
            seriLogger.LogInformation(LogType.Order,"Hello world");//记录到Order文件夹

            SerilogHelper.Info("Hello world");//默认记录到Default文件夹
            SerilogHelper.Info(LogType.Order, "Hello world");//记录到Order文件夹
            return View();
        }
}

至此结束!下一篇可能是NLog

参考:Writing Log Events · serilog/serilog Wiki · GitHub

### 关于ArcGIS License Server无法启动的解决方案 当遇到ArcGIS License Server无法启动的情况时,可以从以下几个方面排查并解决问题: #### 1. **检查网络配置** 确保License Server所在的计算机能够被其他客户端正常访问。如果是在局域网环境中部署了ArcGIS Server Local,则需要确认该环境下的网络设置是否允许远程连接AO组件[^1]。 #### 2. **验证服务状态** 检查ArcGIS Server Object Manager (SOM) 的运行情况。通常情况下,在Host SOM机器上需将此服务更改为由本地系统账户登录,并重启相关服务来恢复其正常工作流程[^2]。 #### 3. **审查日志文件** 查看ArcGIS License Manager的日志记录,寻找任何可能指示错误原因的信息。这些日志可以帮助识别具体是什么阻止了许可证服务器的成功初始化。 #### 4. **权限问题** 确认用于启动ArcGIS License Server的服务账号具有足够的权限执行所需操作。这包括但不限于读取/写入特定目录的权利以及与其他必要进程通信的能力。 #### 5. **软件版本兼容性** 保证所使用的ArcGIS产品及其依赖项之间存在良好的版本匹配度。不一致可能会导致意外行为或完全失败激活license server的功能。 #### 示例代码片段:修改服务登录身份 以下是更改Windows服务登录凭据的一个简单PowerShell脚本例子: ```powershell $serviceName = "ArcGISServerObjectManager" $newUsername = ".\LocalSystemUser" # 替换为实际用户名 $newPassword = ConvertTo-SecureString "" -AsPlainText -Force Set-Service -Name $serviceName -StartupType Automatic New-ServiceCredential -ServiceName $serviceName -Account $newUsername -Password $newPassword Restart-Service -Name $serviceName ``` 上述脚本仅作为示范用途,请依据实际情况调整参数值后再实施。 --- ###
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Boogaloo-Jer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值