Подтвердить что ты не робот

String.format с ленивой оценкой

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

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

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

Я хочу заменить это (logger - экземпляр 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 для форматирования строки только в том случае, если включено ведение журнала отладки.

4b9b3361

Ответ 1

если вы ищете "простое" решение:

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

выходы:

некоторые тексты looong string с рисует другую строку loooong

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

Ответ 2

Это можно сделать, используя замену параметров в новейшей версии 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()); 

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

Ответ 3

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

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

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

Ответ 4

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


В прямом ответе на ваш вопрос, как бы это выглядело, используя 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();}});

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

Ответ 5

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

Вариант 1: передать флаг "сделать форматирование"

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

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. Это остановит форматирование сообщения о регистрации и просто отправит информацию о "dummy".

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

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));

Ответ 6

Вы можете обернуть экземпляр журнала 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(сообщение, исключение)), но это не сложно добавить.

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

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction)

Ответ 7

Представленные в 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); 

Ответ 8

Или вы могли бы написать его как

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

Ответ 9

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

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

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

Ответ 10

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

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

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