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上)