/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation: version 3 of * the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package org.ow2.proactive.scheduler.task; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.io.FileUtils; import org.apache.log4j.Appender; import org.apache.log4j.FileAppender; import org.apache.log4j.Logger; import org.apache.log4j.MDC; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.LoggingEvent; import org.ow2.proactive.scheduler.common.task.Log4JTaskLogs; import org.ow2.proactive.scheduler.common.task.TaskId; import org.ow2.proactive.scheduler.common.task.TaskLogs; import org.ow2.proactive.scheduler.common.util.TaskLoggerRelativePathGenerator; import org.ow2.proactive.scheduler.common.util.logforwarder.AppenderProvider; import org.ow2.proactive.scheduler.common.util.logforwarder.LogForwardingException; import org.ow2.proactive.scheduler.common.util.logforwarder.appenders.AsyncAppenderWithStorage; import org.ow2.proactive.scheduler.common.util.logforwarder.util.LoggingOutputStream; public class TaskLogger { private static final Logger logger = Logger.getLogger(TaskLogger.class); private static final String MAX_LOG_SIZE_PROPERTY = "pas.launcher.logs.maxsize"; // default log size, counted in number of log events private static final int DEFAULT_LOG_MAX_SIZE = 1024; private static final String FILE_APPENDER_NAME = "TASK_LOGGER_FILE_APPENDER"; private AsyncAppenderWithStorage taskLogAppender; private TaskId taskId; private String hostname; private final PrintStream outputSink; private final PrintStream errorSink; private final AtomicBoolean loggersFinalized = new AtomicBoolean(false); private final AtomicBoolean loggersActivated = new AtomicBoolean(false); public TaskLogger(TaskId taskId, String hostname) { logger.debug("Create task logger"); this.taskId = taskId; this.hostname = hostname; Logger taskLogger = createLog4jLogger(taskId); outputSink = new PrintStream(new LoggingOutputStream(taskLogger, Log4JTaskLogs.STDOUT_LEVEL), true); errorSink = new PrintStream(new LoggingOutputStream(taskLogger, Log4JTaskLogs.STDERR_LEVEL), true); } private Logger createLog4jLogger(TaskId taskId) { LogLog.setQuietMode(true); // error about log should not be logged Logger taskLogger = Logger.getLogger(Log4JTaskLogs.JOB_LOGGER_PREFIX + taskId.getJobId() + "." + taskId.value()); taskLogger.setLevel(Log4JTaskLogs.STDOUT_LEVEL); taskLogger.setAdditivity(false); resetLogContextForImmediateService(); taskLogger.removeAllAppenders(); taskLogAppender = new AsyncAppenderWithStorage(getLogMaxSize(taskId)); taskLogger.addAppender(taskLogAppender); return taskLogger; } private int getLogMaxSize(TaskId taskId) { String logMaxSizeProp = System.getProperty(MAX_LOG_SIZE_PROPERTY); int logMaxSize = DEFAULT_LOG_MAX_SIZE; if (logMaxSizeProp != null && !logMaxSizeProp.isEmpty()) { try { logMaxSize = Integer.parseInt(logMaxSizeProp); } catch (NumberFormatException e) { logger.warn(MAX_LOG_SIZE_PROPERTY + " property is not correctly defined. Logs size is bounded to default value " + DEFAULT_LOG_MAX_SIZE + " for task " + taskId, e); } } return logMaxSize; } public TaskLogs getLogs() { return new Log4JTaskLogs(taskLogAppender.getStorage(), this.taskId.getJobId().value()); } public File createFileAppender(File pathToFolder) throws IOException { if (taskLogAppender.getAppender(FILE_APPENDER_NAME) != null) { throw new IllegalStateException("Only one file appender can be created"); } File logFile = new File(pathToFolder, new TaskLoggerRelativePathGenerator(taskId).getRelativePath()); logFile.getParentFile().mkdirs(); FileUtils.touch(logFile); logFile.setWritable(true, false); FileAppender fap = new FileAppender(Log4JTaskLogs.getTaskLogLayout(), logFile.getAbsolutePath(), false); fap.setName(FILE_APPENDER_NAME); taskLogAppender.addAppender(fap); return logFile; } public PrintStream getOutputSink() { return outputSink; } public PrintStream getErrorSink() { return errorSink; } public void activateLogs(AppenderProvider logSink) { logger.info("Activating logs for task " + this.taskId + " (" + taskId.getReadableName() + ")"); if (this.loggersActivated.get()) { logger.info("Logs for task " + this.taskId + " are already activated"); return; } this.loggersActivated.set(true); // create appender Appender appender; try { appender = logSink.getAppender(); } catch (LogForwardingException e) { logger.error("Cannot create log appender.", e); return; } // fill appender if (!this.loggersFinalized.get()) { taskLogAppender.addAppender(appender); } else { logger.info("Logs for task " + this.taskId + " are closed. Flushing buffer..."); // Everything is closed: reopen and close... for (LoggingEvent e : taskLogAppender.getStorage()) { appender.doAppend(e); } appender.close(); this.loggersActivated.set(false); return; } logger.info("Activated logs for task " + this.taskId); } public void getStoredLogs(AppenderProvider logSink) { Appender appender; try { appender = logSink.getAppender(); } catch (LogForwardingException e) { logger.error("Cannot create log appender.", e); return; } taskLogAppender.appendStoredEvents(appender); } // need to reset MDC because calling thread is not active thread (immediate service) public void resetLogContextForImmediateService() { MDC.put(Log4JTaskLogs.MDC_JOB_ID, this.taskId.getJobId().value()); MDC.put(Log4JTaskLogs.MDC_TASK_ID, this.taskId.value()); MDC.put(Log4JTaskLogs.MDC_TASK_NAME, this.taskId.getReadableName()); MDC.put(Log4JTaskLogs.MDC_HOST, hostname); } public void close() { synchronized (this.loggersFinalized) { if (!loggersFinalized.get()) { logger.debug("Terminating loggers for task " + this.taskId + " (" + taskId.getReadableName() + ")" + "..."); this.flushStreams(); this.loggersFinalized.set(true); this.loggersActivated.set(false); removeTaskLogFile(); // Unhandle loggers if (taskLogAppender != null) { taskLogAppender.close(); } logger.debug("Task logger closed"); } } } private void removeTaskLogFile() { FileAppender fileAppender = (FileAppender) taskLogAppender.getAppender(FILE_APPENDER_NAME); if (fileAppender != null && fileAppender.getFile() != null) { FileUtils.deleteQuietly(new File(fileAppender.getFile())); } } private void flushStreams() { this.outputSink.flush(); this.errorSink.flush(); } }