1

如何使用 log4j2 将消息记录到 Jenkins 管道作业控制台输出(在作业运行时)?

通过控制台输出,我的意思是从通常找到的作业输出的文本日志:

  • http://localhost:8080/job/<Job Name>/<Job Run Number>/console
  • C:\Program Files (x86)\Jenkins\jobs\<Job Name>\builds\<Job Run Number>\log

例如,从使用共享库的 Jenkins 管道作业中,调用 log4j2 的类Logger.info()

package mypackage

import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.LogManager

@Grab(group = "org.apache.logging.log4j", module = "log4j-api", version = "2.8.2")
public class MyJobClass {
    // Logger
    private static final Logger logger = LogManager.getLogger(MyJobClass.class)

    public void execute(def script) { // 'script' here is 'this' from within the pipeline script such as in the shared libraries example.

        // This will appear in the job console output.
        script.println("foo")

        // This will appear in files and stdout as defined in the log4j2 configuration file, but not the job console output.
        logger.info("bar")
    }
}

理想情况下,我希望能够在运行时设置额外的 log4j2 配置以添加针对当前正在运行的作业的控制台输出流的“附加程序”。

我计划尝试的一件事是直接从 log4j2 附加到C:\Program Files (x86)\Jenkins\jobs\<Job Name>\builds\<Job Run Number>\log文件,这是我必须在运行时设置的配置。但是,我不知道这与 Jenkins 的控制台输出视图的兼容性如何,或者 Jenkins 在作业执行期间是否锁定了文件,或者在 Jenkins 写入文件的同时是否会出现未知问题。

4

1 回答 1

1

为了让 log4j2 登录到 Jenkins 作业控制台输出,我创建了一个 Logger 包装器,它调用script.echo().

注意:此代码位于Groovy中。

日志管理器:

package myApplication.logging

import com.cloudbees.groovy.cps.NonCPS
import my.util.PathUtils
import my.util.StringUtils
import org.apache.logging.log4j.core.Appender
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.appender.FileAppender
import org.apache.logging.log4j.core.config.Configuration
import org.apache.logging.log4j.core.impl.Log4jLogEvent
import org.apache.logging.log4j.core.layout.PatternLayout
import org.apache.logging.log4j.message.SimpleMessage

/**
 * Log manager.
 */
@Grapes([
    @Grab(group = "org.apache.logging.log4j", module = "log4j-api", version = "2.8.2", initClass = true),
    @Grab(group = "org.apache.logging.log4j", module = "log4j-core", version = "2.8.2", initClass = true),
    @Grab(group = "org.apache.logging.log4j", module = "log4j-web", version = "2.8.2", initClass = true)
])
public class LogManager {

    /** The script object. */
    private static def script

    /** Initialised flag. */
    private static boolean initialised = false

    /** Layout object containing the log format string. */
    private static PatternLayout layout

    /** Jenkins job console output log level. */
    private static Level logLevel = Level.ALL

    /**
     * Initialise the logger with the script object.
     * This allows loading of the log4j settings file and adds the Jenkins job console output appender.
     * Called in JeevesJobTemplate.vm and BuildMyJobsJeeves.
     *
     * @param script The script object.
     * @param logLevel Jenkins job console output log level.
     */
    @NonCPS
    public static void initialise(def script, Level logLevel) {
        if (!script) throw new IllegalArgumentException("script object cannot be null.")
        if (initialised) throw new IllegalStateException("LogManager.initialise() was called more than once.")

        this.script = script
        this.logLevel = logLevel

        // Deal with the 'WARN Unable to instantiate org.fusesource.jansi.WindowsAnsiOutputStream' message.
        System.setProperty("log4j.skipJansi", "true")

        final LoggerContext context = LoggerContext.getContext(false)

        // Set the configuration file.
        context.setConfigLocation(new File("${PathUtils.getResourcePath(script)}/log4j2.json").toURI())

        final Configuration configuration = context.configuration

        // Get 'logFormat' property from the log4j2.json configuration file.
        final String logFormat = configuration.getStrSubstitutor().getVariableResolver().lookup("logFormat")
        layout = PatternLayout.newBuilder().withPattern(logFormat).build()

        // Add job file appender.
        final Appender jobFileAppender = FileAppender.newBuilder()
            .withName("Job File")
            .withFileName("${PathUtils.getJobPath(script)}/Jeeves.log")
            .withLayout(layout)
            .build()
        addAppender(configuration, jobFileAppender)

        // Remove 'Console' appender because Logger will log to the Jenkins job console.
        configuration.rootLogger.removeAppender("Console")

        initialised = true
    }

    /**
     * Helper method to get a Logger without having to import or grab grapes.
     *
     * @param clazz Class to log data from.
     * @return Log4j2 Logger object.
     */
    @NonCPS
    public static Logger getLogger(Class<?> clazz) {
        if (!clazz) throw new IllegalArgumentException("clazz cannot be null.")

        return new Logger(clazz)
    }

    /**
     * Log a copy of a log4j message to the Jenkins job console.
     *
     * @param loggerName Name of the logger, typically the class from which the logger was initialised.
     * @param level Log level.
     * @param message Message to log.
     */
    @NonCPS
    public static void log(String loggerName, Level level, String message) {
        if (!initialised) throw new IllegalStateException("LogManager is not initialised.")

        if (level <= logLevel) {
            final Log4jLogEvent event = Log4jLogEvent.newBuilder().setLoggerName(loggerName).setLevel(level.toLog4jLevel()).setMessage(new SimpleMessage(message)).build()
            final String logMessage = layout.toSerializable(event)
            script.echo(logMessage.substring(0, logMessage.length() - StringUtils.LINE_SEPARATOR.length()))
        }
    }

    /**
     * Add appender to log4j2 configuration.
     *
     * @param configuration Log4j2 configuration object.
     * @param appender Log4j2 appender to add to the configuration.
     */
    @NonCPS
    private static void addAppender(Configuration configuration, Appender appender) {
        if (!configuration) throw new IllegalArgumentException("configuration cannot be null.")
        if (!appender) throw new IllegalArgumentException("appender cannot be null.")

        appender.start()
        configuration.addAppender(appender)
        configuration.rootLogger.addAppender(appender, null, null)
    }
}

记录仪:

package myApplication.logging

import com.cloudbees.groovy.cps.NonCPS

/**
 * Logger wrapper for log4j2's Logger class.
 * Needed to populate the Jenkins job console output.
 */
public class Logger implements Serializable {

    /** Log4j2 Logger object. */
    private org.apache.logging.log4j.Logger logger

    /** Logger constructor. */
    public Logger(Class<?> clazz) {
        logger = org.apache.logging.log4j.LogManager.getLogger(clazz)
    }

    /**
     * Log debug level message.
     * @param message Message to log.
     */
    @NonCPS
    public void debug(String message) {
        logger.debug(message)
        LogManager.log(logger.name, Level.DEBUG, message)
    }

    /**
     * Log error level message.
     * @param message Message to log.
     */
    @NonCPS
    public void error(String message) {
        logger.error(message)
        LogManager.log(logger.name, Level.ERROR, message)
    }

    /**
     * Log fatal level message.
     * @param message Message to log.
     */
    @NonCPS
    public void fatal(String message) {
        logger.fatal(message)
        LogManager.log(logger.name, Level.FATAL, message)
    }

    /**
     * Log info level message.
     * @param message Message to log.
     */
    @NonCPS
    public void info(String message) {
        logger.info(message)
        LogManager.log(logger.name, Level.INFO, message)
    }

    /**
     * Log a message at the supplied level.
     * @param level Level to log the message with.
     * @param message Message to log.
     */
    @NonCPS
    public void log(Level level, String message) {
        logger.log(level.toLog4jLevel(), message)
        LogManager.log(logger.name, level, message)
    }

    /**
     * Log trace level message.
     * @param message Message to log.
     */
    @NonCPS
    public void trace(String message) {
        logger.trace(message)
        LogManager.log(logger.name, Level.TRACE, message)
    }

    /**
     * Log warn level message.
     * @param message Message to log.
     */
    @NonCPS
    public void warn(String message) {
        logger.warn(message)
        LogManager.log(logger.name, Level.WARN, message)
    }
}

等级:

package my.logging

import com.cloudbees.groovy.cps.NonCPS
import org.apache.logging.log4j.Level as Log4jLevel

/**
 * Log levels.
 * Do not change the order of the enumeration elements.
 */
public enum Level implements Serializable {
    OFF(Log4jLevel.OFF),
    FATAL(Log4jLevel.FATAL),
    ERROR(Log4jLevel.ERROR),
    WARN(Log4jLevel.WARN),
    INFO(Log4jLevel.INFO),
    DEBUG(Log4jLevel.DEBUG),
    TRACE(Log4jLevel.TRACE),
    ALL(Log4jLevel.ALL)

    private final Log4jLevel level

    /**
     * Level constructor.
     * @param level Log4j level.
     */
    Level(Log4jLevel level) {
        this.level = level
    }

    /**
     * Get equivalent Log4j level.
     * @return Equivalent Log4j level.
     */
    @NonCPS
    public Log4jLevel toLog4jLevel() {
        return level
    }
}

然后,在初始化期间,调用LogManager.initialisation(script, Level.DEBUG)或您的 Jenkins 输出日志级别应该是什么。

于 2017-07-12T17:25:18.133 回答