我使用以下代码来跟踪 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
如何跟踪此类日志轮转何时发生并进行相应调整?
使用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)
##################################################################
最好使用 inotify 来完成,您不想继续轮询文件系统来询问循环的每次迭代期间是否发生了变化。这浪费了很多 IO。当发生变化时,
inotify
会通知您。手册中有一个示例,显示了它与日志文件的用法。
您可以查看文件的索引节点。
import os
inode = os.stat('/var/log/auth.log').st_ino
当inode改变时,文件就被旋转了。
我真的很喜欢@olisch的答案的简单性:只需通过检测inode编号何时变化来检测日志文件何时轮换。
这是有效的,因为如果您登录到
~/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 = os.path.join(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 存储库中的 cpu_logger.py 程序中使用了上述技术。我使用该日志脚本不断记录我的 CPU 使用情况并检测哪些程序正在占用我的所有 CPU 并锁定我的计算机。
请注意,您还可以在 Linux 中的命令行中使用以下命令读取和验证文件的 inode 编号:
# print the index number (inode, `-i`) of each file
ls -i
为了更容易查看单列输出,请使用:
# print the index number (inode, `-i`) of each file in a single column (`-1`)
ls -i1
显然,在我的声望 >= 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 秒后重新观看文件,但没有更新。