| /******************************************************************************* |
| * Copyright (c) 2011, 2015 Wind River Systems, Inc. and others. All rights reserved. |
| * This program and the accompanying materials are made available under the terms |
| * of the Eclipse Public License v1.0 which accompanies this distribution, and is |
| * available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.tcf.te.tcf.log.core.manager; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.tcf.protocol.IPeer; |
| import org.eclipse.tcf.te.runtime.concurrent.util.ExecutorsUtil; |
| import org.eclipse.tcf.te.runtime.events.EventManager; |
| import org.eclipse.tcf.te.tcf.log.core.activator.CoreBundleActivator; |
| import org.eclipse.tcf.te.tcf.log.core.events.MonitorEvent; |
| import org.eclipse.tcf.te.tcf.log.core.interfaces.IPreferenceKeys; |
| import org.eclipse.tcf.te.tcf.log.core.internal.nls.Messages; |
| |
| |
| /** |
| * TCF logging log manager implementation. |
| */ |
| public final class LogManager { |
| /** |
| * Time format representing date and time with milliseconds. |
| */ |
| public final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$ |
| |
| // Maps file writer per log file base name |
| private final Map<String, FileWriter> fileWriterMap = new HashMap<String, FileWriter>(); |
| |
| // Maximum log file size in bytes |
| private long maxFileSize; |
| // Maximum number of files in cycle |
| private int maxInCycle; |
| |
| /* |
| * Thread save singleton instance creation. |
| */ |
| private static class LazyInstance { |
| public static LogManager instance = new LogManager(); |
| } |
| |
| /** |
| * Constructor. |
| */ |
| /* default */ LogManager() { |
| super(); |
| |
| // initialize from preferences |
| initializeFromPreferences(); |
| } |
| |
| /** |
| * Returns the singleton instance. |
| */ |
| public static LogManager getInstance() { |
| return LazyInstance.instance; |
| } |
| |
| /** |
| * Dispose the log manager instance. |
| * <p> |
| * Note: This method is callable from every thread. |
| */ |
| public void dispose() { |
| // Close all still open file writer instances |
| for (FileWriter writer : fileWriterMap.values()) { |
| try { |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| /* ignored on purpose */ |
| } |
| } |
| fileWriterMap.clear(); |
| } |
| |
| /** |
| * Initialize the log manager based on the current |
| * preference settings |
| */ |
| private void initializeFromPreferences() { |
| String fileSize = CoreBundleActivator.getScopedPreferences().getString(IPreferenceKeys.PREF_MAX_FILE_SIZE); |
| if (fileSize == null) fileSize = "5M"; //$NON-NLS-1$ |
| |
| try { |
| // If the last character is either K, M or G -> convert to bytes |
| char lastChar = fileSize.toUpperCase().charAt(fileSize.length() - 1); |
| if ('K' == lastChar || 'M' == lastChar || 'G' == lastChar) { |
| maxFileSize = Long.parseLong(fileSize.substring(0, fileSize.length() - 1)); |
| switch (lastChar) { |
| case 'K': |
| maxFileSize = maxFileSize * 1024; |
| break; |
| case 'M': |
| maxFileSize = maxFileSize * 1024 * 1024; |
| break; |
| case 'G': |
| maxFileSize = maxFileSize * 1024 * 1024 * 1024; |
| break; |
| } |
| } else { |
| maxFileSize = Long.parseLong(fileSize); |
| } |
| } catch (NumberFormatException e) { |
| maxFileSize = 5242880L; |
| } |
| |
| maxInCycle = CoreBundleActivator.getScopedPreferences().getInt(IPreferenceKeys.PREF_MAX_FILES_IN_CYCLE); |
| if (maxInCycle <= 0) maxInCycle = 5; |
| } |
| |
| /** |
| * Returns the file writer instance to use for the given channel. |
| * <p> |
| * Note: This method is callable from the executor thread only. |
| * |
| * @param logname The log name or <code>null</code>. |
| * @param peer The peer. Must not be <code>null</code>. |
| * @return The file writer instance or <code>null</code>. |
| */ |
| public FileWriter getWriter(String logname, IPeer peer) { |
| Assert.isNotNull(peer); |
| Assert.isTrue(ExecutorsUtil.isExecutorThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Before looking up the writer, check the file limits |
| checkLimits(logname, peer); |
| |
| if (logname == null) logname = getLogName(peer); |
| FileWriter writer = logname != null ? fileWriterMap.get(logname) : null; |
| if (writer == null && logname != null) { |
| // Create the writer |
| IPath path = getLogDir(); |
| if (path != null) { |
| path = path.append(logname + ".log"); //$NON-NLS-1$ |
| try { |
| writer = new FileWriter(path.toFile(), true); |
| fileWriterMap.put(logname, writer); |
| } catch (IOException e) { |
| /* ignored on purpose */ |
| } |
| } |
| } |
| |
| return writer; |
| } |
| |
| /** |
| * Close the writer instance used for the given channel. |
| * <p> |
| * Note: This method is callable from the executor thread only. |
| * |
| * @param logname The log name or <code>null</code>. |
| * @param peer The peer. Must not be <code>null</code>. |
| * @param message The last message to write or <code>null</code>. |
| */ |
| public void closeWriter(String logname, IPeer peer, String message) { |
| Assert.isNotNull(peer); |
| Assert.isTrue(ExecutorsUtil.isExecutorThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Remove the writer from the map |
| if (logname == null) logname = getLogName(peer); |
| FileWriter writer = logname != null ? fileWriterMap.remove(logname) : null; |
| if (writer != null) { |
| try { |
| // If specified, write the last message. |
| if (message != null) { |
| writer.write(message); |
| writer.write("\n"); //$NON-NLS-1$ |
| } |
| } catch (IOException e) { |
| /* ignored on purpose */ |
| } finally { |
| try { |
| writer.flush(); |
| writer.close(); |
| } catch (IOException e) { |
| /* ignored on purpose */ |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the log file base name for the given peer. |
| * <p> |
| * Note: This method is callable from every thread. |
| * |
| * @param peer The peer. Must not be <code>null</code>. |
| * @return The log file base name. |
| */ |
| public String getLogName(IPeer peer) { |
| Assert.isNotNull(peer); |
| |
| String logName = null; |
| |
| // Get the peer name |
| logName = peer.getName(); |
| |
| if (logName != null) { |
| // Get the peer host IP address |
| String ip = peer.getAttributes().get(IPeer.ATTR_IP_HOST); |
| // Fallback: The peer id |
| if (ip == null || "".equals(ip.trim())) { //$NON-NLS-1$ |
| ip = peer.getID(); |
| } |
| |
| // Append the peer host IP address |
| if (ip != null && !"".equals(ip.trim())) { //$NON-NLS-1$ |
| logName += " " + ip.trim(); //$NON-NLS-1$ |
| } |
| |
| // Unify name and replace all undesired characters with '_' |
| logName = makeValid(logName); |
| } |
| |
| return logName; |
| } |
| |
| /** |
| * Replaces a set of predefined patterns with underscore to |
| * make a valid name. |
| * <p> |
| * Note: This method is callable from every thread. |
| * |
| * @param name The name. Must not be <code>null</code>. |
| * @return The modified name. |
| */ |
| public String makeValid(String name) { |
| Assert.isNotNull(name); |
| |
| String result = name.replaceAll("\\s", "_"); //$NON-NLS-1$ //$NON-NLS-2$ |
| result = result.replaceAll("[:/\\;,\\[\\]\\(\\)]", "_"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| return result; |
| } |
| |
| /** |
| * Returns the log directory. |
| * <p> |
| * Note: This method is callable from every thread. |
| * |
| * @return The log directory. |
| */ |
| public IPath getLogDir() { |
| IPath logDir = null; |
| |
| // In some rare cases, we end up here with an NPE on shutdown. |
| // So it does not hurt to check it. |
| if (CoreBundleActivator.getDefault() == null) return logDir; |
| |
| try { |
| File file = CoreBundleActivator.getDefault().getStateLocation().append(".logs").toFile(); //$NON-NLS-1$ |
| boolean exists = file.exists(); |
| if (!exists) exists = file.mkdirs(); |
| if (exists && file.canRead() && file.isDirectory()) { |
| logDir = new Path(file.toString()); |
| } |
| } catch (IllegalStateException e) { |
| // Ignored: Workspace less environment (-data @none) |
| } |
| |
| if (logDir == null) { |
| // First fallback: ${HOME}/.tcf/.logs |
| File file = new Path(System.getProperty("user.home")).append(".tcf/.logs").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| boolean exists = file.exists(); |
| if (!exists) exists = file.mkdirs(); |
| if (exists && file.canRead() && file.isDirectory()) { |
| logDir = new Path(file.toString()); |
| } |
| } |
| |
| if (logDir == null) { |
| // Second fallback: ${TEMP}/.tcf/.logs |
| File file = new Path(System.getProperty("java.io.tmpdir")).append(".tcf/.logs").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| boolean exists = file.exists(); |
| if (!exists) exists = file.mkdirs(); |
| if (exists && file.canRead() && file.isDirectory()) { |
| logDir = new Path(file.toString()); |
| } |
| } |
| |
| return logDir; |
| } |
| |
| /** |
| * Checks the limits set by the preferences. |
| * |
| * @param logname The log name or <code>null</code>. |
| * @param peer The peer. Must not be <code>null</code>. |
| * @return The checked file writer instance. |
| */ |
| private void checkLimits(String logname, IPeer peer) { |
| Assert.isNotNull(peer); |
| |
| String logName = getLogName(peer); |
| if (logName != null && !"".equals(logName.trim())) { //$NON-NLS-1$ |
| IPath path = getLogDir(); |
| if (path != null) { |
| IPath fullPath = path.append(logName + ".log"); //$NON-NLS-1$ |
| File file = fullPath.toFile(); |
| if (file.exists()) { |
| long size = file.length(); |
| if (size >= maxFileSize) { |
| // Max log file size reached -> cycle files |
| |
| // If there is an active writer, flush and close the writer |
| closeWriter(logname, peer, null); |
| |
| // Determine if the maximum number of files in the cycle has been reached |
| File maxFileInCycle = path.append(logName + "_" + maxInCycle + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (maxFileInCycle.exists()) { |
| // We have to rotate the full cycle, first in cycle to be removed. |
| int no = 1; |
| File fileInCycle = path.append(logName + "_" + no + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| boolean rc = fileInCycle.delete(); |
| if (rc) { |
| while (no <= maxInCycle) { |
| no++; |
| fileInCycle = path.append(logName + "_" + no + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| File renameTo = path.append(logName + "_" + (no - 1) + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| rc = fileInCycle.renameTo(renameTo); |
| if (!rc) break; |
| } |
| |
| // Rename the log file if the rotate succeeded, |
| // Delete the log file if not. |
| rc = rc ? file.renameTo(maxFileInCycle) : file.delete(); |
| if (!rc && Platform.inDebugMode()) { |
| System.err.println(NLS.bind(Messages.LogManager_error_renameFailed, fullPath.toOSString(), maxFileInCycle.getAbsolutePath())); |
| } |
| } |
| |
| } else { |
| // Not at the limit, find the next file name in the cycle |
| int no = 1; |
| File fileInCycle = path.append(logName + "_" + no + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| while (fileInCycle.exists()) { |
| no++; |
| fileInCycle = path.append(logName + "_" + no + ".log").toFile(); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| Assert.isTrue(no <= maxInCycle); |
| |
| // Rename the log file |
| boolean rc = file.renameTo(fileInCycle); |
| if (!rc && Platform.inDebugMode()) { |
| System.err.println(NLS.bind(Messages.LogManager_error_renameFailed, fullPath.toOSString(), fileInCycle.getAbsolutePath())); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sends an event to the monitor signaling the given message and type. |
| * |
| * @param peer The peer. Must not be <code>null</code>. |
| * @param type The message type. Must not be <code>null</code>. |
| * @param message The message. Must not be <code>null</code>. |
| */ |
| public void monitor(IPeer peer, MonitorEvent.Type type, MonitorEvent.Message message) { |
| Assert.isNotNull(peer); |
| Assert.isNotNull(type); |
| Assert.isNotNull(message); |
| Assert.isTrue(ExecutorsUtil.isExecutorThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // If monitoring is not enabled, return immediately |
| if (!CoreBundleActivator.getScopedPreferences().getBoolean(IPreferenceKeys.PREF_MONITOR_ENABLED)) { |
| return; |
| } |
| |
| // The source of a monitor event is the peer. |
| MonitorEvent event = new MonitorEvent(peer, type, message); |
| EventManager.getInstance().fireEvent(event); |
| } |
| |
| } |