bwaldvogel / log4j-systemd-journal-appender

Log4j appender for systemd-journal that maintains structured log data
BSD 3-Clause "New" or "Revised" License
24 stars 14 forks source link

logAppenderName #2

Closed iciclespider closed 8 years ago

iciclespider commented 8 years ago

Any chance of adding a logAppenderName configuration property to both branches which does just that?

bwaldvogel commented 8 years ago

I’m sorry. I guess you meant something else than the syslog identifier. Isn’t the LOG4J_LOGGER value what you want?

iciclespider commented 8 years ago

I was trying to replicate the functionality of log4j setup in the Kafka project here: https://github.com/apache/kafka/blob/trunk/config/log4j.properties

In there, different appenders send their output to different files. If the SystemdJournalAppender would log the appender name, then I could filter the journalctl output to specific appender output, much like looking in the different log files on disk. This could be accomplished by using different syslogIdentifier's, but it is overloaded what the syslogIdentifier really means.

Roughly, this means adding something like so in the append(...) method:

    if (logAppender) {
        args.add("LOG4J_APPENDER=%s");
        args.add(getName());
    }

Since I submitted that issue, I created my own version that combined elements of your project and of logback-journal project. Here is the current state of my appender implementation if you want to incorporate any of it into yours:

/*
 * This file was originally part of
 *   https://github.com/bwaldvogel/log4j-systemd-journal-appender
 */

package systemd.journal;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.LocationInfo;

import com.sun.jna.Native;

public class Log4jAppender extends AppenderSkeleton {

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    private final Library library;

    private String syslogIdentifier = null;

    private boolean threadName = true;

    private boolean logger = true;

    private boolean appender = true;

    private boolean location = true;

    private boolean stacktrace = true;

    private boolean mdc = true;

    private String mdcPrefix = "MDC_";

    public Log4jAppender() {
        library = (Library) Native.loadLibrary("systemd-journal", Library.class);
    }

    Log4jAppender(Library library) {
        this.library = library;
    }

    @Override
    public void close() {
        // ignore
    }

    @Override
    public boolean requiresLayout() {
        return false;
    }

    private int log4jLevelToJournalPriority(Level level) {
        //
        // syslog.h
        //
        // #define LOG_EMERG 0 - system is unusable
        // #define LOG_ALERT 1 - action must be taken immediately
        // #define LOG_CRIT 2 - critical conditions
        // #define LOG_ERR 3 - error conditions
        // #define LOG_WARNING 4 - warning conditions
        // #define LOG_NOTICE 5 - normal but significant condition
        // #define LOG_INFO 6 - informational
        // #define LOG_DEBUG 7 - debug-level messages
        //
        switch (level.toInt()) {
        case Level.FATAL_INT:
            return 2; // LOG_CRIT
        case Level.ERROR_INT:
            return 3; // LOG_ERR
        case Level.WARN_INT:
            return 4; // LOG_WARNING
        case Level.INFO_INT:
            return 6; // LOG_INFO
        case Level.DEBUG_INT:
        case Level.TRACE_INT:
            return 7; // LOG_DEBUG
        default:
            throw new IllegalArgumentException("Cannot map log level: " + level);
        }
    }

    @Override
    protected void append(LoggingEvent event) {
        List<Object> args = new ArrayList<Object>();

        args.add(event.getRenderedMessage());

        args.add("PRIORITY=%d");
        args.add(Integer.valueOf(log4jLevelToJournalPriority(event.getLevel())));

        if (syslogIdentifier != null && !syslogIdentifier.isEmpty()) {
            args.add("SYSLOG_IDENTIFIER=%s");
            args.add(syslogIdentifier);
        }

        if (threadName) {
            args.add("THREAD_NAME=%s");
            args.add(event.getThreadName());
        }

        if (logger) {
            args.add("LOGGER=%s");
            args.add(event.getLoggerName());
        }

        if (appender) {
            args.add("APPENDER=%s");
            args.add(getName());
        }

        if (location) {
            LocationInfo info = event.getLocationInformation();
            if (info != null) {
                args.add("CODE_FUNC=%s.%s");
                args.add(info.getClassName());
                args.add(info.getMethodName());
                String name = info.getFileName();
                if (name != null) {
                    args.add("CODE_FILE=%s");
                    args.add(name);
                }
                String number = info.getLineNumber();
                if (number != null) {
                    args.add("CODE_LINE=%s");
                    args.add(number);
                }
            }
        }

        if (stacktrace) {
            String[] throwable = event.getThrowableStrRep();
            if (throwable != null) {
                StringBuilder sb = new StringBuilder();
                for (String line : throwable) {
                    sb.append(line).append(LINE_SEPARATOR);
                }
                args.add("STACKTRACE=%s");
                args.add(sb.toString());
            }
        }

        if (mdc) {
            Map<?, ?> properties = event.getProperties();
            if (properties != null) {
                for (Entry<?, ?> entry : properties.entrySet()) {
                    Object key = entry.getKey();
                    args.add(mdcPrefix + normalizeKey(key) + "=%s");
                    args.add(entry.getValue().toString());
                }
            }
        }

        args.add(null);

        library.sd_journal_send("MESSAGE=%s", args.toArray());
    }

    private static String normalizeKey(Object key) {
        return key.toString().toUpperCase().replaceAll("[^_A-Z0-9]", "_");
    }

    public void setSyslogIdentifier(String syslogIdentifier) {
        this.syslogIdentifier = syslogIdentifier;
    }

    public void setThreadName(boolean threadName) {
        this.threadName = threadName;
    }

    public void setLogger(boolean logger) {
        this.logger = logger;
    }

    public void setAppender(boolean appender) {
        this.appender = appender;
    }

    public void setLocation(boolean location) {
        this.location = location;
    }

    public void setStacktrace(boolean stacktrace) {
        this.stacktrace = stacktrace;
    }

    public void setMdc(boolean mdc) {
        this.mdc = mdc;
    }

    public void setMdcPrefix(String mdcPrefix) {
        this.mdcPrefix = normalizeKey(mdcPrefix);
    }

}
bwaldvogel commented 8 years ago

You didn’t answer the question why the LOG4J_LOGGER value doesn’t work for you?

iciclespider commented 8 years ago

The LOG4J_LOGGER value is the name of the logger the code obtains and is determined in the Java code. The appender value is the organization created in the log4j configuration. I want to group items as determined in my log4j configuration, not as determined by the Java code.

bwaldvogel commented 8 years ago

Oh, I see. Stupid me, sorry. I’m going to add the new value soon.

bwaldvogel commented 8 years ago

Pushed on both branches. Can you (re)test?

iciclespider commented 8 years ago

Ship It!

bwaldvogel commented 8 years ago

Done: Version 2.2.1 and 1.3.1.