Как производители решают проблему уровня журналов Java, повторяющихся записей и потерянных журналов?

Java задняя часть программист
Как производители решают проблему уровня журналов Java, повторяющихся записей и потерянных журналов?

1 SLF4J

Состояние лесозаготовительной отрасли

  • сложная рама

Различные библиотеки классов могут использовать разные структуры журналов, которые трудно совместить и не могут получить доступ к единым журналам, что делает эксплуатацию и обслуживание головной болью!

  • сложная конфигурация

Поскольку файл конфигурации, как правило, представляет собой файл xml, его содержимое сложное! Многие любят закрывать глаза на копирование из других проектов или онлайн!

  • высокая случайность

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

Logback, Log4j, Log4j2, commons-logging и java.util.logging — все это платформы ведения журнала в системе Java. Различные библиотеки классов также могут использовать разные структуры журналов, что затрудняет унифицированное управление журналами.

  • SLF4J (Simple Logging Facade For Java) был создан для решения этой проблемы.

  • Предоставляет единый API бревенчатого фасада.

Фиолетовая часть на рисунке реализует нейтральный API ведения журнала.

  • Функция моста

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

  • Функция адаптации

Красная часть, привязка SLF4J API и фактическая структура ведения журнала (серая часть)

SLF4J — это просто стандарт ведения журнала или для него требуется реальная структура ведения журнала. Сама структура ведения журнала не реализует SLF4J API, поэтому требуется предварительное преобразование. Сам логбэк реализован по стандарту SLF4J API, поэтому нет необходимости привязывать модули для конвертации.

Хотя доступноlog4j-over-slf4jЧтобы реализовать мост Log4j к SLF4J, вы также можете использоватьslf4j-log4j12Реализуйте SLF4J, чтобы адаптироваться к Log4j, и отрисовывайте их в столбец, но он не может использовать их одновременно, иначе возникнет бесконечный цикл. То же самое верно для jcl и jul.

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

Среда ведения журналов Spring Boot также называется Logback. Тогда почему мы можем использовать Logback напрямую, не вводя пакет Logback вручную?

зависимости модуля spring-boot-starterspring-boot-starter-loggingмодуль, покаspring-boot-starter-loggingавтоматическое введениеlogback-classic(содержит структуру ведения журналов SLF4J и Logback) и несколько адаптеров для SLF4J.

2 Асинхронное ведение журнала определенно улучшит производительность?

Как избежать того, чтобы ведение журнала стало узким местом в производительности системы? Это связано с тем, как записывать журналы, когда производительность операций ввода-вывода дисков (например, механических дисков) низкая, а количество журналов велико.

2.1 Случай

Определите следующую конфигурацию журнала с двумя приложениями:

  • FILEэто FileAppender, который записывает все журналы
  • CONSOLEпредставляет собой ConsoleAppender для регистрации журналов с отметками времени.

Если в файл выводится большое количество логов, то лог-файл будет очень большим.Если в нем еще и результаты теста производительности перемешаны, лог найти сложно. Поэтому здесь используется EvaluatorFilter для фильтрации логов по тегам, а отфильтрованные логи выводятся в консоль отдельно. В этом случае журнал, в котором выводятся результаты тестирования, помечается временем.

Используйте теги и EvaluatorFilter вместе, чтобы фильтровать журналы по тегам..

  • Тестовый код: создайте большой журнал, который записывает указанное количество раз, каждый журнал содержит 1 МБ байт смоделированных данных и, наконец, записывает журнал выполнения метода с отметкой времени, отнимающий много времени:

После выполнения программы было обнаружено, что для записи 1000 журналов и 10000 вызовов журнала потребовалось 5,1 с и 39 с соответственно.Для кода, который регистрирует только файлы, это занимает слишком много времени.

2.2 Анализ исходного кода

FileAppender наследуется от OutputStreamAppender.При добавлении журнала журнал записывается непосредственно в OutputStream, который принадлежитСинхронное ведение журнала Таким образом, большое количество записей журнала займет много времени. Как мы можем добиться большого количества записей в журнале, не оказывая чрезмерного влияния на время и производительность выполнения бизнес-логики?

2.3 AsyncAppender

Использование журналаAsyncAppender, вы можете добиться асинхронного ведения журнала.

AsyncAppenderКак и в режиме украшения, в класс добавляются новые функции без изменения исходных базовых функций класса. Это может поставитьAsyncAppenderприкреплен к другимAppender, что делает его асинхронным.

Определите асинхронный Appender ASYNCFILE, оберните FileAppender предыдущей записи журнала синхронного файла, вы можете добиться асинхронного журнала в файл

  • Для записи 1000 журналов и 10000 вызовов журналов требуется 537 мс и 1019 мс соответственно.

Действительно ли асинхронное ведение журнала настолько эффективно? Нет, потому что он не записывает все журналы.

3 Воронка асинхронного журнала AsyncAppender

  • Запись асинхронных журналов приводит к разрыву памяти
  • Потеря журнала происходит при записи асинхронных журналов
  • Ведение асинхронного журнала заблокировано.

3.1 Случай

Смоделируйте сценарий медленного ведения журнала: Сначала настройте тот, который наследуется отConsoleAppenderизMySlowAppender, как устройство вывода для ведения журнала на консоль, при записи журнала засыпает на 1 с.

  • используется в файле конфигурацииAsyncAppender,будетMySlowAppenderОбернут для асинхронного ведения журнала

  • тестовый код

  • Времени уходит очень мало, но журнал теряется: для записи 1000 журналов консоль может искать только 215 журналов, а номер строки журнала становится вопросительным знаком.

  • Анализ причин

AsyncAppenderПредоставляются некоторые параметры конфигурации, которые в настоящее время бесполезны.

Анализ исходного кода

  • includeCallerData

По умолчанию false: номер строки метода, имя метода и другая информация не отображаются.

  • queueSize

Контролируйте размер очереди блокировки, используется очередь блокировки ArrayBlockingQueue, емкость по умолчанию 256: в памяти хранится максимум 256 логов

  • discardingThreshold

Порог для удаления журналов, чтобы предотвратить блокировку при заполнении очереди. дефолт队列剩余容量 < 队列长度的20%, журналы уровней TRACE, DEBUG и INFO удаляются

  • neverBlock

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

public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
	// 是否收集调用方数据
    boolean includeCallerData = false;
    protected boolean isDiscardable(ILoggingEvent event) {
        Level level = event.getLevel();
        // 丢弃 ≤ INFO级日志
        return level.toInt() <= Level.INFO_INT;
    }
    protected void preprocess(ILoggingEvent eventObject) {
        eventObject.prepareForDeferredProcessing();
        if (includeCallerData)
            eventObject.getCallerData();
    }
}
public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {

	// 阻塞队列:实现异步日志的核心
    BlockingQueue<E> blockingQueue;
    // 默认队列大小
    public static final int DEFAULT_QUEUE_SIZE = 256;
    int queueSize = DEFAULT_QUEUE_SIZE;
    static final int UNDEFINED = -1;
    int discardingThreshold = UNDEFINED;
    // 当队列满时:加入数据时是否直接丢弃,不会阻塞等待
    boolean neverBlock = false;

    @Override
    public void start() {
       	...
        blockingQueue = new ArrayBlockingQueue<E>(queueSize);
        if (discardingThreshold == UNDEFINED)
        //默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
            discardingThreshold = queueSize / 5;
        ...
    }

    @Override
    protected void append(E eventObject) {
        if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
            return;
        }
        preprocess(eventObject);
        put(eventObject);
    }

    private boolean isQueueBelowDiscardingThreshold() {
        return (blockingQueue.remainingCapacity() < discardingThreshold);
    }

    private void put(E eventObject) {
        if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
            blockingQueue.offer(eventObject);
        } else {
            putUninterruptibly(eventObject);
        }
    }
    //以阻塞方式添加数据到队列
    private void putUninterruptibly(E eventObject) {
        boolean interrupted = false;
        try {
            while (true) {
                try {
                    blockingQueue.put(eventObject);
                    break;
                } catch (InterruptedException e) {
                    interrupted = true;
                }
            }
        } finally {
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    }
}  

Размер очереди по умолчанию 256. Достигнув 80% и начав отбрасывать журналы

размер очереди слишком велик

может привести кOOM

размер очереди мал

Значение по умолчанию 256 уже очень мало, иdiscardingThresholdЕсли установлено значение больше 0 (или значение по умолчанию), оставшаяся емкость очереди меньшеdiscardingThresholdconfig отбрасывает журналы

  1. потому чтоdiscardingThreshold, поэтому установитеqueueSizeЛегко наступить на яму.

Например, максимальное количество одновременных журналов в этом случае равно 1000, даже если установленоqueueSizeравно 1000, что также приведет к потере журнала 2.discardingThresholdПараметры склонны к неоднозначности, это不是百分比,而是日志条数. Для очереди с общей емкостью 10 000, если вы хотите, чтобы очередь удалялась, когда оставшаяся емкость очереди меньше 1000, необходимо установить значение 1000.

neverBlock по умолчанию false

Значит, всегда есть вероятность блокировки.

  • подобноdiscardingThreshold = 0, то когда очередь будет заполнена, дальнейшая запись в журнал будет заблокирована.
  • подобноdiscardingThreshold != 0, и отбрасывать только журналы уровня ≤INFO. Когда возникает большое количество журналов ошибок, он все равно будет блокировать

Три параметра queueSize, discardingThreshold и neverBlock неразделимы и должны быть установлены в соответствии с бизнес-требованиями:

  • Чтобы отдать приоритет абсолютной производительности, установитеneverBlock = true, никогда не блокировать
  • Если приоритетом является никогда не терять данные, установитеdiscardingThreshold = 0, даже журналы уровня ≤INFO не будут потеряны. Но лучше задать размер очереди побольше, в конце концов, размер очереди по умолчанию явно слишком мал и его слишком легко заблокировать.
  • Если принять во внимание и то, и другое, неважные журналы можно отбросить, аqueueSizeУстановите большую точку, а затем установите разумнуюdiscardingThreshold

Две наиболее распространенные ошибки в приведенной выше конфигурации журнала

Посмотрите на ошибки самого логирования.

4 Как выбрать уровень логирования?

Используя заполнитель {}, разве вам не нужно судить об уровне журнала?

По словам неизвестного пользователя сети, синтаксис заполнителя {} SLF4J не будет получать фактические параметры до тех пор, пока журнал не будет фактически записан, тем самым решая проблему производительности при сборе данных журнала.это правда?

  • Код подтверждения: для возврата результата требуется 1 с.

Если вы записываете журналы DEBUG и устанавливаете только журналы >= уровень INFO, программа также будет занимать 1 с? Три метода тестирования:

  • Запись slowString путем объединения строк
  • Используйте заполнители для записи slowString
  • Сначала определите, включен ли уровень журнала DEBUG.

Первые два метода вызывают медленную строку, поэтому это занимает 1 с. И второй способ — использовать плейсхолдеры для записи slowString.Хотя этот метод позволяет передавать объекты без явного сплайсинга String, это всего лишь задержка (если лог ее не фиксирует, то она опускается).параметр журнала object.toString()а такжеКонкатенация строккропотливый.

В этом случае, если уровень ведения журнала не определен заранее, необходимо вызвать slowString. так что используйте{}占位符Проблема производительности сбора данных журнала не может быть решена задержкой сбора значений параметров.

Помимо предварительной оценки уровня журнала, вы также можете отложить получение содержимого параметра с помощью лямбда-выражений. Но API SLF4J пока не поддерживает лямбду, поэтому нужно использовать API логов Log4j2, поставитьАннотация Ломбока @ Slf4jЗамените его аннотацией **@Log4j2**, чтобы предоставить метод для параметров лямбда-выражения:

Вызвать отладку вот так, подписьSupplier<?>, параметры будут отложены до тех пор, пока регистрация действительно не понадобится:

Таким образом, debug4 не будет вызывать метод slowString.

просто замените его наLog4j2 API, реальная регистрация все равно идетLogback,ЭтоSLF4JПреимущества адаптации.

Суммировать

  • SLF4J унифицирует среду ведения журналов Java. При использовании SLF4J помните о его связующих API и привязках. Если при запуске программы появляется сообщение об ошибке SLF4J, это может быть проблемой конфигурации.Вы можете использовать Maven'sdependency:treeКоманда для прочесывания зависимостей.
  • Асинхронное ведение журналов решает проблемы с производительностью и экономит место в обмен на время. Но пространство в конце концов ограничено.Когда пространство заполнено, рассмотрите возможность блокировки и ожидания или удаления журнала. Если вы предпочитаете не сбрасывать важные логи, выбирайте блокировку и ожидание, если предпочитаете, чтобы программа не блокировалась из-за ведения логов, то вам нужно сбрасывать логи.
  • Параметризованный метод записи, предоставляемый структурой ведения журнала, не может полностью заменить оценку уровня журнала. Если количество журналов велико, стоимость получения параметров журнала также очень высока, поэтому необходимо оценить уровень журнала, чтобы избежать трудоемкого получения параметров журнала без регистрации!