String.format с ленивой оценкой
-
09-09-2019 - |
Вопрос
мне нужно что-то похожее на Строка.формат(...) метод, но с ленивой оценкой.
Этот метод 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 для форматирования строки, только если включено ведение журнала отладки.
Решение
если вы ищете «простое» решение:
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, если хотите.
Другие советы
Это можно сделать с помощью замены параметров в новейшей версии 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());
С кодом выше уровня регистрации будет проверяться только один раз, и строительная конструкция будет происходить только при включении журнала отладки.
если вы ищете ленивую конкатенацию ради эффективного ведения журнала, взгляните на Slf4J это позволяет вам написать:
LOGGER.debug("this is my long string {}", fatObject);
объединение строк произойдет только в том случае, если установлен уровень отладки.
ВАЖНАЯ ЗАМЕТКА: Настоятельно рекомендуется перенести весь код журналирования для использования 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();}});
Теперь я реорганизовал весь свой код ведения журналов, чтобы следовать этой простой модели.Это значительно навело порядок.И теперь, когда я вижу какой-либо код журналирования, который не использует это, я реорганизую код журналирования, чтобы использовать этот новый шаблон, даже если он еще необходим.Таким образом, если/когда позже будет внесено изменение, требующее добавления какой-либо «дорогой» операции, шаблон инфраструктуры уже существует, упрощая задачу до простого добавления операции.
Опираясь на Ответ Андреаса, я могу придумать пару подходов к вопросу выполнения форматирования только в том случае, если 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:Проверьте регистратор
Другой подход — напрямую спросить у регистратора, 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));
Вы можете обернуть экземпляр регистратора 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)
В 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);
Или вы могли бы написать это как
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));
}
Если синтаксис String.format вам нравится больше, чем синтаксис {0}, и вы можете использовать Ява 8/JDK 8 вы можете использовать лямбды/Поставщики:
logger.log(Level.FINER, () -> String.format("SomeOperation %s took %04dms to complete", name, duration));
()->...
выступает здесь в качестве Поставщика и будет оцениваться лениво.
Вы можете определить оболочку для вызова String.format()
только в случае необходимости.
Видеть этот вопрос для подробного примера кода.
Этот же вопрос имеет и пример вариативной функции, как предложено в ответе Андреаса.