16

I'm making a program that controls a game server. One of the functions I'm making, is a live server logfile monitor.

There is a logfile (a simple textfile) that gets updated by the server as it runs.

How do I continuously check the logfile and output it's content in a RichTextBox?

I did this simple function just try and get the content of the log. It will of course just get the text row by row and output it to my textbox. Also it will lock the program for as long as the loop runs, so I know it's useless.

public void ReadLog()
{
  using (StreamReader reader = new StreamReader("server.log"))
  {
    String line;
        
    // Read and display lines from the file until the end of the file is reached.
    while ((line = reader.ReadLine()) != null)
    {
      monitorTextBox.AppendText(line + "\n");
      CursorDown();
    }
  }
}

But how would you go about solving the live monitoring as simple as possible?

*** EDIT ***

I'm using Prescots solution. great stuff.

At the moment I'm using a sstreamreader to put the text from the file to my textbox. I ran into the problem is that, whenever I tried to access any of the gui controls in my event handler the program just stopped with no error or warnings.

I found out that it has to do with threading. I solved that like this:

private void OnChanged(object source, FileSystemEventArgs e)
{
    if (monitorTextField.InvokeRequired)
    {
        monitorTextField.Invoke((MethodInvoker)delegate { OnChanged(source, e); });
    }
    else
    {
      StreamReader reader = new StreamReader("file.txt");

      monitorTextField.Text = "";
      monitorTextField.Text = reader.ReadToEnd();
      reader.Close();
      CursorDown();
    }
}

Now my only problem is that the file.txt is used by the server so I can't access it, since it's "being used by another process". I can't control that process, so maybe I'm out of luck.

But the file can be opened in notepad while the server is running, so somehow it must be possible. Perhaps I can do a temp copy of the file when it updates and read the copy. I don't know.

Christoffer
  • 7,470
  • 9
  • 39
  • 55

5 Answers5

19

Check out the System.IO.FileSystemWatcher class:

public static Watch() 
{
    var watch = new FileSystemWatcher();
    watch.Path = @"D:\tmp";
    watch.Filter = "file.txt";
    watch.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite; //more options
    watch.Changed += new FileSystemEventHandler(OnChanged);
    watch.EnableRaisingEvents = true;
}

/// Functions:
private static void OnChanged(object source, FileSystemEventArgs e)
{
    if(e.FullPath == @"D:\tmp\file.txt")
    {
        // do stuff
    }
}

Edit: if you know some details about the file, you could handle the most efficent way to get the last line. For example, maybe when you read the file, you can wipe out what you've read, so next time it's updated, you just grab whatever is there and output. Perhaps you know one line is added at a time, then your code can immediately jump to the last line of the file. Etc.

Prescott
  • 7,312
  • 5
  • 49
  • 70
  • Thank you! That will help a lot. I'm very new to this... Right now I run in to the noob problem of getting "Method must have a return type", that doesn't happen if I make the Watch() class like this " public void Watch()". I'm probably creating it in the wrong place or way. I'm putting it in my "public partial class MYPROGRAM : Form" – Christoffer Mar 18 '12 at 09:10
  • ah yeah sorry, I wrote that surrounding code super fast. You can rip out the watcher code and put it whenever appropriate in your code, it doesn't have to be in a Watch() function that I have above. – Prescott Mar 18 '12 at 09:12
  • Thank you! I just added some info to the first question (new here.. Am I violating any rules by doing so?) – Christoffer Mar 18 '12 at 10:37
  • If your question was answered, you probably want to mark it as answered and start a new question. It makes it cleaner – Prescott Mar 18 '12 at 19:24
7

Although the FileSystemWatcher is the most simple solution I have found it to be unreliable in reality.. often a file can be updated with new contents but the FileSystemWatcher does not fire an event until seconds later and often never.

The only reliable way I have found to approach this is to check for changes to the file on a regular basis using a System.Timers.Timer object and checking the file size.

I have written a small class that demonstrates this available here:

https://gist.github.com/ant-fx/989dd86a1ace38a9ac58

Example Usage

var monitor = new LogFileMonitor("c:\temp\app.log", "\r\n");

monitor.OnLine += (s, e) =>
{
    // WARNING.. this will be a different thread...
    Console.WriteLine(e.Line);
};

monitor.Start();

The only real disadvantage here (apart from a slight performance delay caused by file size checking) is that because it uses a System.Timers.Timer the callback comes from a different thread.

If you are using a Windows Forms or WPF app you could easily modify the class to accept a SynchronizingObject which would ensure the event handler events are called from the same thread.

antfx
  • 3,205
  • 3
  • 35
  • 45
1

Use this answer on another post c# continuously read file.

This one is quite efficient, and it checks once per second if the file size has changed.

You can either run it on another thread (or convert to async code), but in any case you would need to marshall the text back to the main thread to append to the textbox.

Community
  • 1
  • 1
Kind Contributor
  • 17,547
  • 6
  • 53
  • 70
1

As @Prescott suggested, use a FileSystemWatcher. And make sure, you open the file with the appropriate FileShare mode (FileShare.ReadWrite seems to be appropriate), since the file might still be opened by the server. If you try to open the file exclusively while it is still used by another process, the open operation will fail.

Also in order to gain a bit of performance, you could remember the last position up to which you already have read the file and only read the new parts.

MartinStettner
  • 28,719
  • 15
  • 79
  • 106
  • Can I do that if I use StreamReader? – Christoffer Mar 18 '12 at 09:57
  • I think for seeking a specific position, you'll have to access the underlying `FileStream` directly (via the `Position` property). Thus you create first the `FileStream`, go to the right position, construct a `StreamReader` on top of the stream, read the data and then get the final position again from the `FileStream`. (I've not tried it, I hope it works as described ...) – MartinStettner Mar 18 '12 at 18:53
0

Try adding a Timer and have the Timer.Tick set to an Interval of 1 second. On Timer.Tick you run the function.

private void myTimer_Tick(object sender, EventArgs e)
{
    ReadLog();
}
Joey
  • 344,408
  • 85
  • 689
  • 683
Mr Wednesday
  • 552
  • 4
  • 16