I want to read file continuously like GNU tail with "-f" param. I need it to live-read log file. What is the right way to do it?
7 Answers
More natural approach of using FileSystemWatcher
:
var wh = new AutoResetEvent(false);
var fsw = new FileSystemWatcher(".");
fsw.Filter = "file-to-read";
fsw.EnableRaisingEvents = true;
fsw.Changed += (s,e) => wh.Set();
var fs = new FileStream("file-to-read", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using (var sr = new StreamReader(fs))
{
var s = "";
while (true)
{
s = sr.ReadLine();
if (s != null)
Console.WriteLine(s);
else
wh.WaitOne(1000);
}
}
wh.Close();
Here the main reading cycle stops to wait for incoming data and FileSystemWatcher
is used just to awake the main reading cycle.

- 1,401
- 1
- 18
- 22
-
2You have to do: `fsw.EnableRaisingEvents = true;` before the `fsw.Changed` event is triggered – Mads Y Apr 28 '16 at 14:29
-
Yes, you are right, my mistake. The code above only works because of the `WaitOne` timeout. – tsul Apr 30 '16 at 23:43
-
1@MadsY Fixed the answer as you proposed. – tsul Jun 14 '16 at 17:25
-
5One caveat of file system watcher - it appears the file needs to be flushed before the event is raised. If you want real-time access to file changes, you need to open the file regularly and then set the file position to the end of the file, this will force a flush of any pending in-memory buffers, otherwise you are at the whim of the operating system to decide when it wants to flush. Normally not an issue unless you want any changes immediately. – jjxtra Dec 15 '19 at 18:14
-
1@jixtra Wish I had read your comment 3 hours ago when I started trying to use `FileSystemWatcher`. I didn't see this point anywhere else. Thx – a113nw May 30 '20 at 22:56
You want to open a FileStream
in binary mode. Periodically, seek to the end of the file minus 1024 bytes (or whatever), then read to the end and output. That's how tail -f
works.
Answers to your questions:
Binary because it's difficult to randomly access the file if you're reading it as text. You have to do the binary-to-text conversion yourself, but it's not difficult. (See below)
1024 bytes because it's a nice convenient number, and should handle 10 or 15 lines of text. Usually.
Here's an example of opening the file, reading the last 1024 bytes, and converting it to text:
static void ReadTail(string filename)
{
using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
// Seek 1024 bytes from the end of the file
fs.Seek(-1024, SeekOrigin.End);
// read 1024 bytes
byte[] bytes = new byte[1024];
fs.Read(bytes, 0, 1024);
// Convert bytes to string
string s = Encoding.Default.GetString(bytes);
// or string s = Encoding.UTF8.GetString(bytes);
// and output to console
Console.WriteLine(s);
}
}
Note that you must open with FileShare.ReadWrite
, since you're trying to read a file that's currently open for writing by another process.
Also note that I used Encoding.Default
, which in US/English and for most Western European languages will be an 8-bit character encoding. If the file is written in some other encoding (like UTF-8 or other Unicode encoding), It's possible that the bytes won't convert correctly to characters. You'll have to handle that by determining the encoding if you think this will be a problem. Search Stack overflow for info about determining a file's text encoding.
If you want to do this periodically (every 15 seconds, for example), you can set up a timer that calls the ReadTail
method as often as you want. You could optimize things a bit by opening the file only once at the start of the program. That's up to you.

- 11,146
- 3
- 53
- 61

- 131,090
- 20
- 188
- 351
-
-
If more or less then 1024 bytes were written since the last check, wouldn't this either miss some data or read some data twice? – Baruch Oct 29 '13 at 13:25
-
-
2Worth noting, if the file is less than 1024 bytes, the code will throw an IOException because an attempt is made to move the file-pointer to a point before the start of the file. To avoid issue: fs.Seek(Math.Max(-1024,-fs.Length), SeekOrigin.End); – omglolbah Jan 04 '14 at 08:53
-
12Tail doesn't blindly get the last 1024 bytes - it keeps track of the last size and the new size and only gets the bytes that were recently added. http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tail.c – kah608 Feb 20 '14 at 22:00
-
1@kah608: Thanks for the info. It should be easy enough to modify the code above to do that. – Jim Mischel Feb 20 '14 at 22:51
-
1
-
The other answer http://stackoverflow.com/a/24993767/3195477 seems simpler... is there an advantage to this approach? – StayOnTarget Apr 26 '17 at 11:36
To continuously monitor the tail of the file, you just need to remember the length of the file before.
public static void MonitorTailOfFile(string filePath)
{
var initialFileSize = new FileInfo(filePath).Length;
var lastReadLength = initialFileSize - 1024;
if (lastReadLength < 0) lastReadLength = 0;
while (true)
{
try
{
var fileSize = new FileInfo(filePath).Length;
if (fileSize > lastReadLength)
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
fs.Seek(lastReadLength, SeekOrigin.Begin);
var buffer = new byte[1024];
while (true)
{
var bytesRead = fs.Read(buffer, 0, buffer.Length);
lastReadLength += bytesRead;
if (bytesRead == 0)
break;
var text = ASCIIEncoding.ASCII.GetString(buffer, 0, bytesRead);
Console.Write(text);
}
}
}
}
catch { }
Thread.Sleep(1000);
}
}
I had to use ASCIIEncoding, because this code isn't smart enough to cater for variable character lengths of UTF8 on buffer boundaries.
Note: You can change the Thread.Sleep part to be different timings, and you can also link it with a filewatcher and blocking pattern - Monitor.Enter/Wait/Pulse. For me the timer is enough, and at most it only checks the file length every second, if the file hasn't changed.

- 17,547
- 6
- 53
- 70
-
2Most log files will terminate with new line which you don't need to care about utf-8 encoding, so remembering the last new line position is really what you want – jjxtra Dec 15 '19 at 18:12
This is my solution:
Note this code simulates "tail -f -n +0". It means this code reads the whole file and continues to read the new lines. If you need to only read the new lines "tail -f -n 0", uncomment the reader.BaseStream.Seek(0, SeekOrigin.End)
line.
static IEnumerable<string> TailFrom(string file)
{
using (var reader = File.OpenText(file))
{
// go to end - if the next line is commented out, all the lines from the beginning is returned
// reader.BaseStream.Seek(0, SeekOrigin.End);
while (true)
{
string line = reader.ReadLine();
if (reader.BaseStream.Length < reader.BaseStream.Position)
reader.BaseStream.Seek(0, SeekOrigin.Begin);
if (line != null) yield return line;
else Thread.Sleep(500);
}
}
}
So, in your code you can do:
foreach (string line in TailFrom(file))
{
Console.WriteLine($"line read= {line}");
}
-
-
This seems to loc the file and the application logging can no longer write to the file. Not sure if I am doing something wrong? – russelrillema Feb 10 '23 at 05:29
-
it opens the file for reading, it shouldn't lock. if issue still persist, you can try with StringReader(string, FileStreamOptions) instead of OpenText(string), to control FileAccess and FileShare options. – iojancode Feb 11 '23 at 15:02
You could use the FileSystemWatcher class which can send notifications for different events happening on the file system like file changed.

- 1,023,142
- 271
- 3,287
- 2,928
-
1OK. Let's say I received event that file changed. How can I read to the end from position I've ended reading last time? Eny example appreciated :) – Dmytro Leonenko Sep 25 '10 at 10:08
private void button1_Click(object sender, EventArgs e)
{
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
path = folderBrowserDialog.SelectedPath;
fileSystemWatcher.Path = path;
string[] str = Directory.GetFiles(path);
string line;
fs = new FileStream(str[0], FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
tr = new StreamReader(fs);
while ((line = tr.ReadLine()) != null)
{
listBox.Items.Add(line);
}
}
}
private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
string line;
line = tr.ReadLine();
listBox.Items.Add(line);
}
If you are just looking for a tool to do this then check out free version of Bare tail

- 21
- 7
-
5No, i'm not looking for a tool. I need a way to implement it in C# for my programm. – Dmytro Leonenko Sep 25 '10 at 10:10