22

我们应该什么时候登录?在函数调用之前(示例 A)还是在目标方法的开头(示例 B)?

请注意,这个问题是关于确切的记录器函数调用位置,而不是一般的最佳记录实践。

解决方案 A:记录函数调用

function someProcess() {
    log.info("Reading data");
    readDataFromIO();
    log.info("Outputing data");
    outputDataToScreen();
}

// ... other module:

function readDataFromIO() {
    ...
}

function outputDataToScreen() {
    ... 
}

解决方案 B:在方法开始时记录

function someProcess() {
    readDataFromIO();
    outputDataToScreen();
}

// ... other module:

function readDataFromIO() {
    log.info("Reading data");
    ...
}

function outputDataToScreen() {
    log.info("Outputing data");
    ... 
}

在解决方案 A 中,当效率问题出现时,您可以自定义消息或退出日志记录,但是如果日志消息看起来相同,您可能会忘记记录并且您有很多重复的代码。在解决方案 B 中,没有忘记日志记录和代码重复的风险,但是您不能 100% 关闭日志记录逻辑,并且如果方法调用中出现错误(例如空指针异常),您将遇到麻烦。哪个是最佳实践?

4

7 回答 7

16

当然,最佳实践是将日志记录放在您需要的地方。:-) 但是在您的示例中,最佳做法是根本不使用日志记录。

TRACE尽管在许多日志框架中都存在一个级别,但日志通常不利于跟踪程序流(这似乎是您正在尝试做的事情) 。日志的最佳用途是记录流经系统的数据,尤其是引起问题的数据

记录数据时,应将数据记录在可以最好地解释的上下文中。这通常是

  • 在函数的开头(记录输入),
  • 在函数结束时(记录输出或计时),或
  • 检测到错误的地方。

要找出致命错误发生的位置,您应该有一个错误处理程序,该处理程序被告知致命错误并记录显示错误发生位置的堆栈跟踪。(最现代的语言默认执行此操作,而较旧的语言现在有启用此功能的方法。)您不应尝试记录执行路径以尝试定位问题。

于 2013-04-27T23:11:37.483 回答
10

我在这里给出的做法不是来自任何来源,而是我使用并发现在多年使用中最有效的做法。

登录方法

方法是具有特定目的的代码块。将每个方法的日志记录在方法本身中。这样,当您从其他地方重新使用该方法时,您将不必在每个地方添加记录器。如果该方法恰好是从许多地方调用的 util,请降低该前缀的记录器级别或记录器优先级。

使用 MDC/请求 id/线程名称

要跟踪请求的流程或调用的来源,请在记录器中设置参数或使用线程名称,以便所有后续日志都具有标签并遵循该标签的日志。通常,最好在收到请求后立即在 logger 中设置标签。

避免重复记录

在代码中的某个逻辑阶段捕获异常并记录。例如 - 在具有以下堆栈 Action/JSP/Web 服务等的 Web 应用程序中 -> 模块调用 -> 帮助模块 -> Util -> 库。

在这里,我将在我的模块调用级别登录(对应于代码中的someProcess())。任何内部调用都将是放在方法内的调试级别调用。或者,您可以使用更高的日志过滤器记录辅助模块等。

记录是一个非常主观的事情,它更多地与决定一种方式并在任何地方都坚持下去有关。没有一种适合所有解决方案的尺寸。您需要通过随着时间的推移一点一点地调整参数来确定您正在使用的记录器中的详细程度、性能与信噪比。

于 2013-04-28T20:28:19.727 回答
1

以下是我可以根据我自己的经验给你的提示(因为你已经标记了这个主题与语言无关,我会保持它的通用性):

  • 使用委托函数(或使用日志函数创建中央日志类)来记录消息。参数应包含日志消息、日志严重性和日志级别。应该在需要记录的任何地方调用此委托函数(或日志类中的函数)。它允许您轻松替换日志机制,例如从基于文件的日志更改为 Windows 事件日志。在此功能中,您还可以处理日志级别,即使其可配置以抑制警告等。
  • 添加一个 try-catch以someProcess()允许捕获运行时错误
  • 当你开始某事时记录,当你结束某事时,总是记录可能发生的错误。这些日志调用可以在函数内部本地进行,如示例 B 所示。
  • 如果您使用的语言支持这一点,请编写一个函数/方法来记录异常:例如,在C#中,您可以为异常编写一个扩展方法 public static void Log(this Exception ex) { // ... logging code ... },然后您可以在每个 catch 块中简单地调用它,例如:try { ... } catch (Exception ex) { ex.Log(); }
    它可以给它添加一个可选参数也很有用public static void Log(this Exception ex, string message="") { ... logging code ... },这样你就可以向它传递额外的信息ex.Log("readDataFromIO() - read error occurred");

这样可以避免您在问题中提到的缺点(重复代码,未捕获错误)。

如果可能的话,看看你正在使用的框架或语言是否已经有这样一个类(通常是这种情况)并使用它或开发你自己的使用它的框架(即你自己的集中式日志记录类)而不是重新发明轮子完全。

于 2013-04-22T11:45:30.067 回答
1

最佳实践:

  • 使用某种 Logger 类/模块(取决于语言),允许您在不同级别(DEBUG、INFO 等)进行日志记录
  • 在 LogLevel DEBUG 中记录非常基本的功能
  • 在 LogLevel INFO 中记录更复杂的函数(例如调用其他基本函数的函数)
  • 使用 LogLevel WARN 记录潜在问题
  • 使用 LogLevel ERROR 记录错误和异常

单独的日志记录功能允许您根据效率打开/关闭日志记录。但你也有能力记录一切。

示例:在 Java 中,log4j 提供了出色的自定义选项。您可以定义 logLevel,并且可以定义哪些类应该打开日志记录。这样,您可以在基本级别 (WARN) 监视系统,如果发生错误,您可以为需要检查的某些类设置 DEBUG 级别。

这个过程很大程度上取决于您使用的语言(当然),但我认为这种“log4j”方法是一种非常好的方法。

于 2013-04-22T11:02:42.553 回答
1

除了之前所说的,我使用了一个有点笼统的日志记录概念。它可以帮助我发现某些情况确实比预期的更频繁或更少出现的情况。

我正在使用一个类LogEvent(代码是Java,但想法可以移植到大多数语言):

public class LogEvent {
    private int count;
    private String name;

    public LogEvent(String name) {
        this.name = name;
        count = 1;
    }

    public int count() {
        return this.count;
    }

    public void inc() {
        count++;
    }

    public String name() {
        return this.name;
    }

    public void report() {
        if (count >= 1) {
            Util.info(name + " (x " + count + ")");
        }
        else {
            Util.info(name);
        }
    }
}

如何引发事件并收集它们的发生?

LogEvents可以使用“注册” LogEventCollection

import java.util.Map;
import java.util.TreeMap;

public class LogEventCollection {
    private Map<String, LogEvent> events;

    public EventCollection() {
        events = new TreeMap<String, Event>();
    }

    public void register(String name) {
        LogEvent ev;

        if (events.containsKey(name)) {
            ev = events.get(name);

            ev.inc();
        }
        else {
            ev = new LogEvent(name);

            events.put(name, ev);
        }
    }

    public void report(String title, int minCount) {
        Util.info("");

        if (!title.isEmpty()) {
            Util.info(title);
        }

        for (LogEvent ev : events.values()) {       
            if ((minCount < 0) || (ev.count() >= minCount)) {
                ev.report();
            }
        }
        Util.info("");
    }
}

为了大致了解我的程序的内部事件,该LogEventCollection方法report()给出了所有事件LogEvents及其各自计数的列表。该列表可以按名称或事件频率排序。

多线程应用程序需要额外的锁定代码或线程安全集合,以防止在并发访问 LogEventCollection 期间发生冲突。

显然,这种方法可以通过添加条件事件来扩展(= 调用register()受某些条件保护)。或者可以在生产运行期间禁用事件记录。

于 2013-04-27T20:30:47.370 回答
0

最佳做法是将调用 log() 放在函数中。每次调用该函数时,您都会看到一个跟踪,而不仅仅是从“someProcess”调用它时。

于 2013-04-28T12:46:24.090 回答
-1

最简单的解决方案是重载您的方法,这样您就可以指定何时“记录”以及何时不“记录”。

function someProcess() {
    readDataFromIO(true);  //This will make a "log"
    outputDataToScreen();  //This will make no "log"
}

// ... other module:

function readDataFromIO() {
    readDataFromIO(false); 
}

function readDataFromIO(bool makeLog) {
    if(makeLog)
        log.info("Reading data");
    ...
}

function outputDataToScreen() {
    outputDataToScreen(false);
}

function outputDataToScreen(bool makeLog) {
    if(makeLog)
        log.info("Outputing data");
    ... 
}
于 2013-04-29T10:44:28.020 回答