两个 Web 应用程序共享的 JAR 可以登录到 Tomcat 中的同一个文件吗?
Can a JAR shared by two web applications log to the same file in Tomcat?
假设我们有两个 Web 应用程序和一个 Tomcat 实例从外部目录(通过 catalina.properties
中定义的 shared.loader
加载共享 JAR(两个应用程序的依赖项) ).因此,这些依赖项未打包到 WAR 文件中。
我们也这么说:
- 两个 Web 应用程序都依赖于一个特定的共享 JAR 文件,该文件使用日志框架(目前为 log4j2,但这不是必需的)。
- 两个 Web 应用程序都使用自己的日志记录框架(我们不关心它们是否相同,只要事情按预期工作即可)和不同的日志记录配置。
我们想要实现的是共享 JAR 能够可靠地记录到同一个文件,而不管它的方法被调用的是哪个 Web 应用程序。据我们了解,两个 Web 应用程序都有不同的日志记录上下文,并且将两个这样的上下文记录到同一个文件是不可能的,或者至少是危险的。如果这不是真的或不一定是真的,请详细说明。
问题:是否可以通过单个日志上下文实现上述场景?如果是这样,您能否提供一个使用 lo4j2 或 logback 使其正常工作的示例(关键位就足够了)?有什么收获吗?
请注意,我们希望避免为此在其中一个 Web 应用程序中设置特殊的 servlet(这样另一个 Web 应用程序会调用它而不是直接记录到文件中)。使用(例如)系统日志可能是一个解决方案,但仍然请让我们将这个问题集中在所描述的场景上。
经过一些研究、反复试验,我们设法满足了我们的要求:
- 每个 Web 应用程序都记录到自己的文件中。
- 共享的 JAR 也总是记录到它自己的文件中。
至少对于 Log4j2,问题似乎与 class 加载程序有关。在我们的例子中,共享 JAR 文件中的 classes 及其所有依赖项始终使用专用于 (shared.loader
) 的 class 加载程序进行加载。这意味着 Log4j2 的 JAR 文件需要在共享 JAR 文件旁边,如果我们尝试删除它们,我们会看到 ClassNotFoundExceptions。
现在,我们可以转到 Web 应用程序:
- 如果 Web 应用程序不打包 Log4j2 供自己使用,它的记录器也会使用
shared.loader
的 class 加载程序加载(如回退),Web 应用程序的日志记录配置会覆盖之前应用的配置(在我们的例子中,我们必须调用显式初始化或重新配置,这就是原因)。它不会按预期工作。
- 如果 Web 应用程序确实打包 Log4j2 供自己使用,则使用 Web 应用程序的 class 加载器加载其记录器(因为依赖项打包在 WEB- INF 优先),这是一个与方法 #1 完全不同的 'context'(class 加载程序),并且 Web 应用程序的日志记录配置不会覆盖以前应用的配置(因为共享 JAR 和所有 Web 应用程序有自己的 'context').
这就是我们观察到的行为。在方法 #1 中,每个 Web 应用程序都覆盖了之前应用程序 initialized/started 的日志记录配置,因为共享了 Log4j2 'context'(class 加载程序)。在方法 #2 期间,共享 JAR 的 Log4j2 上下文由调用其初始化的 Web 应用程序初始化(只有其中之一),此后配置未被触及。在我们的例子中,配置文件是由 Web 应用程序提供的(它没有打包在共享 JAR 中)。请注意,在实践中,Web 应用程序之一将始终必须隐式或显式地初始化共享 JAR。
为了绝对确定,我们列出了共享 JAR 日志文件的打开文件描述符,并且使用方法 #2,确实只有一个。使用方法 #2,如果某些 Web 应用程序的配置文件也引用共享 JAR 的日志文件(配置重复),仍然可以打开多个描述符。这正是您通常希望避免的那种情况。
我们发现的陷阱:
- 请注意,
shared JAR's Log4j2 context was initialized by the web application that called its initialization (only one of them)
注释有点乏味。除非我们在每个 Web 应用程序的配置中复制或包含共享 JAR 的配置(出于上述原因,我们希望避免这种情况),否则某些日志消息可能会在其他地方结束或在 servlet 容器启动期间丢失。通常,可能无法保证 Web 应用程序启动的顺序。我可能是错的,但根据我在 Tomcat 日志中看到的内容,Tomcat 按字母顺序启动 Web 应用程序(WAR 文件)。
- 如果共享 JAR 和任何 Web 应用程序碰巧共享更多依赖项(除日志框架外),这些依赖项也必须放在
shared.loader
中。但是,如果共享 JAR 的日志记录配置将任何这些依赖项的日志消息重定向到它自己的日志中,它们就不会最终出现在 Web 应用程序的日志中,除非该依赖项也打包在 Web 应用程序中(与上面的方法 #2 相同的原理) ). 但在实践中,日志记录API和支持实现的分离使这变得困难。例如,您可以在 Whosebug 上阅读此处,SLF4J 绑定的选择相当 "random"(依赖于 JVM)。如果您将不同的绑定放入 shared.loader
和 Web 应用程序的 WEB-INF 文件夹之一,您可能不确定要选择哪个绑定。
我当然不能推荐我们正在努力实现的场景,但所描述的 "solution" 绝对可以按预期工作(尽管有陷阱)。
假设我们有两个 Web 应用程序和一个 Tomcat 实例从外部目录(通过 catalina.properties
中定义的 shared.loader
加载共享 JAR(两个应用程序的依赖项) ).因此,这些依赖项未打包到 WAR 文件中。
我们也这么说:
- 两个 Web 应用程序都依赖于一个特定的共享 JAR 文件,该文件使用日志框架(目前为 log4j2,但这不是必需的)。
- 两个 Web 应用程序都使用自己的日志记录框架(我们不关心它们是否相同,只要事情按预期工作即可)和不同的日志记录配置。
我们想要实现的是共享 JAR 能够可靠地记录到同一个文件,而不管它的方法被调用的是哪个 Web 应用程序。据我们了解,两个 Web 应用程序都有不同的日志记录上下文,并且将两个这样的上下文记录到同一个文件是不可能的,或者至少是危险的。如果这不是真的或不一定是真的,请详细说明。
问题:是否可以通过单个日志上下文实现上述场景?如果是这样,您能否提供一个使用 lo4j2 或 logback 使其正常工作的示例(关键位就足够了)?有什么收获吗?
请注意,我们希望避免为此在其中一个 Web 应用程序中设置特殊的 servlet(这样另一个 Web 应用程序会调用它而不是直接记录到文件中)。使用(例如)系统日志可能是一个解决方案,但仍然请让我们将这个问题集中在所描述的场景上。
经过一些研究、反复试验,我们设法满足了我们的要求:
- 每个 Web 应用程序都记录到自己的文件中。
- 共享的 JAR 也总是记录到它自己的文件中。
至少对于 Log4j2,问题似乎与 class 加载程序有关。在我们的例子中,共享 JAR 文件中的 classes 及其所有依赖项始终使用专用于 (shared.loader
) 的 class 加载程序进行加载。这意味着 Log4j2 的 JAR 文件需要在共享 JAR 文件旁边,如果我们尝试删除它们,我们会看到 ClassNotFoundExceptions。
现在,我们可以转到 Web 应用程序:
- 如果 Web 应用程序不打包 Log4j2 供自己使用,它的记录器也会使用
shared.loader
的 class 加载程序加载(如回退),Web 应用程序的日志记录配置会覆盖之前应用的配置(在我们的例子中,我们必须调用显式初始化或重新配置,这就是原因)。它不会按预期工作。 - 如果 Web 应用程序确实打包 Log4j2 供自己使用,则使用 Web 应用程序的 class 加载器加载其记录器(因为依赖项打包在 WEB- INF 优先),这是一个与方法 #1 完全不同的 'context'(class 加载程序),并且 Web 应用程序的日志记录配置不会覆盖以前应用的配置(因为共享 JAR 和所有 Web 应用程序有自己的 'context').
这就是我们观察到的行为。在方法 #1 中,每个 Web 应用程序都覆盖了之前应用程序 initialized/started 的日志记录配置,因为共享了 Log4j2 'context'(class 加载程序)。在方法 #2 期间,共享 JAR 的 Log4j2 上下文由调用其初始化的 Web 应用程序初始化(只有其中之一),此后配置未被触及。在我们的例子中,配置文件是由 Web 应用程序提供的(它没有打包在共享 JAR 中)。请注意,在实践中,Web 应用程序之一将始终必须隐式或显式地初始化共享 JAR。
为了绝对确定,我们列出了共享 JAR 日志文件的打开文件描述符,并且使用方法 #2,确实只有一个。使用方法 #2,如果某些 Web 应用程序的配置文件也引用共享 JAR 的日志文件(配置重复),仍然可以打开多个描述符。这正是您通常希望避免的那种情况。
我们发现的陷阱:
- 请注意,
shared JAR's Log4j2 context was initialized by the web application that called its initialization (only one of them)
注释有点乏味。除非我们在每个 Web 应用程序的配置中复制或包含共享 JAR 的配置(出于上述原因,我们希望避免这种情况),否则某些日志消息可能会在其他地方结束或在 servlet 容器启动期间丢失。通常,可能无法保证 Web 应用程序启动的顺序。我可能是错的,但根据我在 Tomcat 日志中看到的内容,Tomcat 按字母顺序启动 Web 应用程序(WAR 文件)。 - 如果共享 JAR 和任何 Web 应用程序碰巧共享更多依赖项(除日志框架外),这些依赖项也必须放在
shared.loader
中。但是,如果共享 JAR 的日志记录配置将任何这些依赖项的日志消息重定向到它自己的日志中,它们就不会最终出现在 Web 应用程序的日志中,除非该依赖项也打包在 Web 应用程序中(与上面的方法 #2 相同的原理) ). 但在实践中,日志记录API和支持实现的分离使这变得困难。例如,您可以在 Whosebug 上阅读此处,SLF4J 绑定的选择相当 "random"(依赖于 JVM)。如果您将不同的绑定放入shared.loader
和 Web 应用程序的 WEB-INF 文件夹之一,您可能不确定要选择哪个绑定。
我当然不能推荐我们正在努力实现的场景,但所描述的 "solution" 绝对可以按预期工作(尽管有陷阱)。