Python для эмуляции удаленного хвоста -f?

У нас есть несколько серверов приложений и центральный сервер мониторинга.

В настоящее время мы запускаем ssh с «tail -f» с сервера мониторинга для потоковой передачи нескольких текстовых файлов журнала в реальном времени с серверов приложений.

Проблема, помимо хрупкости всего подхода, заключается в том, что уничтожение процесса ssh иногда может оставить хвостовые процессы-зомби позади. Мы повозились с использованием -t для создания псевдотерминалов, но он все еще иногда оставляет зомби-процессы, и -t, по-видимому, также вызывает проблемы в другом месте с продуктом планирования заданий, который мы используем.

В качестве дешевого и грязного решения, пока мы не сможем получить надлежащее централизованное ведение журнала (надеюсь, Logstash и RabbitMQ), я надеюсь написать простую оболочку Python, которая будет запускать ssh и "tail -f", по-прежнему захватывать вывод, но сохраните PID в текстовом файле на диске, чтобы мы могли позже убить соответствующий хвостовой процесс, если это будет необходимо.

Сначала я пытался использовать subprocess.Popen, но затем у меня возникли проблемы с фактическим получением вывода "tail -f" обратно в реальном времени (который затем нужно перенаправить в файл) - очевидно, будет множество блокировок/ проблемы с буфером.

Несколько источников рекомендовали использовать pexpect, pxssh или что-то в этом роде. В идеале я хотел бы использовать только Python и включенные в него библиотеки, если это возможно, однако, если библиотека действительно единственный способ сделать это, то я открыт для этого.

Есть ли хороший простой способ заставить Python запустить ssh с помощью «tail -f», получить вывод в реальном времени, напечатанный на локальном STDOUT здесь (чтобы я мог перенаправить в локальный файл), а также сохранить PID в файл для убить потом? Или даже если я не использую ssh с tail -f, какой-то способ по-прежнему передавать удаленный файл в (почти) реальном времени, который включает сохранение PID в файл?

Здоровья, Виктор

РЕДАКТИРОВАТЬ: Просто для уточнения - мы хотим, чтобы хвостовой процесс умирал, когда мы завершаем процесс SSH.

Мы хотим запустить ssh и "tail -f" с сервера мониторинга, затем, когда мы нажмем Ctlr-C, хвостовой процесс на удаленном компьютере также должен умереть - мы не хотим этого остаться позади. Обычно ssh с -t должен это исправить, но он не полностью надежен по непонятным мне причинам и не очень хорошо сочетается с нашим расписанием работы.

Следовательно, использование экрана для поддержания процесса на другом конце — это не то, что нам нужно.


person victorhooi    schedule 06.10.2011    source источник
comment
См. также stackoverflow.com/questions/136168/   -  person unmounted    schedule 07.10.2011
comment
@bmvou, этот вопрос не имеет отношения к tail -f   -  person Petr Viktorin    schedule 07.10.2011
comment
Возможно, stackoverflow.com/questions/1703640/?   -  person agf    schedule 07.10.2011
comment
@petr это не обман, просто стоит прочитать, вам также понадобится какая-то комбинация fcntl, disown, polling или что-то еще для всего остального.   -  person unmounted    schedule 07.10.2011


Ответы (6)


Модуль paramiko поддерживает соединение через ssh с python.

http://www.lag.net/paramiko/

В pysftp есть несколько примеров его использования, и метод выполнения команды может быть тем, что вы ищете. Он создаст файл, подобный объекту команды, которую вы выполняете. Я не могу сказать, дает ли он вам живые данные.

http://code.google.com/p/pysftp/

person McLeopold    schedule 08.10.2011

Я знаю, что это не ответ на твои вопросы, но...

Может быть, вы могли бы попробовать использовать screen. Если ваша сессия обрывается, вы всегда можете снова подключиться, и хвост по-прежнему будет работать. Он также поддерживает многопользовательский режим, поэтому 2 пользователя могут просматривать одну и ту же команду tail.

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

создать с именем «журнал»:

screen -S log

Отключить:

[CTRL]+A D

снова прикрепить

screen -r log

список, когда вы можете вспомнить имя

screen -list

Чтобы избавиться от сеанса, просто введите в нем exit.

person McLeopold    schedule 06.10.2011

Я думаю, что идея с экраном — лучшая идея, но если вы не хотите использовать 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()

В идеале вы запускаете сервер один раз, а затем с клиента запускаете «python client.py sample.log http://somehost:8000. "и это должно начаться. Надеюсь, это поможет.

person Hoopdady    schedule 07.10.2011

Я разместил вопрос о чем-то подобном с кодом (paramiko)

tail -f через ssh с Paramiko имеет увеличивающаяся задержка

person studioj    schedule 25.10.2013

Я написал функцию, которая делает это:

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()
person Yonatan Kiron    schedule 04.08.2016

Я написал библиотеку, которая позволяет вам делать именно это — ознакомьтесь с «удаленной» функцией PimpedSubprocess (на github) или PimpedSubprocess (на PyPI)

person Yoav Kleinberger    schedule 22.01.2017