| /* |
| * 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); |
| } |
| } |