Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tail a log file in Python?

Tags:

python

tail

I'd like to make the output of tail -F or something similar available to me in Python without blocking or locking. I've found some really old code to do that here, but I'm thinking there must be a better way or a library to do the same thing by now. Anyone know of one?

Ideally, I'd have something like tail.getNewData() that I could call every time I wanted more data.

like image 764
Eli Avatar asked Sep 21 '12 01:09

Eli


People also ask

How do you use the tail command for logs?

Tail command also comes with an '+' option which is not present in the head command. With this option tail command prints the data starting from specified line number of the file instead of end. For command: tail +n file_name, data will start printing from line number 'n' till the end of the file specified.

How do you tail a log live?

To watch log files that get rotated on a daily base you can use the -F flag to tail command. The tail -F will keep track if new log file being created and will start following the new file instead of the old file. However, by default, tail command will display the last 10 lines of a file.


2 Answers

Non Blocking

If you are on linux (as windows does not support calling select on files) you can use the subprocess module along with the select module.

import time import subprocess import select  f = subprocess.Popen(['tail','-F',filename],\         stdout=subprocess.PIPE,stderr=subprocess.PIPE) p = select.poll() p.register(f.stdout)  while True:     if p.poll(1):         print f.stdout.readline()     time.sleep(1) 

This polls the output pipe for new data and prints it when it is available. Normally the time.sleep(1) and print f.stdout.readline() would be replaced with useful code.

Blocking

You can use the subprocess module without the extra select module calls.

import subprocess f = subprocess.Popen(['tail','-F',filename],\         stdout=subprocess.PIPE,stderr=subprocess.PIPE) while True:     line = f.stdout.readline()     print line 

This will also print new lines as they are added, but it will block until the tail program is closed, probably with f.kill().

like image 95
Matt Avatar answered Oct 10 '22 08:10

Matt


Using the sh module (pip install sh):

from sh import tail # runs forever for line in tail("-f", "/var/log/some_log_file.log", _iter=True):     print(line) 

[update]

Since sh.tail with _iter=True is a generator, you can:

import sh tail = sh.tail("-f", "/var/log/some_log_file.log", _iter=True) 

Then you can "getNewData" with:

new_data = tail.next() 

Note that if the tail buffer is empty, it will block until there is more data (from your question it is not clear what you want to do in this case).

[update]

This works if you replace -f with -F, but in Python it would be locking. I'd be more interested in having a function I could call to get new data when I want it, if that's possible. – Eli

A container generator placing the tail call inside a while True loop and catching eventual I/O exceptions will have almost the same effect of -F.

def tail_F(some_file):     while True:         try:             for line in sh.tail("-f", some_file, _iter=True):                 yield line         except sh.ErrorReturnCode_1:             yield None 

If the file becomes inaccessible, the generator will return None. However it still blocks until there is new data if the file is accessible. It remains unclear for me what you want to do in this case.

Raymond Hettinger approach seems pretty good:

def tail_F(some_file):     first_call = True     while True:         try:             with open(some_file) as input:                 if first_call:                     input.seek(0, 2)                     first_call = False                 latest_data = input.read()                 while True:                     if '\n' not in latest_data:                         latest_data += input.read()                         if '\n' not in latest_data:                             yield ''                             if not os.path.isfile(some_file):                                 break                             continue                     latest_lines = latest_data.split('\n')                     if latest_data[-1] != '\n':                         latest_data = latest_lines[-1]                     else:                         latest_data = input.read()                     for line in latest_lines[:-1]:                         yield line + '\n'         except IOError:             yield '' 

This generator will return '' if the file becomes inaccessible or if there is no new data.

[update]

The second to last answer circles around to the top of the file it seems whenever it runs out of data. – Eli

I think the second will output the last ten lines whenever the tail process ends, which with -f is whenever there is an I/O error. The tail --follow --retry behavior is not far from this for most cases I can think of in unix-like environments.

Perhaps if you update your question to explain what is your real goal (the reason why you want to mimic tail --retry), you will get a better answer.

The last answer does not actually follow the tail and merely reads what's available at run time. – Eli

Of course, tail will display the last 10 lines by default... You can position the file pointer at the end of the file using file.seek, I will left a proper implementation as an exercise to the reader.

IMHO the file.read() approach is far more elegant than a subprocess based solution.

like image 36
14 revs, 2 users 99% Avatar answered Oct 10 '22 10:10

14 revs, 2 users 99%