背景:假设我正在编写一个应用程序,需要具备在各个级别进行日志记录的能力。例如,根据设置(可能是全局设置),我可以根据以下常规类别显示或写入文件日志: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)这是如何在实践中完成的?这种方法是否会对执行速度产生不利影响(我认为会这样做)?有更好的方法吗?
答案 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函数。类似的方法可以在任何使用函数作为一阶对象的语言中使用(尽管显然,代码需要针对特定语言的语法和语义进行调整)。