Java 命令行工具,详细输出标志
Java command line tool, verbose output flag
我正在开发一个基于 Java 的命令行工具。我想利用 slf4j 的日志抽象。
定期调用应用程序时
java -jar myapp.jar someparameter
然后应用程序应在 stdout
上提供 "plain" CLI 输出,例如:
/some/file1
/some/file2
相反,使用附加 --verbose
标志/选项
调用应用程序时
java -jar myapp.jar --verbose someparameter
然后应用程序应提供更高级的日志记录:
16:06:09.031 [main] DEBUG com.example.MyApp - Starting application.
16:06:09.031 [main] DEBUG com.example.MyApp - Entering directory /some/.
16:06:09.046 [main] INFO com.example.MyApp - /some/file1
16:06:09.046 [main] INFO com.example.MyApp - /some/file2
虽然很容易确定是否提供了 --verbose
(例如,通过使用 jCommander CLI 库),但 slf4j 似乎没有允许在运行时设置 root logger 级别,slf4j 也不允许在运行时更改日志条目模式布局。
- 在运行时期间(或至少在启动期间)我还剩下哪些用于修改日志级别和日志条目模式布局的选项?
- 我是否应该放弃使用 slf4j 抽象层的崇高立场,而是使用实际的底层日志记录实现(可能是 logback , slf4j2, ...)
- 其他基于 Java 的命令行工具是如何解决这一难题的?
在运行时动态更改日志记录配置不是 SLF4J 的一部分。日志记录配置特定于抽象的日志记录机制(log4j、logback 等)。
假设您在 SLF4J 下使用 Logback 库,您可以通过更改 JoranConfigurator 以编程方式重新配置所有内容。此代码片段复制自 Logback 文档网站中的 Invoking JoranConfigurator directly。您可以从 JoranConfigurator 更改日志记录模式、附加程序、日志记录级别以及关于记录器的几乎所有其他参数。
package chapters.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
public class MyApp3 {
final static Logger logger = LoggerFactory.getLogger(MyApp3.class);
public static void main(String[] args) {
// assume SLF4J is bound to logback in the current environment
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
}
我建议使用 "decorator" 模式来解决这个问题。
您将必须定义自己的 LoggerFactory,并定义方法 "getLogger"...像这样:
public class LoggerFactory {
public static Logger getLogger(String category) {
return (Boolean.parseBoolean(System.getProperty("verboseFlag"))
? org.slf4j.LoggerFactory.getLogger("verbose-" + category)
: org.slf4j.LoggerFactory.getLogger(category);
}
然后在你的配置文件中,假设你使用的是log4j,你必须配置两个类;一个带有详细前缀,另一个没有它...稍后,每个类别都必须定义自己的附加程序,并且每个附加程序都必须相应地配置...
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="VerboseConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] %m%n"/>
</layout>
</appender>
<appender name="RegularConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
<category name="verbose-org.xyz" additivity="false">
<priority value="DEBUG" />
<appender-ref ref="VerboseConsoleAppender" />
</category>
<category name="org.xyz" additivity="false">
<priority value="INFO" />
<appender-ref ref="RegularConsoleAppender" />
</category>
</log4j:configuration>
我想出了以下解决方案:
对于命令行工具的可执行Java应用端,我决定使用slf4j和登录。
我将应用程序与两个 logback 配置文件捆绑在一起,logback.xml
和 logback-verbose.xml
.
logback.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.example.some.chatty.library.i.want.to.turn.off.logging" level="OFF">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
logback-verbose.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{ISO8601} [%-17thread] %-5level %-36logger - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
在应用程序启动阶段的早期,我检查了 --verbose
标志。如果设置了该标志,我命令 logback
使用配置文件 logback-verbose.xml
而不是默认的 logback.xml
:
Application.java
:
package com.example.my;
//
public class Application {
public static void main(String[] args) throws Exception {
// reconfigure JUL for slf4j
LogManager.getLogManager().reset();
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Logger.getLogger("global").setLevel(Level.FINEST);
// about to check for verbose output request
if ( isVerboseOutputRequested() ) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure( getClass().getResourceAsStream("/logback-verbose.xml") );
}
catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
}
startApplication();
}
// ... implement isVerboseOutputRequested() and startApplication()
}
理由
此设置牢记 可执行 Java 应用程序 与该应用程序使用的库的分离。
库只能依赖于 slf4j,而 可执行的 Java 应用程序(只是一个薄包装库)依赖于 slf4j 和 logback 以便为 final 可执行文件提供实际的日志记录实现神器。
感谢 Carlitos Way 和 Tansir1[=53=] 提供导致此解决方案的答案。
我正在开发一个基于 Java 的命令行工具。我想利用 slf4j 的日志抽象。
定期调用应用程序时
java -jar myapp.jar someparameter
然后应用程序应在 stdout
上提供 "plain" CLI 输出,例如:
/some/file1
/some/file2
相反,使用附加 --verbose
标志/选项
java -jar myapp.jar --verbose someparameter
然后应用程序应提供更高级的日志记录:
16:06:09.031 [main] DEBUG com.example.MyApp - Starting application.
16:06:09.031 [main] DEBUG com.example.MyApp - Entering directory /some/.
16:06:09.046 [main] INFO com.example.MyApp - /some/file1
16:06:09.046 [main] INFO com.example.MyApp - /some/file2
虽然很容易确定是否提供了 --verbose
(例如,通过使用 jCommander CLI 库),但 slf4j 似乎没有允许在运行时设置 root logger 级别,slf4j 也不允许在运行时更改日志条目模式布局。
- 在运行时期间(或至少在启动期间)我还剩下哪些用于修改日志级别和日志条目模式布局的选项?
- 我是否应该放弃使用 slf4j 抽象层的崇高立场,而是使用实际的底层日志记录实现(可能是 logback , slf4j2, ...)
- 其他基于 Java 的命令行工具是如何解决这一难题的?
在运行时动态更改日志记录配置不是 SLF4J 的一部分。日志记录配置特定于抽象的日志记录机制(log4j、logback 等)。
假设您在 SLF4J 下使用 Logback 库,您可以通过更改 JoranConfigurator 以编程方式重新配置所有内容。此代码片段复制自 Logback 文档网站中的 Invoking JoranConfigurator directly。您可以从 JoranConfigurator 更改日志记录模式、附加程序、日志记录级别以及关于记录器的几乎所有其他参数。
package chapters.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
public class MyApp3 {
final static Logger logger = LoggerFactory.getLogger(MyApp3.class);
public static void main(String[] args) {
// assume SLF4J is bound to logback in the current environment
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure(args[0]);
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
logger.info("Entering application.");
Foo foo = new Foo();
foo.doIt();
logger.info("Exiting application.");
}
}
我建议使用 "decorator" 模式来解决这个问题。
您将必须定义自己的 LoggerFactory,并定义方法 "getLogger"...像这样:
public class LoggerFactory {
public static Logger getLogger(String category) {
return (Boolean.parseBoolean(System.getProperty("verboseFlag"))
? org.slf4j.LoggerFactory.getLogger("verbose-" + category)
: org.slf4j.LoggerFactory.getLogger(category);
}
然后在你的配置文件中,假设你使用的是log4j,你必须配置两个类;一个带有详细前缀,另一个没有它...稍后,每个类别都必须定义自己的附加程序,并且每个附加程序都必须相应地配置...
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="VerboseConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] %m%n"/>
</layout>
</appender>
<appender name="RegularConsoleAppender" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%m%n"/>
</layout>
</appender>
<category name="verbose-org.xyz" additivity="false">
<priority value="DEBUG" />
<appender-ref ref="VerboseConsoleAppender" />
</category>
<category name="org.xyz" additivity="false">
<priority value="INFO" />
<appender-ref ref="RegularConsoleAppender" />
</category>
</log4j:configuration>
我想出了以下解决方案:
对于命令行工具的可执行Java应用端,我决定使用slf4j和登录。
我将应用程序与两个 logback 配置文件捆绑在一起,logback.xml
和 logback-verbose.xml
.
logback.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.example.some.chatty.library.i.want.to.turn.off.logging" level="OFF">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
logback-verbose.xml
:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date{ISO8601} [%-17thread] %-5level %-36logger - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
在应用程序启动阶段的早期,我检查了 --verbose
标志。如果设置了该标志,我命令 logback
使用配置文件 logback-verbose.xml
而不是默认的 logback.xml
:
Application.java
:
package com.example.my;
//
public class Application {
public static void main(String[] args) throws Exception {
// reconfigure JUL for slf4j
LogManager.getLogManager().reset();
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Logger.getLogger("global").setLevel(Level.FINEST);
// about to check for verbose output request
if ( isVerboseOutputRequested() ) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
context.reset();
configurator.doConfigure( getClass().getResourceAsStream("/logback-verbose.xml") );
}
catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(context);
}
startApplication();
}
// ... implement isVerboseOutputRequested() and startApplication()
}
理由
此设置牢记 可执行 Java 应用程序 与该应用程序使用的库的分离。
库只能依赖于 slf4j,而 可执行的 Java 应用程序(只是一个薄包装库)依赖于 slf4j 和 logback 以便为 final 可执行文件提供实际的日志记录实现神器。
感谢 Carlitos Way 和 Tansir1[=53=] 提供导致此解决方案的答案。