编写日志的正确姿势

一般来说,对于何时写日志并没有明确的限制和约束,只要你觉得记录的日志是有价值的,对跟踪bug是有帮助的,你就可以去添加日志。当然一些敏感信息除外,比如你正在开发一套支付系统,不要把客户的卡号和密码等信息记录在日志中,因为日志并不会被刻意保护,有可能被其他的用户群体收集到。
另外不要担心大量的日志会对服务器造成压力,一般来说在产品环境都会采用消息队列配合搜索引擎的方式存储日志,通过定义准确的日志级别也会减少生产环境的日志数量。

一、如何记录日志

以log4net为例,在想要添加日志的任何地方使用下列的方法添加日志:

private readonly ILog log = log4net.LogManager.GetLogger(typeof(T));
log.Info("message");

二、选择准确的日志级别

一般来说日志组件都会有Debug,Info,Warn,Error,Fatal五种日志级别,其中Fatal用来处理Unhandled exception,因此对开发者而言在开发过程中只有四种可用的日志级别:

  • Debug 用来记录一些用于有利于调试程序的信息和系统状态,一般来说这种级别的日志只会出现在开发环境
  • Info 跟Debug类似,区别在于这种级别的日志可以上生产环境
  • Warn 警告类日志,比如第三方请求返回了失败的信息,但是开发者知道如何处理这种信息, 不会造成系统奔溃
  • Error 一般可以用来记录一些异常信息,比如配置文件丢失等

三、选择真确的方法重载

以log.Error()举例:

  • 使用log.Error(string message)的时机

    if (string.IsNullOrEmpty(userName))
    {
    var message = "user name is empty";
    logger.Error(message);
    
     throw new UserNameEmptryException();
    }

    上面的使用方式是显而易见的,业务代码发现用户名为空,记录日志同时抛出异常,上面的场景不能使用下面的方式记录日志:

    logger.Error(new Exception(message));

    选择错误的重载会导致日志调用堆栈丢失,日志变得不再完整,最终会在查找日志的时候浪费时间。

  • 使用log.Error(Exeception exception)的时机

在捕获到异常的时候使用此重载:

try
{
    var response = AlipayHandler.Pay();
}
catch (AlipayRequestException e)
{
    logger.Error(e);
    throw;
}
  • 使用log.Error(string message, Exception exception)的时机

上面的场景也符合这个重载,区别在于你不但想记录异常信息,还想添加自定义的信息。

四、设计全局的Unhandler exception handler

一般来说使用不同的框架,添加Unhandler exception handler的方式也不同。

  • 对于ASP.NET MVC 项目,标准的方式是创建自定义ExceptionFilter:

    public class UnhandledExceptionFilterAttribute : ActionFilterAttribute
    {
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var logger = LoggerFactory.GetLogger(typeof(UnhandledExceptionFilterAttribute));
        var exception = filterContext.Exception;
        logger.Fatal("Unhandled exception", exception);
        base.OnActionExecuting(filterContext);
    }
    }
  • 对于Winform则需要用另一种方式:
    AppDomain.CurrentDomain.UnhandledException +=
                        (sender, e) =>
                        {
                            logger.Fatal("Unhandled exception", exception);;
                        };
  • 对于asp.net core可以使用NLog来记录unhandled exception

总之,开发不同类型的应用程序都有相对应的方法来处理unhandled exception,使用时搜索最佳实践即可。

五、错误的日志记录方式

private void RequestAlipay()
{
    try
    {
        var response = httpClient.Request(url);
    }
    catch (Exception e)
    {
        logger.Error(e);
    }
}

上面记录日志的方式犯了两个错误:

  1. 不要捕获通用的异常, 记住:只捕获你知道如何处理的异常,如果你不知道如何处理他,请不要捕获他。Why catch(Exception)/empty catch is bad
    在上面的场景中,你可能会担心由于网络原因或者请求不合理等原因会导致整个请求失败,你想在此时记录日志,因此你的意图是处理HttpRequestException而不是Exception。因此正确的写法如下:
private void RequestAlipay()
{
    try
    {
        var response = httpClient.Request(url);
    }
    catch (HttpRequestException e)
    {
        logger.Error(e);
    }
}

你并不知道如何处理其他类型的异常,因此你不应该捕获其他类型的异常。

  1. 上面的Try...Catch会吞掉异常并吞掉bug,同时还有可能会将本应该在开发阶段就能发现的问题推迟到了产品环境。
    一般来说你之所以要捕获异常是应为你知道如何处理该异常同时还知道一个恢复的方案Swallowing exceptions is hazardous to your health
    这段代码的意图是向第三方请求资源,如果真的由于某些原因导致请求失败,只写日志是不够的,你的应用程序还是可能会由此崩掉。但是开发人员想要查到事情的真相不再容易,因为你静悄悄的吞掉了这个异常,看起来好像什么事情都没有发生一样。如果在此时你并不知道如何处理这种异常,你应该把异常抛出去,让上层调用者做决定。
private void RequestAlipay()
{
    try
    {
        var response = httpClient.Request(url);
    }
    catch (HttpRequestException e)
    {
        logger.Error(e);
        throw;
    }
}

注意: 不要通过下面的方式抛出异常,因为这样会丢掉调用堆栈Why catch and rethrow an exception in C#?

throw ex;

六、一个正确记录日志的例子

比如在一个ProductSellingService中:

public void Sell(string itemId)
{
    Product product;
    try
    {
        car = GetProducts().Single(x => x.ItemId.Contains(itemId));
    }
    catch (InvalidOperationException)
    {
        log.Error($"product {itemId} has been sold out.")
        throw new ProductsHasBeenSoldOutException();
    }
}

当用户要购买的产品已经不存在时,除了记录一条日志,还会抛出一个ProductsHasBeenSoldOutException异常。因为作为ProductSellingService并不知道如何处理这种异常,所以需要上层的调用者做决策。
在ProductSellingController中使用了ProductSellingService:

try
{
    ProductSellingService.Sell(id);
    var response = Request.CreateResponse(HttpStatusCode.OK, "successful");
}
catch (ProductsHasBeenSoldOutException e)
{
    var response = Request.CreateResponse(HttpStatusCode.NotFound,
"product is been sold out");
    return response;
}

ProductSellingController知道如何处理ProductsHasBeenSoldOutException,最终返回给用户一个错误消息。

准确的使用日志可以方便bug追踪,数据分析,达到事半功倍的效果,相反,错误的日志使用方式只会让开发者在调查原因的过程中浪费时间,降低效率。本文将重点描述日志的代码设计部分,在正式环境还要考虑如何通过消息队列和搜索引擎存储海量日志,以及日志监控等解决方案的设计,敬请期待后续文章。

原文地址:https://www.cnblogs.com/xiandnc/p/9175088.html

时间: 2024-08-30 12:59:33

编写日志的正确姿势的相关文章

在Java项目中打印错误日志的正确姿势

在程序中打错误日志的主要目标是为更好地排查问题和解决问题提供重要线索和指导.但是在实际中打的错误日志内容和格式变化多样,错误提示上可能残缺不全.没有相关背景.不明其义,使得排查解决问题成为非常不方便或者耗时的操作. 而实际上,如果编程的时候稍加用心,就会减少排查问题的很多无用功.在阐述如何编写有效的错误日志之前,了解错误是怎么产生的, 非常重要. 错误是如何炼成的 对于当前系统来说, 错误的产生由三个地方引入: 1.上层系统引入的非法参数.对于非法参数引入的错误, 可以通过参数校验和前置条件校验

开发函数计算的正确姿势 —— 使用 Fun Local 本地运行与调试

前言首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传.函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费.函数计算更多信息 参考. Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算.API 网关.日志服务等资源.它通过一个资源配置文件(template.yml),

开发函数计算的正确姿势 —— 爬虫

在 <函数计算本地运行与调试 - Fun Local 基本用法> 中,我们介绍了利用 Fun Local 本地运行.调试函数的方法.但如果仅仅这样简单的介绍,并不能展现 Fun Local 对函数计算开发的巨大效率的提升. 这一次,我们拿一个简单的场景来举例子--开发一个简单的爬虫函数(代码参考函数计算控制台模板),介绍如何以正确姿势,从零开始,开发一个自动伸缩.按调用次数收费的 serverless 爬虫应用. 开发步骤我们将这个完整的应用拆分成多步,并且在每一步完成后,我们都会进行相应的运

开发函数计算的正确姿势——轻松解决大依赖部署

<a name="1"></a> 前言 首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传.函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费.函数计算更多信息 参考.Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算.API 网关.

使用layoutinflater的正确姿势

使用layoutinflater的正确姿势 一开始接触安卓开发的时候,知道layoutinflater是用来将布局文件生成对应的View.那时候还是懵懵懂懂知道需要传递一个layoutId一个parent参数和一个false参数.那时候就这样用,初初还是好好的.直到后来随着进一步学习安卓开发发现layoutinflater的这两个参数是有大大的门道在里面. 然后这一篇博客可以说是我对layoutinflater使用的一个总结. 怎么添加一个View到ViewGroup? 在讨论怎么使用layou

Dagger2 使用正确姿势。

Dagger2 使用正确姿势. 上一篇文章<Dagger2 这次入门就不用放弃了>中介绍了Dagger2的一些显浅的使用方式,我觉得是非常适合入门Dagger2的傻瓜式讲解,后来发现有些内容讲的不够仔细,有些细节没有详细解释清楚.参考了以下三篇文章后,对之前的内容进行一些补充. Android:dagger2让你爱不释手-基础依赖注入框架篇 Android:dagger2让你爱不释手-重点概念讲解.融合篇 Android:dagger2让你爱不释手-终结篇 以上这三篇文章对于Dagger2的思

Git 提交的正确姿势

Git 提交的正确姿势:Commit message 编写指南 SCOP范围 middleware core config plugin test type范围 Git 每次提交代码,都要写 Commit message(提交说明),否则就不允许提交. $ git commit -m "hello world" 上面代码的-m参数,就是用来指定 commit mesage 的. 如果一行不够,可以只执行git commit,就会跳出文本编译器,让你写多行. $ git commit 基

揭秘“撩”大数据的正确姿势:生动示例解说大数据“三驾马车”

我是我:"缘起于美丽,相识于邂逅,厮守到白头!" 众听众:"呃,难道今天是要分享如何作诗?!" 我是我:"大家不要误会,今天主要的分享不是如何作诗,而是<揭秘:'撩'大数据的正确姿势>,下面进入正题." 话说当下技术圈的朋友,一起聚个会聊个天,如果不会点大数据的知识,感觉都融入不了圈子,为了以后聚会时让你有聊有料,接下来就跟随我的讲述,一起与大数据混个脸熟吧,不过在"撩"大数据之前,还是先揭秘一下研发这些年我们都经

程序员取悦女朋友的正确姿势---Tips(iOS美容篇)

前言 女孩子都喜欢用美图工具进行图片美容,近来无事时,特意为某人写了个自定义图片滤镜生成器,安装到手机即可完成自定义滤镜渲染照片.app独一无二,虽简亦繁. JH定律:魔镜:最漂亮的女人是你老婆魔镜:程序员不是木头人 核心技术 图片滤镜核心技术的基本思路如下: 核心技术流程 具体流程 1.创建一个图像处理工具类 注:该类实例包括一个图像处理方法,该方法在传入原始图像和一个颜色矩阵后生成一个处理好的图像. @interface JHFeilterManager : NSObject @proper