Skip to content

Instantly share code, notes, and snippets.

@gyulalaszlo
Created November 9, 2015 16:03
Show Gist options
  • Save gyulalaszlo/8df3f597feab3f2cbe16 to your computer and use it in GitHub Desktop.
Save gyulalaszlo/8df3f597feab3f2cbe16 to your computer and use it in GitHub Desktop.
C# log file watcher
using System;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
///
/// </summary>
/// <example>
/// const string path = @"c:\Users\Miles\Documents\log-tests";
/// Watcher.startWatching(
/// new string[] { path, path + "\\Logs" },
/// 5000,
/// (string file, string[] lines) =>
/// {
/// Console.WriteLine("Got change in:" + file);
/// foreach (var line in lines) { Console.WriteLine(" -> " + line); }
/// });
/// </example>
namespace LogWatcher
{
/// <summary>
/// Utility class for working with existing log files
/// </summary>
class LogFile
{
public string path;
public long lastSize;
public LogFile(string path)
{
this.path = path;
UpdateLastSize(this);
}
/// <summary>
/// Reads any new lines at the end of the file
/// </summary>
/// <returns>Null if no changes since last try, or the appended lines if there are any.</returns>
public string[] read()
{
// Figure out the new size
var newSize = new FileInfo(path).Length;
// If the sizes match return nada
if (newSize == lastSize) return null;
// If the sizes differ, find out the target offset.
var seekOffset = (newSize > lastSize) ? lastSize : 0;
UpdateLastSize(this);
return ReadFromOffset(path, seekOffset);
}
/// <summary>
/// Returns the contents of the file at path from the byte offset
/// seekoffset
/// </summary>
/// <param name="path">The full path of the file</param>
/// <param name="seekOffset">The offset to seek to</param>
/// <returns></returns>
private static string[] ReadFromOffset(string path, long seekOffset)
{
// Open the stream and reader (then close them)
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(fs))
{
// Seek to the end or other start position
reader.BaseStream.Seek(seekOffset, SeekOrigin.Begin);
// Read the lines into a buffer
var lines = new List<string>();
var line = "";
while ((line = reader.ReadLine()) != null)
lines.Add(line);
return lines.ToArray();
}
}
/// <summary>
/// Updates the last size field of this logfile
/// </summary>
private static void UpdateLastSize(LogFile f)
{
f.lastSize = new FileInfo(f.path).Length;
}
}
}
namespace LogWatcher
{
// We need to double namespace it so this using declaration sits where it supposed to sit, on
// the first row of the namespace.
using FileStreamDict = Dictionary<string, LogFile>;
class Watcher
{
/// <summary>
/// Starts a blocking watcher for files provided by config.
/// </summary>
/// <param name="directories">The directories to watch</param>
/// <param name="cycleLength">The amount of milliseconds to wait in between the check attempts.</param>
/// <param name="onChange">
/// A callback action / lambda that gets called on change events.
/// Parameters: (string filename, string[] lines)
/// </param>
public static void startWatching(string[] directories, int cycleLength, Action<string, string[]> onChange)
{
// Get a list of all files in the directory
var streams = ChangeCycle(directories, new FileStreamDict(), (string file, string[] lines) => { });
while (true)
{
streams = ChangeCycle(directories, streams, onChange);
Thread.Sleep(cycleLength);
}
}
/// <summary>
/// Do a single check cycle.
/// </summary>
/// <param name="directories">The directories to check</param>
/// <param name="existingFiles">A list of LogFiles already added to the watcher</param>
/// <param name="onChange">The delegate to callback</param>
/// <returns></returns>
private static FileStreamDict ChangeCycle(string[] directories, FileStreamDict existingFiles, Action<string, string[]> onChange)
{
var streams = addNewFilesToStreams(directories, existingFiles);
// Try to read from each file stream
foreach (KeyValuePair<string, LogFile> entry in streams)
{
var lines = entry.Value.read();
if (lines != null)
// Trigger the change handler
onChange(entry.Key, lines);
}
return streams;
}
/// <summary>
/// Returns a new map of filename/stream maps
/// </summary>
/// <param name="cfg"></param>
/// <param name="existingFiles"></param>
/// <returns></returns>
private static FileStreamDict addNewFilesToStreams(string[] directories, FileStreamDict existingFiles)
{
// Mapcat the files into a single list
var files = directories.Select(path => Directory.GetFiles(path)).SelectMany(x => x);
var outMap = new FileStreamDict();
foreach (var file in files)
outMap[file] = existingFiles.ContainsKey(file) ? existingFiles[file] : new LogFile(file);
return outMap;
}
}
}
@VikramPaul007
Copy link

sdas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment