2017-07-11 103 views
0

如何使用log4j2將消息記錄到Jenkins管道作業控制檯輸出中(作業正在運行時)?Log4j2寫入Jenkins管道作業控制檯輸出?

通過控制檯輸出,我的意思是日誌從一個作業輸出的文本通常發現:使用shared libraries,一個

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

例如,從詹金斯流水線作業類呼叫log4j2's 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配置,以添加一個定位當前運行作業的控制檯輸出流的「appender」。

我打算嘗試的一件事是直接從log4j2追加到C:\Program Files (x86)\Jenkins\jobs\<Job Name>\builds\<Job Run Number>\log文件,這是我在運行時必須設置的配置。但是,我不知道Jenkin的控制檯輸出視圖兼容性如何,或者如果Jenkins在作業執行期間鎖定文件,或者Jenkins同時寫入文件會產生未知問題。

+0

我試着創建一個自定義的Writer,它給了Jenkins管道'腳本'對象,然後用WriterAppender添加Writer,但是這不起作用。 我也嘗試創建一個自定義的Appender,它也採取了'腳本'對象,但這也沒有工作。看起來像log4j2使用的鎖導致當前的Jenkins線程退出。 – Daniel

回答

0

爲了讓log4j2登錄到Jenkins作業控制檯輸出,我創建了一個調用script.echo()的Logger包裝器。

注意:此代碼位於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)或任何你詹金斯輸出日誌水平應該。

+0

看起來像有趣的解決方案,但我看到導入錯誤,在下面,看起來像自定義庫? 'import ea.jeeves.util.PathUtils import ea.jeeves.util.StringUtils' – Balkrishna

+0

對不起@Balkrishna這是我使用的外部庫。你可以推斷出那些做了什麼,並用「我的」命名空間替換了它們。 – Daniel

+0

感謝您的快速回復也請原諒我的無知什麼是腳本對象在這裏? – Balkrishna