linux 检测日志文件循环(同时观察日志文件的修改)

8wtpewkr  于 2022-12-18  发布在  Linux
关注(0)|答案(5)|浏览(202)

我使用以下代码来跟踪ssh登录:

def follow(thefile):
  thefile.seek(0,2)
  while True:
    line = thefile.readline()
    if not line:
      time.sleep(0.1)
      continue
    yield line

if __name__ == '__main__':
  logfile = open('/var/log/auth.log', 'r')
  loglines = follow(logfile)
  for line in loglines:
    print 'do something here'

我注意到这个脚本在几天后突然停止工作,我没有收到任何错误,它没有终止,它只是停止工作,好像readline()永远不会返回。
因此,我执行了一个echo 'test' >> auth.log.1,脚本最终确实处理了它,因为不久前auth.log被重命名为auth.log.1
如何跟踪此类日志循环发生的时间并进行相应调整?

oknrviil

oknrviil1#

使用e4c5的答案,我最终得到了这段代码,它还解决了每秒多次调用readline()的问题。
在第一次调用时,它会跳到文件末尾并等待修改,当文件被移动时,它会重新打开文件并读取整个内容,然后开始等待。

import os
import time
import traceback
import threading
import inotify.adapters

logfile = b'/var/log/auth.log'
#logfile = b'logfile.log'

##################################################################

def process(line, history=False):
  if history:
    print '=', line.strip('\n')
  else:
    print '>', line.strip('\n')

##################################################################

from_beginning = False
notifier = inotify.adapters.Inotify()
while True:
  try:
    #------------------------- check
    if not os.path.exists(logfile):
      print 'logfile does not exist'
      time.sleep(1)
      continue
    print 'opening and starting to watch', logfile
    #------------------------- open
    file = open(logfile, 'r')
    if from_beginning:
      for line in file.readlines():
        process(line, history=True)
    else:
      file.seek(0,2)
      from_beginning = True
    #------------------------- watch
    notifier.add_watch(logfile)
    try:
      for event in notifier.event_gen():
        if event is not None:
          (header, type_names, watch_path, filename) = event
          if set(type_names) & set(['IN_MOVE_SELF']): # moved
            print 'logfile moved'
            notifier.remove_watch(logfile)
            file.close()
            time.sleep(1)
            break
          elif set(type_names) & set(['IN_MODIFY']): # modified
            for line in file.readlines():
              process(line, history=False)
    except (KeyboardInterrupt, SystemExit):
      raise
    except:
      notifier.remove_watch(logfile)
      file.close()
      time.sleep(1)
    #-------------------------
  except (KeyboardInterrupt, SystemExit):
    break
  except inotify.calls.InotifyError:
    time.sleep(1)
  except IOError:
    time.sleep(1)
  except:
    traceback.print_exc()
    time.sleep(1)

##################################################################
ru9i0ody

ru9i0ody2#

最好用inotify来完成,你不想在循环的每次迭代中不断轮询文件系统来询问是否发生了变化。这会浪费很多IO。inotify会在发生变化时 * 通知 * 你。手册中有一个例子显示了它与日志文件的用法。

yrwegjxp

yrwegjxp3#

您可以查看文件的inode。

import os
inode = os.stat('/var/log/auth.log').st_ino

当inode更改时,文件已旋转。

2lpgd968

2lpgd9684#

显然,我不能发表评论,直到我有〉= 50的声誉。
@daniel-f有一个很好的例子!我遇到的唯一一个边缘案例是,当创建我正在阅读的旋转日志文件的服务重新启动时,它会删除旧文件并创建新文件。
这将导致“通知程序”无法看到日志文件(因为它是不同的)。
由于服务每60秒写入一次日志文件,因此我对for循环进行了快速修改,如下所示:

last_pull = datetime.datetime.now()
while True:
...
...

            for event in notifier.event_gen():
                if event is not None:
                    last_pull = datetime.datetime.now()
                    (header, type_names, watch_path, filename) = event
                    if set(type_names) & set(['IN_MOVE_SELF']): # moved
                        notifier.remove_watch(file_watcher.intput_logfile)
                        file.close()
                        time.sleep(1)
                        break
                    elif set(type_names) & set(['IN_MODIFY']): # modified
                        lines = file.readlines()
                        for line in lines:
                            process(line, file_watcher, history=False)
                else:
                    if (datetime.datetime.now() - last_pull).total_seconds() >= time_to_refresh:
                        last_pull = datetime.datetime.now()
                        notifier.remove_watch(file_watcher.intput_logfile)
                        file.close()
                        break

这将在75秒后重新监视文件,而不进行更新。

sh7euo9m

sh7euo9m5#

如何通过查找更改的inode编号来检测日志文件轮换

我真的很喜欢@olisch回答的简洁:通过检测索引节点号何时改变来检测日志文件何时被轮转。
这是因为,如果您登录到~/mylog.log,则日志轮转会定期将此文件重命名为~/mylog.log.1,然后重命名为~/mylog.log.2,最后重命名为~/mylog.log.3,依此类推。每次执行此顺序的文件重命名以执行日志轮转时,它都会在~/mylog.log(活动日志文件)处创建一个全新的文件,从而使该路径处的文件的索引节点号改变。
如果您已经通过循环以固定的时间间隔记录日志,这种循环中轮询的方法尤其有意义!
下面是一个完整的演示(不包括日志记录部分),展示了如何检测给定路径下日志文件的inode编号是否发生了更改:

import os
import pathlib

# Log into ~/mylog.log. Log rotation will periodically rename this file to 
# ~/mylog.log.1, then ~/mylog.log.2, ~/mylog.log.3, etc. Each time it does this,
# it will create a brand new file at ~/mylog.log, making the inode number of 
# the file at that path change.
log_file_path = str(pathlib.Path.home()) + '/mylog.log'

# this is your main logging loop
inode_number_old = os.stat(log_file_path).st_ino
while True:

    # Detect log rotation events
    inode_number_new = os.stat(log_file_path).st_ino
    if inode_number_old != inode_number_new:
        print("Log file rotation just detected!")
        inode_number_old = inode_number_new

        # Now do whatever it is you want to do whenever a log rotation is
        # detected. Ex: change the new log file's permissions to read/write
        # for everyone.
        PERMISSIONS_EVERYONE_READ_WRITE = 0o666
        os.chmod(log_file_path, PERMISSIONS_EVERYONE_READ_WRITE)

我在我的eRCaGuy_dotfiles repo的cpu_logger.py程序中使用了上述技术,我使用该日志脚本不断地记录我的CPU使用情况,并检测哪些程序占用了我所有的CPU并锁定了我的计算机。
请注意,您还可以在Linux中的命令行中使用以下命令读取和验证文件的inode编号:

ls -i

要获得更易于查看的单列输出,请用途:

ls -i1

参考文献

1.@olisch的回答

  1. https://docs.python.org/3/library/os.html#os.stat_result.st_ino-官方Python文档
  2. Python logger file permissions
    1.我的答案:Ask Ubuntu: How to log CPU load?

相关问题