/** * The MIT License * Copyright © 2010 JmxTrans team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.googlecode.jmxtrans.model.output; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.googlecode.jmxtrans.model.Query; import com.googlecode.jmxtrans.model.Result; import com.googlecode.jmxtrans.model.Server; import com.googlecode.jmxtrans.model.ValidationException; import com.googlecode.jmxtrans.model.naming.KeyUtils; import org.apache.log4j.Appender; import org.apache.log4j.LogManager; import org.apache.log4j.PatternLayout; import org.apache.log4j.RollingFileAppender; import org.apache.log4j.spi.LoggerFactory; import org.slf4j.Logger; import org.slf4j.impl.Log4jLoggerFactory; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import static com.googlecode.jmxtrans.util.NumberUtils.isNumeric; /** * Writes out data in the same format as the GraphiteWriter, except to a file * and tab delimited. Takes advantage of Log4J RollingFileAppender to * automatically handle rolling the files after they reach a certain size. * <p/> * The default max size of the log files are 10MB (maxLogFileSize) The default * number of rolled files to keep is 200 (maxLogBackupFiles) * * @author jon */ public class KeyOutWriter extends BaseOutputWriter { protected static final Log4jLoggerFactory log4jLoggerFactory = new Log4jLoggerFactory(); protected static final String SETTING_MAX_LOG_FILE_SIZE = "maxLogFileSize"; protected static final String SETTING_MAX_BACK_FILES = "maxLogBackupFiles"; protected static final String SETTING_DELIMITER = "delimiter"; protected static final String LOG_PATTERN = "%m%n"; protected static final int LOG_IO_BUFFER_SIZE_BYTES = 1024; protected static final Map<String, Logger> loggers = new ConcurrentHashMap<>(); protected static final int MAX_LOG_BACKUP_FILES = 200; protected static final String MAX_LOG_FILE_SIZE = "10MB"; protected static final String DEFAULT_DELIMITER = "\t"; protected Logger logger; private final String outputFile; private final String maxLogFileSize; private final int maxLogBackupFiles; private final String delimiter; @JsonCreator public KeyOutWriter( @JsonProperty("typeNames") ImmutableList<String> typeNames, @JsonProperty("booleanAsNumber") boolean booleanAsNumber, @JsonProperty("debug") Boolean debugEnabled, @JsonProperty("outputFile") String outputFile, @JsonProperty("maxLogFileSize") String maxLogFileSize, @JsonProperty("maxLogBackupFiles") Integer maxLogBackupFiles, @JsonProperty("delimiter") String delimiter, @JsonProperty("settings") Map<String, Object> settings) { super(typeNames, booleanAsNumber, debugEnabled, settings); this.outputFile = MoreObjects.firstNonNull( outputFile, (String) getSettings().get("outputFile")); this.maxLogFileSize = firstNonNull( maxLogFileSize, (String) getSettings().get(SETTING_MAX_LOG_FILE_SIZE), MAX_LOG_FILE_SIZE); this.maxLogBackupFiles = firstNonNull( maxLogBackupFiles, (Integer) getSettings().get(SETTING_MAX_BACK_FILES), MAX_LOG_BACKUP_FILES); this.delimiter = firstNonNull( delimiter, (String) getSettings().get(SETTING_DELIMITER), DEFAULT_DELIMITER ); } /** * Creates the logging */ @Override public void validateSetup(Server server, Query query) throws ValidationException { // Check if we've already created a logger for this file. If so, use it. if (loggers.containsKey(outputFile)) { logger = loggers.get(outputFile); return; } // need to create a logger try { logger = initLogger(outputFile); loggers.put(outputFile, logger); } catch (IOException e) { throw new ValidationException("Failed to setup log4j", query, e); } } /** * The meat of the output. Very similar to GraphiteWriter. */ @Override public void internalWrite(Server server, Query query, ImmutableList<Result> results) throws Exception { List<String> typeNames = getTypeNames(); for (Result result : results) { Map<String, Object> resultValues = result.getValues(); for (Entry<String, Object> values : resultValues.entrySet()) { if (isNumeric(values.getValue())) { logger.info(KeyUtils.getKeyString(server, query, result, values, typeNames, null) + delimiter + values.getValue().toString() + delimiter + result.getEpoch()); } } } } /** * Initializes the logger. This is called when we need to create a new * logger for the given file name. * * @param fileStr * @return a new Logger instance for the given fileStr * @throws IOException */ protected Logger initLogger(String fileStr) throws IOException { Appender appender = buildLog4jAppender(fileStr, getMaxLogFileSize(), getMaxLogBackupFiles()); LoggerFactory loggerFactory = buildLog4jLoggerFactory(appender); String loggerKey = buildLoggerName(); // Create the logger and add to the map of loggers using our factory LogManager.getLogger(loggerKey, loggerFactory); return log4jLoggerFactory.getLogger(loggerKey); } protected String buildLoggerName() { return "KeyOutWriter" + this.hashCode(); } protected Appender buildLog4jAppender( String fileStr, String maxLogFileSize, Integer maxLogBackupFiles) throws IOException { PatternLayout pl = new PatternLayout(LOG_PATTERN); final RollingFileAppender appender = new RollingFileAppender(pl, fileStr, true); appender.setImmediateFlush(true); appender.setBufferedIO(false); appender.setBufferSize(LOG_IO_BUFFER_SIZE_BYTES); appender.setMaxFileSize(maxLogFileSize); appender.setMaxBackupIndex(maxLogBackupFiles); return appender; } protected LoggerFactory buildLog4jLoggerFactory(final Appender appender) { return new LoggerFactory() { @Override public org.apache.log4j.Logger makeNewLoggerInstance(String name) { org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(name); logger.addAppender(appender); logger.setLevel(org.apache.log4j.Level.INFO); logger.setAdditivity(false); return logger; } }; } public String getMaxLogFileSize() { return maxLogFileSize; } public Integer getMaxLogBackupFiles() { return maxLogBackupFiles; } public String getDelimiter() { return delimiter; } public String getOutputFile() { return outputFile; } }