asp.net core日志与异常处理小结

news2024/11/19 1:56:26

asp.net core的webApplicationBuilder中自带了一个日志组件,无需手动注册服务就能直接在控制器中构造注入,本文主要介绍了net core日志与异常处理小结,需要的朋友可以参考下

ILogger简单使用

asp.net corewebApplicationBuilder中自带了一个日志组件。无需手动注册服务就能直接在控制器中构造注入。

public HomeController(ILogger<HomeController> logger)
{
    _logger = logger;
}
_logger.LogTrace("trace{path}", HttpContext.Request.Path);
_logger.LogDebug("debug{path}", HttpContext.Request.Path);
_logger.LogInformation("info{path}", HttpContext.Request.Path);
_logger.LogWarning("warn{path}", HttpContext.Request.Path);
_logger.LogError("error{path}", HttpContext.Request.Path);
_logger.LogCritical("critical{path}", HttpContext.Request.Path);
_logger.Log(LogLevel.Information, "Log{path}", HttpContext.Request.Path);

打印效果如下

 

在一个日志输出中,日志的第一行输出是日志类别,来自泛型参数<HomeController>的完全限定类名。第二行是模板字符串输出,可以看到从info级别开始才有输出,这是因为在appsetting文件中配置了LogLevel为Information,过滤掉了该等级之下的输出。

MSDN推荐在方法中多次详细的打印日志,但打印方法使用TraceInformation。开发时将日志等级设置为Warn,避免过多信息显示。在排查故障时才将日志等级设置为Trace

打印异常信息

try
{
    throw new Exception("自定义异常");
}
catch (Exception e)
{
    _logger.LogTrace("trace{path}", e);
    _logger.LogDebug("debug{path}", e);
    _logger.LogInformation("info{path}", e);
    _logger.LogWarning("warn{path}", e);
    _logger.LogError("error{path}", e);
    _logger.LogCritical("critical{path}", e);
    _logger.Log(LogLevel.Information, "Log{path}", e.ToString());
}

打印效果如下

 

日志的第一行是泛型参数,第二行开始是字符串模板。其中有参数e的异常类型,异常信息e.Message。第三行开始时异常的调用堆栈e.StackTrace最后一个打印中调用了e.ToString(),结果是一样的。由此可见日志打印时,碰到了非字符串参数,就会调用其ToString()方法。那么我们可以通过重写这个方法来改变日志输出吗?

重写ToString

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString()
    {
        return JsonSerializer.Serialize(this);
    }
}
_logger.LogError("{path}", new Person() { Id=1,Name="张三",Age=19});
_logger.LogCritical("{path}", new Person() { Id = 2, Name = "王浩", Age = 20 });

这里我在重写中调用了序列化方法,打印结果是一个json数据

 

日志配置方式

配置日志文件介质,有三种方法,都是ILoggingBilder调用AddConsole。他们差不多,但有微小差别。第一种是配置容器中的日志服务,后面两种是配置服务主机上的日志。

主机生成器会初始化默认配置,然后在生成主机时将已配置的 ILoggerFactory 对象添加到主机的 DI 容器

所以效果是一样的

 

//第一种,配置应用程序级别的日志记录器
builder.Services.AddLogging(configure =>
{
    configure.AddConsole();
});
//第二种,配置应用程序主机级别的日志记录器。和第三种一样,但不推荐使用这个。
builder.Host.ConfigureLogging(configure =>
{
    configure.AddConsole();
});
//第三种,配置应用程序主机级别的日志记录器
builder.Logging.AddConsole();

Log等级

在asp.net core的日志默认扩展中,定义LogTrace LogDebug LogInformation LogWarning LogError LogCritical Log 7种常用方法。对应的日志有7个级别

// 定义日志严重性级别。
public enum LogLevel
{
    //包含最详细消息的日志。这些消息可能包含敏感的应用程序数据
    //默认情况下禁用这些消息,生产环境中永远不应启用
    Trace = 0,
    //用于开发过程中的交互式调查。这些日志主要包含调试有用的信息,没有长期价值
    Debug = 1,
    //跟踪应用程序的一般流程。这些日志应具有长期价值
    Information = 2,
    //强调应用程序流程中的异常或意外事件,但不会导致应用程序执行停止
    Warning = 3,
    //强调由于失败导致当前执行流程停止的日志。这些日志应指示当前活动中的失败,而不是应用程序范围的失败
    Error = 4,
    //描述不可恢复的应用程序或系统崩溃,或者需要立即关注的灾难性失败
    Critical = 5,
    //不用于编写日志消息。指定日志类别不应写入任何消息
    None = 6,
}

异常

在开发环境,builder默认会为应用配置这个异常中间件app.UseDeveloperExceptionPage(),这个配置是写在源码中的,所以不需要我们显式配置。如果我们不想显示这个页面,那可以配置中间件UseExceptionHandler。这个中间件要配置在UseDeveloperExceptionPage后面,因为中间件调用是递归的,异常发生在终结点中间件中,之后就是请求处理管道递归跳出阶段,所以异常捕获也在这个阶段。调用顺序和配置顺序是相反的。

或者根据环境app.Environment.IsDevelopment()来判断使用是否使用UseExceptionHandler。至于UseDeveloperExceptionPage则不用担心,只要不是显示配置,开发环境中不会进入这个中间件。不知道是由于条件编译,还是做了环境判断。

还还有一种方法是在这两个中间件后面自定义中间件捕获异常,短路管道。

我注意到,不管使用UseDeveloperExceptionPage还是UseExceptionHandler中间件,控制台中都会出现异常日志,这是为什么?这是因为这两个中间件中都调用了日志打印。所以我们可以自己实现一个异常处理中间件,这样就不会打印日志了。

app.UseDeveloperExceptionPage();
app.UseExceptionHandler("/Home/Error");
app.Use(async (context, next) =>
{
    try
    {
        await next(context);
    }
    catch (Exception e)
    {
        await context.Response.WriteAsync("Custom Exception MiddleWare");
    }
});

碰到异常打印日志是正常操作,所以这不需要我们去关心。但是,在前面的截图中,UseExceptionHandler中间件的异常打印的问题在于,没有打印请求路径、请求参数等信息。我们只知道哪里出错了,却不知道是什么原因引起的,怎么复现,这是很不完善的。不知道当初为什么这样设计。所以最好是自己重新写个异常处理中间件,设置响应内容以及控制日志打印。

自定义异常中间件打印请求和异常日志

public class ExceptionMiddleware
{
    private readonly RequestDelegate next;
    public ExceptionMiddleware(RequestDelegate next)
    {
        this.next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception e)
        {
            var _logger=context.RequestServices.GetService<ILogger<ExceptionMiddleware>>();
            var bodystring="";
            if (context.Request.ContentType==null || !context.Request.ContentType.Contains("multipart/form-data"))
            {
                using (StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8))
                {
                    bodystring = await reader.ReadToEndAsync();
                }
            }
            else
            {
                //不包含文件实际内容
                foreach (var formField in context.Request.Form)
                {
                    bodystring += formField.Key + " " + formField.Value+",";
                }
            }
            _logger.LogError("堆栈:{e}\n\t路径:{c}\n\t查询字符串:{p}\n\t内容:{f}", e,context.Request.Path,context.Request.QueryString,bodystring);
            await context.Response.WriteAsync("Custom Exception MiddleWare");
        }
    }
}

使用Serilog输出日志到文件介质

由于asp.net core自己没有提供文件介质的提供程序,所以我转到使用Serilog。仅仅注册了一个服务,其他的一切不变。
引入包Serilog.AspNetCore。并不需要引入Serilog.Sinks.File,因为Serilog.AspNetCore已经引入了文件介质的提供程序

日志管线配置Serilog提供程序

日志系统包括如下几个概念:日志管线提供程序介质。严格来说介质不算。
日志系统又叫日志管线logging pipeline。日志管线中的每个提供程序都会处理一次日志,并输出到自己的介质中。

  • 配置serilog子管线

 

Log.Logger = new LoggerConfiguration()
    //最小输出等级
    .MinimumLevel.Warning()
    //增加介质
    .WriteTo.Console()
    .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();
//注册serilog服务
builder.Services.AddSerilog();

配置完整的日志管线这里默认的控制台介质得到了两个提供程序,一个是默认的AddConsole,一个是Serilog的WriteTo.Console。按照管线中配置的顺序,这两个提供程序都会向控制台打印一次。而Serilog还向管线中添加了一个到文件介质的提供程序,所以还会产生一个日志文件。

builder.Services.AddLogging(logbuilder =>
{
    logbuilder
	//删除管线上默认的提供程序
    .ClearProviders()
	//serilog子管线上的提供程序合并进来
	.AddSerilog()
    .AddConsole();
});

控制台介质两个提供程序均处理了日志打印

  • 文件介质

输出模板

回想默认日志提供程序和serilog日志提供程序,都会在我们的消息之外附加信息,比如等级、类名。这是怎么配置的?

  • 默认日志提供程序的格式是 [LogLevel]: [EventId] [Category] Message。可以通过继承ConsoleFormatter改写模板。但是很麻烦。而且颜色,字体还不知道怎么控制。
public class CustomConsoleFormatter : ConsoleFormatter
{
    private readonly string _formatTemplate;
    public CustomConsoleFormatter(IOptions<TemplatedConsoleFormatterOptions> options) : base(name: "Custom")
    {
        _formatTemplate = options.Value.Template ?? "[{Timestamp:HH:mm:ss} {Level}] [{EventId}] {Message}{NewLine}{Exception}";
    }
    public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
    {
        var timestamp = DateTime.UtcNow;
        var level = logEntry.LogLevel.ToString();
        var eventId = logEntry.EventId.Id;
        var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
        var exception = logEntry.Exception != null ? logEntry.Exception.ToString() : "";
        var newLine = Environment.NewLine;
        var logOutput = ReplaceTemplateValues(_formatTemplate, timestamp, level, eventId, message, exception, newLine);
        textWriter.Write(logOutput);
    }
    private string ReplaceTemplateValues(string template, DateTime timestamp, string level, int eventId, string message, string exception, string newLine)
    {
        return Regex.Replace(template, @"{(\w+)(?::([^}]+))?}", match =>
        {
            var key = match.Groups[1].Value;
            var format = match.Groups[2].Value;
            switch (key)
            {
                case "Timestamp":
                    return timestamp.ToString(format);
                case "Level":
                    return level;
                case "EventId":
                    return eventId.ToString();
                case "Message":
                    return message;
                case "NewLine":
                    return newLine;
                case "Exception":
                    return exception;
                default:
                    return match.Value;
            }
        });
    }
}
public class TemplatedConsoleFormatterOptions : ConsoleFormatterOptions
{
    public string? Template { get; set; }
}
//使用模板
builder.Services.AddLogging(logbuilder =>
{
    logbuilder
    .ClearProviders()
   //.AddSerilog()
   .AddConsole(option =>
   {
       //使用名为Custom的模板配置
       option.FormatterName = "Custom";
   })
   //添加一个控制台模板配置
   .AddConsoleFormatter<CustomConsoleFormatter, TemplatedConsoleFormatterOptions>(foption =>
   {
       foption.Template = "[{Timestamp:HH:mm:ss} {Level}] [{EventId}] {Message:lj}{NewLine}{Exception}";
   });
});

 

 

Serilog输出模板相比之下Serilog就简单得多,直接指定模板就行 

Log.Logger = new LoggerConfiguration()
    //最小输出等级
    .MinimumLevel.Warning()
    //增加介质
    .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

Serilog还能向模板中增加字段,使用Enrichers

Log.Logger = new LoggerConfiguraition()  
     .Enrich.FromLogContext()
	 .Enrich.WithProperty("Application", "Demo")
	  .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{Properties:j}{NewLine}{Exception}")
// 设置 ThreadContext 或 CallContext 的属性
ThreadContext.Properties["UserId"] = 42;
CallContext.LogicalSetData("UserName", "John Doe");
Log.Information("User {UserId} logged in as {UserName}", ThreadContext.Properties["UserId"], CallContext.LogicalGetData("UserName"));

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2162103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch可视化工具ElasticHD

目录 介绍 ElasticHD应用程序页面 安装 基本用法 独立可执行文件 ES版本支持 SQL特性支持: 超越SQL功能支持: SQL的用法 Docker快速入门: 下载地址 介绍 ElasticHD是ElasticSearch可视化管理工具。它不需要任何软件。它在您的Web浏览器中工作,允许您随时随地管理…

unshare -p时提示Cannot allocate memory如何解决

当使用unshare -p命令时&#xff0c;出现如下报错&#xff1a; unshare -p /bin/bash bash: fork: Cannot allocate memory 如果想要正常使用&#xff0c;只需要添加–fork选项就行 unshare -p --fork /bin/bash 在使用 unshare -p 创建新的 PID 命名空间时&#xff0c;存在一…

aws s3 存储桶 前端组件上传简单案例

写一个vue3 上传aws oss存储的案例 使用到的插件 npm install aws-sdk/client-s3 注意事项 &#xff1a; 1. 本地调试 &#xff0c; 需要设置在官网设置跨域 必须&#xff01;&#xff01;&#xff01; 否则调试不了 &#xff0c;前端代理是不起作用的 &#xff0c;因为是插…

如何通过蜂巢(容器安全)管理内部部署数据安全产品与云数据安全产品?

本文将探讨内部部署和云数据安全产品之间的主要区别。在思考这个问题之前&#xff0c;首先了解内部部署和云数据安全产品之间的主要区别。 内部部署数据安全产品意味着管理控制台位于企业客户的内部部署&#xff0c;而德迅云安全则在云中托管云数据安全产品。德迅云安全供应商通…

TAPD_保密需求介绍

功能指引 本文档将介绍&#xff1a;保密需求的基本介绍、如何配置保密需求和保密需求相关的常见问题。 一、基本介绍 伴随业务的拓展&#xff0c;团队成员们在工作中不免要遇到跨团队协作和外包人员管理等需要 权限控制和信息保密 的场景。 此情况下&#xff0c;项目数据的权…

Python接口自动化测试输出日志到控制台和文件

一、日志的作用 一般程序日志出自下面几个方面的需求&#xff1a; 1. 记录用户操作的审计日志&#xff0c;甚至有的时候就是监管部门的要求。 2. 快速定位问题的根源 3. 追踪程序执行的过程。 4. 追踪数据的变化 5. 数据统计和性能分析 6. 采集运行环境数据 一般在程序上线之后…

图文组合商标部分驳回后优化后初审通过!

这几天以前有个企业的商标初审下来了&#xff0c;以前是加了图形个别部分没有通过初审&#xff0c;后面是把图形去掉重新用文字申请下来初审。 图形与文字同时申请&#xff0c;会分别审查有一个元素过不了&#xff0c;整体就会过不了&#xff0c;所以平常就会建议分开申请注册商…

Transformers | 在自己的电脑上开启预训练大模型使用之旅!

本文内容主要包括两部分&#xff1a; Hugging Face 社区介绍 如何使用 Transformers 库的模型 1. Hugging Face 社区介绍 Hugging Face (https://huggingface.co/) 是一个 Hub 社区&#xff0c;它和 GitHub 相同的是&#xff0c;他们都是基于 Git 进行版本控制的存储库社区&…

探寻大模型时代智慧农业新未来,商汤与上海市农委达成战略合作

近日&#xff0c;在中国农民丰收节上海会场丰收庆典活动上&#xff0c;商汤科技与上海市农业农村委员会&#xff08;下称&#xff1a;上海市农委&#xff09;签署战略合作协议&#xff0c;双方将依托先进的AI大模型技术&#xff0c;共同推进上海智慧农业发展&#xff0c;打造国…

ESXI主机加入VCENTER现有集群提示出现常规性错误

背景&#xff1a;由于忘记了这台主机的root密码&#xff0c;所以在迁移完虚拟机后给这台主机重新安装了操作系统&#xff0c;装完操作系统加集群提示如下报错&#xff1a; 查阅了一些资料后发现主机的CPU是一样的&#xff0c;不需要开EVC&#xff1b; 也有一些说需要改这个配置…

《关键跃升读书笔记》11

协作&#xff1a; 怎么解决“容忍⿊”这类问题&#xff1f;我们要重新理解“⽂化”。⼈类⽂化、企 业⽂化&#xff0c;都是为了让⼈们更好地协作。 再⼩的公司&#xff0c;再⼩的团队&#xff0c;都是⼀个共同协作体&#xff0c;就像整个⼈类社会 是共同协作体。理解了⼈类社会…

“被卷”还是“破卷”,咱有得选

职场内卷是一个当下社会备受热议的话题。身处内卷中的人&#xff0c;所感受到的是价值感不足、低效、无奈等消极内容&#xff0c;但哪怕知道处于那样的工作环境是不健康的&#xff0c;因为环境所迫&#xff0c;似乎也只能被裹挟。 就如当下热播的都市剧《凡人歌》中的那隽&…

Kubernetes 深入浅出系列 | 容器剖析之容器基本实现原理

一、容器基本实现原理 Docker 主要通过如下三个方面来实现容器化&#xff1a; ① 使用操作系统的 namespace 隔离系统资源技术&#xff0c;通过隔离 网络、PID 进程、系统信号量、文件系统挂载、主机名和域名&#xff0c;来实现在同一宿主机系统中&#xff0c;运行不同的容器&…

6种常见位运算符+异或运算符的使用(加密、解密)

一、位运算符 位运算符进行的是整数与整数之间的运算 1、右移运算符&#xff1a;>> &#xff08;1&#xff09;相当于对整数除以2 &#xff08;2&#xff09;举例&#xff1a; int num 2; System.out.println(num >> 1); 2、左移运算符&#xff1a;<< …

数据结构---顺序表之单链表

1.链表的概念 链表是一种逻辑上是线性的&#xff0c;但物理结构不一定是线性的数据结构&#xff0c;它通过链表中的指针链接次序实现的 链表的存储空间是我们通过动态内存开辟的内存空间&#xff0c;所以他们的地址可能是连续的也可能不是连续的 2.链表的分类 1.单向或者双向…

PostgreSQL JAVA与SQL集成之PL/Java

PostgreSQL pljava PL/Java 作为 PostgreSQL 的编程语言扩展之一&#xff0c;与 PL/pgSQL&#xff08;PostgreSQL 原生的存储过程语言&#xff09;相比&#xff0c;提供了 Java 语言特有的面向对象功能&#xff0c;并支持 Java 的标准库和第三方库。由于 Java 是一种跨平台的语…

Python 聊聊有内置函数,又该怎么学习内置函数

前言 python有内置函数的概念&#xff0c;从Python3.x开始&#xff0c;内置函数位于builtins模块&#xff0c;比如我们常用的内置函数len()&#xff0c;其实它是builtins模块下的属性&#xff0c;我们也可以builtins.len&#xff08;&#xff09;去访问&#xff0c;当然因为每个…

海豚调度运行成功但无法生成实例解决

海豚调度运行成功但无法生成实例解决 问题描述 点击运行&#xff0c;提示运行成功但无法在工作实例中看到 问题定位 查看资源监控&#xff0c;内存占用80% 查看master日志 tail -f /home/dolphinscheduler/tmp/dolphinscheduler/master-server/logs/dolphinscheduler-m…

无刷直流电机内阻小和大有什么区别

‌直流电机的内阻小和大各有其特点和应用场景。 ‌ 内阻小的直流电机通常具有较小的线圈匝数&#xff0c;这意味着在工作时消耗的电流较大&#xff0c;从而在同一转速下能够提供较大的扭矩。这种电机适用于需要较大扭矩的应用场景&#xff0c;例如重载启动或需要较高机械效率的…

模型django封装uvicorn服务器部署实战

Uvicorn 是一个轻量级的 ASGI 服务器&#xff0c;它基于 uvloop 和 httptools 这两个高性能的异步库。Uvicorn 提供了快速的启动时间和低延迟的响应&#xff0c;非常适合用于生产环境。 Django&#xff1a; 是一个开源且强大的Web框架&#xff0c;适用于快速开发和部署Python …