Python для эмуляции удаленного хвоста -f? - PullRequest
9 голосов
/ 07 октября 2011

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

В настоящее время мы запускаем 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 в файл?

Ура, Victor

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

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

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

Ответы [ 6 ]

6 голосов
/ 07 октября 2011

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

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

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

создать с именем «log»:

screen -S log

отключить:

[CTRL]+A D

reattach

screen -r log

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

screen -list

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

2 голосов
/ 07 октября 2011

Я думаю, что идея с экраном - лучшая идея, но если вы не хотите использовать ssh и хотите, чтобы скрипт Python делал это. Вот простой питонный 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", и он должен начать работать. Надеюсь, это поможет.

1 голос
/ 25 октября 2013

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

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

0 голосов
/ 23 января 2017

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

0 голосов
/ 04 августа 2016

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

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 голосов
/ 08 октября 2011

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

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

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

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

...