blob: 326b2d133b3c59b2ffd9e5040b34bc221623dcf1 [file] [log] [blame]
/*
* 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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import org.apache.geode.LogWriter;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.internal.OSProcess;
import org.apache.geode.internal.io.MainWithChildrenRollingFileHandler;
import org.apache.geode.internal.io.RollingFileHandler;
import org.apache.geode.internal.util.LogFileUtils;
import org.apache.geode.logging.internal.spi.LogConfig;
import org.apache.geode.logging.internal.spi.LogFileDetails;
/**
* Implementation of {@link LogWriter} for distributed system members. It's just like {@link
* org.apache.geode.internal.logging.LocalLogWriter} except it has support for rolling and alerts.
*
* @since Geode 1.0
*/
public class ManagerLogWriter extends LocalLogWriter implements LogFileDetails {
private 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 config;
private LocalLogWriter mainLogWriter = this;
private File logDir;
private int mainLogId = -1;
private int childId;
private boolean useChildLogging;
/**
* Set to true when roll is in progress
*/
private boolean rolling;
private boolean mainLog = true;
private File activeLogFile;
private boolean started;
private final boolean loner;
/**
* Creates a writer that logs to <code>printStream</code>.
*
* @param level only messages greater than or equal to this value will be logged.
* @param printStream is the stream that message will be printed to.
* @param loner if the distributed member is not part of a cluster
*
* @throws IllegalArgumentException if level is not in legal range
*/
public ManagerLogWriter(final int level, final PrintStream printStream, final boolean loner) {
this(level, printStream, null, loner);
}
/**
* Creates a writer that logs to <code>printStream</code>.
*
* @param level only messages greater than or equal to this value will be logged.
* @param printStream is the stream that message will be printed to.
* @param connectionName Name of the connection associated with this logger
* @param loner if the distributed member is not part of a cluster
*
* @throws IllegalArgumentException if level is not in legal range
*
* @since GemFire 3.5
*/
public ManagerLogWriter(final int level, final PrintStream printStream,
final String connectionName, final boolean loner) {
super(level, printStream, connectionName);
fileSizeLimitInKB = Boolean.getBoolean(TEST_FILE_SIZE_LIMIT_IN_KB_PROPERTY);
rollingFileHandler = new MainWithChildrenRollingFileHandler();
this.loner = loner;
}
/**
* Sets the config that should be used by this manager to decide how to manage its logging.
*/
public void setConfig(final LogConfig config) {
this.config = config;
configChanged();
}
public LogConfig getConfig() {
return config;
}
/**
* Call when the config changes at runtime.
*/
public void configChanged() {
setLevel(config.getLogLevel());
useChildLogging = config.getLogFile() != null && !config.getLogFile().equals(new File(""))
&& config.getLogFileSizeLimit() != 0;
if (useChildLogging()) {
logDir = rollingFileHandler.getParentFile(config.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(config.getLogFile(), true);
}
}
}
@Override
public File getChildLogFile() {
return activeLogFile;
}
@Override
public File getLogDir() {
return logDir;
}
@Override
public int getMainLogId() {
return mainLogId;
}
@Override
public boolean useChildLogging() {
return useChildLogging;
}
private File getNextChildLogFile() {
String path = config.getLogFile().getPath();
int extIndex = path.lastIndexOf('.');
String ext = "";
if (extIndex != -1) {
ext = path.substring(extIndex);
path = path.substring(0, extIndex);
}
path = path + rollingFileHandler.formatId(mainLogId) + rollingFileHandler.formatId(childId)
+ ext;
childId++;
File result = new File(path);
if (result.exists()) {
// try again until a unique name is found
return getNextChildLogFile();
} else {
return result;
}
}
private long getLogFileSizeLimit() {
if (rolling || mainLog) {
return Long.MAX_VALUE;
} else {
long result = config.getLogFileSizeLimit();
if (result == 0) {
return Long.MAX_VALUE;
}
if (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 = config.getLogDiskSpaceLimit();
return result * (1024 * 1024);
}
private String getMetaLogFileName(final String baseLogFileName, final int mainLogId) {
String metaLogFile = null;
int extIndex = baseLogFileName.lastIndexOf('.');
String ext = "";
if (extIndex != -1) {
ext = baseLogFileName.substring(extIndex);
metaLogFile = baseLogFileName.substring(0, extIndex);
}
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(final File newLog, final boolean newIsMain) {
rolling = true;
try {
try {
if (!newIsMain) {
if (mainLog && mainLogWriter == this && config.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(config.getLogFile().getPath(), mainLogId);
mainLogWriter = new LocalLogWriter(INFO_LEVEL,
new PrintStream(new FileOutputStream(metaLogFile, true), true));
if (activeLogFile == null) {
mainLogWriter.info(String.format("Switching to log %s",
config.getLogFile()));
}
} else {
mainLogWriter.info(String.format("Rolling current log to %s", newLog));
}
}
boolean renameOK = true;
String oldName = config.getLogFile().getAbsolutePath();
File tempFile = null;
if (activeLogFile != null) {
// 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 (!activeLogFile.getAbsolutePath().equals(newLog.getAbsolutePath())) {
boolean isWindows = false;
String os = System.getProperty("os.name");
if (os != null) {
if (os.contains("Windows")) {
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 tempLogDir = rollingFileHandler.getParentFile(config.getLogFile());
tempFile = File.createTempFile("mlw", null, tempLogDir);
// close the old print writer down before we do the rename
// do not redirect if loner -- see #49492
PrintStream tempPrintStream = OSProcess.redirectOutput(tempFile, !loner);
PrintWriter oldPrintWriter = setTarget(new PrintWriter(tempPrintStream, true));
if (oldPrintWriter != null) {
oldPrintWriter.close();
}
}
File oldFile = activeLogFile;
renameOK = LogFileUtils.renameAggressively(oldFile, newLog.getAbsoluteFile());
if (!renameOK) {
mainLogWriter
.warning("Could not delete original file '" + oldFile + "' after copying to '"
+ newLog.getAbsoluteFile() + "'. Continuing without rolling.");
} else {
renameOK = true;
}
}
}
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 printStream = OSProcess.redirectOutput(activeLogFile, !loner);
PrintWriter oldPrintWriter =
setTarget(new PrintWriter(printStream, true), activeLogFile.length());
if (oldPrintWriter != null) {
oldPrintWriter.close();
}
if (tempFile != null) {
tempFile.delete();
}
mainLog = newIsMain;
if (mainLogWriter == null) {
mainLogWriter = this;
}
if (!renameOK) {
mainLogWriter.warning("Could not rename \"" + activeLogFile + "\" to \"" + newLog
+ "\". Continuing without rolling.");
}
} catch (IOException ex) {
mainLogWriter.warning("Could not open log \"" + newLog + "\" because " + ex);
}
checkDiskSpace(activeLogFile);
} finally {
rolling = false;
}
}
/**
* notification from manager that the output file is being closed
*/
public void closingLogFile() {
OutputStream nullOutputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
// --> /dev/null
}
};
close();
if (mainLogWriter != null) {
mainLogWriter.close();
}
PrintWriter printWriter = setTarget(new PrintWriter(nullOutputStream, true));
if (printWriter != null) {
printWriter.close();
}
}
public static File getLogNameForOldMainLog(final File log, final 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;
}
int childId = rollingFileHandler.calcNextChildId(log, previousMainId > 0 ? previousMainId : 0);
StringBuilder sb = new StringBuilder(log.getPath());
int insertIdx = sb.lastIndexOf(".");
if (insertIdx == -1) {
sb.append(rollingFileHandler.formatId(previousMainId))
.append(rollingFileHandler.formatId(childId));
} else {
sb.insert(insertIdx, rollingFileHandler.formatId(childId));
sb.insert(insertIdx, rollingFileHandler.formatId(previousMainId));
}
return new File(sb.toString());
}
private void checkDiskSpace(final File newLog) {
rollingFileHandler.checkDiskSpace("log", newLog, getLogDiskSpaceLimit(), logDir, mainLogWriter);
}
private void rollLog() {
rollLog(false);
}
private void rollLogIfFull() {
rollLog(true);
}
private void rollLog(final 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(config.getLogFile(), true);
}
}
private boolean activeLogFull() {
long limit = getLogFileSizeLimit();
if (limit == Long.MAX_VALUE) {
return false;
} else {
return getBytesLogged() >= limit;
}
}
@Override
public void writeFormattedMessage(final String message) {
rollLogIfFull();
super.writeFormattedMessage(message);
}
}