Log4j2 RollingFile Appender - добавете персонализирана информация в началото на всеки лог файл

Използване на log4j2 (beta9) с java 1.7.

Моят пълен log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
  <Properties>
    <Property name="projectPrefix">Tts</Property>
    <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
    <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
    <Property name="fileName">Log/${projectPrefix}.log</Property>
    <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
  </Properties>
  <Appenders>
    <Console name="Stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="${coloredPattern}"/>
    </Console>
    <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
      <PatternLayout pattern="${rawPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="16 MB"/>
      </Policies>
      <DefaultRolloverStrategy fileIndex="min" max="16"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Stdout"/>
      <AppenderRef ref="Logfile"/>
    </Root>
  </Loggers>
</Configuration>

Искам да добавя персонализирана информация в горната част на всеки лог файл, като низа на версията на моето приложение, времето за работа на приложението и времето за работа на системата. И дори писането на някои "чао, чао / eof" в края на току-що затворения лог файл също би било добре.

Има ли нещо като кука или обратно извикване, за да получа известие, когато RollingFileAppander е създал нов файл, така че да мога да поставя нещата си първо в този нов лог файл (или всяко друго предложение)?


person Joe    schedule 28.12.2013    source източник


Отговори (3)


Добре, има работещо решение за този проблем чрез разширяване на DefaultRolloverStrategy, както е описано тук. Но

  • има нужда от около 150 реда код (включително опаковане на RolloverDescription и appender.rolling.helper.Action) и
  • мирише малко, защото необходимостта от пълно копиране на фабричния метод на DefaultRolloverStrategy (прави това решение неудобно за поддръжка, напр. ако DefaultRolloverStrategy получи повече конфигурационни параметри в бъдещи версии)

За да позволим на log4j2 да извика нашия фабричен метод, основният таг на log4j2.xml трябва да бъде приписан с пакета на нашия клас, напр.:

<Configuration packages="de.jme.toolbox.logging">
...
</Configuration>

и в рамките на нашата собствена RolloverStrategy трябва да се справим с @Plugin и @PluginFactory, както е описано тук.

Най-накрая ето моя пълен log4j2.xml (нямате нужда от всички тези свойства - това е просто начинът, по който обичам да конфигурирам моето регистриране):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="de.jme.toolbox.logging">
  <Properties>
    <Property name="projectPrefix">Tts</Property>
    <Property name="rawPattern">%d %-5p [%t] %C{2} (%F:%L) - %m%n</Property>
    <Property name="coloredPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] %C{2} (%F:%L) -}{bright,black} %m%n</Property>
    <Property name="coloredShortPattern">%d %highlight{%-5p}{FATAL=bright red, ERROR=red, WARN=yellow, INFO=cyan, DEBUG=green, TRACE=bright blue} %style{[%t] -}{bright,black} %m%n</Property>
    <Property name="fileName">Log/${projectPrefix}.log</Property>
    <Property name="filePattern">Log/${projectPrefix}-%i.log</Property>
  </Properties>
  <Appenders>
    <Console name="Stdout" target="SYSTEM_OUT">
      <PatternLayout pattern="${coloredPattern}"/>
    </Console>
    <RollingFile name="Logfile" fileName="${fileName}" filePattern="${filePattern}">
      <PatternLayout pattern="${rawPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="1 MB"/>
      </Policies>
      <MyRolloverStrategy fileIndex="min" max="16"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Stdout"/>
      <AppenderRef ref="Logfile"/>
    </Root>
  </Loggers>
</Configuration>

И тук MyRolloverStrategy.java:

package de.jme.toolbox.logging;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.zip.Deflater;

import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverDescription;
import org.apache.logging.log4j.core.appender.rolling.helper.Action;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.helpers.Integers;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.status.StatusLogger;

/**
 * Own RolloverStrategy to hook the DefaultRolloverStrategy's rollover events
 *
 * Siehe auch:
 * - https://issues.apache.org/jira/browse/LOG4J2-486
 * - http://apache-logging.6191.n7.nabble.com/log4j2-getting-started-amp-rolling-files-tt8406.html#a42402
 * - http://stackoverflow.com/questions/20819376/log4j2-rollingfile-appender-add-custom-info-at-the-start-of-each-logfile
 *
 * @author Joe Merten
 */
@org.apache.logging.log4j.core.config.plugins.Plugin(name="MyRolloverStrategy", category="Core", printObject=true)
public class MyRolloverStrategy extends DefaultRolloverStrategy {

    protected static final Logger logger = StatusLogger.getLogger();

    // ==============================
    // ↓↓↓ Some stuff copied from ↓↓↓
    // https://svn.apache.org/repos/asf/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java r1556050
    // Just changed »DefaultRolloverStrategy« to »MyRolloverStrategy«

    private static final int MIN_WINDOW_SIZE = 1;
    private static final int DEFAULT_WINDOW_SIZE = 7;

    @PluginFactory
    public static MyRolloverStrategy createStrategy(
            @PluginAttribute("max") final String max,
            @PluginAttribute("min") final String min,
            @PluginAttribute("fileIndex") final String fileIndex,
            @PluginAttribute("compressionLevel") final String compressionLevelStr,
            @PluginConfiguration final Configuration config) {
        final boolean useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max");
        int minIndex;
        if (min != null) {
            minIndex = Integer.parseInt(min);
            if (minIndex < 1) {
                LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE);
                minIndex = MIN_WINDOW_SIZE;
            }
        } else {
            minIndex = MIN_WINDOW_SIZE;
        }
        int maxIndex;
        if (max != null) {
            maxIndex = Integer.parseInt(max);
            if (maxIndex < minIndex) {
                maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex;
                LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex);
            }
        } else {
            maxIndex = DEFAULT_WINDOW_SIZE;
        }
        final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION);
        return new MyRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor());
    }
    // ↑↑↑ Some stuff copied from ↑↑↑
    // ==============================

    protected MyRolloverStrategy(int minIndex, int maxIndex, boolean useMax, int compressionLevel, StrSubstitutor subst) {
        super(minIndex, maxIndex, useMax, compressionLevel, subst);
    }

    // Wrapper class only for setting a hook to execute()
    static class MyAction implements Action {
        final Action delegate;
        final String fileName;

        public MyAction(final Action delegate, final String fileName) {
            this.delegate = delegate;
            this.fileName = fileName;
        }

        @Override public void run() {
            delegate.run();
        }

        @Override public boolean execute() throws IOException {
            try {
                BufferedWriter writer = null;
                try {
                    writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                    writer.write("****************************\n");
                    writer.write("*** Bye, bye old logfile ***\n");
                    writer.write("****************************\n");
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to bottom of old logfile \"" + fileName + "\" with", e);
            }

            boolean ret = delegate.execute();

            try {
                BufferedWriter writer = null;
                try {
                    writer = new BufferedWriter(new FileWriter(new File(fileName), true));
                    writer.write("*************************\n");
                    writer.write("*** Hello new logfile ***\n");
                    writer.write("*************************\n");
                } finally {
                    if (writer != null)
                        writer.close();
                }
            } catch (Throwable e) {
                logger.error("Writing to top of new logfile \"" + fileName + "\" with", e);
            }

            return ret;
        }

        @Override public void close() {
            delegate.close();
        }

        @Override public boolean isComplete() {
            return delegate.isComplete();
        }
    }

    // Wrapper class only for setting a hook to getSynchronous().execute()
    static class MyRolloverDescription implements RolloverDescription {
        final RolloverDescription delegate;

        public MyRolloverDescription(final RolloverDescription delegate) {
            this.delegate = delegate;
        }

        @Override public String getActiveFileName() {
            return delegate.getActiveFileName();
        }

        @Override public boolean getAppend() {
            //return delegate.getAppend();
            // As long as we already put some data to the top of the new logfile, subsequent writes should be performed with "append".
            return true;
        }

        // The synchronous action is for renaming, here we want to hook
        @Override public Action getSynchronous() {
            Action delegateAction = delegate.getSynchronous();
            if (delegateAction == null) return null;
            return new MyAction(delegateAction, delegate.getActiveFileName());
        }

        // The asynchronous action is for compressing, we don't need to hook here
        @Override public Action getAsynchronous() {
            return delegate.getAsynchronous();
        }
    }

    public RolloverDescription rollover(final RollingFileManager manager) {
        RolloverDescription ret = super.rollover(manager);
        return new MyRolloverDescription(ret);
    }
}

Решаването на това изискване може да бъде по-лесно в бъдещите версии на log4j2, ако моята публикувана заявка за функция ще бъдат изпълнени.

person Joe    schedule 07.01.2014

Налично ново решение

Измина известно време, откакто беше зададен този въпрос и сега, когато исках да направя същото, открих, че е възможно да се реши без да се забърквате с фабрики, но не беше лесно да се намери. Екипът на log4j2 направи възможно това чрез конфигурация. Надявам се публикацията ми да е полезна и да спести време на някой друг.

Те са скрили тази функция в елемента PatternLayout. Не там, където погледнах за първи път, но защо да се оплаквам, когато работи?

Това е моята конфигурация (обърнете внимание на горния и долния колонтитул и свойствата, които използват):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="log-path">${sys:catalina.home}/logs/my-app</Property>
        <Property name="archive">${log-path}/archive</Property>
        <Property name="defaultPattern">[%d] [%-5p] [%t] %C{5} - %m%n</Property>
        <Property name="defaultRollOverMax">450</Property>
        <Property name="fileHeader">[%d] Start of log \n========================================================================\n
Will be archived in ${archive}\n\n</Property>
        <Property name="fileFooter">\n========================================================================\n[%d] End of log</Property>
    </Properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%highlight{[%d] [%-5p] [%t] %C{3} (%F:%L) - %m%n}" charset="UTF-8"/>
        </Console>

        <RollingFile name="Root"
                     fileName="${log-path}/root.log"
                     filePattern="${archive}/root.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>

            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="This is the ROOT logger and it should be silent \n - define loggers when you see something in here"
                           footer="Closing"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>

        <RollingFile name="System"
                     fileName="${log-path}/system.log"
                     filePattern="${archive}/system.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="${fileHeader}"
                           footer="${fileFooter}"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>

        <RollingFile name="Error"
                     fileName="${log-path}/error.log"
                     filePattern="${archive}/error.log.%d{yyyy-MM-dd}_%i.gz"
                     immediateFlush="true">
            <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <PatternLayout pattern="${defaultPattern}"
                           charset="UTF-8"
                           header="${fileHeader}"
                           footer="${fileFooter}"/>
            <DefaultRolloverStrategy max="${defaultRollOverMax}" fileIndex="max"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Logger name="org.myOrganization.myApplication" additivity="false" level="INFO">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="org.myOrganization.myApplication.peculiarPackage" additivity="false" level="TRACE">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="org.springframework" additivity="false" level="WARN">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <Logger name="javax" additivity="false" level="WARN">
            <appender-ref ref="System"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="Error"/>
        </Logger>

        <!-- Root logger should be empty -->
        <Root level="all">
            <AppenderRef ref="Root"/>
            <AppenderRef ref="Console"/>
            <!--Make sure all errors are logged to the error log-->
            <appender-ref ref="Error"/>
        </Root>
    </Loggers>
</Configuration>

Както можете да видите, включих клеймо за време и свойство, което включва системно свойство. Log4j2 може да показва много различни видове свойства, с това можете да правите много неща от това, което сте поискали.

Регистрационният файл изглежда така:

[2016-08-09 17:00:43,924] Start of log 
========================================================================

Will be archived in /home/emanciperingsivraren/program/apache-tomcat-8.0.32/logs/my-app/archive

[2016-08-09 17:00:44,000] [INFO ] [RMI TCP Connection(2)-127.0.0.1]    [snip]

========================================================================
[2016-08-09 17:02:17,871] End of log

Имате ли нужда от повече персонализирана информация? - опитайте се да поставите тази информация в свойствата, системните свойства или нещо друго, което log4j2 може да прочете.

Вижте Замяна на собственост в log4j2 за подробности относно това какви свойства можете да имате.

Коментари за конфигурацията

  • Използвам свойства вместо да повтарям общи настройки
  • Ако нещо е записано в основния регистратор, то не е обработено и трябва да се запише регистратор, за да се определи какво искаме да правим с него
  • Тъй като грешките/изключенията могат да бъдат "скрити" сред много други регистрационни съобщения, те също се отпечатват там и могат лесно да бъдат намерени
  • Съобщенията, отпечатани на конзолата, имат различни цветове
person emanciperingsivraren    schedule 09.08.2016

В момента няма кука за обратно извикване за преобръщане. Мога ли да предложа да повдигнете това като заявка за функция в програмата за проследяване на проблеми log4j2?

person Remko Popma    schedule 28.12.2013
comment
Сега бях публикувал заявка за функция → issues.apache.org/jira/browse/LOG4J2 -491 - person Joe; 07.01.2014