String.format с ленивой оценкой - PullRequest
22 голосов
/ 03 июня 2009

Мне нужно что-то похожее на метод String.format (...) , но с ленивой оценкой.

Этот метод lazyFormat должен возвращать некоторый объект, метод toString () которого затем будет оценивать шаблон формата.

Я подозреваю, что кто-то уже сделал это. Это доступно в каких-либо библиотеках?

Я хочу заменить это (журнал - это экземпляр log4j):

if(logger.isDebugEnabled() ) {
   logger.debug(String.format("some texts %s with patterns %s", object1, object2));
}

с этим:

logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2));

Мне нужно lazyFormat для форматирования строки, только если включено ведение журнала отладки.

Ответы [ 10 ]

22 голосов
/ 03 июня 2009

, если вы ищете «простое» решение:

 public class LazyFormat {

    public static void main(String[] args) {
        Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string");
        System.out.println(o);
    }

    private static Object lazyFormat(final String s, final Object... o) {
        return new Object() {
            @Override
            public String toString() {
                return String.format(s,o);
            }
        };
    }
}

выходы:

В некоторых текстах есть строка с выкройки еще одной длинной строки

Вы, конечно, можете добавить любой оператор isDebugEnabled() в lazyFormat, если хотите.

16 голосов
/ 29 декабря 2012

Это можно сделать с помощью подстановки параметров в новейшей версии log4j 2.X http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:

4.1.1.2 Подстановка параметров

Часто целью регистрации является предоставление информации о том, что происходит в системе, которая требует включения информации об объектах, которыми манипулируют. В Log4j 1.x это можно сделать, выполнив:

if (logger.isDebugEnabled()) {     
  logger.debug("Logging in user " + user.getName() + " with id " + user.getId()); 
} 

Повторное выполнение этого действия приводит к код кажется, что это больше о регистрации, чем о реальной задаче. Кроме того, это приводит к тому, что уровень регистрации проверяется дважды; один раз на вызове isDebugEnabled и один раз на методе отладки. Лучше альтернатива будет:

logger.debug("Logging in user {} with id {}", user.getName(), user.getId()); 

С кодом выше уровня регистрации будет проверяться только один раз, а построение String будет происходить только когда включено ведение журнала отладки.

13 голосов
/ 03 июня 2009

, если вы ищете ленивую конкатенацию для эффективной регистрации, взгляните на Slf4J это позволяет вам написать:

LOGGER.debug("this is my long string {}", fatObject);

конкатенация строк будет иметь место, только если установлен уровень отладки.

5 голосов
/ 19 августа 2013

ВАЖНОЕ ПРИМЕЧАНИЕ: Настоятельно рекомендуется переместить весь код регистрации для использования SLF4J (особенно log4j 1.x). Он защищает вас от застревания с какими-либо специфическими проблемами (например, ошибками) с конкретными реализациями ведения журналов. Он не только имеет «исправления» для хорошо известных проблем реализации бэкэнда, он также работает с более новыми, более быстрыми реализациями, появившимися за эти годы.


В прямом ответе на ваш вопрос, вот как это будет выглядеть при использовании SLF4J :

LOGGER.debug("some texts {} with patterns {}", object1, object2);

Самым важным из того, что вы предоставили, является тот факт, что вы передаете два экземпляра объекта. Методы object1.toString() и object2.toString() оцениваются не сразу. Что еще более важно, методы toString() оцениваются только в том случае, если возвращаемые данные действительно будут использоваться; то есть истинное значение ленивых оценок.

Я пытался придумать более общий шаблон, который мог бы использовать, который не требовал от меня переопределения toString() в тоннах классов (и есть классы, где у меня нет доступа для переопределения). Я придумала простое решение с заменой на месте. Опять же, используя SLF4J , я составляю строку, только если / когда включена регистрация уровня. Вот мой код:

    class SimpleSfl4jLazyStringEvaluation {
      private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class);

      ...

      public void someCodeSomewhereInTheClass() {
//all the code between here
        LOGGER.debug(
            "{}"
          , new Object() {
              @Override
              public String toString() {
                return "someExpensiveInternalState=" + getSomeExpensiveInternalState();
              }
            }
//and here can be turned into a one liner
        );
      }

      private String getSomeExpensiveInternalState() {
        //do expensive string generation/concatenation here
      }
    }

И для упрощения в однострочник вы можете сократить строку LOGGER в someCodeSomewhereInTheClass () до:

LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}});

Я теперь реорганизовал весь свой код регистрации, чтобы следовать этой простой модели. Это привело в порядок вещи. И теперь, когда я вижу любой код регистрации, который не использует это, я реорганизую код регистрации, чтобы использовать этот новый шаблон, даже если он еще необходим. Таким образом, если / когда позднее вносится изменение, требующее добавления некоторой «дорогой» операции, шаблон инфраструктуры уже существует, упрощая задачу просто добавлению операции.

3 голосов
/ 03 июня 2009

Вы можете поместить экземпляр регистратора Log4J в свой собственный класс, совместимый с Java5 / String.format. Что-то вроде:

public class Log4jWrapper {

    private final Logger inner;

    private Log4jWrapper(Class<?> clazz) {
        inner = Logger.getLogger(clazz);
    }

    public static Log4jWrapper getLogger(Class<?> clazz) {
        return new Log4jWrapper(clazz);
    }

    public void trace(String format, Object... args) {
        if(inner.isTraceEnabled()) {
            inner.trace(String.format(format, args));    
        }
    }

    public void debug(String format, Object... args) {
        if(inner.isDebugEnabled()) {
            inner.debug(String.format(format, args));    
        }
    }

    public void warn(String format, Object... args) {
        inner.warn(String.format(format, args));    
    }

    public void error(String format, Object... args) {
        inner.error(String.format(format, args));    
    }

    public void fatal(String format, Object... args) {
        inner.fatal(String.format(format, args));    
    }    
}

Чтобы использовать оболочку, измените объявление поля регистратора на:

private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class);

Классу-оболочке потребуется несколько дополнительных методов, например, в настоящее время он не обрабатывает исключения журналирования (например, logger.debug (message, exception)), но добавить его нетрудно.

Использование класса будет почти идентично log4j, за исключением того, что строки форматируются:

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)
3 голосов
/ 03 июня 2009

Опираясь на ответ Андреаса , я могу вспомнить пару подходов к вопросу о выполнении форматирования только в том случае, если Logger.isDebugEnabled возвращает true:

Вариант 1: передать флаг "do formatting"

Один из вариантов - иметь аргумент метода, который сообщает, нужно ли на самом деле выполнять форматирование. Вариант использования может быть:

System.out.println(lazyFormat(true, "Hello, %s.", "Bob"));
System.out.println(lazyFormat(false, "Hello, %s.", "Dave"));

Где будет вывод:

Hello, Bob.
null

Код для lazyFormat:

private String lazyFormat(boolean format, final String s, final Object... o) {
  if (format) {
    return String.format(s, o);
  }
  else {
    return null;
  }
}

В этом случае String.format выполняется только тогда, когда флаг format установлен на true, а если он установлен на false, он вернет null. Это остановит форматирование сообщения регистрации и просто отправит некоторую «фиктивную» информацию.

Таким образом, вариант использования с регистратором может быть:

logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue));

Этот метод не совсем соответствует форматированию, которое запрашивается в вопросе.

Вариант 2: Проверьте регистратор

Другой подход - напрямую спросить у логгера: isDebugEnabled:

private static String lazyFormat(final String s, final Object... o) {
  if (logger.isDebugEnabled()) {
    return String.format(s, o);
  }
  else {
    return null;
  }
}

В этом подходе ожидается, что logger будет виден в методе lazyFormat. Преимущество этого подхода заключается в том, что вызывающей стороне не нужно проверять метод isDebugEnabled при вызове lazyFormat, поэтому типичное использование может быть следующим:

logger.debug(lazyFormat("Debug message is %s", someMessage));
2 голосов
/ 23 февраля 2014

В Log4j 1.2.16 введены два класса, которые сделают это за вас.

org.apache.log4j.LogMF, который использует java.text.MessageFormat для форматирования ваших сообщений и org.apache.log4j.LogSF, который использует "синтаксис шаблона SLF4J" и, как говорят, быстрее.

Вот примеры:

LogSF.debug(log, "Processing request {}", req);

и

 LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
1 голос
/ 14 июня 2017

Если вам нравится синтаксис String.format лучше, чем синтаксис {0} и вы можете использовать Java 8 / JDK 8 , вы можете использовать лямбда-выражения / Suppliers:

logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));

()->... выступает здесь в качестве поставщика и будет оцениваться лениво.

1 голос
/ 04 июня 2009

Или вы можете написать это как

debug(logger, "some texts %s with patterns %s", object1, object2);

с

public static void debug(Logger logger, String format, Object... args) {
    if(logger.isDebugEnabled()) 
       logger.debug(String.format("some texts %s with patterns %s", args));
}
0 голосов
/ 03 июня 2009

Вы можете определить оболочку для вызова String.format() только при необходимости.

См. этот вопрос для подробного примера кода.

В этом же вопросе есть также пример функции вариации , как предлагается в ответе Андреаса.

...