/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.logging;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.i18n.LogWriterI18n;
import org.apache.geode.internal.OSProcess;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.io.MainWithChildrenRollingFileHandler;
import org.apache.geode.internal.io.RollingFileHandler;
import org.apache.geode.internal.logging.log4j.AlertAppender;
import org.apache.geode.internal.util.LogFileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Date;
/**
* Implementation of {@link LogWriterI18n} for distributed system members. It's just like
* {@link LocalLogWriter} except it has support for rolling and alerts.
*
* @since Geode 1.0
*/
public class ManagerLogWriter extends LocalLogWriter {
public static final String TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY =
DistributionConfig.GEMFIRE_PREFIX + "logging.test.fileSizeLimitInKB";
private final boolean fileSizeLimitInKB;
private final RollingFileHandler rollingFileHandler;
private LogConfig cfg = null;
private LocalLogWriter mainLogger = this;
private File logDir = null;
private int mainLogId = -1;
private int childId = 0;
private boolean useChildLogging = false;
/**
* Set to true when roll is in progress
*/
private boolean rolling = false;
private boolean mainLog = true;
private File activeLogFile = null;
private boolean started = false;
/**
* Creates a writer that logs to <code>System.out</code>.
*
* @param level only messages greater than or equal to this value will be logged.
* @throws IllegalArgumentException if level is not in legal range
*/
public ManagerLogWriter(int level, PrintStream out) {
this(level, out, null);
}
/**
* Creates a writer that logs to <code>System.out</code>.
*
* @param level only messages greater than or equal to this value will be logged.
* @param connectionName Name of the connection associated with this logger
*
* @throws IllegalArgumentException if level is not in legal range
*
* @since GemFire 3.5
*/
public ManagerLogWriter(int level, PrintStream out, String connectionName) {
super(level, out, connectionName);
this.fileSizeLimitInKB = Boolean.getBoolean(TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY);
this.rollingFileHandler = new MainWithChildrenRollingFileHandler();
}
/**
* Sets the config that should be used by this manager to decide how to manage its logging.
*/
public void setConfig(LogConfig cfg) {
this.cfg = cfg;
configChanged();
}
/**
* Call when the config changes at runtime.
*/
public void configChanged() {
setLevel(cfg.getLogLevel());
useChildLogging = cfg.getLogFile() != null && !cfg.getLogFile().equals(new File(""))
&& cfg.getLogFileSizeLimit() != 0;
if (useChildLogging()) {
logDir = rollingFileHandler.getParentFile(this.cfg.getLogFile());
// let archive id always follow main log id, not the vice versa
// e.g. in getArchiveName(), it's using mainArchiveId = calcNextMainId(archiveDir);
// This is the only place we assign mainLogId.
// mainLogId is only referenced when useChildLogging==true
mainLogId = rollingFileHandler.calcNextMainId(logDir, true);
}
if (started) {
if (useChildLogging()) {
if (mainLog) {
rollLog();
}
} else {
switchLogs(this.cfg.getLogFile(), true);
}
}
}
public File getChildLogFile() {
return this.activeLogFile;
}
public File getLogDir() {
return this.logDir;
}
public int getMainLogId() {
return this.mainLogId;
}
private File getNextChildLogFile() {
String path = this.cfg.getLogFile().getPath();
int extIdx = path.lastIndexOf('.');
String ext = "";
if (extIdx != -1) {
ext = path.substring(extIdx);
path = path.substring(0, extIdx);
}
path = path + rollingFileHandler.formatId(mainLogId) + rollingFileHandler.formatId(this.childId)
+ ext;
this.childId++;
File result = new File(path);
if (result.exists()) {
// try again until a unique name is found
return getNextChildLogFile();
} else {
return result;
}
}
public boolean useChildLogging() {
return this.useChildLogging;
}
private long getLogFileSizeLimit() {
if (rolling || mainLog) {
return Long.MAX_VALUE;
} else {
long result = cfg.getLogFileSizeLimit();
if (result == 0) {
return Long.MAX_VALUE;
}
if (this.fileSizeLimitInKB) {
// use KB instead of MB to speed up log rolling for test purpose
return result * (1024);
} else {
return result * (1024 * 1024);
}
}
}
private long getLogDiskSpaceLimit() {
long result = cfg.getLogDiskSpaceLimit();
return result * (1024 * 1024);
}
private String getMetaLogFileName(String baseLogFileName, int mainLogId) {
String metaLogFile = null;
int extIdx = baseLogFileName.lastIndexOf('.');
String ext = "";
if (extIdx != -1) {
ext = baseLogFileName.substring(extIdx);
metaLogFile = baseLogFileName.substring(0, extIdx);
}
String fileName = new File(metaLogFile).getName();
String parent = new File(metaLogFile).getParent();
metaLogFile = "meta-" + fileName + rollingFileHandler.formatId(mainLogId) + ext;
if (parent != null) {
metaLogFile = parent + File.separator + metaLogFile;
}
return metaLogFile;
}
private synchronized void switchLogs(File newLog, boolean newIsMain) {
rolling = true;
try {
try {
if (!newIsMain) {
if (mainLog && mainLogger == this && this.cfg.getLogFile() != null) {
// this is the first child. Save the current output stream
// so we can log special messages to the main log instead
// of the current child log.
String metaLogFile =
getMetaLogFileName(this.cfg.getLogFile().getPath(), this.mainLogId);
mainLogger = new LocalLogWriter(INFO_LEVEL,
new PrintStream(new FileOutputStream(metaLogFile, true), true));
if (activeLogFile == null) {
mainLogger.info(LocalizedStrings.ManagerLogWriter_SWITCHING_TO_LOG__0,
this.cfg.getLogFile());
}
} else {
mainLogger.info(LocalizedStrings.ManagerLogWriter_ROLLING_CURRENT_LOG_TO_0, newLog);
}
}
boolean renameOK = true;
String oldName = this.cfg.getLogFile().getAbsolutePath();
File tmpFile = null;
if (this.activeLogFile != null) {
// @todo gregp: is a bug that we get here and try to rename the activeLogFile
// to a newLog of the same name? This works ok on Unix but on windows fails.
if (!this.activeLogFile.getAbsolutePath().equals(newLog.getAbsolutePath())) {
boolean isWindows = false;
String os = System.getProperty("os.name");
if (os != null) {
if (os.indexOf("Windows") != -1) {
isWindows = true;
}
}
if (isWindows) {
// For windows to work we need to redirect everything
// to a temporary file so we can get oldFile closed down
// so we can rename it. We don't actually write to this tmp file
File tmpLogDir = rollingFileHandler.getParentFile(this.cfg.getLogFile());
tmpFile = File.createTempFile("mlw", null, tmpLogDir);
// close the old guy down before we do the rename
PrintStream tmpps = OSProcess.redirectOutput(tmpFile,
AlertAppender.getInstance().isAlertingDisabled()/* See #49492 */);
PrintWriter oldPW = this.setTarget(new PrintWriter(tmpps, true));
if (oldPW != null) {
oldPW.close();
}
}
File oldFile = this.activeLogFile;
renameOK = LogFileUtils.renameAggressively(oldFile, newLog.getAbsoluteFile());
if (!renameOK) {
mainLogger
.warning("Could not delete original file '" + oldFile + "' after copying to '"
+ newLog.getAbsoluteFile() + "'. Continuing without rolling.");
} else {
renameOK = true;
}
}
}
this.activeLogFile = new File(oldName);
// Don't redirect sysouts/syserrs to client log file. See #49492.
// IMPORTANT: This assumes that only a loner would have sendAlert set to false.
PrintStream ps = OSProcess.redirectOutput(activeLogFile,
AlertAppender.getInstance().isAlertingDisabled());
PrintWriter oldPW = this.setTarget(new PrintWriter(ps, true), this.activeLogFile.length());
if (oldPW != null) {
oldPW.close();
}
if (tmpFile != null) {
tmpFile.delete();
}
mainLog = newIsMain;
if (mainLogger == null) {
mainLogger = this;
}
if (!renameOK) {
mainLogger.warning("Could not rename \"" + this.activeLogFile + "\" to \"" + newLog
+ "\". Continuing without rolling.");
}
} catch (IOException ex) {
mainLogger.warning("Could not open log \"" + newLog + "\" because " + ex);
}
checkDiskSpace(this.activeLogFile);
} finally {
rolling = false;
}
}
/** notification from manager that the output file is being closed */
public void closingLogFile() {
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
// --> /dev/null
}
};
close();
if (mainLogger != null) {
mainLogger.close();
}
PrintWriter pw = this.setTarget(new PrintWriter(out, true));
if (pw != null) {
pw.close();
}
}
public static File getLogNameForOldMainLog(File log, boolean useOldFile) {
/*
* this is just searching for the existing logfile name we need to search for meta log file name
*
*/
RollingFileHandler rollingFileHandler = new MainWithChildrenRollingFileHandler();
File dir = rollingFileHandler.getParentFile(log.getAbsoluteFile());
int previousMainId = rollingFileHandler.calcNextMainId(dir, true);
if (useOldFile) {
if (previousMainId > 0) {
previousMainId--;
}
}
if (previousMainId == 0) {
previousMainId = 1;
}
File result = null;
int childId = rollingFileHandler.calcNextChildId(log, previousMainId > 0 ? previousMainId : 0);
StringBuffer buf = new StringBuffer(log.getPath());
int insertIdx = buf.lastIndexOf(".");
if (insertIdx == -1) {
buf.append(rollingFileHandler.formatId(previousMainId))
.append(rollingFileHandler.formatId(childId));
} else {
buf.insert(insertIdx, rollingFileHandler.formatId(childId));
buf.insert(insertIdx, rollingFileHandler.formatId(previousMainId));
}
result = new File(buf.toString());
return result;
}
private void checkDiskSpace(File newLog) {
rollingFileHandler.checkDiskSpace("log", newLog, getLogDiskSpaceLimit(), logDir, mainLogger);
}
public void rollLog() {
rollLog(false);
}
private void rollLogIfFull() {
rollLog(true);
}
private void rollLog(boolean ifFull) {
if (!useChildLogging()) {
return;
}
synchronized (this) {
// need to do the activeLogFull call while synchronized
if (ifFull && !activeLogFull()) {
return;
}
switchLogs(getNextChildLogFile(), false);
}
}
/**
* Called when manager is done starting up. This is when a child log will be started if rolling is
* configured.
*/
public void startupComplete() {
started = true;
rollLog();
}
/**
* Called when manager starts shutting down. This is when any current child log needs to be closed
* and the rest of the logging reverted to the main.
*/
public void shuttingDown() {
if (useChildLogging()) {
switchLogs(this.cfg.getLogFile(), true);
}
}
private boolean activeLogFull() {
long limit = getLogFileSizeLimit();
if (limit == Long.MAX_VALUE) {
return false;
} else {
return getBytesLogged() >= limit;
}
}
@Override
public String put(int msgLevel, Date msgDate, String connectionName, String threadName, long tid,
String msg, String exceptionText) {
String result = null; // This seems to workaround a javac bug
result = super.put(msgLevel, msgDate, connectionName, threadName, tid, msg, exceptionText);
return result;
}
@Override
public void writeFormattedMessage(String s) {
rollLogIfFull();
super.writeFormattedMessage(s);
}
}