日志记录和配置系统:循环依赖
Logging and Configuration Systems: Circular Dependency
假设一个典型场景,应用程序的初始设置在启动时从配置文件加载。该应用程序还有一个记录器。记录器的参数(例如详细级别、日志文件路径等)理想情况下也存储在配置文件中。但是,也希望配置加载器记录有关参数加载的信息,例如
Attempting to load parameter LogVerbosity. Parameter is absent. Using the default value: 4.
因此,我们看到了两个组件之间的循环依赖。配置加载器需要一个随时可用的记录器,而记录器需要配置参数来初始化自己。在我看来,这似乎是一个典型的问题,足以对它有规范的解决方案,但我找不到任何东西。
当然,可以推出一些临时解决方案,但我对最佳行业实践很感兴趣。
希望问题有道理。
我假设您想要记录参数以便以后能够调查它们,例如在出现错误的情况下。很明显,只有将它们保存在文件或其他存储中才有可能。这意味着记录器 必须 正确配置才能这样做。所以基本问题是:
我们可以假设记录器至少能够记录启动参数吗?
是
这个案例很简单。我只是:
- 读取记录器的配置参数。
- 初始化记录器。
- 记录器的日志参数。
- 读取其他参数。
- 记录其他参数。
否
这个案例比较复杂。我认为一个好的解决方案是为记录器使用一组默认的、硬编码的参数。它将保证可以安全地记录启动参数:
- 使用默认(硬编码)参数初始化记录器。
- 读取配置参数。
- 日志参数。
- 根据配置文件中的参数初始化记录器。
- 再次尝试记录参数。
如果记录器配置正确,您将能够在标准日志位置找到所有参数。如果配置错误,您仍然可以在默认(硬编码)位置找到它们。
或者,您可以使用 fstream
...[=11 等标准机制将启动参数简单地写入文件(在应用程序的工作目录中),而不是在步骤 1 中使用记录器=]
补充评论
一些日志库,如 nlog 可以配置(throwExceptions = true)以在配置错误的情况下引发错误。操作系统应该记录未处理的异常。
如果您使用此类日志库,您可以打开此选项并使用上面的解决方案 1。如果应用程序启动,那么您将在日志中找到启动参数。如果没有启动,您应该检查系统日志。
我会说规范的解决方案是:不要混用日志记录和配置!
想象一下,配置文件解析触发了一个错误(但你还不知道!)。您更改配置文件以启用日志记录。现在解析不会触发错误,一切看起来都正常,直到您再次禁用日志记录...
所以:更一般地说,从原则上讲,人们希望最大限度地减少调试基础设施(包括日志记录)对程序造成的干扰;在理想情况下,调试功能将与应用程序所做的任何事情正交,以避免导致heisenbugs。将普通配置与日志配置混合已经违反了这一原则。
而且根据经验,您可以看到著名的日志记录库是如何工作的:它们都有自己的配置文件,独立于应用程序中正在进行的任何配置,因此您可以更改日志记录行为而不会弄乱应用程序本身。因此,您可以重复一个有问题的 运行 应用程序,仅更改其日志记录行为,但愿别无其他。这就是 Log4j 及其端口系列或已经提到的 nlog 的工作方式。
假设一个典型场景,应用程序的初始设置在启动时从配置文件加载。该应用程序还有一个记录器。记录器的参数(例如详细级别、日志文件路径等)理想情况下也存储在配置文件中。但是,也希望配置加载器记录有关参数加载的信息,例如
Attempting to load parameter LogVerbosity. Parameter is absent. Using the default value: 4.
因此,我们看到了两个组件之间的循环依赖。配置加载器需要一个随时可用的记录器,而记录器需要配置参数来初始化自己。在我看来,这似乎是一个典型的问题,足以对它有规范的解决方案,但我找不到任何东西。
当然,可以推出一些临时解决方案,但我对最佳行业实践很感兴趣。
希望问题有道理。
我假设您想要记录参数以便以后能够调查它们,例如在出现错误的情况下。很明显,只有将它们保存在文件或其他存储中才有可能。这意味着记录器 必须 正确配置才能这样做。所以基本问题是:
我们可以假设记录器至少能够记录启动参数吗?
是
这个案例很简单。我只是:
- 读取记录器的配置参数。
- 初始化记录器。
- 记录器的日志参数。
- 读取其他参数。
- 记录其他参数。
否
这个案例比较复杂。我认为一个好的解决方案是为记录器使用一组默认的、硬编码的参数。它将保证可以安全地记录启动参数:
- 使用默认(硬编码)参数初始化记录器。
- 读取配置参数。
- 日志参数。
- 根据配置文件中的参数初始化记录器。
- 再次尝试记录参数。
如果记录器配置正确,您将能够在标准日志位置找到所有参数。如果配置错误,您仍然可以在默认(硬编码)位置找到它们。
或者,您可以使用 fstream
...[=11 等标准机制将启动参数简单地写入文件(在应用程序的工作目录中),而不是在步骤 1 中使用记录器=]
补充评论
一些日志库,如 nlog 可以配置(throwExceptions = true)以在配置错误的情况下引发错误。操作系统应该记录未处理的异常。
如果您使用此类日志库,您可以打开此选项并使用上面的解决方案 1。如果应用程序启动,那么您将在日志中找到启动参数。如果没有启动,您应该检查系统日志。
我会说规范的解决方案是:不要混用日志记录和配置!
想象一下,配置文件解析触发了一个错误(但你还不知道!)。您更改配置文件以启用日志记录。现在解析不会触发错误,一切看起来都正常,直到您再次禁用日志记录...
所以:更一般地说,从原则上讲,人们希望最大限度地减少调试基础设施(包括日志记录)对程序造成的干扰;在理想情况下,调试功能将与应用程序所做的任何事情正交,以避免导致heisenbugs。将普通配置与日志配置混合已经违反了这一原则。
而且根据经验,您可以看到著名的日志记录库是如何工作的:它们都有自己的配置文件,独立于应用程序中正在进行的任何配置,因此您可以更改日志记录行为而不会弄乱应用程序本身。因此,您可以重复一个有问题的 运行 应用程序,仅更改其日志记录行为,但愿别无其他。这就是 Log4j 及其端口系列或已经提到的 nlog 的工作方式。