前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >零基础写框架(2):故障排查和日志基础

零基础写框架(2):故障排查和日志基础

作者头像
痴者工良
发布于 2024-06-06 01:05:33
发布于 2024-06-06 01:05:33
14100
代码可运行
举报
文章被收录于专栏:痴者工良痴者工良
运行总次数:0
代码可运行

故障排查和日志

.NET 程序进行故障排查的方式有很多,笔者个人总结常用的有以下方式:

IDE 调试、Visual Studio 中的诊断工具、性能探测器

一般来说,使用 IDE 进行断点调试和诊断只适合在本地开发环境,我们可以借助 IDE 中的工具断点调试以及收集程序详细的运行信息,IDE 是功能最全、最有效的诊断程序问题的工具。

NET CLI 工具如 dotnet-dump、dotnet-trace 等

.NET CLI 工具本身是基于 System.Diagnostics 、Microsoft.Diagnostics 中的接口实现的,可以跨进程监听收集 .NET 进程的信息,比如内存快照。

使用 System.Diagnostics 、Microsoft.Diagnostics 中的接口

新版本的 .NET 使用这些接口做堆栈追踪、性能探测等,微软官方和社区中的很多工具使用了这些接口,比如 prometheus-net、opentelemetry-dotnet 等,在微服务场景下,这些接口提供了大量有用的信息,可以集成到可观测性平台中。

打印日志

日志是程序进行故障排查最常用最不可缺少的一部分,也是最简单的故障排查方法。程序输出的日志可以为故障排查提供有用的信息,同时通过日志观察程序的运行状态,日志也可以记录审计信息供日后回溯查找。可是在多年开发工作中,笔者发现大多数开发人员都很少打印日志,而且打印的日志信息对诊断故障几乎没帮助,因为这些日志往往只是使用 try-catch{} 包裹代码直接打印异常,或者直接打印 API 请求和响应内容。日志对于排查问题是很有帮助的,可是开发者往往不重视打印日志,或者只是打印一些信息。

基础设施可观测性平台,以及客户端包如 prometheus-net 等

而对于生产环境,则需要在架构上考虑,根据运行环境采用不同的技术,比如裸机、dockerKubernetes云函数等环境。以 Kubernetes 集群环境为例,随着微服务的发展和现有的专业监控平台的成熟,需要考虑从基础设施上去监听程序的运行状态,减少在代码上对程序的侵入。我们可以采用 Fluentd、Logstash 等收集容器的日志、Elasticsearch 聚合和存储日志,然后使用 Kibana 进行可视化日志查询。这种在程序之后使用工具观测程序运行状态的技术被称为可观测性技术,目前在可观测性领域,主要有链路追踪(Tracing)、日志(Logging)、指标(Metrics) 三类技术,这些技术偏于架构和运维方面,因此在本章的最后一节只作简单介绍。

我们常常会碰到在开发测试环境千测万试没问题,项目上线之后却出现了意想不到的问题,比如接口性能差、代码运行的顺序不符合预期等。在线上排查问题比较麻烦,生产环境不能直接使用开发工具调试,也不能因为排查问题影响到用户的体验,因此开发者必须在日志中预留足够多的信息,或者使用各种监控工具收集程序运行信息,同时开发者需要掌握多种诊断工具的使用方法。对于程序故障的诊断,从开发角度、架构角度和运维角度去看会有不同的工具和方法,而本章是从开发者的角度,介绍一些在设计或定制企业内部开发框架时需要考虑的技术。

日志

在程序中使用打印运行日志,是最简单、最常用的方法,也是最有效的,在本节中,我们来了解在程序中编写日志的一些方法以及常用日志框架的定制使用方法。

日志抽象接口

.NET 通过 Microsoft.Extensions.Logging.Abstractions 抽象了日志接口,目前流行的日志框架都会基于该抽象包实现响应的接口,使得我们在项目中使用抽象日志接口,而不需要关注使用了哪个日志框架。

.NET 官方使用Microsoft.Extensions.Logging 实现了这些抽象,而且社区中还有 Serilog 等日志框架 ,由于 Serilog 框架的扩展非常方法,可以灵活地定制需求,所以在本章中笔者会详细介绍 Serilog 框架的使用方法。

Microsoft.Extensions.Logging.Abstractions 有三个主要接口:

ILogger 用于输出日志

ILoggerFactory 获取日志接口,并保存日志提供器。

ILoggerProvider 提供日志接口。

ILoggerFactory

.NET Core 中很多标准接口都实践了工厂模式的思想,ILoggerFactory 正是工厂模式的接口,而 LoggerFactory 是工厂模式的实现。

其定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ILoggerFactory : IDisposable
{
    ILogger CreateLogger(string categoryName);
    void AddProvider(ILoggerProvider provider);
}

ILoggerFactory 工厂接口的作用是创建一个 ILogger 类型的实例,即 CreateLogger 接口。

logging providers 称为日志记录程序。Logging Providers 将日志显示或存储到特定介质,例如 控制台、日志文件、Elasticsearch 等。

微软官方提供了很多很多日志包:

  • Microsoft.Extensions.Logging.Console
  • Microsoft.Extensions.Logging.AzureAppServices
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.Extensions.Logging.EventLog
  • Microsoft.Extensions.Logging.EventSource
  • Microsoft.Extensions.Logging.TraceSource

ILoggerProvider

通过实现ILoggerProvider接口可以创建自己的日志记录提供程序,比如控制台、文件等,表示可以创建 ILogger 实例的类型。

其定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ILoggerProvider : IDisposable
{
    ILogger CreateLogger(string categoryName);
}

ILogger

ILogger 接口提供了将日志记录到基础存储的方法,其定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ILogger
{
    void Log<TState>(LogLevel logLevel, 
                     EventId eventId, 
                     TState state, 
                     Exception exception, 
                     Func<TState, Exception, string> formatter);
    
    bool IsEnabled(LogLevel logLevel);
    IDisposable BeginScope<TState>(TState state);
} 

ILogger 虽然只有三个接口的,但是添加日志类库之后,会有很多扩展方法。

总结一下,如果要使用一个日志框架,需要实现 ILogger 、ILoggerFactory 、ILoggerProvider 。

而社区中使用最广泛的 Serilog 框架则提供了 File、Console、Elasticsearch、Debug、MSSqlServer、Email 等,还包含大量的扩展。

日志等级

Logging API 中,规定了 7 种日志等级,其定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public enum LogLevel
{
  Debug = 1,
  Verbose = 2,
  Information = 3,
  Warning = 4,
  Error = 5,
  Critical = 6,
  None = int.MaxValue
}

我们可以通过 ILogger 中的函数,输出以下几种等级的日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            logger.LogInformation("Logging information.");
            logger.LogCritical("Logging critical information.");
            logger.LogDebug("Logging debug information.");
            logger.LogError("Logging error information.");
            logger.LogTrace("Logging trace");
            logger.LogWarning("Logging warning.");

在日志配置文件中,我们常常看到这样的配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Default": "Debug",
        "Microsoft": "Warning",
        "System": "Warning"
      }

MinimumLevel 属性配置了日志打印的最低等级限制,低于此等级的日志不会输出。Override 则可以对不同的命名空间进行自定义限制。

比如,我们希望能够将程序的业务日志详细打印出来,所以我们默认等级可以设置为 Debug,但是 System、Microsoft 开头的命名空间也会打印大量的日志,这些日志用处不大,所以我们可以设置等级为 Warning,这样日志程序针对 System、Microsoft 开头的命名空间,只会输出 Warning 等级以上的日志。

当然,System、Microsoft 中也有一些类库打印的日志比较重要,因此我们可以单独配置此命名空间的输出等级:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
      "Override": {
        "Default": "Debug",
        "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
        "Microsoft": "Warning",
        "System": "Warning"
      }

在 ASP.NET Core 中,以下命名空间各有不同的用途,读者可以单独为这些命名空间进行配置最小日志打印等级。

类别

说明

Microsoft.AspNetCore

常规 ASP.NET Core 诊断。

Microsoft.AspNetCore.DataProtection

考虑、找到并使用了哪些密钥。

Microsoft.AspNetCore.HostFiltering

所允许的主机。

Microsoft.AspNetCore.Hosting

HTTP 请求完成的时间和启动时间。 加载了哪些承载启动程序集。

Microsoft.AspNetCore.Mvc

MVC 和 Razor 诊断。 模型绑定、筛选器执行、视图编译和操作选择。

Microsoft.AspNetCore.Routing

路由匹配信息。

Microsoft.AspNetCore.Server

连接启动、停止和保持活动响应。 HTTP 证书信息。

Microsoft.AspNetCore.StaticFiles

提供的文件。

在本章的剩余小节中,笔者将会介绍如何实现自定义日志框架、Serilog 的使用、如何使用 .NET 设计诊断工具。

自定义日志框架

本节示例项目在 Demo2.MyLogger.Console 中。

创建控制台项目后,添加 Microsoft.Extensions.Logging.Console 引用。

创建 MyLoggerOptions ,存储日志配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public class MyLoggerOptions
	{
		/// <summary>
		/// 最小日志等级
		/// </summary>
		public LogLevel DefaultLevel { get; set; } = LogLevel.Debug;
	}

创建自定义日志记录器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/// <summary>
	/// 自定义的日志记录器
	/// </summary>
	public class MyConsoleLogger : ILogger
	{
		// 日志名称
		private readonly string _name;
		private readonly MyLoggerOptions _options;

		public MyConsoleLogger(string name, MyLoggerOptions options)
		{
			_name = name;
			_options = options;
		}

		public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

		// 判断是否启用日志等级
		public bool IsEnabled(LogLevel logLevel)
		{
			return logLevel >= _options.DefaultLevel;
		}

		// 记录日志,formatter 由框架提供的字符串格式化器
		public void Log<TState>(
			LogLevel logLevel,
			EventId eventId,
			TState state,
			Exception? exception,
			Func<TState, Exception?, string> formatter)
		{
			if (!IsEnabled(logLevel))
			{
				return;
			}
			if (logLevel == LogLevel.Critical)
			{
				System.Console.ForegroundColor = System.ConsoleColor.Red;
				System.Console.WriteLine($"[{logLevel}] {_name} {formatter(state, exception)} {exception}");
				System.Console.ResetColor();
			}
			else if (logLevel == LogLevel.Error)
			{
				System.Console.ForegroundColor = System.ConsoleColor.DarkRed;
				System.Console.WriteLine($"[{logLevel}] {_name} {formatter(state, exception)} {exception}");
				System.Console.ResetColor();
			}
			else
			{
				System.Console.WriteLine($"[{logLevel}] {_name} {formatter(state, exception)} {exception}");
			}
		}
	}

创建自定义日志提供器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	[ProviderAlias("MyConsole")]
	public sealed class MyLoggerProvider : ILoggerProvider
	{
		private MyLoggerOptions _options;
		private readonly ConcurrentDictionary<string, MyConsoleLogger> _loggers =
			new(StringComparer.OrdinalIgnoreCase);

		public MyLoggerProvider(MyLoggerOptions options)
		{
			_options = options;
		}

		public ILogger CreateLogger(string categoryName) =>
			_loggers.GetOrAdd(categoryName, name => new MyConsoleLogger(name, _options));

		public void Dispose()
		{
			_loggers.Clear();
		}
	}

编写扩展函数,注入自定义日志提供器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static class MyLoggerExtensions
	{
		public static ILoggingBuilder AddMyConsoleLogger(
			this ILoggingBuilder builder, Action<MyLoggerOptions> action)
		{
			MyLoggerOptions options = new();
			if (action != null)
			{
				action.Invoke(options);
			}

			builder.AddConfiguration();
			builder.Services.TryAddEnumerable(
				ServiceDescriptor.Singleton<ILoggerProvider>(new MyLoggerProvider(options)));
			return builder;
		}
	}

最后使用 Microsoft.Extensions.Logging 中的 LoggerFactory,构建日志工厂,从中生成 ILogger 对象,最后打印日志:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		static void Main(string[] args)
		{
			using ILoggerFactory factory = LoggerFactory.Create(builder =>
			{
				builder.AddConsole();
				builder.AddMyConsoleLogger(options =>
				{
					options.DefaultLevel = LogLevel.Debug;
				});
			});
			ILogger logger1 = factory.CreateLogger("Program");
			ILogger logger2 = factory.CreateLogger<Program>();

			logger1.LogError(new Exception("报错了"), message: "Hello World! Logging is {Description}.", args: "error");
			logger2.LogError(new Exception("报错了"), message: "Hello World! Logging is {Description}.", args: "error");
		}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
「R」grid 图形对象 grobs
学习材料:https://bookdown.org/rdpeng/RProgDA/the-grid-package.html#grobs
王诗翔呀
2021/06/16
1.4K0
「R」grid 图形对象 grobs
111-R可视化35-结合grid与ggplot输出
在先前的内容中[[101-R可视化29-底层绘图系统grid学习之使用grid作图]],我们说过,如果可以结合grid 与ggplot 绘图就好了:一方面,通过ggplot 绘图的高级语法,可以省去许多绘图中复杂的代码设置;另一方面,通过grid 底层的调用,我们也可以实现更加灵活的图层设置。
北野茶缸子
2022/04/05
8640
111-R可视化35-结合grid与ggplot输出
96-R可视化25-底层绘图系统grid学习之viewports
接着前面[[91-R可视化23-底层绘图系统grid学习之grob对象]] 继续介绍。
北野茶缸子
2022/04/05
6560
96-R可视化25-底层绘图系统grid学习之viewports
目前最全的R语言-图片的组合与拼接
李誉辉,四川大学硕士在读,研究数据分析与可视化,以及网络爬虫。誉辉兄最近出的文章都是很系统的,从R ggplot2的基础讲解到三维数据可视化plot3D,文章都整理讲解得很全面系统,我本人也是很喜欢这样的文章,故而推荐给大家。
生信宝典
2019/09/29
5.7K0
目前最全的R语言-图片的组合与拼接
91-R可视化23-底层绘图系统grid学习之grob对象
grid包是一个底层的绘图系统,能够灵活地控制图形输出的外观和布局,但是grid包不提供创建完整图形的高级绘图系统,例如,ggplot2和lattice,而是提供绘制开发这些高级绘图的基础接口,
北野茶缸子
2022/02/08
1K0
91-R可视化23-底层绘图系统grid学习之grob对象
97-R可视化26-底层绘图系统grid学习之拓展包gridExtra
我们接着来唠唠R 的grid 绘图。gridExtra包人如其名,拓展包,自然就是要拓展的。
北野茶缸子
2022/04/05
8450
97-R可视化26-底层绘图系统grid学习之拓展包gridExtra
grid包just参数如何just图形位置
  grid的画图函数都含有just,但是just参数的是怎么调节图形位置的总是让人非常费解,于是便写了代码来一探究竟。   思路非常简单:放一个2*2的布局viewport,每个布局里面放一个viewport,每个viewport都用了不同的just参数。just之后的矩形用蓝色显示,中心点的移动用箭头表示出来, 这样每个参数对应图形怎么移动的都能一目了然。从以下的代码也能学到如何安排布局, 如何使用grobX和grobY获得grob对象的坐标, 如何进行基本的viewport切换等。
用户1680321
2018/09/28
5740
grid包just参数如何just图形位置
R语言之可视化①⑥一页多图(2)目录
cowplot包是ggplot2的简单附加组件。 它旨在为ggplot2提供一个出版物就绪的主题,这个主题需要最少量的轴标签尺寸,情节背景等。对'ggplot2'库的一些有用的扩展和修改。 特别是,这个软件包可以很容易地将多个'ggplot2'图组合成一个并用字母标记它们,例如 A,B,C等,这是科学出版物经常需要的。 该软件包还提供了一个流线型和干净的主题,用于Wilke实验室,因此包名称代表Claus O. Wilke的绘图库。
用户1359560
2018/12/14
1K0
R语言之可视化①⑥一页多图(2)目录
ggplot2学习笔记之图形排列
作者:严涛 浙江大学作物遗传育种在读研究生(生物信息学方向)伪码农,R语言爱好者,爱开源
生信宝典
2018/12/18
2.5K0
ggplot2学习笔记之图形排列
用ggplot2画了一个我也叫不上名的炫酷图表
今日心血来潮,看到一幅制作精良的图表,就想使用ggplot2代码实现,虽然不知道该怎么称呼这个图表,但是能顺利做出来也是很有成就感的! 加载数据包 library("ggplot2") library("grid") library("showtext") library("Cairo") font.add("myfont","msyh.ttc") 构造图形数据源 mydata<-data.frame( id=1:13, class=rep_len(1:4, length=13), Label=c("Eve
数据小磨坊
2018/04/11
1K0
用ggplot2画了一个我也叫不上名的炫酷图表
106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象之一
虽然ggplot2 和它的朋友们[[xx-R可视化30-ggplot又一拓展包之ggforce]], [[xx-R可视化xx-用ggalt体验ggplot新版DLC(拓展)]] 给我们提供了大量绘图的选项。比如通过操纵 geom_** 和stat_** 函数。
北野茶缸子
2022/04/05
8670
106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象之一
美美的商务范儿——ggplot2蝴蝶图
一个小案例,使用ggplot2绘制蝴蝶图,在巩固温习条形图坐标轴翻转的同时,重新熟悉一下如何利用grid系统进行版式布局。 原图如下: 该图表思路很简单,就是两个条形图通过坐标轴翻转,使用grid包
数据小磨坊
2018/04/11
1.7K0
美美的商务范儿——ggplot2蝴蝶图
R包:gtable包用于处理ggplot2图像
ggplot2是基于grid的绘图工具,它绘制的图像其实是由多个grob(grid graph object)组成的,比如一张点图,它的标题是titleGrob,点图的基本单元包括pointsGrob。
生信菜鸟团
2020/10/12
2.5K0
101-R可视化29-底层绘图系统grid学习之使用grid作图
学习了grob 和viewport,我们可不可以用它们,通过创建一个个绘图对象,像搭积木般来画个图呢?
北野茶缸子
2022/04/05
6250
101-R可视化29-底层绘图系统grid学习之使用grid作图
ggplot2扩展
写在最后:有时间我们会努力更新的。大家互动交流可以前去论坛,地址在下面,复制去浏览器即可访问,弥补下公众号没有留言功能的缺憾。
生信喵实验柴
2022/10/25
3870
ggplot2扩展
「R」ggplotify——连接各类R图形
在作图的时候想拼接不同的图形对象,如pheatmap 包的热图、ggplot2 对象以及 base 图形等。这里找到了Y叔的ggplotify包,以下是对 Vignette 的翻译,以帮助自己理解用法,也希望读者受益。
王诗翔呀
2020/07/06
1.5K0
「R」ggplotify——连接各类R图形
R tips:debug并修复一个ggplot2绘图错误的例子
最近将使用的R版本升级到4.0+之后,遇到了一个以前从未遇到的报错,报错信息如下所示:
生信菜鸟团
2021/10/21
2.9K0
R tips:debug并修复一个ggplot2绘图错误的例子
R中的循环绘图
上面我们将每一张图都单独输出了,下面来介绍如何将其全部组合起来,分别介绍两种R包的方法gridExtra&patchwork
R语言数据分析指南
2022/09/21
4.7K0
R中的循环绘图
高阶可视化绘图系统:ggplot2入门
ggplot2是《The Grammar of Graphics》/《图形的语法》中提出了一套图形语法,将图形元素抽象成可以自由组合的要素,类似Photoshop中的图层累加,ggplot2将指定的元素/映射关系逐层叠加,最终形成所图形。更加深入学习ggplot2,请参考《ggplot2: 数据分析与图形艺术》。
1480
2019/07/22
1.8K0
高阶可视化绘图系统:ggplot2入门
可视化绘制技巧|对多图合理排版布局
通常而言,在绘制图形的时候都是绘制某一种类型的一张图形,例如绘制一张散点图,绘制直方图。但有的时候我们希望同时展示多幅图形,可能是因为这些图形有某种联系,需要共同展示才能够更好的表达数据中蕴含的信息。之前介绍的边际图形就是这样的一个例子。本章节会介绍,当我们绘制了好了多幅图形之后,如何将多幅图形合并起来。
DataCharm
2021/02/22
2.9K0
可视化绘制技巧|对多图合理排版布局
相关推荐
「R」grid 图形对象 grobs
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档