#!/usr/bin/env python3
# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Watch the log file.

Usage: %prog [COUNT]

If a COUNT is given, show the last COUNT lines of the log file;
without any arguments, show the last 10 lines and then stick around
showing all lines added to the log file in approximately real time.

This program shows the tail of the log file, and shows new contents as
it is appended (unless COUNT is given).  It knows which log file to
read, even overnight as log files are rotated.  To stop it, you must
kill it with ^C.

Note: logging uses local time.  The first line of the log file should
show the UTC time it was started.

"""

import os
import sys
import time
import optparse

from sigma.util import config
from sigma.util.errors import usage, error

parser = optparse.OptionParser(usage=__doc__.strip())
options = config.Options()
logdir = options.agentlogs
pattern = options.log_prefix + ".%04d-%02d-%02d.txt"

def main(args=None):
    ops, args = parser.parse_args(args)
    count = None
    if args:
        if len(args) > 1:
            return usage("Too many arguments")
        try:
            count = int(args[0])
            if count <= 0:
                raise ValueError
        except ValueError:
            return usage("Invalid COUNT argument %r", args[0])
    try:
        return logtail(logdir, count)
    except KeyboardInterrupt:
        return 0

def logtail(logdir, count=None):
    # The first time is special:
    # - if the log file doesn't exist, exit with an error
    # - only print the last count lines of existing contents
    if count is None:
        limit = 10
    else:
        limit = count
        assert limit > 0
    firsttime = True
    while True:
        ymd = time.localtime()[:3]
        logfile = os.path.join(logdir, pattern % ymd)
        try:
            f = open(logfile)
        except IOError as err:
            if firsttime:
                return error("Can't open %s: %s", logfile, err)
            # Wait for something to be logged
            time.sleep(1)
            continue
        try:
            print("=====>", logfile, "<=====")
            if not firsttime:
                pos = 0
            else:
                try:
                    f.seek(-1000*limit, 2) # Assume N lines fit in N KB
                    skipfirst = True
                except IOError as err:
                    # f.seek(X, 2) fails if the file size is less than X
                    f.seek(0)
                    skipfirst = False
                lines = f.readlines()
                pos = f.tell()
                if lines:
                    if skipfirst:
                        del lines[0] # Delete partial first line
                    if len(lines) > limit:
                        del lines[:-limit]
                    sys.stdout.writelines(lines)
                    sys.stdout.flush()
                    if count is not None:
                        break
                firsttime = False
            fno = f.fileno()
            while time.localtime()[:3] == ymd:
                size = os.fstat(fno).st_size
                if size == pos:
                    time.sleep(0.1)
                else:
                    if size < pos:
                        print("-----> file truncated, starting over <-----")
                        break
                    f.seek(pos)
                    text = f.read()
                    pos = f.tell()
                    if text:
                        sys.stdout.write(text)
                        sys.stdout.flush()
            else:
                # It's possible that some more was appended to the log file
                f.seek(pos)
                text = f.read()
                if text:
                    sys.stdout.write(text)
                    sys.stdout.flush()
        finally:
            f.close()

if __name__ == "__main__":
    sys.exit(main())
