// Search down the stack for the next caller for the specified binary file. private string GetCaller(BinaryFileState fileData) { // For binary files, the stack depth is decremented AFTER this is called, // so check stackdepth > 1. if (fileData.StackDepth > 1) { for (StackEntry caller = TopStackEntry.Caller; caller != null; caller = caller.Caller) { if (caller.BinaryFileState == fileData) { return(caller.MethodName); } } Debug.Assert(false, "A caller should have been found."); } return(""); }
// Looks up the thread-specific state for the specified binary file, // creating a new one if necessary. internal BinaryFileState GetBinaryFileState(BinaryFile file) { BinaryFileState result = BinaryFileState; if (BinaryFileState == null || BinaryFileState.File != file) { // Look up or create the state data for the specified file. if (!_binaryFileStatesForThisThread.TryGetValue(file, out result)) { result = new BinaryFileState(); result.File = file; _binaryFileStatesForThisThread.Add(file, result); } BinaryFileState = result; } else { // We already have the data for the specified file. } return(result); }
// This sets bits in the flags parameter that specify what data to include with the line. private DataFlags SetDataFlags(DataFlags flags, ThreadData threadData, BinaryFileState fileThreadState, Logger logger, TraceLevel lineLevel) { if (_lastBlock != _curBlock) { // The very first line in each block (regardless of thread) // includes the time in case this line ends up being the first line due to wrapping. flags |= DataFlags.Time; if (CircularStarted) { flags |= DataFlags.BlockStart; } } if (fileThreadState.LastBlock != _curBlock) { // First line in current block for the thread. Include all per-thread data. flags |= DataFlags.StackDepth | DataFlags.MethodName | DataFlags.TraceLevel | DataFlags.ThreadId | DataFlags.LoggerName; if (threadData.Name != null) { flags |= DataFlags.ThreadName; } } else { if (_lastThread != threadData) { // This line's thread differs from the last line's thread. flags |= DataFlags.ThreadId; } if (fileThreadState.LastThreadName != threadData.Name) { // Thread's name has changed. flags |= DataFlags.ThreadId | DataFlags.ThreadName; } if (fileThreadState.CurrentMethod != fileThreadState.LastMethod) { // We have a new method name for this thread. flags |= DataFlags.MethodName; } if (fileThreadState.LastTraceLevel != lineLevel) { // This line's trace Level differs from the previous line // logged by this thread. flags |= DataFlags.TraceLevel; } if (fileThreadState.LastLogger != logger) { // This line's logger name differs from the previous line // logged by this thread. flags |= DataFlags.LoggerName; } } return(flags); }
// This is what actually writes the output. The dataFlags parameter specifies what to write. private void WriteData(DataFlags dataFlags, ThreadData threadData, BinaryFileState fileThreadState, Logger logger, TraceLevel lineLevel, string msg) { ++_lineCnt; // Write the flags first so the viewer will know what else the record contains. _logfile.Write((ushort)dataFlags); if (CircularStarted) { _logfile.Write(_curBlock); if ((dataFlags & DataFlags.BlockStart) != DataFlags.None) { // This will be the first record in the block. // This stuff helps the viewer find the first chronological block // even after wrapping. Writting _lastBlockPosition forms a linked // list of blocks that the viewer can follow. //System.Diagnostics.Debug.Print("Block {0} starting at line {1}, position {2}", _curBlock, _lineCnt, _logfile.BaseStream.Position); _logfile.Write(_lineCnt); _logfile.Write(_lastBlockPosition); } } if ((dataFlags & DataFlags.Time) != DataFlags.None) { _logfile.Write(_curTime.Ticks); } if ((dataFlags & DataFlags.ThreadId) != DataFlags.None) { _logfile.Write(threadData.TracerXID); } if ((dataFlags & DataFlags.ThreadName) != DataFlags.None) { // ThreadPool thread names get reset to null when a thread is returned // to the pool and reused later. if (_hasPassword) { _encryptor.Encrypt(threadData.Name ?? string.Empty); } else { _logfile.Write(threadData.Name ?? string.Empty); } } if ((dataFlags & DataFlags.TraceLevel) != DataFlags.None) { _logfile.Write((byte)lineLevel); } // In format version 5 and later, the viewer subtracts 1 from the stack depth on // MethodExit lines instead of the logger, so just write the depth as-is. if ((dataFlags & DataFlags.StackDepth) != DataFlags.None) { _logfile.Write(fileThreadState.StackDepth); if (CircularStarted) { // In the circular part, include the thread's call stack with the first line // logged for each thread in each block. This enables the viewer to // regenerate method entry/exit lines lost due to wrapping. // Added in format version 5. int count = 0; for (StackEntry stackEntry = threadData.TopStackEntry; stackEntry != null; stackEntry = stackEntry.Caller) { if (stackEntry.BinaryFileState == fileThreadState) { ++count; _logfile.Write(stackEntry.EntryLine); // Changed to ulong in version 6. _logfile.Write((byte)stackEntry.Level); if (_hasPassword) { Debug.Assert(stackEntry.Logger.Name != null); _encryptor.Encrypt(stackEntry.Logger.Name); Debug.Assert(stackEntry.MethodName != null); _encryptor.Encrypt(stackEntry.MethodName); } else { _logfile.Write(stackEntry.Logger.Name); _logfile.Write(stackEntry.MethodName); } } } // The StackDepth we wrote previously is how the viewer will know how many // stack entries to read. System.Diagnostics.Debug.Assert(count == fileThreadState.StackDepth); } } if ((dataFlags & DataFlags.LoggerName) != DataFlags.None) { if (_hasPassword) { _encryptor.Encrypt(logger.Name); } else { _logfile.Write(logger.Name); } } if ((dataFlags & DataFlags.MethodName) != DataFlags.None) { if (_hasPassword) { _encryptor.Encrypt(fileThreadState.CurrentMethod); } else { _logfile.Write(fileThreadState.CurrentMethod); } fileThreadState.LastMethod = fileThreadState.CurrentMethod; } if ((dataFlags & DataFlags.Message) != DataFlags.None) { if (_hasPassword) { _encryptor.Encrypt(msg ?? ""); } else { _logfile.Write(msg); } } _lastBlock = _curBlock; _lastThread = threadData; fileThreadState.LastBlock = _curBlock; fileThreadState.LastThreadName = threadData.Name; fileThreadState.LastTraceLevel = lineLevel; fileThreadState.LastLogger = logger; }
// Determine what data needs to be written based on dataFlags, // whether circular logging has or should be started, // and whether we're starting a new circular block. // Write the output to the file. Manage the circular part of the log. // Return the line number just written. private ulong WriteLine(DataFlags dataFlags, ThreadData threadData, Logger logger, TraceLevel lineLevel, DateTime explicitUtcTime, string msg, bool recursive) { BinaryFileState fileThreadState = threadData.GetBinaryFileState(this); lock (_fileLocker) { try { if (IsOpen) { if (fileThreadState.LastFileNumber != CurrentFile) { // First time writing to this file. threadData.ResetBinaryFileStateData(CurrentFile, dataFlags); } // Calling IsNewTime() can change _curTime. if (IsNewTime(explicitUtcTime) || recursive) { // Time differs from previous line. // Set the flag indicating it will be written dataFlags |= DataFlags.Time; } // Possibly start the circular log based on _curTime and/or file size. // Put this after calling IsNewTime() so _curTime will have // the latest DateTime value. if (FullFilePolicy == FullFilePolicy.Wrap && !recursive && !CircularStarted && (_curTime >= _circularStartTime || (CircularStartSizeKb > 0 && (_logfile.BaseStream.Position - _openSize) >= CircularStartSizeKb << 10))) { // This will start the circular part of the log if there is enough // room based on current file position and max file size. // It will increment _curBlock if it starts the circular log. // It will also make a recursive call to this method via Metalog. StartCircular(logger, lineLevel); } // Set bits in Flags that indicate what data should be written for this line. dataFlags = SetDataFlags(dataFlags, threadData, fileThreadState, logger, lineLevel); // We need to know the start position of the line we're about // to write to determine if it overwrites the beginning of the oldest block. long startPos = _logfile.BaseStream.Position; // We capture the size of the file before writing the message so we can tell // if the size changes. long startSize = _logfile.BaseStream.Length; // Write the Flags to the file followed by the data the Flags say to log. WriteData(dataFlags, threadData, fileThreadState, logger, lineLevel, msg); // If the file is being viewed, this will notify the viewer that the file was changed. _viewerSignaler.SignalEvents(); if (CircularStarted) { ManageCircularPart(startPos, startSize, dataFlags); } else if (_logfile.BaseStream.Position >= _maxFilePosition) { // We can't do any meta-logging here because the viewer expects the first record to reach // _maxFilePosition (which we just wrote) to be the last. Writing another record would cause errors. switch (FullFilePolicy) { case FullFilePolicy.Close: Close(); break; case FullFilePolicy.Roll: // If logging a method-entry or method-exit, wait until the next call // to close and open. if ((dataFlags & (DataFlags.MethodEntry | DataFlags.MethodExit)) == 0) { // These calls may raise events whose handlers modify our properties. Close(); Open(); } break; case FullFilePolicy.Wrap: // Reaching max file size/position without being in circular mode means we'll never write to // this file again, so we might as well close it. Since this is probably not what the user intended, // also log an event. string errmsg = "The maximum file size of " + _maxFilePosition + " was reached before circular logging was engaged. The log file is " + FullPath; Logger.EventLogging.Log(errmsg, Logger.EventLogging.MaxFileSizeReached); Close(); break; } } } } catch (Exception ex) { // Give up! close the log file, free whatever memory we can. Logger.EventLogging.Log("An exception was thrown while logging: " + ex.ToString(), Logger.EventLogging.ExceptionInLogger); Close(); } return(_lineCnt); } }