Kittyfisto / Tailviewer

Open source log file viewer
https://kittyfisto.github.io/Tailviewer/
MIT License
200 stars 37 forks source link

Can we change the visualization of the .txt log files by writing plugins? For an example by changing the time stamp of the existing log file to display in some other format. If so can you redirect to some of the examples ? #182

Closed abani1986 closed 5 years ago

abani1986 commented 5 years ago

Current behaviour

### Expected behaviour ### Steps to reproduce the problem
abani1986 commented 5 years ago

@Kittyfisto Could you please provide some input on this?

Kittyfisto commented 5 years ago

Yes, that's absolutely possible.

Assuming you've already referenced the latest Tailviewer.Api nuget package:

You start by implemening the IFileFormatPlugin interface which is at least partly described here.

However you most likely do not want to implement ILogFile itself because it's rather complicated. Instead you want IFileFormatPlugin.Open to return a new instance of Tailviewer.Core.LogFiles.TextLogFile (which is part of the Tailviewer.Core assembly, also part of the Tailviewer.Api nuget package). This class allows you to inject two (optional) objects: ITimestampParser and ILogLineTranslator.

ITimestampParser is responsible for finding out which format is used to express timestamps in a particular log file and for parsing them into DateTime objects.

ILogLineTranslator is responsible for translating line by line. From your title I think you want to implement this class. Currently, Tailviewer simply displays the LogLine.Message property in its entirety, which means in order to change the displayed timestamp, you'll have to create a new LogLine, forwarding all properties of the existing LogLine object (which are required for book-keeping) and simply change the message to your liking.

Here is a real world example of a translator I've developed for work:

using System.Text;
using Tailviewer.BusinessLogic.LogFiles;

namespace Phylis
{
    public sealed class MessageTracerTranslator
        : ILogLineTranslator
    {
        private readonly int[] _columnLengths;

        public const int TimestampLength = 26;
        public const int ClockLength = 6;
        public const int TimeProcessLength = 13;
        public const int ModuleProcessLength = 15;
        public const int ModuleCodeLength = 4;
        public const int InstanceLength = 9;
        public const int SourceIdLength = 6;
        public const int TargetIdLength = 9;
        public const int MessageTypeLength = 9;

        public MessageTracerTranslator()
        {
            _columnLengths = new[]
            {
                TimestampLength,
                ClockLength,
                TimeProcessLength,
                ModuleProcessLength,
                ModuleCodeLength,
                InstanceLength,
                SourceIdLength,
                TargetIdLength,
                MessageTypeLength,
                1,
                1,
                1,
                1,
                1,
                2
            };
        }

        public LogLine Translate(ILogFile logFile, LogLine line)
        {
            if (line.OriginalLineIndex == 0)
                return TranslateHeader(line);

            return TranslateLogLine(line);
        }

        private LogLine TranslateHeader(LogLine line)
        {
            return MakeTabular(line, Alignment.Center);
        }

        private LogLine TranslateLogLine(LogLine line)
        {
            var next = ReplaceTimestamp(line);
            return MakeTabular(next, Alignment.Right);
        }

        private LogLine ReplaceTimestamp(LogLine line)
        {
            var timestamp = line.Timestamp;
            var message = line.Message;
            if (timestamp == null || message == null)
                return line;

            var idx = message.IndexOf(';');
            if (idx < MessageTracerTimestampParser.TimestampLength)
                return line;

            var nonTimestampPart = message.Substring(MessageTracerTimestampParser.TimestampLength);
            var desiredMessage = string.Format("{0}{1}", timestamp, nonTimestampPart);
            return new LogLine(line.LineIndex, line.OriginalLineIndex, line.LogEntryIndex, desiredMessage,
                                           line.Level,
                                           line.Timestamp);
        }

        enum Alignment
        {
            Center,
            Right
        }

        private LogLine MakeTabular(LogLine line, Alignment alignment)
        {
            var columns = line.Message.Split(';');
            var header = new StringBuilder(line.Message.Length);
            for (int i = 0; i < columns.Length; ++i)
            {
                if (i != 0)
                    header.Append(";");

                var column = columns[i];
                var length = i < _columnLengths.Length ? _columnLengths[i] : column.Length;
                if (length > column.Length)
                {
                    var padding = length - column.Length;

                    switch (alignment)
                    {
                        case Alignment.Center:var leftPadding = padding / 2;
                            var rightPadding = padding - leftPadding;
                            header.Append(' ', leftPadding);
                            header.Append(column);
                            header.Append(' ', rightPadding);
                            break;

                        case Alignment.Right:
                            header.Append(' ', padding);
                            header.Append(column);
                            break;
                    }

                }
                else
                {
                    header.Append(column);
                }
            }

            return new LogLine(line.LineIndex, line.LogEntryIndex, header.ToString(), line.Level);
        }
    }
}

Let me know if this helps.

abani1986 commented 5 years ago

Hi,

Your suggestion works fine for m. Thanks a lot for this.

I have few more questions on this. Could you please answer this ?

I have few log files where the logging time stamp are different. That's the reason I wanted to write plugin so that I can view all logs with a common time stamp.

Now I wanted to merge those log files so that I can see all log files in a single window to verify different logging happened at the same time.

But after writing plugin for a common time stamp formatter I am unable to do so. It gives me the below error.

I have converted my log time stmap to String.Format("{0:yyyy-MM-dd HH:mm:ss:fff }", timestamp); [image: image.png]

Could you please help on this?

On Wed, Apr 24, 2019 at 6:06 PM Simon Mießler notifications@github.com wrote:

Yes, that's absolutely possible.

Assuming you've already referenced the latest Tailviewer.Api nuget package:

You start by implemening the IFileFormatPlugin interface which is at least partly described here https://github.com/Kittyfisto/Tailviewer/blob/master/docs/DevelopingFileFormatPlugins.md .

However you most likely do not want to implement ILogFile itself because it's rather complicated. Instead you want IFileFormatPlugin.Open to return a new instance of Tailviewer.Core.LogFiles.TextLogFile (which is part of the Tailviewer.Core assembly, also part of the Tailviewer.Api nuget package). This class allows you to inject two (optional) objects: ITimestampParser and ILogLineTranslator.

ITimestampParser is responsible for finding out which format is used to express timestamps in a particular log file and for parsing them into DateTime objects.

ILogLineTranslator is responsible for translating line by line. From your title I think you want to implement this class. Currently, Tailviewer simply displays the LogLine.Message property in its entirety, which means in order to change the displayed timestamp, you'll have to create a new LogLine, forwarding all properties of the existing LogLine object (which are required for book-keeping) and simply change the message to your liking.

Here is a real world example of a translator I've developed for work:

using System.Text; using Tailviewer.BusinessLogic.LogFiles;

namespace Phylis { public sealed class MessageTracerTranslator : ILogLineTranslator { private readonly int[] _columnLengths;

  public const int TimestampLength = 26;
  public const int ClockLength = 6;
  public const int TimeProcessLength = 13;
  public const int ModuleProcessLength = 15;
  public const int ModuleCodeLength = 4;
  public const int InstanceLength = 9;
  public const int SourceIdLength = 6;
  public const int TargetIdLength = 9;
  public const int MessageTypeLength = 9;

  public MessageTracerTranslator()
  {
      _columnLengths = new[]
      {
          TimestampLength,
          ClockLength,
          TimeProcessLength,
          ModuleProcessLength,
          ModuleCodeLength,
          InstanceLength,
          SourceIdLength,
          TargetIdLength,
          MessageTypeLength,
          1,
          1,
          1,
          1,
          1,
          2
      };
  }

  public LogLine Translate(ILogFile logFile, LogLine line)
  {
      if (line.OriginalLineIndex == 0)
          return TranslateHeader(line);

      return TranslateLogLine(line);
  }

  private LogLine TranslateHeader(LogLine line)
  {
      return MakeTabular(line, Alignment.Center);
  }

  private LogLine TranslateLogLine(LogLine line)
  {
      var next = ReplaceTimestamp(line);
      return MakeTabular(next, Alignment.Right);
  }

  private LogLine ReplaceTimestamp(LogLine line)
  {
      var timestamp = line.Timestamp;
      var message = line.Message;
      if (timestamp == null || message == null)
          return line;

      var idx = message.IndexOf(';');
      if (idx < MessageTracerTimestampParser.TimestampLength)
          return line;

      var nonTimestampPart = message.Substring(MessageTracerTimestampParser.TimestampLength);
      var desiredMessage = string.Format("{0}{1}", timestamp, nonTimestampPart);
      return new LogLine(line.LineIndex, line.OriginalLineIndex, line.LogEntryIndex, desiredMessage,
                                     line.Level,
                                     line.Timestamp);
  }

  enum Alignment
  {
      Center,
      Right
  }

  private LogLine MakeTabular(LogLine line, Alignment alignment)
  {
      var columns = line.Message.Split(';');
      var header = new StringBuilder(line.Message.Length);
      for (int i = 0; i < columns.Length; ++i)
      {
          if (i != 0)
              header.Append(";");

          var column = columns[i];
          var length = i < _columnLengths.Length ? _columnLengths[i] : column.Length;
          if (length > column.Length)
          {
              var padding = length - column.Length;

              switch (alignment)
              {
                  case Alignment.Center:var leftPadding = padding / 2;
                      var rightPadding = padding - leftPadding;
                      header.Append(' ', leftPadding);
                      header.Append(column);
                      header.Append(' ', rightPadding);
                      break;

                  case Alignment.Right:
                      header.Append(' ', padding);
                      header.Append(column);
                      break;
              }

          }
          else
          {
              header.Append(column);
          }
      }

      return new LogLine(line.LineIndex, line.LogEntryIndex, header.ToString(), line.Level);
  }

} }

Let me know if this helps.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-486209460, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57HVUIOJ4FC75V7HCLLPSBH4ZANCNFSM4HHWKUZA .

-- Thanks,

Abani @Kittyfisto

abani1986 commented 5 years ago

image

Kittyfisto commented 5 years ago

The message suggests that Tailviewer is unable to parse the timestamps on the log files (Tailviewer only supports a very limited amount of timestamps by default). This is necessary to merge log files (the little crossed out clock icon is supposed to indicate that timestamps could not be parsed for the given number of lines).

You can remedy that by implementing ITimestampParser and using either DateTime.TryParse or DateTime.TryParseExact with a pattern describing your timestamp. Upon creating a TextLogFile in your plugin, you forward an instance of your ITimestampParser implementation to its ctor and then Tailviewer should be able to merge your log files.

abani1986 commented 5 years ago

Hi,

I have attached the screen shot of code here. I have used DateTime.TryParseExact but it shows the same message while merging two log files.

Could you please have a look and let me know if I need to changes anything in my code ?

[image: image.png]

On Sat, Apr 27, 2019 at 5:32 PM Simon Mießler notifications@github.com wrote:

The message suggests that Tailviewer is unable to parse the timestamps on the log files (Tailviewer only supports a very limited amount of timestamps by default). This is necessary to merge log files (the little crossed out clock icon is supposed to indicate that timestamps could not be parsed for the given number of lines).

You can remedy that by implementing ITimestampParser and using either DateTime.TryParse or DateTime.TryParseExact with a pattern describing your timestamp. Upon creating a TextLogFile in your plugin, you forward an instance of your ITimestampParser implementation to its ctor and then Tailviewer should be able to merge your log files.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-487280224, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57F6TCRUASBV2BCZXWDPSQ6FLANCNFSM4HHWKUZA .

-- Thanks,

Abani

Kittyfisto commented 5 years ago

There seems to be a problem when attaching images. Could you post both your code and an example line (including the actual timestamp)? I'll definitely have a look at it.

abani1986 commented 5 years ago

Hi,

Please find the below attached code.

private LogLine ReplaceTimestamp(LogLine line) { var timestamp = line.Timestamp; var message = line.Message; DateTime newTimestamp1=DateTime.Now; if (string.IsNullOrEmpty(timestamp.ToString())) { timestamp = newTimestamp1; } var datetime = DateTime.Parse(timestamp.ToString()).ToString("yyyy/dd/MM hh:mm:ss tt");

        DateTime.TryParseExact(datetime,
                               "yyyy/MM/dd hh:mm:ss tt",
                               CultureInfo.InvariantCulture,
                               DateTimeStyles.None,
                               out newTimestamp1);
        var nonTimestampPart

=!string.IsNullOrEmpty(message)?message.Substring(MessageTracerTranslator.TimestampLength):string.Empty; var desiredMessage = string.Format("{0}{1}{2}", newTimestamp1," ", nonTimestampPart); return new LogLine(line.LineIndex, line.OriginalLineIndex, line.LogEntryIndex, desiredMessage, line.Level, line.Timestamp); }

On Mon, Apr 29, 2019 at 5:33 PM Simon Mießler notifications@github.com wrote:

There seems to be a problem when attaching images. Could you post both your code and an example line (including the actual timestamp)? I'll definitely have a look at it.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-487552906, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57A7TMH447SPUNLUMSLPS3PZ3ANCNFSM4HHWKUZA .

-- Thanks,

Abani

abani1986 commented 5 years ago

code

Kittyfisto commented 5 years ago

Sorry that I haven't been able to take a closer look just yet, been busy otherwise. Can you also send me an example log file or even just an example log line? I'll get back to you on the weekend to solve this.

abani1986 commented 5 years ago

Log1:----

2019-03-18 14:09:54:177 1 00:00:00:0000000 Information Initialize Globals Started BTPVM3372 05:30:00 6060 2019-03-18 14:09:54:177 1 00:00:00:0000000 Information Loading preferences Started BTPVM3372 05:30:00 6060 2019-03-18 14:09:54:551 1 00:00:00:0000000 Information RMClientURL: BTPVM3372 05:30:00 6060

Log2:---

29/03/2019 14:09:54:177 1 Information BTPVM3372 05:30:00 6060 29/03/2019 14:09:54:177 1 Information Loading preferences Started BTPVM3372 05:30:00 6060 29/03/2019 14:09:54:551 1 Information RMClientURL: BTPVM3372 05:30:00 6060

On Thu, May 2, 2019 at 12:44 AM Simon Mießler notifications@github.com wrote:

Sorry that I haven't been able to take a closer look just yet, been busy otherwise. Can you also send me an example log file or even just an example log line? I'll get back to you on the weekend to solve this.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-488384507, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57ESMZQ27UFE6ABAPLTPTHTZBANCNFSM4HHWKUZA .

-- Thanks,

Abani

Kittyfisto commented 5 years ago

Found some time :)

Based on your screenshot I assumed that you actually want to achieve two things:

1) Be able to merge log files with these timestamps 2) Display the timestamps of those log files in one format

The first problem can be solved by implementing ITimestampParser and the second problem by implementing ILogLineTranslator as follows:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using Tailviewer.BusinessLogic.LogFiles;
using Tailviewer.BusinessLogic.Plugins;
using Tailviewer.Core.LogFiles;
using Tailviewer.Core.Parsers;

namespace SomePlugin
{
    public class FileFormatPlugin
        : IFileFormatPlugin
    {
        public ILogFile Open(string fileName, ITaskScheduler taskScheduler)
        {
            return new TextLogFile(taskScheduler, fileName,
                new MyTimestampParser(),
                new MyTimestampTranslator());
        }

        public IReadOnlyList<string> SupportedExtensions => new[] {".bar"};
    }

    /// <summary>
    ///     This class is only responsible for parsing the timestamps of this custom format into a DateTime so that Tailviewer
    ///     is able to merge two or more log files using this timestamp format.
    /// </summary>
    public class MyTimestampParser : ITimestampParser
    {
        public bool TryParse(string content, out DateTime timestamp)
        {
            if (TryParse24CharacterTimestamp(content, out timestamp))
                return true;

            if (TryParse23CharacterTimestamp(content, out timestamp))
                return true;

            return false;
        }

        private bool TryParse23CharacterTimestamp(string content, out DateTime timestamp)
        {
            // Example strings:
            // "2019-03-18 14:09:54:177"
            // "29/03/2019 14:09:54:177"

            const int timestampPartLength = 23;
            var formats = new[]
            {
                "yyyy-MM-dd HH:mm:ss:fff",
                "dd/MM/yyyy HH:mm:ss:fff"
            };
            return TryParseExact(content, timestampPartLength, formats, out timestamp);
        }

        private bool TryParse24CharacterTimestamp(string content, out DateTime timestamp)
        {
            // Example strings:
            // "2019-03-18  14:09:54:177"
            // "29/03/2019  14:09:54:177"

            const int timestampPartLength = 24;
            var formats = new[]
            {
                "yyyy-MM-dd  HH:mm:ss:fff",
                "dd/MM/yyyy  HH:mm:ss:fff"
            };
            return TryParseExact(content, timestampPartLength, formats, out timestamp);
        }

        private static bool TryParseExact(string content, int timestampPartLength, string[] formats,
            out DateTime timestamp)
        {
            if (content.Length < timestampPartLength)
            {
                timestamp = DateTime.MinValue;
                return false;
            }

            var timestampPart = content.Substring(0, timestampPartLength);
            return DateTime.TryParseExact(timestampPart, formats, CultureInfo.InvariantCulture,
                DateTimeStyles.AssumeLocal,
                out timestamp);
        }
    }

    /// <summary>
    ///     This class is responsible for displaying timestamps in a common format.
    /// </summary>
    public class MyTimestampTranslator : ILogLineTranslator
    {
        public LogLine Translate(ILogFile logFile, LogLine line)
        {
            // If there's no timestamp in this line then we don't need to do anything...
            if (line.Timestamp == null)
                return line;

            int indexOfSecondSpace = FindIndexOfSecondSpace(line.Message);
            if (indexOfSecondSpace == -1)
                return line;

            var message = new StringBuilder(line.Message);
            var newTimestamp = line.Timestamp.Value.ToString(CultureInfo.GetCultureInfo("en-US"));
            message.Remove(0, indexOfSecondSpace);
            message.Insert(0, newTimestamp);

            return new LogLine(line.LineIndex, line.LogEntryIndex, message.ToString(), line.Level, line.Timestamp);
        }

        private static int FindIndexOfSecondSpace(string message)
        {
            int firstSpace = message.IndexOf(' ');
            if (firstSpace == -1)
                return -1;

            int nextNonWhitespace = FirstIndexOfNonWhitespace(message, firstSpace + 1);
            if (nextNonWhitespace == -1)
                return -1;

            int secondSpace = message.IndexOf(' ', nextNonWhitespace);
            return secondSpace;
        }

        private static int FirstIndexOfNonWhitespace(string source, int startIndex = 0)
        {
            if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex");
            if (source != null)
                for (int i = startIndex; i < source.Length; i++)
                    if (!char.IsWhiteSpace(source[i])) return i;
            return -1;
        }
    }
}

You probably need to change the file extension from ".bar" to ".txt" so it works for your log files.

I've compiled the attached code and ran it with Tailviewer 0.7.2.632 (available from my build server: https://ci.appveyor.com/project/Kittyfisto/sharptail/build/artifacts) and gotten the following result:

image

This successfully merges all log lines containing timestamps, however I don't know how useful this is to you in its current state because log lines without a timestamp are completely ignored in this example. There isn't much you can do from your end as far as this issue is concerned (Tailviewer assumes that multi-line log entries have the following format: The first line must contain a log level, be that debug, info, warning or error, then any following line is assumed to be part of the previous log entry until a line with another log entry is detected) - I'll have to solve this in Tailviewer's source code. I'll classify this issue as a bug and will prioritize it depending on your feedback.

Hope this helps anyways!

abani1986 commented 5 years ago

Hi,

Yes that works for me. I have to add few more formatters for my case and it works fine.But the problem is the multi line log line. It skips during merge. So can this be fixed ?

Is there any way we can see the skip lines. It only shows the number count how many lines been skipped. But is there any way we can see which are the lines got skipped.

These are the two issues I found . Can this be fixed as a priority?

Please let me know your thoughts on this ?

Thank you Abani

On Thu, May 2, 2019 at 4:53 PM Simon Mießler notifications@github.com wrote:

Found some time :)

Based on your screenshot I assumed that you actually want to achieve two things:

  1. Be able to merge log files with these timestamps
  2. Display the timestamps of those log files in one format

The first problem can be solved by implementing ITimestampParser and the second problem by implementing ILogLineTranslator as follows:

using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Threading; using Tailviewer.BusinessLogic.LogFiles; using Tailviewer.BusinessLogic.Plugins; using Tailviewer.Core.LogFiles; using Tailviewer.Core.Parsers;

namespace SomePlugin { public class FileFormatPlugin : IFileFormatPlugin { public ILogFile Open(string fileName, ITaskScheduler taskScheduler) { return new TextLogFile(taskScheduler, fileName, new MyTimestampParser(), new MyTimestampTranslator()); }

  public IReadOnlyList<string> SupportedExtensions => new[] {".bar"};

}

///

/// This class is only responsible for parsing the timestamps of this custom format into a DateTime so that Tailviewer /// is able to merge two or more log files using this timestamp format. /// public class MyTimestampParser : ITimestampParser { public bool TryParse(string content, out DateTime timestamp) { if (TryParse24CharacterTimestamp(content, out timestamp)) return true;

      if (TryParse23CharacterTimestamp(content, out timestamp))
          return true;

      return false;
  }

  private bool TryParse23CharacterTimestamp(string content, out DateTime timestamp)
  {
      // Example strings:
      // "2019-03-18 14:09:54:177"
      // "29/03/2019 14:09:54:177"

      const int timestampPartLength = 23;
      var formats = new[]
      {
          "yyyy-MM-dd HH:mm:ss:fff",
          "dd/MM/yyyy HH:mm:ss:fff"
      };
      return TryParseExact(content, timestampPartLength, formats, out timestamp);
  }

  private bool TryParse24CharacterTimestamp(string content, out DateTime timestamp)
  {
      // Example strings:
      // "2019-03-18  14:09:54:177"
      // "29/03/2019  14:09:54:177"

      const int timestampPartLength = 24;
      var formats = new[]
      {
          "yyyy-MM-dd  HH:mm:ss:fff",
          "dd/MM/yyyy  HH:mm:ss:fff"
      };
      return TryParseExact(content, timestampPartLength, formats, out timestamp);
  }

  private static bool TryParseExact(string content, int timestampPartLength, string[] formats,
      out DateTime timestamp)
  {
      if (content.Length < timestampPartLength)
      {
          timestamp = DateTime.MinValue;
          return false;
      }

      var timestampPart = content.Substring(0, timestampPartLength);
      return DateTime.TryParseExact(timestampPart, formats, CultureInfo.InvariantCulture,
          DateTimeStyles.AssumeLocal,
          out timestamp);
  }

}

///

/// This class is responsible for displaying timestamps in a common format. /// public class MyTimestampTranslator : ILogLineTranslator { public LogLine Translate(ILogFile logFile, LogLine line) { // If there's no timestamp in this line then we don't need to do anything... if (line.Timestamp == null) return line;

      int indexOfSecondSpace = FindIndexOfSecondSpace(line.Message);
      if (indexOfSecondSpace == -1)
          return line;

      var message = new StringBuilder(line.Message);
      var newTimestamp = line.Timestamp.Value.ToString(CultureInfo.GetCultureInfo("en-US"));
      message.Remove(0, indexOfSecondSpace);
      message.Insert(0, newTimestamp);

      return new LogLine(line.LineIndex, line.LogEntryIndex, message.ToString(), line.Level, line.Timestamp);
  }

  private static int FindIndexOfSecondSpace(string message)
  {
      int firstSpace = message.IndexOf(' ');
      if (firstSpace == -1)
          return -1;

      int nextNonWhitespace = FirstIndexOfNonWhitespace(message, firstSpace + 1);
      if (nextNonWhitespace == -1)
          return -1;

      int secondSpace = message.IndexOf(' ', nextNonWhitespace);
      return secondSpace;
  }

  private static int FirstIndexOfNonWhitespace(string source, int startIndex = 0)
  {
      if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex");
      if (source != null)
          for (int i = startIndex; i < source.Length; i++)
              if (!char.IsWhiteSpace(source[i])) return i;
      return -1;
  }

} }

You probably need to change the file extension from ".bar" to ".txt" so it works for your log files.

I've compiled the attached code and ran it with Tailviewer 0.7.2.632 (available from my build server: https://ci.appveyor.com/project/Kittyfisto/sharptail/build/artifacts) and gotten the following result:

[image: image] https://user-images.githubusercontent.com/11990827/57072047-9df20680-6cdc-11e9-8b6d-6ef17009a1f1.png

This successfully merges all log lines containing timestamps, however I don't know how useful this is to you in its current state because log lines without a timestamp are completely ignored in this example. There isn't much you can do from your end as far as this issue is concerned (Tailviewer assumes that multi-line log entries have the following format: The first line must contain a log level, be that debug, info, warning or error, then any following line is assumed to be part of the previous log entry until a line with another log entry is detected) - I'll have to solve this in Tailviewer's source code. I'll classify this issue as a bug and will prioritize it depending on your feedback.

Hope this helps anyways!

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-488636738, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57AUMZVNKSON7HQ4YCDPTLFLNANCNFSM4HHWKUZA .

-- Thanks,

Abani

Kittyfisto commented 5 years ago

I'm glad to hear that it's working for you.

Concerning your two questions:

Changing the way TV handles multiline log entries should not be a lot of work - expect it to be solved in one or max 2 weeks from now (depending on my spare time).

Once this change is in action, your second question should not be an issue anymore because every line following a line with a timestamp is assumed to be part of the same "log event". Merging two log files produces a collection of log events sorted by their timestamp (ignoring log events without a detectable timestamp)., This means that as long as a log file contains one timestamp in the first line, you're golden (and merging files without timestamps is pointless anyways).

This means that at worst, all lines before the first timestamp are swallowed up when merging (if there are any).

I think your second request requires more work than just a few hours - maybe you can give me some more feedback once I've implemented the first request (because then it hopefully isn't event an issue anymore - unless I missed something ;)).

abani1986 commented 5 years ago

Ok, Thank you. Please let me know once the multi line logging issue is solved. @Kittyfisto Any Update?

Any Update on this multi line logging issue ? @Kittyfisto

On Tue, May 7, 2019 at 4:39 AM Simon Mießler notifications@github.com wrote:

I'm glad to hear that it's working for you.

Concerning your two questions:

Changing the way TV handles multiline log entries should not be a lot of work - expect it https://github.com/Kittyfisto/Tailviewer/issues/183 to be solved in one or max 2 weeks from now (depending on my spare time).

Once this change is in action, your second question should not be an issue anymore because every line following a line with a timestamp is assumed to be part of the same "log event". Merging two log files produces a collection of log events sorted by their timestamp (ignoring log events without a detectable timestamp)., This means that as long as a log file contains one timestamp in the first line, you're golden (and merging files without timestamps is pointless anyways).

This means that at worst, all lines before the first timestamp are swallowed up when merging (if there are any).

I think your second request requires more work than just a few hours - maybe you can give me some more feedback once I've implemented the first request (because then it hopefully isn't event an issue anymore - unless I missed something ;)).

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-489819570, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57H7CPCYY3NNJZAV4D3PUC3B7ANCNFSM4HHWKUZA .

-- Thanks,

Abani

abani1986 commented 5 years ago

Hi,

I have one more query reg this.

[image: image.png]

My log file Contains log level as "Information", but tail viewer it is "Infos". So it doesn't recognize the header filter.

Is there any way to resolve this? @Kittyfisto Any Update?

On Tue, May 7, 2019 at 4:39 AM Simon Mießler notifications@github.com wrote:

I'm glad to hear that it's working for you.

Concerning your two questions:

Changing the way TV handles multiline log entries should not be a lot of work - expect it https://github.com/Kittyfisto/Tailviewer/issues/183 to be solved in one or max 2 weeks from now (depending on my spare time).

Once this change is in action, your second question should not be an issue anymore because every line following a line with a timestamp is assumed to be part of the same "log event". Merging two log files produces a collection of log events sorted by their timestamp (ignoring log events without a detectable timestamp)., This means that as long as a log file contains one timestamp in the first line, you're golden (and merging files without timestamps is pointless anyways).

This means that at worst, all lines before the first timestamp are swallowed up when merging (if there are any).

I think your second request requires more work than just a few hours - maybe you can give me some more feedback once I've implemented the first request (because then it hopefully isn't event an issue anymore - unless I missed something ;)).

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182#issuecomment-489819570, or mute the thread https://github.com/notifications/unsubscribe-auth/AFJO57H7CPCYY3NNJZAV4D3PUC3B7ANCNFSM4HHWKUZA .

-- Thanks,

Abani image

image

Kittyfisto commented 5 years ago

Unfortunately the attached picture doesn't show up on any of my devices. Regarding your query, something like that should be possible, but not out of the box. I've created an issue out of your request and will update it once I've developed a solution.

Kittyfisto commented 5 years ago

I'll close this issue, since the original issue (concerning writing a custom plugin) appears to be solved. I will notify you once #184 is done. Feel free to add new issues / questions if you have them.

abani1986 commented 4 years ago

Hi Simon,

I have downloaded the new TailViwer 9.0 and referring the the new TailViwerApi. Looks like there are significant changes to the API and the below code you have provided not working. Could you please help me out how to resolve this ?

I want exactly the same code with the new API.

I want to resolve the below with NEW API. I dont find any sample with your new API. Before we were adding pluginID inside plugin.cs file. Does that holds good now also? If not please advise.

public class FileFormatPlugin : IFileFormatPlugin { public ILogFile Open(string fileName, ITaskScheduler taskScheduler) { return new TextLogFile(taskScheduler, fileName, new MyTimestampParser(), new MyTimestampTranslator()); }

    public IReadOnlyList<string> SupportedExtensions => new[] {".bar"};
}

Thanks, Abani

Kittyfisto commented 4 years ago

Hi Abani,

unfortunately I had to introduce some breaking changes to the API in the hopes that it will enable me to not make any more breaking changes.

Regarding your IFileFormatPlugin issues, the following should compile against v0.9:

public class FileFormatPlugin
    : IFileFormatPlugin
{
    public ILogFile Open(IServiceContainer services, string fileName)
    {
        services.RegisterInstance<ITimestampParser>(new MyTimestampParser());
        services.RegisterInstance<ILogLineTranslator>(new MyTimestampTranslator());
        return services.CreateTextLogFile(fileName);
    }

    public IReadOnlyList<string> SupportedExtensions => new[] {".bar"};
}

To summarize:

Before we were adding pluginID inside plugin.cs file. Does that holds good now also? If not please advise.

I don't remember changing this, so it should still be as in 0.8.

If you continue to have problems, please let me know. Also, if possible, please attach specific compile errors which may help me solve them.

abani1986 commented 4 years ago

Hi Simon,

That issue got resolved , but while building I got the below.

1>------ Build started: Project: UnifiedLogViewerPlugins, Configuration: Debug Any CPU ------ 1>C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(1988,5): warning MSB3277: Found conflicts between different versions of the same dependent assembly that could not be resolved. These reference conflicts are listed in the build log when log verbosity is set to detailed. 1> UnifiedLogViewerPlugins -> C:\POC\UnifiedLogViewer\UnifiedLogViewerPlugins\bin\Debug\UnifiedLogViewerPlugins.dll 1> A subdirectory or file Plugins\ already exists. 1> 1> Creating Tailviewer plugin... 1> Adding C:\POC\UnifiedLogViewer\UnifiedLogViewerPlugins\bin\Debug\UnifiedLogViewerPlugins.dll...

1> Adding file 'Index.xml'...ERROR: Plugin 'C:\POC\UnifiedLogViewer\UnifiedLogViewerPlugins\bin\Debug\UnifiedLogViewerPlugins.dll' is missing the reqired PluginId attribute, please add it ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

I have a Plugins.cs file where PluginId attribute is specified.

using Tailviewer.BusinessLogic.Plugins;

[assembly: PluginId("abc", "abc")] [assembly: PluginAuthor("Abani Ranjan")] [assembly: PluginWebsite("https://kittyfisto.github.io/Tailviewer/")] [assembly: PluginDescription("A plugin to display all logs in a common time stamp format")] [assembly: PluginVersion(0, 1)]

Could you please help me to resolve this?

On Wed, Nov 27, 2019 at 12:50 AM Simon Mießler notifications@github.com wrote:

Hi Abani,

unfortunately I had to introduce some breaking changes to the API in the hopes that it will enable me to not make any more breaking changes.

Regarding your IFileFormatPlugin issues, the following should compile against v0.9:

public class FileFormatPlugin : IFileFormatPlugin { public ILogFile Open(IServiceContainer services, string fileName) { services.RegisterInstance(new MyTimestampParser()); services.RegisterInstance(new MyTimestampTranslator()); return services.CreateTextLogFile(fileName); }

public IReadOnlyList<string> SupportedExtensions => new[] {".bar"};

}

To summarize:

  • You are no longer able to directly instantiate classes such as TextLogFile, FilteredLogFile, etc.. but indirectly via the IServiceContainer (This is because in the future, these classes may require more dependencies and since those are passed via the constructor, this would result in breaking existing plugins) for example via IServiceContainer.CreateTextLogFile
  • This required the signature of IFileFormatPlugin.Open to change to accept an IServiceContainer instead
  • Overriding the behaviour of the TextLogFile is done by registering instances of ITimestampParser, ILogLineTranslator, etc.. with the IServiceContainer
  • The type of interfaces which can be overwritten are mentioned in IServiceContainers documentation https://github.com/Kittyfisto/Tailviewer/blob/master/src/Tailviewer.Api/IServiceContainer.cs

Before we were adding pluginID inside plugin.cs file. Does that holds good now also? If not please advise.

I don't remember changing this, so it should still be as in 0.8.

If you continue to have problems, please let me know. Also, if possible, please attach specific compile errors which may help me solve them.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182?email_source=notifications&email_token=AFJO57CPLRUGK4VA3LVQ3SLQVVZJBA5CNFSM4HHWKUZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFHE2FI#issuecomment-558779669, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFJO57HJR43HL3VK3RWNL53QVVZJBANCNFSM4HHWKUZA .

-- Thanks,

Abani

Kittyfisto commented 4 years ago

I'm glad we're making some progress.

Regarding that other issue, I have a suspicion that you're not using the same archive.exe that's part of the 0.9 nuget package.

How do you invoke archive.exe in your build? I would guess that you're compiling your assembly against the newer nuget package, but keep using the old archive.exe to pack it. This doesn't work yet and as you've unfortunately had to find out, the error message is incredibly misleading (ERROR: Plugin [...] is missing the reqired PluginId attribute, please add it).

Can you take a look at how you're invoking archive.exe? Maybe you've specified the path to the old nuget package in your post-build event command line.

abani1986 commented 4 years ago

Hey Simon,

You are correct.I was reffering to the old archive.exe.

It worked after reffering 9.0exe.Thanks a lot for the support.

On Wed, Nov 27, 2019, 6:44 PM Simon Mießler notifications@github.com wrote:

I'm glad we're making some progress.

Regarding that other issue, I have a suspicion that you're not using the same archive.exe that's part of the 0.9 nuget package.

How do you invoke archive.exe in your build? I would guess that you're compiling your assembly against the newer nuget package, but keep using the old archive.exe to pack it. This doesn't work yet and as you've unfortunately had to find out, the error message is incredibly misleading (ERROR: Plugin [...] is missing the reqired PluginId attribute, please add it).

Can you take a look at how you're invoking archive.exe? Maybe you've specified the path to the old nuget package in your post-build event command line.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Kittyfisto/Tailviewer/issues/182?email_source=notifications&email_token=AFJO57HJEYVJ4IWLSMNV52LQVZXDHA5CNFSM4HHWKUZKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFJOWTA#issuecomment-559082316, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFJO57FKIPMZHTJPLGW32QTQVZXDHANCNFSM4HHWKUZA .

Kittyfisto commented 4 years ago

Hi Abani,

I'm glad it's working for you. I have created an issue so that future versions of archiver.exe will print a more useful error message.

Let me know when you need any more support.