74

I'm trying to find a nice way to read a log file in real time using python. I'd like to process lines from a log file one at a time as it is written. Somehow I need to keep trying to read the file until it is created and then continue to process lines until I terminate the process.

Is there an appropriate way to do this?

vvvvv
  • 25,404
  • 19
  • 49
  • 81
Anon
  • 5,103
  • 11
  • 45
  • 58
  • This one is good too... I think it fits your criteria well and provides a class that could be extended easily. [http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/](http://code.activestate.com/recipes/577968-log-watcher-tail-f-log/) – rjmoggach Oct 23 '12 at 18:55

5 Answers5

62

Take a look at this PDF starting at page 38, ~slide I-77 and you'll find all the info you need. Of course the rest of the slides are amazing, too, but those specifically deal with your issue:

import time
def follow(thefile):
    thefile.seek(0,2) # Go to the end of the file
    while True:
        line = thefile.readline()
        if not line:
            time.sleep(0.1) # Sleep briefly
            continue
        yield line
Wayne Werner
  • 49,299
  • 29
  • 200
  • 290
  • 7
    Worth noting that this will skip any contents already in the log file, only printing "new" entries created after this iterator was created. Also that PDF really is a gold mine ;) – blented Sep 02 '16 at 00:48
  • 3
    What to do, if I watch rotating logfiles? https://stackoverflow.com/questions/44407834/python-detect-log-file-rotation-while-watching-log-file-for-modification – jaromrax Jul 25 '18 at 12:26
  • @HarisGodil why do you think this doesn't work in Python2? – Wayne Werner Oct 25 '18 at 12:44
  • Why replacing `thefile.seek(0,OS.SEEK_END)` by `thefile.seek(0,2)` ? Edit: Ho, actually its [the same](https://docs.python.org/3/library/os.html#os.SEEK_END). – H4dr1en Aug 08 '19 at 12:08
  • Tested on a short file and it keeps running with no end. Did I do something wrong ? `with open(myfile, "r") as f : x = follow(f) for l in x: print(l)` – Kenny Sep 24 '20 at 16:00
  • @Kenny that should work, but it's possible your other process isn't flushing the write – Wayne Werner Sep 24 '20 at 17:57
  • Thanks @Wayne. Will this work if the other process is writing at the same time ? I notice it only work if the writer stops. What I am doing is : a Jupyter notebook writer writes continuously into a file. Another Jupyter notebook reads the same file using your method . My objective is to have the reader read only a couple of final lines (continuously as the file grows) in the file, not all. – Kenny Sep 24 '20 at 20:27
  • @Kenny make sure that your other process is calling f.flush() after writing. They're probably never getting flushed to disk – Wayne Werner Sep 25 '20 at 02:26
33

You could try with something like this:

import time

while 1:
    where = file.tell()
    line = file.readline()
    if not line:
        time.sleep(1)
        file.seek(where)
    else:
        print line, # already has newline

Example was extracted from here.

Pablo Santa Cruz
  • 176,835
  • 32
  • 241
  • 292
  • This seems to be working but it won't allow me to create objects or write to a database at the same time in my django app. I don't see an obvious reason for this; is there a simple fix? – Anon Jul 20 '10 at 19:37
  • I don't know. You should post some code in a separate question to get answers to this one, I guess. I don't see any reason not to get database updated if you place that code inside this one... – Pablo Santa Cruz Jul 20 '10 at 21:16
  • Got this to work but I had to mess with the string a lot before I could get it to write to my database. Thanks. – Anon Jul 21 '10 at 18:23
  • 3
    `file` appears to be undefined in this context, fyi. – Serguei Fedorov Jan 04 '19 at 16:08
  • This seems to get one byte at a time or depending on speed partial lines – user1529413 Jul 19 '19 at 11:44
  • file refers to a file object. – T3metrics Sep 27 '21 at 09:16
8

As this is Python and logging tagged, there is another possibility to do this.

I assume this is based on a Python logger, logging.Handler based.

You can just create a class that gets the (named) logger instance and overwrite the emit function to put it onto a GUI (if you need console just add a console handler to the file handler)

Example:

import logging

class log_viewer(logging.Handler):
    """ Class to redistribute python logging data """

    # have a class member to store the existing logger
    logger_instance = logging.getLogger("SomeNameOfYourExistingLogger")

    def __init__(self, *args, **kwargs):
         # Initialize the Handler
         logging.Handler.__init__(self, *args)

         # optional take format
         # setFormatter function is derived from logging.Handler 
         for key, value in kwargs.items():
             if "{}".format(key) == "format":
                 self.setFormatter(value)

         # make the logger send data to this class
         self.logger_instance.addHandler(self)

    def emit(self, record):
        """ Overload of logging.Handler method """

        record = self.format(record)

        # ---------------------------------------
        # Now you can send it to a GUI or similar
        # "Do work" starts here.
        # ---------------------------------------

        # just as an example what e.g. a console
        # handler would do:
        print(record)

I am currently using similar code to add a TkinterTreectrl.Multilistbox for viewing logger output at runtime.

Off-Side: The logger only gets data as soon as it is initialized, so if you want to have all your data available, you need to initialize it at the very beginning. (I know this is what is expected, but I think it is worth being mentioned.)

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
R4PH43L
  • 2,122
  • 3
  • 18
  • 30
1

I stumbled upon this question cause I wanted to output the log line to the browser/webservice. The top answer got me most of the way there but my webservice would just not send the lines to the browser. What the issue was actually not thinking about async vs not-async. This took an entire afternoon to figure out, so I wanted to add my addition here.

First, we have to convert the follow() method into an asynchronous one, so use asyncio.sleep instead:

import asyncio
from io import TextIOWrapper

async def follow(thefile: TextIOWrapper):
    thefile.seek(0,2) # Go to the end of the file
    while True:
        line = thefile.readline()
        if not line:
            await asyncio.sleep(0.1) # Sleep briefly
            continue
        yield line

follow() became an Asynchronous Generator, which means to use the result we'll have to use anext():

with open("text.txt", "r") as log_file:
    print("opened file")
    follow_generator = follow(log_file)
    while True:
        line = await anext(follow_generator)
        # do whatever you want with line here
        # here I am sending the new log line to my websocket
        await websocket.send_text(str(line))

I made a demo which uses FastAPI.

Viet Than
  • 164
  • 1
  • 10
-7

Maybe you could do a system call to

tail -f

using os.system()

Guillaume Lebourgeois
  • 3,796
  • 1
  • 20
  • 23