Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/312.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 带有可选参数的日志记录_Java_Log4j_Log4j2_Logback_Slf4j - Fatal编程技术网

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
现在,可以使用例如自定义布局来实现相同的功能。我正在使用logback,所以这是logback的一个示例

我们可以定义一个
布局
,它拥有如何处理自定义格式说明的知识

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(""));
  }