Вопрос

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

Этот метод 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() только в случае необходимости.

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

Этот же вопрос имеет и пример вариативной функции, как предложено в ответе Андреаса.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top