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