思路历程:
- 一开始以为小小的tailf命令功能很容易实现
- 可是真到动手写的时候,还是迷糊了好一会。
- 到找到一点思路,又掉进代threading.Condition的坑
- 最后是中间的逻辑,有点绕
功能:
- 就是一个python版本的tailf
有待扩展:
- 暂时不支持Ctrl+C结束进程
- 暂时没有实现监控多个文件及发送到kafka或者logstash
直接上代码:
#!/usr/bin/env python
#coding:utf-8
"""
实现思想:
用两个线程:
线程1用来读取日志文件,并记录读取位置
线程2用发送日条目
两个线程之间使用多线程的条件进行控制
多个文件时,可以使用pyinotify,真正的可以实现filebeat功能
# from pyinotify import WatchManager,Notifier,ProcessEvent,IN_MODIFY,IN_DELETE
# pyinotify用法略
"""
import os,sys
import threading
import time
class argvError(Exception):
pass
# 初始化两个全局变量
ret_lines = []
pos = 0
# 发送数据线程,可以自己实现发送到kafka/logstash
def Return(cond):
with cond:
while True:
cond.wait()
global ret_lines
if len(ret_lines) >= 1:
for line in ret_lines:
print(line.strip())
cond.notify()
# 模拟tailf -n参数,当然此脚本是用argv,因此直接指定数字即可
def readFirst(file_path, unit=200, n = 10):
while True:
ret = []
with open(file_path, "r") as f:
f.seek(0, 2)
start_pos = f.tell()
global pos
pos = start_pos
my_offset = unit * n
if start_pos - my_offset <= 0:
dest_pos = 0
else:
dest_pos = start_pos - my_offset
f.seek(dest_pos, 0)
# print(dest_pos)
while True:
line = f.readline()
if line:
ret.append(line)
else:
break
f.seek(start_pos, 0)
# 判断获取日志行数是否满足,不满足加大unit,继续循环
if len(ret) > n:
break
elif len(ret) <= n and dest_pos == 0:
break
elif len(ret) <= n and dest_pos != 0:
unit += unit
continue
return ret
# 实时追踪新日志条目
def readContinue(file_path):
start_time = time.time()
global ret_lines
ret_lines = []
while True:
with open(file_path,"r") as f:
global pos
f.seek(pos,0)
while True:
line = f.readline()
if line:
cur_pos = f.tell()
pos = cur_pos
ret_lines.append(line)
elif not line:
break
middle_time = time.time()
if middle_time - start_time > 1 or len(ret_lines) >= 5:
break
else:
time.sleep(0.3)
continue
# 实现tailf的线程
def Tailf(cond,file_path,n=10):
with cond:
while True:
global pos
if pos == 0:
unit = 200
ret = readFirst(file_path,unit, n)
global ret_lines
if len(ret) > n:
ret_lines = ret[-n::]
else:
ret_lines = ret
else:
readContinue(file_path)
cond.notify()
cond.wait()
def main(file_path,n=10):
cond = threading.Condition()
# 这里有个大坑,t1和t2启动顺序,应该是wait()的一方先启动,类似socker server端
# 而notify应该后启动.如果notify先启动,会阻塞,并不会报错,后面的顺序也就没办法继续了
t1 = threading.Thread(target=Return,args=(cond,))
t1.start()
t2 = threading.Thread(target=Tailf,args=(cond,file_path,n))
t2.start()
# t1.join()
# t2.join()
time.sleep(1800) # 发现Ctrl+c无法结束此程序,因此加一个超时时间
if __name__ == "__main__":
if len(sys.argv) < 2:
raise argvError("请指定tailf文件对象")
elif len(sys.argv) == 2:
if os.path.isfile(sys.argv[1]):
main(file_path=sys.argv[1])
else:
raise argvError("文件不存在")
elif len(sys.argv) == 3:
try:
line = int(sys.argv[1])
except Exception as e:
raise argvError("第一个参数是数字")
if not os.path.isfile(sys.argv[2]):
raise argvError("文件不存在")
main(n=line,file_path = sys.argv[2])
elif len(sys.argv) > 3:
raise argvError("tailf一次只能监控一个文件")