| /******************************************************************************* |
| * Copyright (c) 2009, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.osgi.internal.debug; |
| |
| import java.io.BufferedWriter; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FilterOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintStream; |
| import java.io.Writer; |
| import java.nio.charset.StandardCharsets; |
| import java.security.AccessController; |
| import java.text.MessageFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import org.eclipse.core.runtime.adaptor.EclipseStarter; |
| import org.eclipse.osgi.framework.util.SecureAction; |
| import org.eclipse.osgi.service.debug.DebugTrace; |
| |
| /** |
| * The DebugTrace implementation for Eclipse. |
| */ |
| class EclipseDebugTrace implements DebugTrace { |
| |
| /** The system property used to specify size a trace file can grow before it is rotated */ |
| private static final String PROP_TRACE_SIZE_MAX = "eclipse.trace.size.max"; //$NON-NLS-1$ |
| /** The system property used to specify the maximum number of backup trace files to use */ |
| private static final String PROP_TRACE_FILE_MAX = "eclipse.trace.backup.max"; //$NON-NLS-1$ |
| /** The trace message for a thread stack dump */ |
| private final static String MESSAGE_THREAD_DUMP = "Thread Stack dump: "; //$NON-NLS-1$ |
| /** The trace message for a method completing with a return value */ |
| private final static String MESSAGE_EXIT_METHOD_WITH_RESULTS = "Exiting method {0}with result: "; //$NON-NLS-1$ |
| /** The trace message for a method completing with no return value */ |
| private final static String MESSAGE_EXIT_METHOD_NO_RESULTS = "Exiting method {0}with a void return"; //$NON-NLS-1$ |
| /** The trace message for a method starting with a set of arguments */ |
| private final static String MESSAGE_ENTER_METHOD_WITH_PARAMS = "Entering method {0}with parameters: ("; //$NON-NLS-1$ |
| /** The trace message for a method starting with no arguments */ |
| private final static String MESSAGE_ENTER_METHOD_NO_PARAMS = "Entering method {0}with no parameters"; //$NON-NLS-1$ |
| /** The version attribute written in the header of a new session */ |
| private final static String TRACE_FILE_VERSION_COMMENT = "version: "; //$NON-NLS-1$ |
| /** The verbose attribute written in the header of a new session */ |
| private final static String TRACE_FILE_VERBOSE_COMMENT = "verbose: "; //$NON-NLS-1$ |
| /** The version value written in the header of a new session */ |
| private final static String TRACE_FILE_VERSION = "1.1"; //$NON-NLS-1$ |
| /** The new session identifier to be written whenever a new session starts */ |
| private final static String TRACE_NEW_SESSION = "!SESSION "; //$NON-NLS-1$ |
| /** The date attribute written to the header of the trace file to show when this file was created */ |
| private final static String TRACE_FILE_DATE = "Time of creation: "; //$NON-NLS-1$ |
| /** Trace date formatter using the pattern: yyyy-MM-dd HH:mm:ss.SSS */ |
| private final static SimpleDateFormat TRACE_FILE_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$ |
| /** The comment character used by the trace file */ |
| private final static String TRACE_COMMENT = "#"; //$NON-NLS-1$ |
| /** The delimiter used to separate trace elements such as the time stamp, message, etc */ |
| private final static String TRACE_ELEMENT_DELIMITER = "|"; //$NON-NLS-1$ |
| /** The string written in place of the {@link EclipseDebugTrace#TRACE_TRACE_ELEMENT_DELIMITER} in entries */ |
| private final static String TRACE_ELEMENT_DELIMITER_ENCODED = "|"; //$NON-NLS-1$ |
| /** OS-specific line separator */ |
| private static final String LINE_SEPARATOR; |
| static { |
| String s = System.getProperty("line.separator"); //$NON-NLS-1$ |
| LINE_SEPARATOR = s == null ? "\n" : s; //$NON-NLS-1$ |
| } |
| /** The value written to the trace file if a null object is being traced */ |
| private final static String NULL_VALUE = "<null>"; //$NON-NLS-1$ |
| /** */ |
| private final static SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); |
| |
| /******************* Tracing file attributes **************************/ |
| /** The default size a trace file can grow before it is rotated */ |
| private static final int DEFAULT_TRACE_FILE_SIZE = 1000; // The value is in KB. |
| /** The default number of backup trace files */ |
| private static final int DEFAULT_TRACE_FILES = 10; |
| /** The minimum size limit for trace file rotation */ |
| private static final int DEFAULT_TRACE_FILE_MIN_SIZE = 10; |
| /** The extension used for log files */ |
| private static final String TRACE_FILE_EXTENSION = ".trace"; //$NON-NLS-1$ |
| /** The extension markup to use for backup log files*/ |
| private static final String BACKUP_MARK = ".bak_"; //$NON-NLS-1$ |
| /** The maximum size that a trace file should grow (0 = unlimited) */ |
| private int maxTraceFileSize = DEFAULT_TRACE_FILE_SIZE; // The value is in KB. |
| /** The maximum number of trace files that should be saved */ |
| private int maxTraceFiles = DEFAULT_TRACE_FILES; |
| /** The index of the currently backed-up trace file */ |
| private int backupTraceFileIndex = 0; |
| |
| /** An optional argument to specify the name of the class used by clients to trace messages. If no trace class is specified |
| * then the class calling this API is assumed to be the class being traced. |
| */ |
| private String traceClass = null; |
| /** The symbolic name of the bundle being traced */ |
| private String bundleSymbolicName = null; |
| /** DebugOptions are used to determine if the specified bundle symbolic name + option-path has debugging enabled */ |
| private FrameworkDebugOptions debugOptions = null; |
| |
| private final boolean consoleLog; |
| |
| /** |
| * Construct a new EclipseDebugTrace for the specified bundle symbolic name and write messages to the specified |
| * trace file. |
| * |
| * @param bundleSymbolicName The symbolic name of the bundle being traced |
| * @param debugOptions Used to determine if the specified bundle symbolic name + option-path has tracing enabled |
| * @param traceClass The class that the client is using to perform trace API calls |
| */ |
| EclipseDebugTrace(final String bundleSymbolicName, final FrameworkDebugOptions debugOptions, final Class<?> traceClass) { |
| this.consoleLog = "true".equals(debugOptions.getConfiguration().getConfiguration(EclipseStarter.PROP_CONSOLE_LOG)); //$NON-NLS-1$ |
| this.traceClass = traceClass != null ? traceClass.getName() : null; |
| this.debugOptions = debugOptions; |
| this.bundleSymbolicName = bundleSymbolicName; |
| readLogProperties(); |
| } |
| |
| /** |
| * Is debugging enabled for the specified option-path |
| * |
| * @param optionPath The <i>option-path</i> |
| * @return Returns true if debugging is enabled for the specified option-path on this bundle; Otherwise false. |
| */ |
| private final boolean isDebuggingEnabled(final String optionPath) { |
| if (optionPath == null) |
| return true; |
| boolean debugEnabled = false; |
| if (debugOptions.isDebugEnabled()) { |
| final String option = bundleSymbolicName + optionPath; |
| debugEnabled = debugOptions.getBooleanOption(option, false); |
| } |
| return debugEnabled; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#trace(java.lang.String, java.lang.String) |
| */ |
| @Override |
| public void trace(final String optionPath, final String message) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, message, traceClass); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#trace(java.lang.String, java.lang.String, java.lang.Throwable) |
| */ |
| @Override |
| public void trace(final String optionPath, final String message, final Throwable error) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, message, error, traceClass); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String) |
| */ |
| @Override |
| public void traceEntry(final String optionPath) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); |
| setMessage(record, EclipseDebugTrace.MESSAGE_ENTER_METHOD_NO_PARAMS); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String, java.lang.Object) |
| */ |
| @Override |
| public void traceEntry(final String optionPath, final Object methodArgument) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| traceEntry(optionPath, new Object[] {methodArgument}); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String, java.lang.Object[]) |
| */ |
| @Override |
| public void traceEntry(final String optionPath, final Object[] methodArguments) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_ENTER_METHOD_WITH_PARAMS); |
| if (methodArguments != null) { |
| int i = 0; |
| while (i < methodArguments.length) { |
| if (methodArguments[i] != null) { |
| messageBuffer.append(methodArguments[i].toString()); |
| } else { |
| messageBuffer.append(EclipseDebugTrace.NULL_VALUE); |
| } |
| i++; |
| if (i < methodArguments.length) { |
| messageBuffer.append(" "); //$NON-NLS-1$ |
| } |
| } |
| messageBuffer.append(")"); //$NON-NLS-1$ |
| } |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); |
| setMessage(record, messageBuffer.toString()); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceExit(java.lang.String) |
| */ |
| @Override |
| public void traceExit(final String optionPath) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); |
| setMessage(record, EclipseDebugTrace.MESSAGE_EXIT_METHOD_NO_RESULTS); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceExit(java.lang.String, java.lang.Object) |
| */ |
| @Override |
| public void traceExit(final String optionPath, final Object result) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_EXIT_METHOD_WITH_RESULTS); |
| if (result == null) { |
| messageBuffer.append(EclipseDebugTrace.NULL_VALUE); |
| } else { |
| messageBuffer.append(result.toString()); |
| } |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); |
| setMessage(record, messageBuffer.toString()); |
| writeRecord(record); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceDumpStack(java.lang.String) |
| */ |
| @Override |
| public void traceDumpStack(final String optionPath) { |
| |
| if (isDebuggingEnabled(optionPath)) { |
| final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_THREAD_DUMP); |
| StackTraceElement[] elements = new Exception().getStackTrace(); |
| // the first element in this stack trace is going to be this class, so ignore it |
| // the second element in this stack trace is going to either be the caller or the trace class. Ignore it only if a traceClass is defined |
| // the rest of the elements should be included in the file array |
| int firstIndex = (traceClass == null) ? 1 : 2; |
| int endIndex = elements.length - firstIndex; |
| final StackTraceElement[] newElements = new StackTraceElement[endIndex]; |
| int i = 0; |
| while (i < endIndex) { |
| newElements[i] = elements[firstIndex]; |
| i++; |
| firstIndex++; |
| } |
| messageBuffer.append(convertStackTraceElementsToString(newElements)); |
| final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, messageBuffer.toString(), traceClass); |
| writeRecord(record); |
| } |
| } |
| |
| /** |
| * Set the trace message for the specified record to include class and method information |
| * if verbose debugging is disabled. |
| * |
| * @param record The {@link FrameworkDebugTraceEntry} containing the information to persist to the trace file. |
| * @param originalMessage The original tracing message |
| */ |
| private final void setMessage(final FrameworkDebugTraceEntry record, final String originalMessage) { |
| |
| String argument = null; |
| if (!debugOptions.isVerbose()) { |
| final StringBuilder classMethodName = new StringBuilder(record.getClassName()); |
| classMethodName.append("#"); //$NON-NLS-1$ |
| classMethodName.append(record.getMethodName()); |
| classMethodName.append(" "); //$NON-NLS-1$ |
| argument = classMethodName.toString(); |
| } else { |
| argument = ""; //$NON-NLS-1$ |
| } |
| String newMessage = MessageFormat.format(originalMessage, new Object[] {argument}); |
| record.setMessage(newMessage); |
| } |
| |
| /** |
| * Utility method to convert an array of StackTraceElement objects to form a String representation of a stack dump |
| * |
| * @param elements |
| * The array of StackTraceElement objects |
| * @return A String of the stack dump produced by the list of elements |
| */ |
| private final String convertStackTraceElementsToString(final StackTraceElement[] elements) { |
| |
| final StringBuilder buffer = new StringBuilder(); |
| if (elements != null) { |
| buffer.append("java.lang.Throwable: "); //$NON-NLS-1$ |
| buffer.append(EclipseDebugTrace.LINE_SEPARATOR); |
| int i = 0; |
| while (i < elements.length) { |
| if (elements[i] != null) { |
| buffer.append("\tat "); //$NON-NLS-1$ |
| buffer.append(elements[i].toString()); |
| buffer.append(EclipseDebugTrace.LINE_SEPARATOR); |
| } |
| i++; |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Write the specified FrameworkTraceEntry to trace file |
| * |
| * @param entry The FrameworkTraceEntry to write to the log file. |
| */ |
| private void writeRecord(final FrameworkDebugTraceEntry entry) { |
| |
| if (entry != null) { |
| synchronized (debugOptions.getWriteLock()) { |
| final File tracingFile = debugOptions.getFile(); // the tracing file may be null if it has not been set |
| Writer traceWriter = null; |
| try { |
| // check to see if the file should be rotated |
| checkTraceFileSize(tracingFile, entry.getTimestamp()); |
| // open the trace file |
| traceWriter = openWriter(tracingFile); |
| if (debugOptions.newSession()) { |
| writeSession(traceWriter, entry.getTimestamp()); |
| } |
| writeMessage(traceWriter, entry); |
| // flush the writer |
| traceWriter.flush(); |
| } catch (Exception ex) { |
| // any exceptions during tracing should be caught |
| System.err.println("An exception occurred while writing to the platform trace file: ");//$NON-NLS-1$ |
| ex.printStackTrace(System.err); |
| } finally { |
| // close the trace writer |
| closeWriter(traceWriter); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Reads the PROP_TRACE_SIZE_MAX and PROP_TRACE_FILE_MAX properties. |
| */ |
| private void readLogProperties() { |
| |
| String newMaxTraceFileSize = debugOptions.getConfiguration().getConfiguration(PROP_TRACE_SIZE_MAX); |
| if (newMaxTraceFileSize != null) { |
| maxTraceFileSize = Integer.parseInt(newMaxTraceFileSize); |
| if (maxTraceFileSize != 0 && maxTraceFileSize < DEFAULT_TRACE_FILE_MIN_SIZE) { |
| // If the value is '0', then it means no size limitation. |
| // Also, make sure no inappropriate(too small) assigned value. |
| maxTraceFileSize = DEFAULT_TRACE_FILE_MIN_SIZE; |
| } |
| } |
| |
| String newMaxLogFiles = debugOptions.getConfiguration().getConfiguration(PROP_TRACE_FILE_MAX); |
| if (newMaxLogFiles != null) { |
| maxTraceFiles = Integer.parseInt(newMaxLogFiles); |
| if (maxTraceFiles < 1) { |
| // Make sure no invalid assigned value. (at least >= 1) |
| maxTraceFiles = DEFAULT_TRACE_FILES; |
| } |
| } |
| } |
| |
| /** |
| * Checks the trace file size. If the file size reaches the limit then the trace file is rotated. |
| * |
| * @param traceFile The tracing file |
| * @param timestamp the timestamp for the session; this is the same timestamp as the first entry |
| * @return false if an error occurred trying to rotate the trace file |
| */ |
| private boolean checkTraceFileSize(final File traceFile, long timestamp) { |
| |
| // 0 file size means there is no size limit |
| boolean isBackupOK = true; |
| if (maxTraceFileSize > 0) { |
| if ((traceFile != null) && traceFile.exists()) { |
| if ((traceFile.length() >> 10) > maxTraceFileSize) { // Use KB as file size unit. |
| final String traceFileName = traceFile.getAbsolutePath(); |
| |
| // Delete old backup file that will be replaced. |
| String backupFilename = ""; //$NON-NLS-1$ |
| if (traceFileName.toLowerCase().endsWith(TRACE_FILE_EXTENSION)) { |
| backupFilename = traceFileName.substring(0, traceFileName.length() - TRACE_FILE_EXTENSION.length()) + BACKUP_MARK + backupTraceFileIndex + TRACE_FILE_EXTENSION; |
| } else { |
| backupFilename = traceFileName + BACKUP_MARK + backupTraceFileIndex; |
| } |
| final File backupFile = new File(backupFilename); |
| if (backupFile.exists()) { |
| if (!backupFile.delete()) { |
| System.err.println("Error when trying to delete old trace file: " + backupFile.getName());//$NON-NLS-1$ |
| if (backupFile.renameTo(new File(backupFile.getAbsolutePath() + System.currentTimeMillis()))) { |
| System.err.println("So we rename it to filename: " + backupFile.getName()); //$NON-NLS-1$ |
| } else { |
| System.err.println("And we also cannot rename it!"); //$NON-NLS-1$ |
| isBackupOK = false; |
| } |
| } |
| } |
| |
| // Rename current log file to backup one. |
| boolean isRenameOK = traceFile.renameTo(backupFile); |
| if (!isRenameOK) { |
| System.err.println("Error when trying to rename trace file to backup one."); //$NON-NLS-1$ |
| isBackupOK = false; |
| } |
| /* |
| * Write a header to new log file stating that this new file is a continuation file. |
| * This method should already be called with the file lock set so we should be safe |
| * to update it here. |
| */ |
| Writer traceWriter = null; |
| try { |
| traceWriter = openWriter(traceFile); |
| writeComment(traceWriter, "This is a continuation of trace file " + backupFile.getAbsolutePath()); //$NON-NLS-1$ |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERSION_COMMENT + EclipseDebugTrace.TRACE_FILE_VERSION); |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERBOSE_COMMENT + debugOptions.isVerbose()); |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_DATE + getFormattedDate(timestamp)); |
| traceWriter.flush(); |
| } catch (IOException ioEx) { |
| ioEx.printStackTrace(); |
| } finally { |
| closeWriter(traceWriter); |
| } |
| backupTraceFileIndex = (++backupTraceFileIndex) % maxTraceFiles; |
| } |
| } |
| } |
| return isBackupOK; |
| } |
| |
| /** |
| * Writes a comment to the trace file |
| * |
| * @param traceWriter the trace writer |
| * @param comment the comment to be written to the trace file |
| * @throws IOException If an error occurs while writing the comment |
| */ |
| private void writeComment(final Writer traceWriter, final String comment) throws IOException { |
| |
| StringBuilder commentText = new StringBuilder(EclipseDebugTrace.TRACE_COMMENT); |
| commentText.append(" "); //$NON-NLS-1$ |
| commentText.append(comment); |
| commentText.append(EclipseDebugTrace.LINE_SEPARATOR); |
| traceWriter.write(commentText.toString()); |
| } |
| |
| /** |
| * Accessor to retrieve the time stamp in a formatted manner. |
| * |
| * @return A formatted time stamp based on the {@link EclipseDebugTrace#TRACE_FILE_DATE_FORMATTER} formatter |
| */ |
| private final String getFormattedDate(long timestamp) { |
| |
| return EclipseDebugTrace.TRACE_FILE_DATE_FORMATTER.format(new Date(timestamp)); |
| } |
| |
| /** |
| * Accessor to retrieve the text of a {@link Throwable} in a formatted manner so that it can be written to the |
| * trace file. |
| * |
| * @param error The {@lnk Throwable} to format |
| * @return The complete text of a {@link Throwable} as a {@link String} or null if the input error is null. |
| */ |
| private final String getFormattedThrowable(Throwable error) { |
| |
| String result = null; |
| if (error != null) { |
| PrintStream throwableStream = null; |
| try { |
| ByteArrayOutputStream throwableByteOutputStream = new ByteArrayOutputStream(); |
| throwableStream = new PrintStream(throwableByteOutputStream, false); |
| error.printStackTrace(throwableStream); |
| result = encodeText(throwableByteOutputStream.toString()); |
| } finally { |
| if (throwableStream != null) { |
| throwableStream.close(); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Writes header information to a new trace file |
| * |
| * @param traceWriter the trace writer |
| * @param timestamp the timestamp for the session; this is the same timestamp as the first entry |
| * @throws IOException If an error occurs while writing this session information |
| */ |
| private void writeSession(final Writer traceWriter, long timestamp) throws IOException { |
| |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_NEW_SESSION + this.getFormattedDate(timestamp)); |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERSION_COMMENT + EclipseDebugTrace.TRACE_FILE_VERSION); |
| writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERBOSE_COMMENT + debugOptions.isVerbose()); |
| writeComment(traceWriter, "The following option strings are specified for this debug session:"); //$NON-NLS-1$ |
| final String[] allOptions = debugOptions.getAllOptions(); |
| for (int i = 0; i < allOptions.length; i++) { |
| writeComment(traceWriter, "\t" + allOptions[i]); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Writes the specified trace entry object to the trace file using the |
| * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} as the delimiter between |
| * each element of the entry. |
| * |
| * @param traceWriter the trace writer |
| * @param entry The trace entry object to write to the trace file |
| * @throws IOException If an error occurs while writing this message |
| */ |
| private void writeMessage(final Writer traceWriter, final FrameworkDebugTraceEntry entry) throws IOException { |
| |
| final StringBuilder message = new StringBuilder(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(encodeText(entry.getThreadName())); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(this.getFormattedDate(entry.getTimestamp())); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| if (!debugOptions.isVerbose()) { |
| // format the trace entry for quiet tracing: only the thread name, timestamp, trace message, and exception (if necessary) |
| message.append(encodeText(entry.getMessage())); |
| } else { |
| // format the trace entry for verbose tracing |
| message.append(entry.getBundleSymbolicName()); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(encodeText(entry.getOptionPath())); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(entry.getClassName()); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(entry.getMethodName()); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(entry.getLineNumber()); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(encodeText(entry.getMessage())); |
| } |
| if (entry.getThrowable() != null) { |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(" "); //$NON-NLS-1$ |
| message.append(this.getFormattedThrowable(entry.getThrowable())); |
| } |
| message.append(" "); //$NON-NLS-1$ |
| message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); |
| message.append(EclipseDebugTrace.LINE_SEPARATOR); |
| // write the message |
| if ((traceWriter != null) && (message != null)) { |
| traceWriter.write(message.toString()); |
| } |
| } |
| |
| /** |
| * Encodes the specified string to replace any occurrence of the {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} |
| * string with the {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER_ENCODED} |
| * string. This can be used to ensure that the delimiter character does not break parsing when |
| * the entry text contains the delimiter character. |
| * |
| * @param inputString The original string to be written to the trace file. |
| * @return The original input string with all occurrences of |
| * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} replaced with |
| * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER_ENCODED}. A <code>null</code> value will be |
| * returned if the input string is <code>null</code>. |
| */ |
| private static String encodeText(final String inputString) { |
| if (inputString == null || inputString.indexOf(TRACE_ELEMENT_DELIMITER) < 0) |
| return inputString; |
| final StringBuilder tempBuffer = new StringBuilder(inputString); |
| int currentIndex = tempBuffer.indexOf(TRACE_ELEMENT_DELIMITER); |
| while (currentIndex >= 0) { |
| tempBuffer.replace(currentIndex, currentIndex + TRACE_ELEMENT_DELIMITER.length(), TRACE_ELEMENT_DELIMITER_ENCODED); |
| currentIndex = tempBuffer.indexOf(TRACE_ELEMENT_DELIMITER); |
| } |
| return tempBuffer.toString(); |
| } |
| |
| /** |
| * Returns a Writer for the given OutputStream |
| * @param output an OutputStream to use for the Writer |
| * @return A Writer for the given OutputStream |
| */ |
| private Writer logForStream(OutputStream output) { |
| return new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); |
| } |
| |
| /** |
| * Creates the trace writer. |
| * If the tracing file is null then the writer will use System.out to print any messages. |
| * |
| * @param traceFile The tracing file |
| * @return Returns a new Writer object |
| */ |
| private Writer openWriter(final File traceFile) { |
| OutputStream out = null; |
| if (traceFile != null) { |
| try { |
| out = secureAction.getFileOutputStream(traceFile, true); |
| } catch (IOException ioEx) { |
| // ignore and fall back to system.out; but print error message to indicate what happened |
| System.err.println("Unable to open trace file: " + traceFile + ": " + ioEx.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| if (out == null) { |
| out = new FilterOutputStream(System.out) { |
| /** |
| * @throws IOException |
| */ |
| @Override |
| public void close() throws IOException { |
| // We don't want to close System.out |
| } |
| |
| @Override |
| public void write(byte[] var0, int var1, int var2) throws IOException { |
| this.out.write(var0, var1, var2); |
| } |
| }; |
| } else if (consoleLog) { |
| out = new FilterOutputStream(out) { |
| @Override |
| public void write(int b) throws IOException { |
| System.out.write(b); |
| out.write(b); |
| } |
| |
| @Override |
| public void write(byte[] b) throws IOException { |
| System.out.write(b); |
| out.write(b); |
| } |
| |
| @Override |
| public void write(byte[] b, int off, int len) throws IOException { |
| System.out.write(b, off, len); |
| out.write(b, off, len); |
| } |
| }; |
| } |
| return logForStream(out); |
| } |
| |
| /** |
| * Close the trace writer |
| * |
| * @param traceWriter The trace writer |
| */ |
| private void closeWriter(Writer traceWriter) { |
| |
| if (traceWriter != null) { |
| try { |
| traceWriter.close(); |
| } catch (IOException ioEx) { |
| // we cannot log here; just print the stacktrace. |
| ioEx.printStackTrace(); |
| } |
| traceWriter = null; |
| } |
| } |
| } |