如何使用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
例如,在使用shared libraries的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配置,以添加一个“appender”,目标是当前正在运行的作业的控制台输出流。
我计划尝试的一件事是直接从log4j2附加到C:\Program Files (x86)\Jenkins\jobs\<Job Name>\builds\<Job Run Number>\log
文件,这是我在运行时必须设置的配置。但是,我不知道这与Jenkin的控制台输出视图的兼容性如何,或者Jenkins在作业执行期间是否锁定文件,或者是否在Jenkins同时写入文件时会出现未知问题。
答案 0 :(得分: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)
或Jenkins输出日志级别应该是什么。