2011-10-06 44 views
9

我们有几个应用程序服务器和一个中央监控服务器。Python模拟远程尾部-f?

我们目前正在监视服务器上运行带有“tail -f”的ssh,以便从应用服务器实时流式传输多个文本日志文件。

这个问题,除了整个方法的脆弱性之外,杀死ssh进程有时会让僵尸尾进程落后。我们已经使用-t来创建伪终端,但它仍然有时会留下僵尸进程,并且-t显然也会在我们正在使用的作业调度产品的其他地方引发问题。

作为一个便宜而又脏的解决方案,直到我们能够得到正确的集中式日志(Logstash和RabbitMQ,希望),我希望编写一个简单的Python包装器,它将启动ssh和“tail -f”,仍然捕获输出,但将PID存储到磁盘上的文本文件中,以便稍后在需要时可以终止适当的尾部进程。

我起初尝试使用subprocess.Popen,但后来我碰到问题,实际上得到了“tail -f”输出实时(然后需要重定向到一个文件) - 显然会有一个主机的阻塞/缓冲区问题。

一些消息来源似乎建议使用pexpect或pxssh或类似的东西。理想情况下,我只想使用Python,如果可能的话,它就是包含的库 - 但是,如果库真的是唯一的方法来做到这一点,那么我愿意接受。

是否有一个很好的简单方法让Python启动带有“tail -f”的ssh,在这里将输出实时打印到本地STDOUT(这样我可以重定向到本地文件),并且还保存PID到一个文件杀死以后?或者即使我没有使用带-f尾部的ssh,某种方式仍然可以实时(接近)流式传输远程文件,其中包括将PID保存到文件中?

干杯, 维克多

编辑:只是为了澄清 - 我们尾部处理死当我们杀了SSH进程。

我们希望从监控服务器启动SSH和“尾-f”,那么当我们认为,在遥控盒尾部的过程应该死以及CTLR-C - 我们希望它留下来背后。通常ssh和-t应该修复它,但由于我不明白的原因,它不是完全可靠的,而且它与我们的工作安排并不一致。

因此,使用屏幕保持进程在另一端处于活动状态并不是我们想要的。

+0

参见http://stackoverflow.com/questions/136168/get-last-n-lines-of-a-file-with-python-similar-to-tail – unmounted

+0

@bmvou,that问题没有关于'tail -f' –

+0

也许http://stackoverflow.com/questions/1703640/how-to-implement-a-pythonic-equivalent-of-tail-f? – agf

回答

6

我知道这并不回答你的问题,但...

也许你可以尝试使用屏幕。如果您的会话下降,您可以随时重新挂接,尾部仍然会运行。它也支持多用户,所以2个用户可以查看同一个tail命令。

http://en.wikipedia.org/wiki/GNU_Screen

名为创建 “日志”:

screen -S log 

断开:

[CTRL]+A D 

重新连接

screen -r log 

列表时,你能记住名称

screen -list 

要摆脱会议,只需在其中键入exit

+5

+1使用正确的工具进行工作 –

+0

屏幕和结构 – Tom

2

我认为屏幕的想法是最好的主意,但如果你不想ssh,你想要一个python脚本来做到这一点。以下是获取信息的简单Pythonic XMLRPC方式。它只会在有问题的文件附加了某些内容时才会更新。

这是客户端文件。你告诉你这个文件你想读取和它的电脑。

#!/usr/bin/python 
# This should be run on the computer you want to output the files 
# You must pass a filename and a location 
# filename must be the full path from the root directory, or relative path 
# from the directory the server is running 
# location must be in the form of http://location:port (i.e. http:localhost:8000) 

import xmlrpclib, time, sys, os 

def tail(filename, location): 
    # connect to server 
    s = xmlrpclib.ServerProxy(location) 

    # get starting length of file 
    curSeek = s.GetSize(filename) 

    # constantly check 
    while 1: 
     time.sleep(1) # make sure to sleep 

     # get a new length of file and check for changes 
     prevSeek = curSeek 

     # some times it fails if the file is being writter to, 
     # we'll wait another second for it to finish 
     try: 
     curSeek = s.GetSize(filename) 
     except: 
     pass 

     # if file length has changed print it 
     if prevSeek != curSeek: 
     print s.tail(filename, prevSeek), 


def main(): 
    # check that we got a file passed to us 
    if len(sys.argv) != 3 or not os.path.isfile(sys.argv[1]): 
     print 'Must give a valid filename.' 
     return 

    # run tail function 
    tail(sys.argv[1], sys.argv[2]) 

main() 

这是您将拥有你想看看一个文件中的每一台计算机上运行该服务器。它没什么特别。如果您愿意,可以将其进行守护。您只需运行它,如果您告诉客户端它在哪里,并且您打开了正确的端口,则客户端应该连接到它。

#!/usr/bin/python 
# This runs on the computer(s) you want to read the file from 
# Make sure to change out the HOST and PORT variables 
HOST = 'localhost' 
PORT = 8000 

from SimpleXMLRPCServer import SimpleXMLRPCServer 
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler 

import time, os 

def GetSize(filename): 
    # get file size 
    return os.stat(filename)[6] 

def tail(filename, seek): 
    #Set the filename and open the file 
    f = open(filename,'r') 

    #Find the size of the file and move to the end 
    f.seek(seek) 
    return f.read() 

def CreateServer(): 
    # Create server 
    server = SimpleXMLRPCServer((HOST, PORT), 
           requestHandler=SimpleXMLRPCRequestHandler) 

# register functions 
    server.register_function(tail, 'tail') 
    server.register_function(GetSize, 'GetSize') 

    # Run the server's main loop 
    server.serve_forever() 

# start server 
CreateServer() 

理想情况下,从客户端运行“蟒蛇client.py sample.log http://somehost:8000”运行服务器一次,那么它应该开始准备。希望有所帮助。

0

一个问题,我写了这样做的功能:

import paramiko 
import time 
import json 

DEFAULT_MACHINE_USERNAME="USERNAME" 
DEFAULT_KEY_PATH="DEFAULT_KEY_PATH" 

def ssh_connect(machine, username=DEFAULT_MACHINE_USERNAME, 
       key_filename=DEFAULT_KEY_PATH): 
    ssh = paramiko.SSHClient() 
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 
    ssh.connect(hostname=machine, username=username, key_filename=key_filename) 
    return ssh 

def tail_remote_file(hostname, filepath, key_path=DEFAULT_KEY_PATH, 
        close_env_variable="CLOSE_TAIL_F", env_file='~/.profile'): 
    ssh = ssh_connect(hostname, key_filename=key_path) 

    def set_env_variable(to_value): 
     to_value_str = "true" if to_value else "false" 
     from_value_str = "false" if to_value else "true" 
     ssh.exec_command('sed -i \'s/export %s=%s/export %s=%s/g\' %s' % 
         (close_env_variable, from_value_str, 
          close_env_variable, to_value_str, env_file)) 
     time.sleep(1) 

    def get_env_variable(): 
     command = "source .profile; echo $%s" % close_env_variable 
     stdin, stdout_i, stderr = ssh.exec_command(command) 
     print(command) 
     out = stdout_i.read().replace('\n', '') 
     return out 

    def get_last_line_number(lines_i, line_num): 
     return int(lines_i[-1].split('\t')[0]) + 1 if lines_i else line_num 

    def execute_command(line_num): 
     command = "cat -n %s | tail --lines=+%d" % (filepath, line_num) 
     stdin, stdout_i, stderr = ssh.exec_command(command) 
     stderr = stderr.read() 
     if stderr: 
      print(stderr) 
     return stdout_i.readlines() 

    stdout = get_env_variable() 
    if not stdout: 
     ssh.exec_command("echo 'export %s=false' >> %s" % 
         (close_env_variable, env_file)) 
    else: 
     ssh.exec_command(
      'sed -i \'s/export %s=true/export %s=false/g\' %s' % 
      (close_env_variable, close_env_variable, env_file)) 
    set_env_variable(False) 

    lines = execute_command(0) 
    last_line_num = get_last_line_number(lines, 0) 

    while not json.loads(get_env_variable()): 
     for l in lines: 
      print('\t'.join(t.replace('\n', '') for t in l.split('\t')[1:])) 
     last_line_num = get_last_line_number(lines, last_line_num) 
     lines = execute_command(last_line_num) 
     time.sleep(1) 

    ssh.close() 
0

我已经写了一个库,可以让你做到这一点 - 检查PimpedSubprocess(在github上)或的“远程”功能(在PyPI上)