/// <summary> /// Get list of records bounded by specified start message filetime and message count /// </summary> /// <param name="StartFileTime">Starting message filetime</param> /// <param name="MessageCount"></param> /// <returns>List of Log Records</returns> public List<LogRecord> GetRecordsByStartFileTimeAndCount(ulong StartFileTime, int MessageCount = 1000) { log.DebugFormat("StartFileTime - {0}", StartFileTime); log.DebugFormat("MaximumMessageCount - {0}", MessageCount); List<LogRecord> returnValue = new List<LogRecord>(); LogRecord localRecord = new LogRecord(); //Find the exact message number and use the search by message number function localRecord = this.GetRecordByFileTime(StartFileTime, EarliestOrLatest.Latest); if (!localRecord.ReturnCode.Status) { log.WarnFormat("Can't locate a record considering filetime {0}", StartFileTime); return returnValue; } //Record retrieval was succesful return this.GetRecordsByStartMessageNumberAndCount(localRecord.MessageNumber, MessageCount); }
/// <summary> /// Return a single log record identified by the specific message number. If no record is found a blank log record is returned. /// </summary> /// <param name="MessageNumber">Specific message number to search for</param> /// <returns>A single log record</returns> public LogRecord GetRecordByMessageNumber(ulong MessageNumber) { log.DebugFormat("MessageNumber - {0}", MessageNumber); LogRecord returnValue = new LogRecord(); /* For optimization purposes first locate the log files that may contain the specific message number We say file(s) because there is currently an issue with how the log system writes files that may repeat a message number in that case will find the first match and return that */ foreach(string logFilePath in GetLogFilePathsForMessageNumber(MessageNumber)) { // Get a reference to the log file by opening it if(!OpenLogFile(logFilePath).Status) { throw new aaLogReaderException(string.Format("Error opening log file {0}",logFilePath)); } //Get the header which should be loaded into a global in memory now LogHeader localHeader = this.CurrentLogHeader; //Determine if we are closer to the beginning or end if((MessageNumber - localHeader.StartMsgNumber) <= (localHeader.EndMsgNumber - MessageNumber)) { //Looks like we are closer to beginning to start at beginning and go next returnValue = GetFirstRecord(); // Start looping until we find the record we are looking for while(returnValue.ReturnCode.Status && returnValue.MessageNumber <MessageNumber ) { returnValue = GetNextRecord(); } } else { //Looks like we are closer to the end so start at end and go previous returnValue = GetLastRecord(); // Start looping until we find the record we are looking for while (returnValue.ReturnCode.Status && returnValue.MessageNumber > MessageNumber) { returnValue = GetPrevRecord(); } } // Check to see if we have found our record if(returnValue.MessageNumber == MessageNumber) { // Dump out of the for loop break; } } return returnValue; }
/// <summary> /// Get list of records bounded by the specified start and end message number /// </summary> /// <param name="StartMessageNumber">Specified starting message number</param> /// <param name="EndMessageNumber">Specified ending message number</param> /// <returns>List of Log Records</returns> public List<LogRecord> GetRecordsByStartandEndMessageNumber(ulong StartMessageNumber, ulong EndMessageNumber) { log.DebugFormat("StartMessageNumber - {0}", StartMessageNumber); log.DebugFormat("EndMessageNumber - {0}", EndMessageNumber); List<LogRecord> returnValue = new List<LogRecord>(); LogRecord localRecord = new LogRecord(); if (StartMessageNumber > EndMessageNumber) { // Reverse log.WarnFormat("Start ({0}) and End ({0}) Message Numbers Reversed. Correcting before proceeding", StartMessageNumber, EndMessageNumber); ulong temp = EndMessageNumber; EndMessageNumber = StartMessageNumber; StartMessageNumber = temp; } localRecord = this.GetRecordByMessageNumber(StartMessageNumber); if (localRecord.ReturnCode.Status) { returnValue.Add(localRecord); } while ((localRecord.MessageNumber <= EndMessageNumber) && localRecord.ReturnCode.Status) { localRecord = GetNextRecord(); if(localRecord.ReturnCode.Status) { returnValue.Add(localRecord); } else { break; } } return returnValue; }
/// <summary> /// Get the lastRecordRead immediately previous to the current lastRecordRead in the log file. This call will swap to previous log files as required. /// </summary> /// <returns>A single log record</returns> public LogRecord GetPrevRecord() { log.Debug(""); ulong LastMessageNumber; LogRecord localRecord = new LogRecord(); if (this.LastRecordRead.OffsetToPrevRecord != 0) { log.Debug("this.lastRecordRead.OffsetToPrevRecord != 0"); // Cache the last message number LastMessageNumber = this.LastRecordRead.MessageNumber; // Read the lastRecordRead based off offset information from last lastRecordRead read localRecord = this.ReadLogRecord(this.LastRecordRead.OffsetToPrevRecord, Convert.ToUInt64(decimal.Subtract(new decimal(LastMessageNumber), decimal.One))); } // Check to see if we are at the beginning of if there is another log file we can connect to else if (System.String.Compare(this.CurrentLogHeader.PrevFileName, "", false) == 0) { log.Debug("this.lastRecordRead.OffsetToPrevRecord = 0 AND this.logHeader.PrevFileName == 0"); localRecord.ReturnCode.Status = false; // Beginning of Log localRecord.ReturnCode.Message = "BOL"; } else { log.Debug("Close current log file"); // Close the currently opened log file this._fileStream.Close(); string newPreviousLogFile = Path.Combine(this.GetLogDirectory(), this.CurrentLogHeader.PrevFileName); log.Debug("newPreviousLogFile - " + newPreviousLogFile); try { if (this.OpenLogFile(newPreviousLogFile).Status) { localRecord = this.GetLastRecord(); log.Debug("localRecord.ReturnCode.Status - " + localRecord.ReturnCode.Status); } else { log.ErrorFormat("Error attempting to open previous log file: {0}", newPreviousLogFile); } } catch (Exception ex) { log.ErrorFormat("Error attempting to open previous log file: {0} - {1}", newPreviousLogFile, ex.Message); throw new aaLogReaderException("Error attempting to open previous log file.", ex); } } return localRecord; }
/// <summary> /// Return a single log record identified by the specific message filetime. If no record is found a blank log record is returned. /// </summary> /// <param name="MessageFiletime">Message filetime to use when searching</param> /// <param name="TimestampEarlyOrLate">Earliest = Message immediately before timestamp, Latest = Message immediately after timestamp</param> /// <returns>A single log record</returns> public LogRecord GetRecordByFileTime(ulong MessageFiletime, EarliestOrLatest TimestampEarlyOrLate = EarliestOrLatest.Earliest) { log.DebugFormat("MessageFiletime - {0}", MessageFiletime); LogRecord returnValue = new LogRecord(); bool foundRecord = false; /* For optimization purposes first locate the log files that may contain a message with the specified filetime We say file(s) because there is currently an issue with how the log system writes files that may overlap timestamps in that case will find the first match and return that * * General premise of searching early or late is that early will get message immediately on or before target timestamp * and late will get message immediately on or after target timestamp */ foreach (string logFilePath in GetLogFilePathsForMessageFileTime(MessageFiletime)) { // Get a reference to the log file by opening it if (!OpenLogFile(logFilePath).Status) { throw new aaLogReaderException(string.Format("Error opening log file {0}", logFilePath)); } //Get the header which should be loaded into a global in memory now LogHeader localHeader = this.CurrentLogHeader; //Determine if we are closer to the beginning or end if ((MessageFiletime - localHeader.StartFileTime) <= (localHeader.EndFileTime - MessageFiletime)) { log.DebugFormat("Starting from beginning of file at filetime {0}", localHeader.StartFileTime); //Looks like we are closer to beginning to start at beginning and go next returnValue = GetFirstRecord(); // Start looping until we find the record we are looking for considering the Early or Late Timestamp parameters while (returnValue.ReturnCode.Status) { // If we have gone past our target timestamp then go back and get the last record if (returnValue.EventFileTime >= MessageFiletime) { if (TimestampEarlyOrLate == EarliestOrLatest.Earliest) { // Go back one record returnValue = GetPrevRecord(); // Make sure we got a good record then dump out of the while loop if (returnValue.ReturnCode.Status) { foundRecord = true; break; } } else { break; } } // Get the next record returnValue = GetNextRecord(); } } else { //Looks like we are closer to the end so start at end and go previous returnValue = GetLastRecord(); // Start looping until we find the record we are looking for considering the Early or Late Timestamp parameters while (returnValue.ReturnCode.Status) { // If we have gone past our target timestamp then go back and get the last record if (returnValue.EventFileTime <= MessageFiletime) { if (TimestampEarlyOrLate == EarliestOrLatest.Latest) { // Go back one record returnValue = GetNextRecord(); // Make sure we got a good record then dump out of the while loop if (returnValue.ReturnCode.Status) { foundRecord = true; break; } } else { break; } } // Get the previous record returnValue = GetPrevRecord(); } } // Check to see if we have found our record if (foundRecord) { // Dump out of the for loop break; } } return returnValue; }
/// <summary> /// Write a text file out with metadata that can be used if the application is closed and reopened to read logs again /// </summary> /// <param name="CacheRecord">Complete record to write out containing cache information</param> private ReturnCodeStruct WriteStatusCacheFile(LogRecord CacheRecord) { log.Debug(""); log.Debug("CacheRecord - " + CacheRecord.ToJSON()); ReturnCodeStruct returnValue; try { System.IO.File.WriteAllText(this.GetStatusCacheFilePath(), CacheRecord.ToJSON()); returnValue = new ReturnCodeStruct { Status = true, Message = "" }; } catch(Exception ex) { log.Error(ex); returnValue = new ReturnCodeStruct { Status = false, Message = ex.Message}; } return returnValue; }
/// <summary> /// Get the last lastRecordRead in the log as specified by the OffsetLastRecord in the header. /// </summary> /// <returns>A single log record</returns> public LogRecord GetLastRecord() { log.Debug(""); LogRecord localRecord = new LogRecord(); if (this.CurrentLogHeader.OffsetLastRecord == 0) { localRecord.ReturnCode.Status = false; localRecord.ReturnCode.Message = "Offset to Last Record is 0. No record returned."; } else { localRecord = this.ReadLogRecord(this.CurrentLogHeader.OffsetLastRecord, this.CurrentLogHeader.EndMsgNumber); } return localRecord; }
/// <summary> /// Get the first lastRecordRead in the log as specified by the OffsetFirstRecord in the header. /// </summary> /// <returns>A single log record</returns> public LogRecord GetFirstRecord() { log.Debug(""); LogRecord localRecord = new LogRecord(); if (this.CurrentLogHeader.OffsetFirstRecord == 0) { this.LastRecordRead = new LogRecord(); } else { localRecord = this.ReadLogRecord(this.CurrentLogHeader.OffsetFirstRecord, this.CurrentLogHeader.StartMsgNumber); } return localRecord; }
/// <summary> /// Calculate if the logic should get the next record based on multiple factors /// </summary> /// <param name="lastRecord">The last record retrieved</param> /// <param name="logRecordCount">Current number of records retrieved</param> /// <param name="lastReadMessageNumber">Message number indicating it should be the last message to retrieve</param> /// <param name="maximumMessages">Maximum number of messages to retrieve. Will be compared to logRecordCount</param> /// <param name="messagePatternToStop">A specific message pattern to indicate the logic should not retrieve the next record</param> /// <returns></returns> private bool ShouldGetNextRecord(LogRecord lastRecord,ulong logRecordCount, ulong lastReadMessageNumber, ulong maximumMessages, string messagePatternToStop) { bool returnValue = false; try { log.Debug(""); log.Debug("lastReadMessageNumber - " + lastReadMessageNumber.ToString()); log.Debug("maximumMessages - " + maximumMessages.ToString()); log.Debug("messagePattern - " + messagePatternToStop); /* If the last retrieval was good * and we have an offset for previous lastRecordRead * and we haven't passed the maximum lastRecordRead count limit * retrieve the next previous lastRecordRead */ returnValue = (lastRecord.ReturnCode.Status && (lastRecord.OffsetToNextRecord > 0) && (lastRecord.MessageNumber > (lastReadMessageNumber+1)) && (logRecordCount < maximumMessages)); /* If the message pattern is not blank then apply a regex to see if we get a match * If we match then that means this is the last lastRecordRead we should retrieve so return false */ if(returnValue && messagePatternToStop != "") { returnValue &= !System.Text.RegularExpressions.Regex.IsMatch(lastRecord.Message, messagePatternToStop, System.Text.RegularExpressions.RegexOptions.IgnoreCase); } } catch(Exception ex) { log.Warn(ex); returnValue = false; } return returnValue; }
/// <summary> /// Read a log lastRecordRead that starts at the specified offset /// </summary> /// <param name="FileOffset">Offset for the current file stream</param> /// <param name="MessageNumber">Passed message number to set on the log lastRecordRead. This should be calculated from external logic</param> /// <returns>A single log record</returns> private LogRecord ReadLogRecord(int FileOffset, ulong MessageNumber = 0) { log.Debug(""); log.Debug("FileOffset - " + FileOffset.ToString()); log.Debug("MessageNumber - " + MessageNumber.ToString()); int recordLength = 0; LogRecord localRecord = new LogRecord(); byte[] byteArray = new byte[1]; int workingOffset = 0; int fieldLength; try { // Initialize the return status localRecord.ReturnCode.Status = false; localRecord.ReturnCode.Message = ""; // Initialize working position workingOffset = 0; // Check to make sure we can even read from the file if(!_fileStream.CanSeek) { throw new aaLogReaderException("Log file not open for reading"); } // Go to the spot in the file stream specified by the offset this._fileStream.Seek((long)FileOffset, SeekOrigin.Begin); // Make sure we have at least 8 byteArray of data to read before hitting the end byteArray = new byte[8]; if (this._fileStream.Read(byteArray, 0, 8) == 0) { throw new aaLogReaderException("Attempt to read past End-Of-Log-File"); } //Get the first 4 byteArray of data byte array that we just retrieved. // This tells us how long this lastRecordRead is. recordLength = BitConverter.ToInt32(byteArray, 4); // If the lastRecordRead length is not > 0 then bail on the function, returning an empty lastRecordRead with status code if(recordLength <= 0) { throw new aaLogReaderException("Record Length is 0"); } //Go back and reset to the specified offset this._fileStream.Seek((long)FileOffset, SeekOrigin.Begin); //Recreate the byte array with the proper length byteArray = new byte[checked(recordLength + 1)]; //Now get the actual lastRecordRead data into the byte array for processing this._fileStream.Read(byteArray, 0, recordLength); // Record Length. We've already calculated this so just use internal variable localRecord.RecordLength = recordLength; // Offset to Previous Record. localRecord.OffsetToPrevRecord = byteArray.GetInt(8); // Offset to Next Record localRecord.OffsetToNextRecord = checked((int)FileOffset + recordLength); // Session ID localRecord.SessionID = byteArray.GetSessionIDSegments(12).SessionID; //this._sessionSeg.SessionID; // Process ID localRecord.ProcessID = byteArray.GetUInt32(16); // Thread ID localRecord.ThreadID = byteArray.GetUInt32(20); // File Time localRecord.EventFileTime = byteArray.GetFileTime(24); // Log Flag workingOffset = 32; localRecord.LogFlag = byteArray.GetString(workingOffset, out fieldLength); /* * Calc new working offset based on length of previously retrieved field. * Can't forget that we're dealing with Unicode so we have to double the * length to find the proper byte offset */ workingOffset += fieldLength + 2; localRecord.Component = byteArray.GetString(workingOffset, out fieldLength); workingOffset += fieldLength + 2; localRecord.Message = byteArray.GetString(workingOffset, out fieldLength); workingOffset += fieldLength + 2; localRecord.ProcessName = byteArray.GetString(workingOffset, out fieldLength); // Get the host from the header information localRecord.HostFQDN = ReadLogHeader().HostFQDN; localRecord.ReturnCode.Status = true; localRecord.ReturnCode.Message = ""; // Set the message number on the lastRecordRead based on the value passed localRecord.MessageNumber = MessageNumber; } catch (System.ApplicationException saex) { // If this is a past the end of file message then handle gracefully if(saex.Message == "Attempt to read past End-Of-Log-File") { this.ReturnCloseValue = this.CloseCurrentLogFile(); // Re-init the lastRecordRead to make sure it's totally blank. Don't want to return a partial lastRecordRead localRecord = new LogRecord(); localRecord.ReturnCode.Status = false; localRecord.ReturnCode.Message = saex.Message; } else { throw; } } catch(Exception ex) { log.Error(ex); throw; } // Set the last lastRecordRead read to this one. this.LastRecordRead = localRecord; // Return the working lastRecordRead return localRecord; }
private static LogRecord ReadLogRecord(Stream stream, int offset, int previousOffset, LogHeader header) { LOG.DebugFormat("offset: {0}", offset); // ReSharper disable once UseObjectOrCollectionInitializer var record = new LogRecord(); record.RecordLength = stream.GetInt(offset + 4); record.OffsetToPrevRecord = previousOffset; record.OffsetToNextRecord = checked(offset + record.RecordLength); stream.Seek(offset, SeekOrigin.Begin); var bytes = new byte[record.RecordLength]; stream.Read(bytes, 0, record.RecordLength); record.MessageNumber = bytes.GetULong(16); record.SessionID = string.Format("{0}.{1}.{2}.{3}", bytes[27], bytes[26], bytes[25], bytes[24]); record.ProcessID = (uint)bytes.GetInt(28); record.ThreadID = (uint)bytes.GetInt(32); record.EventFileTime = bytes.GetFileTime(36); var position = 44; int length; record.LogFlag = bytes.GetString(position, out length); position += length + 2; record.Component = bytes.GetString(position, out length); position += length + 2; record.Message = bytes.GetString(position, out length); position += length + 2; record.ProcessName = bytes.GetString(position, out length); return record; }
public static string Header(char Delimiter = ',', ExportFormat format = ExportFormat.Full) { LogRecord lr = new LogRecord(); return lr.localHeader(Delimiter, format); }
public static string HeaderTSV(ExportFormat format = ExportFormat.Full) { return(LogRecord.Header('\t', format)); }
public static string Header(char Delimiter = ',', ExportFormat format = ExportFormat.Full) { LogRecord lr = new LogRecord(); return(lr.localHeader(Delimiter, format)); }