編寫日誌的正確姿式

通常來講,對於什麼時候寫日誌並無明確的限制和約束,只要你以爲記錄的日誌是有價值的,對跟蹤bug是有幫助的,你就能夠去添加日誌。固然一些敏感信息除外,好比你正在開發一套支付系統,不要把客戶的卡號和密碼等信息記錄在日誌中,由於日誌並不會被刻意保護,有可能被其餘的用戶羣體收集到。
另外不要擔憂大量的日誌會對服務器形成壓力,通常來講在產品環境都會採用消息隊列配合搜索引擎的方式存儲日誌,經過定義準確的日誌級別也會減小生產環境的日誌數量。java

1、如何記錄日誌

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

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

2、選擇準確的日誌級別

通常來講日誌組件都會有Debug,Info,Warn,Error,Fatal五種日誌級別,其中Fatal用來處理Unhandled exception,所以對開發者而言在開發過程當中只有四種可用的日誌級別:github

  • Debug 用來記錄一些用於有利於調試程序的信息和系統狀態,通常來講這種級別的日誌只會出如今開發環境
  • Info 跟Debug相似,區別在於這種級別的日誌能夠上生產環境
  • Warn 警告類日誌,好比第三方請求返回了失敗的信息,可是開發者知道如何處理這種信息, 不會形成系統奔潰
  • Error 通常能夠用來記錄一些異常信息,好比配置文件丟失等

3、選擇正確的方法重載

以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)的時機

在捕獲到異常的時候使用此重載:asp.net

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

上面的場景也符合這個重載,區別在於你不但想記錄異常信息,還想添加自定義的信息。ide

4、設計全局的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);;
                        };

總之,開發不一樣類型的應用程序都有相對應的方法來處理unhandled exception,使用時搜索最佳實踐便可。url

5、錯誤的日誌記錄方式

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;

6、一個正確記錄日誌的例子

好比在一個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追蹤,數據分析,達到事半功倍的效果,相反,錯誤的日誌使用方式只會讓開發者在調查緣由的過程當中浪費時間,下降效率。本文將重點描述日誌的代碼設計部分,在正式環境還要考慮如何經過消息隊列和搜索引擎存儲海量日誌,以及日誌監控等解決方案的設計,敬請期待後續文章。

相關文章
相關標籤/搜索