/* * Copyright © 2015 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.etl.log; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * Sets up a LogAppender that will inject the ETL stage name into the log message. */ public class LogStageInjector { private static final Logger LOG = LoggerFactory.getLogger(LogStageInjector.class); private static final AtomicBoolean initialized = new AtomicBoolean(false); private LogStageInjector() { } /** * Hijacks the appenders for the root logger and replaces them with a {@link LogStageAppender} that will insert * the ETL stage name at the start of each message if the stage name is set. Uses {@link org.slf4j.MDC} to look up * the current stage name. */ public static void start() { if (!initialized.compareAndSet(false, true)) { return; } LogContext.enable(); ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); if (!(loggerFactory instanceof LoggerContext)) { LOG.warn("LoggerFactory is not a logback LoggerContext. ETLStage names will not be injected into log messages."); return; } LoggerContext loggerContext = (LoggerContext) loggerFactory; ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); List<Appender<ILoggingEvent>> appenders = new ArrayList<>(); Iterator<Appender<ILoggingEvent>> appenderIterator = rootLogger.iteratorForAppenders(); while (appenderIterator.hasNext()) { Appender<ILoggingEvent> appender = appenderIterator.next(); // this is only for cdap standalone, where the same jvm launches multiple mapreduce jobs // in those scenarios, the second time a program runs and gets to this part of the code, // the root logger will already have the LogStageAppender as its appender. In that case, just return immediately. // hack... can't do (appender instanceof LogStageAppender) here because // appender will have a different classloader than LogStageAppender... if (appender.getClass().getName().equals(LogStageAppender.class.getName())) { return; } appenders.add(appender); } Appender<ILoggingEvent> stageAppender = new LogStageAppender(appenders); stageAppender.setContext(loggerContext); stageAppender.start(); rootLogger.addAppender(stageAppender); // the LogStageAppender calls the original appenders. // To avoid duplicate messages, need to detach the original appenders for (Appender<ILoggingEvent> appender : appenders) { rootLogger.detachAppender(appender); } } }