Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Log Monitor

4.71/5 (22 votes)
6 Jun 20074 min read 1   6.4K  
This article describes how to write an application to monitor text log files

Screenshot - LogMonitorScreen.jpg

Introduction

I often need to watch text-based log files for services or IIS logs. I got tired of having to refresh the file in my editor or having to close and re-open the file to see the latest changes. So, I wrote a tool that would do the work for me and show me the latest additions to a log file continuously. The application is pretty full-featured. It supports the following:

  • Multiple log files can be open at the same time.
  • File names can be passed as arguments to the application to be loaded at startup.
  • A TabControl is used to switch between the log files being monitored.
  • Files can be dropped onto the application.
  • Logs can be updated via a timer or immediately using FileSystemWatcher.
  • Updates can be paused.
  • Only the lines added to the log since the last update are read in to optimize performance.
  • If the file is too large then only the end of the log is read in.
  • Correctly handles log files that only terminate lines with linefeed.
  • Settings and window position are saved in the configuration file.

Using the code

To work with the source code, just unzip the Source Files and open the LogMonitor.sln solution file. This was written using .NET 2.0 entirely in C#.

Points of interest

LogMonitorControl user control

The main worker class in this application is LogMonitorControl, which is a user control that watches a single log file. A TabControl is used to hold multiple instances of LogMonitorControl.

Threading concerns

One tricky bit was that the LogMonitorControl can use a Timer or a FileSystemWatcher to monitor the log file. These behave differently in terms of threading. When using the Timer, the timer tick callbacks occur on the main thread. However, when using the FileSystemWatcher method, the callback happens on a different thread. So, updating the child controls involved checking whether or not Invoke should be used via the InvokeRequired property. Anonymous delegates help greatly with this. However, if a lot of code is to be executed for a control, then it is also convenient to write a method that performs those actions. Then either Invoke that method or call it directly. Below is an example of where this is needed. Note that the method DoScrollToEnd is a method of the LogMonitorControl class and not a member of the control Invoking the method:

C#
public void ScrollToEnd()
{
    if (_textBoxContents.InvokeRequired)
    {
        _textBoxContents.Invoke(new MethodInvoker(DoScrollToEnd));
    }
    else
    {
        DoScrollToEnd();
    }
}

Drag & drop support
(Well, just drop support really.)

Adding drop support was very easy and consisted of only 2 methods. The code is as follows:

C#
private void tabControl_DragDrop(object sender, DragEventArgs e)
{
    // Accept files dropped and create new a tab each file.
    IDataObject data = e.Data;
    // Only accept file drops
    if (data.GetDataPresent(DataFormats.FileDrop))
    {
        // Get the list of files dropped and create tabs for each
        string[] filesToDrop = 
            (string[])e.Data.GetData(DataFormats.FileDrop, false);
        for (int idx = 0; idx < filesToDrop.Length; idx++)
        {
            MonitorNewFile(filesToDrop[idx]);
        }
    }
}

private void tabControl_DragEnter(object sender, DragEventArgs e)
{
    IDataObject data = e.Data;
    // Only accept file drops
    if (data.GetDataPresent(DataFormats.FileDrop))
    {
        e.Effect = DragDropEffects.Copy;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

FileSystemWatcher

The FileSystemWatcher class is found in the System.IO namespace and is used to monitor changes to a file or any file in a directory. The constructor takes a path and a filename pattern. The LogMonitor application calls the constructor with a filename for the pattern to monitor a single file. We are interested in all of the change events that this class can raise, so we sign up for all of them. We could actually track this file through renames and continue to monitor the log file, but I choose to simply keep watching the same file. Then if the file is renamed back, we will detect that. Similarly, if another file is created with the same filename, that will also be detected. There is one thing that is not completely obvious about the FileSystemWatcher class. That is, if you sign up for the file events you will never receive them unless you set the property EnableRaisingEvents to true.

C#
_watcher = new System.IO.FileSystemWatcher(path, baseName);
FileSystemEventHandler handler = new FileSystemEventHandler(watcher_Changed);
_watcher.Changed += handler;
_watcher.Created += handler;
_watcher.Deleted += handler;
_watcher.Renamed += watcher_Renamed;
// Without setting EnableRaisingEvents nothing happens
_watcher.EnableRaisingEvents = true;

Open file with lowest amount of locking

Some applications keep the log file open during execution, like IIS. To give the LogMonitor the best chance of working with these types of applications, it opens the log file with the friendliest settings possible. The easiest way to read a text file in .NET is to use the File.ReadAllText() method. However, this does not open the file with the proper sharing flags. So, I open the file using File.Open and specify the access and sharing modes. Then open a StreamReader and use ReadToEnd on that stream. The code for reading the file is shown below, with some lines that are irrelevant to this point having been removed:

C#
string newFileLines = "";
using (FileStream stream = File.Open(_fileName, 
    FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    using (StreamReader reader = new StreamReader(stream))
    {
        newFileLines = reader.ReadToEnd();
    }
}

History

  • 5 June, 2007 -- Original version posted
  • 6 June, 2007 -- Version 1.0.0.1
    • Fixed bug with trimming text for large files. (Thanks for the catch s_mom).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here