Как отладить программу Python, работающую как сервис? - PullRequest
0 голосов
/ 05 июля 2018

У меня есть скрипт Python, который хорошо работает при запуске в консоли. Однако при использовании pywin32 для запуска в качестве службы служба устанавливается и запускается нормально, но требуемый вывод не создается. Так что, должно быть, что-то идет не так, но я не вижу, что происходит, чтобы выяснить причину.


Сценарий выполняет следующие действия:

  1. Поиск * .csv файлов в заданном входном каталоге
  2. Если такие файлы не найдены, он ждет 1 минуту. Если он найден, он использует его в качестве входных данных для шага 3. Если найдено несколько CSV-файлов, он использует первый.
  3. Выполняет ли преобразование порядка столбцов
  4. Записывает преобразованный контент в вывод csv
  5. Перемещает входной CSV в подкаталог и переименовывает его.

Позвольте мне сначала показать вам код версии, которая реализована как сервис:

#!/usr/bin/env python3

import sys
import os
import csv
from pathlib import Path
import time
import win32service
import win32serviceutil
import win32event


def reorder_AT_csv(ifile, ofile):
    "Öffnet die CSV Datei, überführt sie in das neue Format und exportiert sie."

    print('[i] Lese aus ' + ifile.name + ' und schreibe in ' +
          ofile.name)
    with open(
            ifile, mode='r') as infile, open(
                ofile, mode='w') as outfile:
        reader = csv.DictReader(
            infile,
            fieldnames=[
                'Post Nummer', 'Sendungsnummer', 'Referenz1', 'Referenz2',
                'Gewicht', 'Empf.Name1', 'Empf.Adresse', 'Empf.PLZ',
                'Empf.Ort', 'Kostenstelle', 'Produkt'
            ],
            delimiter=';')
        fn = [
            'Post Nummer', 'Referenz1', 'Referenz2', 'Sendungsnummer',
            'Gewicht', 'Empf.Name1', 'Empf.Adresse', 'Empf.PLZ', 'Empf.Ort',
            'Kostenstelle', 'Produkt'
        ]
        writer = csv.DictWriter(
            outfile, extrasaction='ignore', fieldnames=fn, delimiter=';')
        # reorder the header first
        try:
            for row in reader:
                # writes the reordered rows to the new file
                writer.writerow(row)
        except Exception as e:
            print('[!] Fehler bei der Bearbeitung der CSV Datei:')
            print('[!] ' + str(e))
            print(
                '[!] Bitte ueberpruefen, ob es sich um eine korrekte CSV Datei handelt!'
            )
            sys.exit(1)


def checkInputFromUser(path):
    "Überprüfe ob das Verzeichnis existiert."

    if not path.exists():
        print(
            '[!] Die Eingabe ist kein Verzeichnis. Bitte ein gueltiges Verzeichnis eingeben.'
        )
        sys.exit(1)

    return True


def findCSVFile(path):
    "Finde alle CSV Dateien im Verzeichnis path."

    all_files = []
    all_files.extend(Path(path).glob('*.csv'))
    if len(all_files) == 0:
        # print('[!] Keine CSV Dateien gefunden. Bitte Pfad überprüfen.')
        # sys.exit(1)
        return None
    elif len(all_files) > 1:
        print('[i] Mehrere CSV Dateien gefunden. Nehme ersten Fund:')
        return all_files[0]


def moveInputFile(input):
    "Verschiebe Input Datei in Unterordner und füge Suffix hinzu."

    movepath = Path(input.parent / 'processed')
    targetname = input.with_suffix(input.suffix + '.success')
    fulltarget = movepath / targetname.name
    input.replace(fulltarget)


class CSVConvertSvc(win32serviceutil.ServiceFramework):
    # you can NET START/STOP the service by the following name
    _svc_name_ = "blub"
    # this text shows up as the service name in the Service
    # Control Manager (SCM)
    _svc_display_name_ = "bar der AT CSV Dateien."
    # this text shows up as the description in the SCM
    _svc_description_ = "Dieser Dienst öffnet die AT CSV Datei und überführt sie in das DPD Format."

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        # create an event to listen for stop requests on
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

    # core logic of the service
    def SvcDoRun(self):
        import servicemanager

        rc = None
        inputpath = Path(r'C:\Users\Dennis\Documents')
        outputpath = Path(r'C:\Users\Dennis\Desktop')
        file = None

        # if the stop event hasn't been fired keep looping
        while rc != win32event.WAIT_OBJECT_0:

            checkInputFromUser(inputpath)

            while file is None:
                file = findCSVFile(inputpath)
                if file is None:
                    time.sleep(60)

            inputfile = file
            outputfile = outputpath / 'out.csv'
            reorder_AT_csv(inputfile, outputfile)
            moveInputFile(inputfile)

            # block for 5 seconds and listen for a stop event
            rc = win32event.WaitForSingleObject(self.hWaitStop, 5000)

    # called when we're being shut down
    def SvcStop(self):
        # tell the SCM we're shutting down
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        # fire the stop event
        win32event.SetEvent(self.hWaitStop)


if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(CSVConvertSvc)

Эта версия не делает то, что должна. Тем не менее, я начал с этой версии как не-сервисной версии, а скорее с простого скрипта на Python, который делает то, что должен:

#!/usr/bin/env python3

import sys
import os
import csv
import time
from pathlib import Path




def reorder_AT_csv(ifile, ofile):
    "Öffnet die CSV Datei, überführt sie in das neue Format und exportiert sie."

    print('[i] Lese aus ' + ifile.name + ' und schreibe in ' +
          ofile.name)
    with open(
            ifile, mode='r') as infile, open(
                ofile, mode='w') as outfile:
        reader = csv.DictReader(
            infile,
            fieldnames=[
                'Post Nummer', 'Sendungsnummer', 'Referenz1', 'Referenz2',
                'Gewicht', 'Empf.Name1', 'Empf.Adresse', 'Empf.PLZ',
                'Empf.Ort', 'Kostenstelle', 'Produkt'
            ],
            delimiter=';')
        fn = [
            'Post Nummer', 'Referenz1', 'Referenz2', 'Sendungsnummer',
            'Gewicht', 'Empf.Name1', 'Empf.Adresse', 'Empf.PLZ', 'Empf.Ort',
            'Kostenstelle', 'Produkt'
        ]
        writer = csv.DictWriter(
            outfile, extrasaction='ignore', fieldnames=fn, delimiter=';')
        # reorder the header first
        try:
            for row in reader:
                # writes the reordered rows to the new file
                writer.writerow(row)
        except Exception as e:
            print('[!] Fehler bei der Bearbeitung der CSV Datei:')
            print('[!] ' + str(e))
            print(
                '[!] Bitte ueberpruefen, ob es sich um eine korrekte CSV Datei handelt!'
            )
            sys.exit(1)


def checkInputFromUser(path):
    "Überprüfe ob das Verzeichnis existiert."

    if not path.exists():
        print(
            '[!] Die Eingabe ist kein Verzeichnis. Bitte ein gueltiges Verzeichnis eingeben.'
        )
        sys.exit(1)

    return True


def findCSVFile(path):
    "Finde alle CSV Dateien im Verzeichnis path."

    all_files = []
    all_files.extend(Path(path).glob('*.csv'))
    if len(all_files) == 0:
        # print('[!] Keine CSV Dateien gefunden. Bitte Pfad überprüfen.')
        # sys.exit(1)
        return None
    elif len(all_files) > 1:
        print('[i] Mehrere CSV Dateien gefunden. Nehme ersten Fund:')
    return all_files[0]


def moveInputFile(input):

    movepath = Path(input.parent / 'processed')
    targetname = input.with_suffix(input.suffix + '.success')
    fulltarget = movepath / targetname.name
    input.replace(fulltarget)


def main():

    inputpath = Path(r'C:\Users\Dennis\Documents')
    outputpath = Path(r'C:\Users\Dennis\Desktop')
    file = None

    checkInputFromUser(inputpath)

    while file is None:
        file = findCSVFile(inputpath)
        if file is None:
            time.sleep(60)

    inputfile = file
    outputfile = outputpath / 'out.csv'
    reorder_AT_csv(inputfile, outputfile)
    moveInputFile(inputfile)


if __name__ == '__main__':
    main()

Будучи очень новым для Python, я не могу понять, чего мне не хватает. Служба установлена ​​правильно и также запускается без проблем. Я использую ActiveState Python 3.6, который поставляется с библиотеками pywin32.

1 Ответ

0 голосов
/ 05 июля 2018

Используйте logging, чтобы сообщить о ходе выполнения программы и выполнить отладочную печать . Таким образом, вы увидите, что делает программа и где она застряла.

(Также обратите внимание, что ServiceFramework имеет «режим отладки». Запуск <your_program> debug с консоли указывает HandleCommandLine на интерактивное выполнение вашей программы: вы получаете консольный вывод, и Ctrl-C завершает программу. Это заставляет итерации исправления выполнения занимать меньше времени, но не выявляет ошибок, которые проявляются только при запуске в качестве службы, поэтому это полезно, но это только предварительный шаг, запуск в качестве службы и запись в файл является окончательным тестом. )


Вот как я размещаю свои сценарии, которые выполняются без присмотра / как служба.

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

    # set up logging #####################################
    import sys,logging,logging.handlers,os.path
    #in this particular case, argv[0] is likely pythonservice.exe deep in python's lib\
    # so it makes no sense to write log there
    log_file=os.path.splitext(__file__)[0]+".log"
    l = logging.getLogger()
    l.setLevel(logging.INFO)
    f = logging.Formatter('%(asctime)s %(process)d:%(thread)d %(name)s %(levelname)-8s %(message)s')
    h=logging.StreamHandler(sys.stdout)
    h.setLevel(logging.NOTSET)
    h.setFormatter(f)
    l.addHandler(h)
    h=logging.handlers.RotatingFileHandler(log_file,maxBytes=1024**2,backupCount=1)
    h.setLevel(logging.NOTSET)
    h.setFormatter(f)
    l.addHandler(h)
    del h,f
    #hook to log unhandled exceptions
    def excepthook(type,value,traceback):
        logging.error("Unhandled exception occured",exc_info=(type,value,traceback))
        #Don't need another copy of traceback on stderr
        if old_excepthook!=sys.__excepthook__:
            old_excepthook(type,value,traceback)
    old_excepthook = sys.excepthook
    sys.excepthook = excepthook
    del log_file,os
    # ####################################################
    
  • Журнал каждого запуска и остановки программы.
    В случае службы также необходимо вручную регистрировать необработанные исключения на уровне SvcDoRun, поскольку pythonservice.exe, похоже, поглощает все исключения. На всякий случай сохраняйте замену sys.excepthook.

    def SvcDoRun(self):
        #sys.excepthook doesn't seem to work in this routine -
        # apparently, everything is handled by the ServiceFramework machinery
        try:
            l.info("Starting service")
            main()
        except Exception,e:
            excepthook(*sys.exc_info())
        else:
            l.info("Finished successfully")
    def SvcStop(self):
        l.info("Stop request received")
        <...>
    
  • Записывает любой значимый шаг, с точки зрения пользователя, который делает программа. Сообщайте о тривиальных или очень частых случаях на более высоком уровне многословия. Выберите уровни таким образом, чтобы при любом значении l.setLevel() была получена полная картина с соответствующим уровнем детализации.

    file_ = findCSVFile(inputpath)
    if file_:
        l.info("Processing `%s'", file_)    # Note the quotes around the name.
                                            # They will help detect any leading/trailing
                                            #space in the path.
    else:
        l.debug("No more files")
    
    <...>
    
    l.info("Moving to `%s'",fulltarget)
    input.replace(fulltarget)
    
    <...>
    
    l.debug("waiting")
    rc = win32event.WaitForSingleObject(<...>)
    
...