如何调试作为服务运行的Python程序?

如何调试作为服务运行的Python程序?,python,windows-10,pywin32,Python,Windows 10,Pywin32,我有一个python脚本,在控制台中运行时效果很好。 但是,当使用pywin32以使其作为服务运行时,服务可以正常安装和启动,但不会产生所需的输出。所以一定是出了什么问题,但我看不出发生了什么事情来找出原因 该脚本执行以下操作: 在给定的输入目录中搜索*.csv文件 如果未找到此类文件,则等待1分钟。如果找到一个,则将其用作步骤3的输入。如果发现多个csv文件,则使用第一个 对列的顺序进行一些转换 将转换后的内容写入输出csv 将输入csv移动到子目录并重命名它 首先让我向您展示作为服务实现的

我有一个python脚本,在控制台中运行时效果很好。 但是,当使用
pywin32
以使其作为服务运行时,服务可以正常安装和启动,但不会产生所需的输出。所以一定是出了什么问题,但我看不出发生了什么事情来找出原因


该脚本执行以下操作:

  • 在给定的输入目录中搜索*.csv文件
  • 如果未找到此类文件,则等待1分钟。如果找到一个,则将其用作步骤3的输入。如果发现多个csv文件,则使用第一个
  • 对列的顺序进行一些转换
  • 将转换后的内容写入输出csv
  • 将输入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非常陌生,我不知道自己缺少了什么。该服务安装正确,并且启动时没有问题。我使用的是pywin32 libs附带的ActiveState Python 3.6。

    使用
    日志记录来报告程序的进度。通过这种方式,您将看到程序正在做什么以及它被卡住的位置

    (还要注意,
    ServiceFramework
    具有“调试模式”.从控制台运行
    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")
          <...>
      

    服务不应该是交互式的。您可以在挂起/崩溃之前添加python
    日志记录
    ,以查看服务的运行情况。
    虽然file是None
    ,但作为旁注,
    file
    是内置类型,因此不应将该词用于变量。规范的方法是添加下划线:
    文件
    。不要
    打印
    sys.exit(1)
    <代码>引发
    异常。谢谢。这回答了我的后续问题,即如何记录/调试作为windows服务运行的python脚本。感谢@ivan_pozdeev提供的全面帮助。它无法工作的主要原因是
    findCSVFile(path)
    中存在逻辑错误,无法处理仅找到一个csv文件的情况。
    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(<...>)