Java 带有可选参数的日志记录
我有一个方法,我想在其中添加特定的日志记录:Java 带有可选参数的日志记录,java,log4j,log4j2,logback,slf4j,Java,Log4j,Log4j2,Logback,Slf4j,我有一个方法,我想在其中添加特定的日志记录: @Slf4j @Service public class SomethingService { public void doSomething(Something data, String comment, Integer limit) { Long id = saveSomethingToDatabase(data, comment); boolean sentNotification = doSometh
@Slf4j
@Service
public class SomethingService {
public void doSomething(Something data, String comment, Integer limit) {
Long id = saveSomethingToDatabase(data, comment);
boolean sentNotification = doSomething(id);
// ...
// Log what you done.
// Variables that always have important data: data.getName(), id
// Variables that are optional: sentNotification, comment, limit
// (optional means they aren't mandatory, rarely contains essential data, often null, false or empty string).
}
}
我可以简单地记录所有:
log.info("Done something '{}' and saved (id {}, sentNotification={}) with comment '{}' and limit {}",
something.getName(), id, sentNotification, comment, limit);
// Done something 'Name of data' and saved (id 23, sentNotification=true) with comment 'Comment about something' and limit 2
但大多数时候,大多数参数都是无关的。通过以上步骤,我得到如下日志:
// Done something 'Name of data' and saved (id 23, sentNotification=false) with comment 'null' and limit null
这使得日志很难阅读,很长,而且不必要地复杂(在大多数情况下,其他参数不存在)
我想处理所有情况,只保留基本数据。示例:
// Done something 'Name of data' and saved (id 23)
// Done something 'Name of data' and saved (id 23) with comment 'Comment about something'
// Done something 'Name of data' and saved (id 23) with limit 2
// Done something 'Name of data' and saved (id 23) with comment 'Comment about something' and limit 2
// Done something 'Name of data' and saved (id 23, sent notification)
// Done something 'Name of data' and saved (id 23, sent notification) with limit 2
// Done something 'Name of data' and saved (id 23, sent notification) with comment 'Comment about something'
// Done something 'Name of data' and saved (id 23, sent notification) with comment 'Comment about something' and limit 2
我可以手工编写代码:
String notificationMessage = sentNotification ? ", sent notification" : "";
String commentMessage = comment != null ? String.format(" with comment '%s'", comment) : "";
String limitMessage = "";
if (limit != null) {
limitMessage = String.format("limit %s", limit);
limitMessage = comment != null ? String.format(" and %s", limitMessage) : String.format(" with %s", limitMessage);
}
log.info("Done something '{}' and saved (id {}{}){}{}",
something.getName(), id, notificationMessage, commentMessage, limitMessage);
但是它很难写,很难读,很复杂并且会导致错误
我想指定部分日志
伪代码示例:
log.info("Done something '{}' and saved (id {} $notification) $parameters",
something.getName(), id,
$notification: sentNotification ? "sent notification" : "",
$parameters: [comment, limit]);
它应该支持可选参数,用给定字符串替换布尔/条件,支持用和和分隔空格、逗号和单词
也许有现成的图书馆来做这个?或者也许至少有一种更简单的方法来编码
如果没有,我就没有其他的事情可以写我自己的日志库了。此外,这种库将提供所有日志的一致性
如果您没有看到三个可选参数的问题,只需想象还有更多(并且您不能总是将它们打包到一个类中-另一个仅用于参数记录的类层会导致更复杂的问题)
最后,我知道我可以分别记录每个操作。但是有了这个,我会得到更多的日志,我不会在一个地方有最重要的信息。其他日志位于
调试
级别,而不是信息
这两种日志都是可能的。您可以:
- 向记录器注册一个组件,以便为您执行此工作
- 编写一个包装器类供记录器使用
记录器
掌握如何格式化特定属性的知识,不如让您的代码承担这一责任
例如,与其记录每个参数,不如收集它们并分别定义它们的日志记录。请参阅此代码:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingExample {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingExample.class);
public static void main(String[] args) {
LogObject o = new LogObject();
LOGGER.info("{}", o);
o.first = "hello";
LOGGER.info("{}", o);
o.second = "World";
LOGGER.info("{}", o);
o.last = "And finally";
LOGGER.info("{}", o);
}
public static class LogObject {
String first;
String second;
String last;
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Log Object: ");
if (first != null) {
buffer.append("First: " + first + " ");
}
if (second != null) {
buffer.append("Second: " + second + " ");
}
if (last != null) {
buffer.append("Second: " + last + " ");
}
return buffer.toString();
}
}
}
我们将LogObject
定义为一个容器,这个容器实现toString
。所有日志记录器都将对其对象调用toString()
,这就是他们如何确定应该打印什么(除非应用了特殊的格式化程序等)
这样,日志语句将打印:
11:04:12.465 [main] INFO LoggingExample - Log Object:
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello Second: World
11:04:12.467 [main] INFO LoggingExample - Log Object: First: hello Second: World Second: And finally
优点:
- 这适用于任何
。您不必根据您想要使用的内容来实现细节记录器
- 知识封装在一个易于测试的对象中。这将减轻您所说的容易出错的格式化问题
- 不需要复杂的格式化程序库或实现
- 它最终将使日志记录看起来更漂亮、更紧凑<代码>log.info(“{}”,对象)代码>
- 您需要编写Bean
布局
,它拥有如何处理自定义格式说明的知识
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.LayoutBase;
public class LoggingExample2 {
private static final Logger CUSTOM_LOGGER = createLoggerFor("test");
public static void main(String[] args) {
LogObject o = new LogObject();
CUSTOM_LOGGER.info("{}", o);
o.first = "hello";
CUSTOM_LOGGER.info("{}", o);
o.second = "World";
CUSTOM_LOGGER.info("{}", o);
o.last = "And finally";
CUSTOM_LOGGER.info("{}", o);
}
public static class LogObject {
String first;
String second;
String last;
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("Log Object: ");
if (first != null) {
buffer.append("First: " + first + " ");
}
if (second != null) {
buffer.append("Second: " + second + " ");
}
if (last != null) {
buffer.append("Second: " + last + " ");
}
return buffer.toString();
}
}
public static class ModifyLogLayout extends LayoutBase<ILoggingEvent> {
@Override
public String doLayout(ILoggingEvent event) {
String formattedMessage = event.getFormattedMessage() + "\n";
Object[] args = event.getArgumentArray();
return String.format(formattedMessage, args);
}
}
private static Logger createLoggerFor(String string) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
ple.setContext(lc);
ple.start();
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>();
consoleAppender.setEncoder(ple);
consoleAppender.setLayout(new ModifyLogLayout());
consoleAppender.setContext(lc);
consoleAppender.start();
Logger logger = (Logger) LoggerFactory.getLogger(string);
logger.addAppender(consoleAppender);
logger.setLevel(Level.DEBUG);
logger.setAdditive(false); /* set to true if root should log too */
return logger;
}
}
这基本上只是用一个键存储所有变量。它允许您有效地搜索这些密钥,将它们放入数据库,等等
在日志中,您只需执行以下操作:
var meta = // create class
meta.put("comment", comment);
// put other properties here
log.info("formatted string", formattedArguments, meta); // meta is always the last arg
在布局中
这可以很好地转换。因为您不再记录“人类语言”,所以没有“with”和“in”可替换。您的日志将仅为:
{
"time" : "...",
"message" : "...",
"meta" : {
"comment" : "this is a comment"
// no other variables set, so this was it
}
}
最后一个(最后一个)纯java中的一个,如果您需要的话。你可以写:
public static void main(String[] args) {
String comment = null;
String limit = "test";
String id = "id";
LOGGER.info(
"{} {} {}",
Optional.ofNullable(comment).map(s -> "The comment " + s).orElse(""),
Optional.ofNullable(limit).map(s -> "The Limit " + s).orElse(""),
Optional.ofNullable(id).map(s -> "The id " + s).orElse(""));
}
它有效地将格式化中所需的条件逻辑移动到Java的可选
我发现这也很难阅读和测试,仍然建议使用第一种解决方案另一种替代方法也可以使用MDC,但是这会增加保持上下文干净的复杂性。
public static void main(String[] args) {
String comment = null;
String limit = "test";
String id = "id";
LOGGER.info(
"{} {} {}",
Optional.ofNullable(comment).map(s -> "The comment " + s).orElse(""),
Optional.ofNullable(limit).map(s -> "The Limit " + s).orElse(""),
Optional.ofNullable(id).map(s -> "The id " + s).orElse(""));
}