【问题标题】:Detect log file rotation (while watching log file for modification)检测日志文件轮换(同时观察日志文件进行修改)
【发布时间】:2017-11-08 12:32:41
【问题描述】:

我使用以下代码来跟踪 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

如何跟踪此类日志轮换发生的时间并进行相应调整?

【问题讨论】:

  • 啊。我很抱歉。我承认在标记之前我没有仔细阅读这个问题。我现在正在责备自己。

标签: python linux logging


【解决方案1】:

显然,在我的声望 >= 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 秒后重新观看文件,但没有更新。

【讨论】:

  • 我刚刚发出sudo service ssh restart 进行检查,但它继续使用相同的日志文件。我什至在同一日志中看到sshd[1226]: Received signal 15; terminating. 后跟sshd[29099]: Server listening on 0.0.0.0 port 22.。我已经使用了几个月了,没有任何问题。我将添加一个IN_DELETE_SELF 和其他人,并让他们登录以查看它们是否发生。
  • 我一直在思考这个问题,我怀疑重启sshd 会导致创建新日志。日志用于故障排除和取证,因此很可能不会删除它。当它被移动到另一个文件系统(复制+删除)时,删除只会在“非擦除上下文”中发生。只有日志轮换应该发生(指向一个新文件,重命名旧文件)。当我找到时间时,我会开始阅读一些关于它的内容。
  • 此外,它不会是一个无限循环,它根本不会摆脱阻塞notifier.event_gen(),因为不会有新事件进入。这首先是导致我的问题的原因。我赞成您的回答有两个原因:这是非常有用的输入,并使您的此帐户更接近 50 声望点。
  • 澄清我所处的情况。
  • 感谢您的来信。让我意识到了这个问题。正在写入日志文件的服务会删除旧文件并创建新文件。这导致通知者基本上失去了对文件的监视。而不是关闭和中断,我只需要向文件添加一个新的手表,如果它已经 > 文件被修改的正常间隔(例如 logFile 被写入每 1 分钟,所以我等待 75 秒看看是否没有读取任何内容)。编辑代码以确定更好的方法(等待您的想法)
【解决方案2】:

使用 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)

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

【讨论】:

    【解决方案3】:

    最好使用inotify 完成此操作,您不想继续轮询文件系统以询问在循环的每次迭代期间是否发生了变化。这是很多浪费的IO。 inotify 将在发生更改时通知您。手册中有一个示例,它显示了它与日志文件的用法。

    【讨论】:

    • 谢谢,看来这是要走的路
    • 很高兴你把它填满了。我相信其他人会发现它非常有用。 +1 来自我
    【解决方案4】:

    您可以查看文件的 inode。

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

    当 inode 发生变化时,文件已被轮转。

    【讨论】:

    • 我在考虑os.stat('/var/log/auth.log').st_size 并在尺寸缩小时采取相应措施。那么文件重命名时inode会发生变化吗?我需要在每隔几秒钟检查一次的线程中执行此操作吗?然后我将如何告诉脚本停止在readline() 中等待?
    • @DanielF no 当文件被重命名时,原始文件的 inode 不会改变。但是,如果您总是在 while 循环中观察 /var/log/auth.log 的 inode,并且您识别出文件更改,您将关闭您的文件句柄并在新的 auth.log 上重新打开它。与其在__main__ 中打开文件,不如在follow 函数中这样做。
    • 哦,对了。在检查inotify 时,我相信inotify 和您的建议的组合是最好的解决方案,所以我不会轮询而是知道inode 何时更改,因为 inotify 显然没有告诉我文件已重命名但只是文件属性发生了变化。
    猜你喜欢
    • 2020-07-17
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-14
    相关资源
    最近更新 更多