处理各种日志记录级别

时间:2011-08-26 18:38:29

标签: algorithm debugging

背景:假设我正在编写一个应用程序,需要具备在各个级别进行日志记录的能力。例如,根据设置(可能是全局设置),我可以根据以下常规类别显示或写入文件日志:debug,warn和error。这里debug应该输出所有可以帮助开发人员调试应用程序的消息,警告可能是用户正在做的但不喜欢的事情,并且当出现问题时会出错。

问题:在编写应用程序的每个单元(功能,方法,语句等)时,确定是否应输出日志的标准做法是什么。例如,在下面的伪代码中,我是在正确的道路上还是有其他方法可以做到这一点?

myfunction(params)
    do something
    // need to log for debugging purposes
    can_I_log('debug')
    if I_can_log
        print debug message
    do something more
    // need to log for debugging purposes
    can_I_log('debug')
    if I_can_log
        print debug message
    do something more
    // something went wrong
    can_I_log('error')
    if I_can_log
        print error message

基本上,在每个重要语句或代码块之前/之后,我正在检查全局日志设置是否允许我记录此部分(通过使用can_I_log()函数/方法)。例如,如果全局设置为'warn'并且我使用can_I_log('debug'),则I_can_log将为false(其中error> warn> debug)这是如何在实践中完成的?这种方法是否会对执行速度产生不利影响(我认为会这样做)?有更好的方法吗?

2 个答案:

答案 0 :(得分:1)

一个问题: 为什么要保留这些global debugging states?如果某个事件发生,那么它可能只是您要记录的事件,或者您要记录的警告,或者您要记录的严重错误。

这是我的看法。

不是每次都检查,如果发生某些事件,你应该记录它但是有不同的级别:

例如:

文件复制失败。您可以将其视为warning(如果您仍然可以在没有此文件的情况下继续)或error(如果没有该文件,此代码后面的功能无效)。

在第一种情况 - 警告 - 你会这样做:

log_message("file copy failed", WARN);

在第二种情况 - 错误 - 你会这样做:

log_message("file copy failed", ERR);

然后假设您的日志文件是/ var / logs,它可能如下所示:

在第一种情况下:

  

警告文件复制失败。

在第二种情况下:

  

错误文件复制失败。

所以你的log_message()函数将完成这项工作。

PS:您可能想看看syslog是如何做到的,并使用它来重新发明轮子。

答案 1 :(得分:1)

<强>重组

老实说,您要做的主要更改是“if”语句发生的更改。您实际上是在程序中反复重复相同的代码。这绝不是一个好主意。

相反,我强烈建议使用日志功能,它接收消息和级别,然后根据全局设置记录或不记录。这样可以减少错误,并且更容易阅读生成的代码,因为您的日志消息不会再占用4行。

所以,相反,我会把它看起来像这样:

function log(String mess, Enum level) {
    if (globalLevel >= level) {
        print level mess
    }
}

myfunction(params) {
    do something
    // need to log for debugging purposes
    log (message, debug)
    do something more
    // need to log for debugging purposes
    log (message, debug)
    do something more
    // something went wrong
    log (message, error)
}

现在您的代码更短更清晰。如果在将来某个时候,您决定开始将日志存储在文件或数据库中,那么您需要更改一个打印语句,而不是一百万个。

避免使用

此外,根据您的语言,您甚至可以完全避免使用日志功能中的if语句。为此,您需要创建三个日志记录函数,然后适当地使用继承,lambda函数或宏。这样做的缺点是,改变日志记录级别需要编写一些额外的代码,而不仅仅是更改常量。

在下面的示例中,我假设您有三个函数:log_debug,log_warn,log_error,您的代码如下所示:    myfunction(params){         做一点事         //需要登录以进行调试         log_debug(消息)         做更多的事情         //需要登录以进行调试         log_debug(消息)         做更多的事情         // 有些不对劲         log_error(消息)     }

因此,在C中,例如,如果在编译时已知调试级别,则可以按如下方式使用编译指示:

#if __DEBUG__
  void log_debug(char* c) {
    printf("debug: %s\n",c);
  }
  void log_warn(char* c) {
    printf("warn: %s\n",c);
  }
#endif
#if __WARN__
  void log_debug(char* c) { }
  void log_warn(char* c) {
    printf("warn: %s\n",c);
  }
#endif
#if __ERROR__
  void log_debug(char* c) { }
  void log_warn(char* c) { }
#endif

void log_error(char* c) { 
    printf("error: %s\n",c);
}

这样做会为您提供一组函数,这些函数可以使用no语句记录或不记录。如果在编译时不知道,在运行时使用函数指针可以完成类似的事情(类似于本答案后面出现的Ocaml示例)。

在Java中,您可以使用继承来执行以下操作:

interface ILogger {
    public static final int DEBUG = 3;
    public static final int WARN = 2;
    public static final int ERROR = 1;

    public void logError(String message);
    public void logWarn(String message);
    public void logDebug(String message);

}

class Logger {
    public static ILogger getLogger(int level) {
        if (level == DEBUG) { return new DebugLogger; }
        if (level == WARN) { return new WarnLogger; }
        if (level == ERROR) { return new ErrorLogger; }
        return null;
    }
    public static ILogger logger = null;
}

class DebugLogger implements ILogger {
    public void logError(String message) {
        System.err.println("Error: "+message);
    }
    public void logWarn(String message) {
        System.err.println("Warn: "+message);
    }
    public void logDebug(String message) {
        System.err.println("Debug: "+message);
    }
}

class WarnLogger implements ILogger {
    public void logError(String message) {
    }
    public void logWarn(String message) {
        System.err.println("Warn: "+message);
    }
    public void logDebug(String message) {
        System.err.println("Debug: "+message);
    }
}

class ErrorLogger implements ILogger {
    public void log_error(String message) {
    }
    public void log_warn(String message) {
    }
    public void log_debug(String message) {
        System.err.println("Debug: "+message);
    }
}

然后在代码的开头,你有一行:

Logger.logger = Logger.getLogger(ILogger.WARN);

将设置正在使用的记录器并记录您的呼叫

Logger.logger.logDebug("Something happened!");

等等。基本上,我们正在做的是预先确定将调用哪些方法集,然后引用具有存储在全局变量中的正确方法的类。

在Ocaml中,我们可以使用函数作为一阶对象,为正确的名称分配适当的函数。代码将编写如下代码:

let log_level = "WARN"

let log type message = (print_string (type ^ ": " ^ message); print_newline ())
let dontlog message = ()
let log_error = log "Error"
let log_warn = if (log_level = "WARN" || log_level = "DEBUG") then (log "WARN") else dontlog
let log_debug = if (log_level = "DEBUG") then (log "DEBUG") else dontlog

这恰好为适当的名称分配了一次适当的函数,然后我们只需按名称调用日志函数。虽然乍一看它可能看起来像是log_warn和log_debug中的if语句,但它们是函数赋值而不是函数定义,所以if语句只发生在程序执行let语句而不是实际调用函数时。因此,当调用log_debug时,将调用日志“DEBUG”或dontlog函数。类似的方法可以在任何使用函数作为一阶对象的语言中使用(尽管显然,代码需要针对特定​​语言的语法和语义进行调整)。