2009-06-03 3 views
21

Мне нужно что-то похожее на String.format(...) метод, но с ленивой оценкой.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 форматировать строку только в том случае отладки ведение журнала включено.

ответ

22

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

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 строку с узорами другой займет слишком много строки

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

+0

Более поздние версии Log4J позволяют замещать параметры, см. http://stackoverflow.com/a/14078904/620113 – computermacgyver

+0

Действительно ли это работает - он будет форматировать строку, но делает ли она «ленивую» вещь? Я сделал небольшой тест, в котором аргументы lazyFormat являются вызовами функции, которая печатает в System.err, и казалось, что аргументы в Object ... o получают оценку, даже если String.format (s, o) не является. – OneSolitaryNoob

+0

OneSolitaryNoob - Это не делает ленивую вещь. Поставщик ответа указывает, что это можно сделать, используя метод isDebugEnabled() в методе toString(). Я предоставил более общий ленивый шаблон регистрации в моем ответе: http://stackoverflow.com/a/18317629/501113 – chaotic3quilibrium

13

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

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

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

+2

проблема ленивой оценки была именно поэтому был введен синтаксис {} синтаксиса slf4j –

+0

В противном случае это хорошо, но я застрял с log4j. У меня есть сложная настройка регистрации, поэтому я не могу просто удалить slf4j. –

+2

SLF4J имеет привязку для log4j. Так что независимо от вашей «сложной настройки регистрации», SLF4J прекрасно справится с этим. Вы можете продолжать использовать log4j напрямую и SLF4J только тогда, когда необходима ленивая оценка строки. – Ceki

0

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

См. this question для детального примера кода.

Тот же вопрос имеет также variadic function example, как это было предложено в ответе Андреаса.

3

Опираясь на Andreas' answer, я могу думать о нескольких подходах к проблеме только выполняя форматирование, если 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. Это остановит форматирование сообщения о регистрации и просто отправит некоторую «фиктивную» информацию.

Так Прецедент с регистратором может быть:

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

Этот метод не совсем подходит для форматирования, который просил в этом вопросе.

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

Другой подход, чтобы спросить регистратора непосредственно, если это 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)); 
+0

Не должен ли последний пример быть logger.debug (lazyFormat ("Debug message is% s", someMessage)); ? –

+0

@ Джуга С .: Да, вы правы. Я заметил ошибку немного после того, как я отправил ответ, поэтому он исправлен. – coobird

3

Вы можете обернуть экземпляр регистратора 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) 
1

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

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

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

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

+1

Ваш пример немного вводит в заблуждение, поскольку между вашим неформатированным и отформатированным примером мало различий; то есть нет реального значения в форматировании для операций user.getName() или user.getId(), поскольку они вызываются немедленно, а их значения передаются в метод logger.debug. Первоначальный плакат пропускал два экземпляра объекта, рассчитывая на то, что метод toString() для этих экземпляров не будет вызываться, если только они не понадобятся. Я отправил ответ, который более точно фиксирует это состояние «только вызов для данных, если он будет использоваться». – chaotic3quilibrium

+0

Log4j 2.4 [добавляет поддержку lambdas] (http://logging.apache.org/log4j/2.x/manual/api.html#LambdaSupport). –

5

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


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

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

Самый важный бит, что вы предоставили является тот факт, вы передаете два экземпляра Object. Методы 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 
     } 
    } 

И упрощать в один-лайнер, вы можете сократить РЕГИСТРАТОР линию в someCodeSomewhereInTheClass(), чтобы быть:

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

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

2

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

Если вам нравится string.Format Синтаксис лучше, чем {0} Синтаксис и может использовать Java 8/JDK 8 вы можете





Поставщики:

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

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

Смежные вопросы