1 /******************************************************************************* 2 * Copyright (c) 2009, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.osgi.internal.debug; 15 16 import java.io.BufferedWriter; 17 import java.io.ByteArrayOutputStream; 18 import java.io.File; 19 import java.io.FilterOutputStream; 20 import java.io.IOException; 21 import java.io.OutputStream; 22 import java.io.OutputStreamWriter; 23 import java.io.PrintStream; 24 import java.io.Writer; 25 import java.nio.charset.StandardCharsets; 26 import java.security.AccessController; 27 import java.text.MessageFormat; 28 import java.text.SimpleDateFormat; 29 import java.util.Date; 30 import org.eclipse.core.runtime.adaptor.EclipseStarter; 31 import org.eclipse.osgi.framework.util.SecureAction; 32 import org.eclipse.osgi.service.debug.DebugTrace; 33 34 /** 35 * The DebugTrace implementation for Eclipse. 36 */ 37 class EclipseDebugTrace implements DebugTrace { 38 39 /** The system property used to specify size a trace file can grow before it is rotated */ 40 private static final String PROP_TRACE_SIZE_MAX = "eclipse.trace.size.max"; //$NON-NLS-1$ 41 /** The system property used to specify the maximum number of backup trace files to use */ 42 private static final String PROP_TRACE_FILE_MAX = "eclipse.trace.backup.max"; //$NON-NLS-1$ 43 /** The trace message for a thread stack dump */ 44 private final static String MESSAGE_THREAD_DUMP = "Thread Stack dump: "; //$NON-NLS-1$ 45 /** The trace message for a method completing with a return value */ 46 private final static String MESSAGE_EXIT_METHOD_WITH_RESULTS = "Exiting method {0}with result: "; //$NON-NLS-1$ 47 /** The trace message for a method completing with no return value */ 48 private final static String MESSAGE_EXIT_METHOD_NO_RESULTS = "Exiting method {0}with a void return"; //$NON-NLS-1$ 49 /** The trace message for a method starting with a set of arguments */ 50 private final static String MESSAGE_ENTER_METHOD_WITH_PARAMS = "Entering method {0}with parameters: ("; //$NON-NLS-1$ 51 /** The trace message for a method starting with no arguments */ 52 private final static String MESSAGE_ENTER_METHOD_NO_PARAMS = "Entering method {0}with no parameters"; //$NON-NLS-1$ 53 /** The version attribute written in the header of a new session */ 54 private final static String TRACE_FILE_VERSION_COMMENT = "version: "; //$NON-NLS-1$ 55 /** The verbose attribute written in the header of a new session */ 56 private final static String TRACE_FILE_VERBOSE_COMMENT = "verbose: "; //$NON-NLS-1$ 57 /** The version value written in the header of a new session */ 58 private final static String TRACE_FILE_VERSION = "1.1"; //$NON-NLS-1$ 59 /** The new session identifier to be written whenever a new session starts */ 60 private final static String TRACE_NEW_SESSION = "!SESSION "; //$NON-NLS-1$ 61 /** The date attribute written to the header of the trace file to show when this file was created */ 62 private final static String TRACE_FILE_DATE = "Time of creation: "; //$NON-NLS-1$ 63 /** Trace date formatter using the pattern: yyyy-MM-dd HH:mm:ss.SSS */ 64 private final static SimpleDateFormat TRACE_FILE_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$ 65 /** The comment character used by the trace file */ 66 private final static String TRACE_COMMENT = "#"; //$NON-NLS-1$ 67 /** The delimiter used to separate trace elements such as the time stamp, message, etc */ 68 private final static String TRACE_ELEMENT_DELIMITER = "|"; //$NON-NLS-1$ 69 /** The string written in place of the {@link EclipseDebugTrace#TRACE_TRACE_ELEMENT_DELIMITER} in entries */ 70 private final static String TRACE_ELEMENT_DELIMITER_ENCODED = "|"; //$NON-NLS-1$ 71 /** OS-specific line separator */ 72 private static final String LINE_SEPARATOR; 73 static { 74 String s = System.getProperty("line.separator"); //$NON-NLS-1$ 75 LINE_SEPARATOR = s == null ? "\n" : s; //$NON-NLS-1$ 76 } 77 /** The value written to the trace file if a null object is being traced */ 78 private final static String NULL_VALUE = "<null>"; //$NON-NLS-1$ 79 /** */ 80 private final static SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); 81 82 /******************* Tracing file attributes **************************/ 83 /** The default size a trace file can grow before it is rotated */ 84 private static final int DEFAULT_TRACE_FILE_SIZE = 1000; // The value is in KB. 85 /** The default number of backup trace files */ 86 private static final int DEFAULT_TRACE_FILES = 10; 87 /** The minimum size limit for trace file rotation */ 88 private static final int DEFAULT_TRACE_FILE_MIN_SIZE = 10; 89 /** The extension used for log files */ 90 private static final String TRACE_FILE_EXTENSION = ".trace"; //$NON-NLS-1$ 91 /** The extension markup to use for backup log files*/ 92 private static final String BACKUP_MARK = ".bak_"; //$NON-NLS-1$ 93 /** The maximum size that a trace file should grow (0 = unlimited) */ 94 private int maxTraceFileSize = DEFAULT_TRACE_FILE_SIZE; // The value is in KB. 95 /** The maximum number of trace files that should be saved */ 96 private int maxTraceFiles = DEFAULT_TRACE_FILES; 97 /** The index of the currently backed-up trace file */ 98 private int backupTraceFileIndex = 0; 99 100 /** An optional argument to specify the name of the class used by clients to trace messages. If no trace class is specified 101 * then the class calling this API is assumed to be the class being traced. 102 */ 103 private String traceClass = null; 104 /** The symbolic name of the bundle being traced */ 105 private String bundleSymbolicName = null; 106 /** DebugOptions are used to determine if the specified bundle symbolic name + option-path has debugging enabled */ 107 private FrameworkDebugOptions debugOptions = null; 108 109 private final boolean consoleLog; 110 111 /** 112 * Construct a new EclipseDebugTrace for the specified bundle symbolic name and write messages to the specified 113 * trace file. 114 * 115 * @param bundleSymbolicName The symbolic name of the bundle being traced 116 * @param debugOptions Used to determine if the specified bundle symbolic name + option-path has tracing enabled 117 * @param traceClass The class that the client is using to perform trace API calls 118 */ EclipseDebugTrace(final String bundleSymbolicName, final FrameworkDebugOptions debugOptions, final Class<?> traceClass)119 EclipseDebugTrace(final String bundleSymbolicName, final FrameworkDebugOptions debugOptions, final Class<?> traceClass) { 120 this.consoleLog = "true".equals(debugOptions.getConfiguration().getConfiguration(EclipseStarter.PROP_CONSOLE_LOG)); //$NON-NLS-1$ 121 this.traceClass = traceClass != null ? traceClass.getName() : null; 122 this.debugOptions = debugOptions; 123 this.bundleSymbolicName = bundleSymbolicName; 124 readLogProperties(); 125 } 126 127 /** 128 * Is debugging enabled for the specified option-path 129 * 130 * @param optionPath The <i>option-path</i> 131 * @return Returns true if debugging is enabled for the specified option-path on this bundle; Otherwise false. 132 */ isDebuggingEnabled(final String optionPath)133 private final boolean isDebuggingEnabled(final String optionPath) { 134 if (optionPath == null) 135 return true; 136 boolean debugEnabled = false; 137 if (debugOptions.isDebugEnabled()) { 138 final String option = bundleSymbolicName + optionPath; 139 debugEnabled = debugOptions.getBooleanOption(option, false); 140 } 141 return debugEnabled; 142 } 143 144 /* 145 * (non-Javadoc) 146 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#trace(java.lang.String, java.lang.String) 147 */ 148 @Override trace(final String optionPath, final String message)149 public void trace(final String optionPath, final String message) { 150 151 if (isDebuggingEnabled(optionPath)) { 152 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, message, traceClass); 153 writeRecord(record); 154 } 155 } 156 157 /* 158 * (non-Javadoc) 159 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#trace(java.lang.String, java.lang.String, java.lang.Throwable) 160 */ 161 @Override trace(final String optionPath, final String message, final Throwable error)162 public void trace(final String optionPath, final String message, final Throwable error) { 163 164 if (isDebuggingEnabled(optionPath)) { 165 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, message, error, traceClass); 166 writeRecord(record); 167 } 168 } 169 170 /* 171 * (non-Javadoc) 172 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String) 173 */ 174 @Override traceEntry(final String optionPath)175 public void traceEntry(final String optionPath) { 176 177 if (isDebuggingEnabled(optionPath)) { 178 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); 179 setMessage(record, EclipseDebugTrace.MESSAGE_ENTER_METHOD_NO_PARAMS); 180 writeRecord(record); 181 } 182 } 183 184 /* 185 * (non-Javadoc) 186 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String, java.lang.Object) 187 */ 188 @Override traceEntry(final String optionPath, final Object methodArgument)189 public void traceEntry(final String optionPath, final Object methodArgument) { 190 191 if (isDebuggingEnabled(optionPath)) { 192 traceEntry(optionPath, new Object[] {methodArgument}); 193 } 194 } 195 196 /* 197 * (non-Javadoc) 198 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceEntry(java.lang.String, java.lang.Object[]) 199 */ 200 @Override traceEntry(final String optionPath, final Object[] methodArguments)201 public void traceEntry(final String optionPath, final Object[] methodArguments) { 202 203 if (isDebuggingEnabled(optionPath)) { 204 final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_ENTER_METHOD_WITH_PARAMS); 205 if (methodArguments != null) { 206 int i = 0; 207 while (i < methodArguments.length) { 208 if (methodArguments[i] != null) { 209 messageBuffer.append(methodArguments[i].toString()); 210 } else { 211 messageBuffer.append(EclipseDebugTrace.NULL_VALUE); 212 } 213 i++; 214 if (i < methodArguments.length) { 215 messageBuffer.append(" "); //$NON-NLS-1$ 216 } 217 } 218 messageBuffer.append(")"); //$NON-NLS-1$ 219 } 220 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); 221 setMessage(record, messageBuffer.toString()); 222 writeRecord(record); 223 } 224 } 225 226 /* 227 * (non-Javadoc) 228 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceExit(java.lang.String) 229 */ 230 @Override traceExit(final String optionPath)231 public void traceExit(final String optionPath) { 232 233 if (isDebuggingEnabled(optionPath)) { 234 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); 235 setMessage(record, EclipseDebugTrace.MESSAGE_EXIT_METHOD_NO_RESULTS); 236 writeRecord(record); 237 } 238 } 239 240 /* 241 * (non-Javadoc) 242 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceExit(java.lang.String, java.lang.Object) 243 */ 244 @Override traceExit(final String optionPath, final Object result)245 public void traceExit(final String optionPath, final Object result) { 246 247 if (isDebuggingEnabled(optionPath)) { 248 final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_EXIT_METHOD_WITH_RESULTS); 249 if (result == null) { 250 messageBuffer.append(EclipseDebugTrace.NULL_VALUE); 251 } else { 252 messageBuffer.append(result.toString()); 253 } 254 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, null, traceClass); 255 setMessage(record, messageBuffer.toString()); 256 writeRecord(record); 257 } 258 } 259 260 /* 261 * (non-Javadoc) 262 * @see org.eclipse.osgi.framework.debug.FrameworkDebugTrace#traceDumpStack(java.lang.String) 263 */ 264 @Override traceDumpStack(final String optionPath)265 public void traceDumpStack(final String optionPath) { 266 267 if (isDebuggingEnabled(optionPath)) { 268 final StringBuilder messageBuffer = new StringBuilder(EclipseDebugTrace.MESSAGE_THREAD_DUMP); 269 StackTraceElement[] elements = new Exception().getStackTrace(); 270 // the first element in this stack trace is going to be this class, so ignore it 271 // 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 272 // the rest of the elements should be included in the file array 273 int firstIndex = (traceClass == null) ? 1 : 2; 274 int endIndex = elements.length - firstIndex; 275 final StackTraceElement[] newElements = new StackTraceElement[endIndex]; 276 int i = 0; 277 while (i < endIndex) { 278 newElements[i] = elements[firstIndex]; 279 i++; 280 firstIndex++; 281 } 282 messageBuffer.append(convertStackTraceElementsToString(newElements)); 283 final FrameworkDebugTraceEntry record = new FrameworkDebugTraceEntry(bundleSymbolicName, optionPath, messageBuffer.toString(), traceClass); 284 writeRecord(record); 285 } 286 } 287 288 /** 289 * Set the trace message for the specified record to include class and method information 290 * if verbose debugging is disabled. 291 * 292 * @param record The {@link FrameworkDebugTraceEntry} containing the information to persist to the trace file. 293 * @param originalMessage The original tracing message 294 */ setMessage(final FrameworkDebugTraceEntry record, final String originalMessage)295 private final void setMessage(final FrameworkDebugTraceEntry record, final String originalMessage) { 296 297 String argument = null; 298 if (!debugOptions.isVerbose()) { 299 final StringBuilder classMethodName = new StringBuilder(record.getClassName()); 300 classMethodName.append("#"); //$NON-NLS-1$ 301 classMethodName.append(record.getMethodName()); 302 classMethodName.append(" "); //$NON-NLS-1$ 303 argument = classMethodName.toString(); 304 } else { 305 argument = ""; //$NON-NLS-1$ 306 } 307 String newMessage = MessageFormat.format(originalMessage, new Object[] {argument}); 308 record.setMessage(newMessage); 309 } 310 311 /** 312 * Utility method to convert an array of StackTraceElement objects to form a String representation of a stack dump 313 * 314 * @param elements 315 * The array of StackTraceElement objects 316 * @return A String of the stack dump produced by the list of elements 317 */ convertStackTraceElementsToString(final StackTraceElement[] elements)318 private final String convertStackTraceElementsToString(final StackTraceElement[] elements) { 319 320 final StringBuilder buffer = new StringBuilder(); 321 if (elements != null) { 322 buffer.append("java.lang.Throwable: "); //$NON-NLS-1$ 323 buffer.append(EclipseDebugTrace.LINE_SEPARATOR); 324 int i = 0; 325 while (i < elements.length) { 326 if (elements[i] != null) { 327 buffer.append("\tat "); //$NON-NLS-1$ 328 buffer.append(elements[i].toString()); 329 buffer.append(EclipseDebugTrace.LINE_SEPARATOR); 330 } 331 i++; 332 } 333 } 334 return buffer.toString(); 335 } 336 337 /** 338 * Write the specified FrameworkTraceEntry to trace file 339 * 340 * @param entry The FrameworkTraceEntry to write to the log file. 341 */ writeRecord(final FrameworkDebugTraceEntry entry)342 private void writeRecord(final FrameworkDebugTraceEntry entry) { 343 344 if (entry != null) { 345 synchronized (debugOptions.getWriteLock()) { 346 final File tracingFile = debugOptions.getFile(); // the tracing file may be null if it has not been set 347 Writer traceWriter = null; 348 try { 349 // check to see if the file should be rotated 350 checkTraceFileSize(tracingFile, entry.getTimestamp()); 351 // open the trace file 352 traceWriter = openWriter(tracingFile); 353 if (debugOptions.newSession()) { 354 writeSession(traceWriter, entry.getTimestamp()); 355 } 356 writeMessage(traceWriter, entry); 357 // flush the writer 358 traceWriter.flush(); 359 } catch (Exception ex) { 360 // any exceptions during tracing should be caught 361 System.err.println("An exception occurred while writing to the platform trace file: ");//$NON-NLS-1$ 362 ex.printStackTrace(System.err); 363 } finally { 364 // close the trace writer 365 closeWriter(traceWriter); 366 } 367 } 368 } 369 } 370 371 /** 372 * Reads the PROP_TRACE_SIZE_MAX and PROP_TRACE_FILE_MAX properties. 373 */ readLogProperties()374 private void readLogProperties() { 375 376 String newMaxTraceFileSize = debugOptions.getConfiguration().getConfiguration(PROP_TRACE_SIZE_MAX); 377 if (newMaxTraceFileSize != null) { 378 maxTraceFileSize = Integer.parseInt(newMaxTraceFileSize); 379 if (maxTraceFileSize != 0 && maxTraceFileSize < DEFAULT_TRACE_FILE_MIN_SIZE) { 380 // If the value is '0', then it means no size limitation. 381 // Also, make sure no inappropriate(too small) assigned value. 382 maxTraceFileSize = DEFAULT_TRACE_FILE_MIN_SIZE; 383 } 384 } 385 386 String newMaxLogFiles = debugOptions.getConfiguration().getConfiguration(PROP_TRACE_FILE_MAX); 387 if (newMaxLogFiles != null) { 388 maxTraceFiles = Integer.parseInt(newMaxLogFiles); 389 if (maxTraceFiles < 1) { 390 // Make sure no invalid assigned value. (at least >= 1) 391 maxTraceFiles = DEFAULT_TRACE_FILES; 392 } 393 } 394 } 395 396 /** 397 * Checks the trace file size. If the file size reaches the limit then the trace file is rotated. 398 * 399 * @param traceFile The tracing file 400 * @param timestamp the timestamp for the session; this is the same timestamp as the first entry 401 * @return false if an error occurred trying to rotate the trace file 402 */ checkTraceFileSize(final File traceFile, long timestamp)403 private boolean checkTraceFileSize(final File traceFile, long timestamp) { 404 405 // 0 file size means there is no size limit 406 boolean isBackupOK = true; 407 if (maxTraceFileSize > 0) { 408 if ((traceFile != null) && traceFile.exists()) { 409 if ((traceFile.length() >> 10) > maxTraceFileSize) { // Use KB as file size unit. 410 final String traceFileName = traceFile.getAbsolutePath(); 411 412 // Delete old backup file that will be replaced. 413 String backupFilename = ""; //$NON-NLS-1$ 414 if (traceFileName.toLowerCase().endsWith(TRACE_FILE_EXTENSION)) { 415 backupFilename = traceFileName.substring(0, traceFileName.length() - TRACE_FILE_EXTENSION.length()) + BACKUP_MARK + backupTraceFileIndex + TRACE_FILE_EXTENSION; 416 } else { 417 backupFilename = traceFileName + BACKUP_MARK + backupTraceFileIndex; 418 } 419 final File backupFile = new File(backupFilename); 420 if (backupFile.exists()) { 421 if (!backupFile.delete()) { 422 System.err.println("Error when trying to delete old trace file: " + backupFile.getName());//$NON-NLS-1$ 423 if (backupFile.renameTo(new File(backupFile.getAbsolutePath() + System.currentTimeMillis()))) { 424 System.err.println("So we rename it to filename: " + backupFile.getName()); //$NON-NLS-1$ 425 } else { 426 System.err.println("And we also cannot rename it!"); //$NON-NLS-1$ 427 isBackupOK = false; 428 } 429 } 430 } 431 432 // Rename current log file to backup one. 433 boolean isRenameOK = traceFile.renameTo(backupFile); 434 if (!isRenameOK) { 435 System.err.println("Error when trying to rename trace file to backup one."); //$NON-NLS-1$ 436 isBackupOK = false; 437 } 438 /* 439 * Write a header to new log file stating that this new file is a continuation file. 440 * This method should already be called with the file lock set so we should be safe 441 * to update it here. 442 */ 443 Writer traceWriter = null; 444 try { 445 traceWriter = openWriter(traceFile); 446 writeComment(traceWriter, "This is a continuation of trace file " + backupFile.getAbsolutePath()); //$NON-NLS-1$ 447 writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERSION_COMMENT + EclipseDebugTrace.TRACE_FILE_VERSION); 448 writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERBOSE_COMMENT + debugOptions.isVerbose()); 449 writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_DATE + getFormattedDate(timestamp)); 450 traceWriter.flush(); 451 } catch (IOException ioEx) { 452 ioEx.printStackTrace(); 453 } finally { 454 closeWriter(traceWriter); 455 } 456 backupTraceFileIndex = (++backupTraceFileIndex) % maxTraceFiles; 457 } 458 } 459 } 460 return isBackupOK; 461 } 462 463 /** 464 * Writes a comment to the trace file 465 * 466 * @param traceWriter the trace writer 467 * @param comment the comment to be written to the trace file 468 * @throws IOException If an error occurs while writing the comment 469 */ writeComment(final Writer traceWriter, final String comment)470 private void writeComment(final Writer traceWriter, final String comment) throws IOException { 471 472 StringBuilder commentText = new StringBuilder(EclipseDebugTrace.TRACE_COMMENT); 473 commentText.append(" "); //$NON-NLS-1$ 474 commentText.append(comment); 475 commentText.append(EclipseDebugTrace.LINE_SEPARATOR); 476 traceWriter.write(commentText.toString()); 477 } 478 479 /** 480 * Accessor to retrieve the time stamp in a formatted manner. 481 * 482 * @return A formatted time stamp based on the {@link EclipseDebugTrace#TRACE_FILE_DATE_FORMATTER} formatter 483 */ getFormattedDate(long timestamp)484 private final String getFormattedDate(long timestamp) { 485 486 return EclipseDebugTrace.TRACE_FILE_DATE_FORMATTER.format(new Date(timestamp)); 487 } 488 489 /** 490 * Accessor to retrieve the text of a {@link Throwable} in a formatted manner so that it can be written to the 491 * trace file. 492 * 493 * @param error The {@lnk Throwable} to format 494 * @return The complete text of a {@link Throwable} as a {@link String} or null if the input error is null. 495 */ getFormattedThrowable(Throwable error)496 private final String getFormattedThrowable(Throwable error) { 497 498 String result = null; 499 if (error != null) { 500 PrintStream throwableStream = null; 501 try { 502 ByteArrayOutputStream throwableByteOutputStream = new ByteArrayOutputStream(); 503 throwableStream = new PrintStream(throwableByteOutputStream, false); 504 error.printStackTrace(throwableStream); 505 result = encodeText(throwableByteOutputStream.toString()); 506 } finally { 507 if (throwableStream != null) { 508 throwableStream.close(); 509 } 510 } 511 } 512 return result; 513 } 514 515 /** 516 * Writes header information to a new trace file 517 * 518 * @param traceWriter the trace writer 519 * @param timestamp the timestamp for the session; this is the same timestamp as the first entry 520 * @throws IOException If an error occurs while writing this session information 521 */ writeSession(final Writer traceWriter, long timestamp)522 private void writeSession(final Writer traceWriter, long timestamp) throws IOException { 523 524 writeComment(traceWriter, EclipseDebugTrace.TRACE_NEW_SESSION + this.getFormattedDate(timestamp)); 525 writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERSION_COMMENT + EclipseDebugTrace.TRACE_FILE_VERSION); 526 writeComment(traceWriter, EclipseDebugTrace.TRACE_FILE_VERBOSE_COMMENT + debugOptions.isVerbose()); 527 writeComment(traceWriter, "The following option strings are specified for this debug session:"); //$NON-NLS-1$ 528 final String[] allOptions = debugOptions.getAllOptions(); 529 for (String allOption : allOptions) { 530 writeComment(traceWriter, "\t" + allOption); //$NON-NLS-1$ 531 } 532 } 533 534 /** 535 * Writes the specified trace entry object to the trace file using the 536 * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} as the delimiter between 537 * each element of the entry. 538 * 539 * @param traceWriter the trace writer 540 * @param entry The trace entry object to write to the trace file 541 * @throws IOException If an error occurs while writing this message 542 */ writeMessage(final Writer traceWriter, final FrameworkDebugTraceEntry entry)543 private void writeMessage(final Writer traceWriter, final FrameworkDebugTraceEntry entry) throws IOException { 544 545 final StringBuilder message = new StringBuilder(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 546 message.append(" "); //$NON-NLS-1$ 547 message.append(encodeText(entry.getThreadName())); 548 message.append(" "); //$NON-NLS-1$ 549 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 550 message.append(" "); //$NON-NLS-1$ 551 message.append(this.getFormattedDate(entry.getTimestamp())); 552 message.append(" "); //$NON-NLS-1$ 553 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 554 message.append(" "); //$NON-NLS-1$ 555 if (!debugOptions.isVerbose()) { 556 // format the trace entry for quiet tracing: only the thread name, timestamp, trace message, and exception (if necessary) 557 message.append(encodeText(entry.getMessage())); 558 } else { 559 // format the trace entry for verbose tracing 560 message.append(entry.getBundleSymbolicName()); 561 message.append(" "); //$NON-NLS-1$ 562 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 563 message.append(" "); //$NON-NLS-1$ 564 message.append(encodeText(entry.getOptionPath())); 565 message.append(" "); //$NON-NLS-1$ 566 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 567 message.append(" "); //$NON-NLS-1$ 568 message.append(entry.getClassName()); 569 message.append(" "); //$NON-NLS-1$ 570 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 571 message.append(" "); //$NON-NLS-1$ 572 message.append(entry.getMethodName()); 573 message.append(" "); //$NON-NLS-1$ 574 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 575 message.append(" "); //$NON-NLS-1$ 576 message.append(entry.getLineNumber()); 577 message.append(" "); //$NON-NLS-1$ 578 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 579 message.append(" "); //$NON-NLS-1$ 580 message.append(encodeText(entry.getMessage())); 581 } 582 if (entry.getThrowable() != null) { 583 message.append(" "); //$NON-NLS-1$ 584 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 585 message.append(" "); //$NON-NLS-1$ 586 message.append(this.getFormattedThrowable(entry.getThrowable())); 587 } 588 message.append(" "); //$NON-NLS-1$ 589 message.append(EclipseDebugTrace.TRACE_ELEMENT_DELIMITER); 590 message.append(EclipseDebugTrace.LINE_SEPARATOR); 591 // write the message 592 if ((traceWriter != null) && (message != null)) { 593 traceWriter.write(message.toString()); 594 } 595 } 596 597 /** 598 * Encodes the specified string to replace any occurrence of the {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} 599 * string with the {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER_ENCODED} 600 * string. This can be used to ensure that the delimiter character does not break parsing when 601 * the entry text contains the delimiter character. 602 * 603 * @param inputString The original string to be written to the trace file. 604 * @return The original input string with all occurrences of 605 * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER} replaced with 606 * {@link EclipseDebugTrace#TRACE_ELEMENT_DELIMITER_ENCODED}. A <code>null</code> value will be 607 * returned if the input string is <code>null</code>. 608 */ encodeText(final String inputString)609 private static String encodeText(final String inputString) { 610 if (inputString == null || inputString.indexOf(TRACE_ELEMENT_DELIMITER) < 0) 611 return inputString; 612 final StringBuilder tempBuffer = new StringBuilder(inputString); 613 int currentIndex = tempBuffer.indexOf(TRACE_ELEMENT_DELIMITER); 614 while (currentIndex >= 0) { 615 tempBuffer.replace(currentIndex, currentIndex + TRACE_ELEMENT_DELIMITER.length(), TRACE_ELEMENT_DELIMITER_ENCODED); 616 currentIndex = tempBuffer.indexOf(TRACE_ELEMENT_DELIMITER); 617 } 618 return tempBuffer.toString(); 619 } 620 621 /** 622 * Returns a Writer for the given OutputStream 623 * @param output an OutputStream to use for the Writer 624 * @return A Writer for the given OutputStream 625 */ logForStream(OutputStream output)626 private Writer logForStream(OutputStream output) { 627 return new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)); 628 } 629 630 /** 631 * Creates the trace writer. 632 * If the tracing file is null then the writer will use System.out to print any messages. 633 * 634 * @param traceFile The tracing file 635 * @return Returns a new Writer object 636 */ openWriter(final File traceFile)637 private Writer openWriter(final File traceFile) { 638 OutputStream out = null; 639 if (traceFile != null) { 640 try { 641 out = secureAction.getFileOutputStream(traceFile, true); 642 } catch (IOException ioEx) { 643 // ignore and fall back to system.out; but print error message to indicate what happened 644 System.err.println("Unable to open trace file: " + traceFile + ": " + ioEx.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ 645 } 646 } 647 if (out == null) { 648 out = new FilterOutputStream(System.out) { 649 /** 650 * @throws IOException 651 */ 652 @Override 653 public void close() throws IOException { 654 // We don't want to close System.out 655 } 656 657 @Override 658 public void write(byte[] var0, int var1, int var2) throws IOException { 659 this.out.write(var0, var1, var2); 660 } 661 }; 662 } else if (consoleLog) { 663 out = new FilterOutputStream(out) { 664 @Override 665 public void write(int b) throws IOException { 666 System.out.write(b); 667 out.write(b); 668 } 669 670 @Override 671 public void write(byte[] b) throws IOException { 672 System.out.write(b); 673 out.write(b); 674 } 675 676 @Override 677 public void write(byte[] b, int off, int len) throws IOException { 678 System.out.write(b, off, len); 679 out.write(b, off, len); 680 } 681 }; 682 } 683 return logForStream(out); 684 } 685 686 /** 687 * Close the trace writer 688 * 689 * @param traceWriter The trace writer 690 */ closeWriter(Writer traceWriter)691 private void closeWriter(Writer traceWriter) { 692 693 if (traceWriter != null) { 694 try { 695 traceWriter.close(); 696 } catch (IOException ioEx) { 697 // we cannot log here; just print the stacktrace. 698 ioEx.printStackTrace(); 699 } 700 traceWriter = null; 701 } 702 } 703 }