如何将 Python 程序 运行 作为服务进行调试?
How to debug a Python program running as a service?
我有一个 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 的新手,我不知道我错过了什么。该服务已正确安装,也可以正常启动。我正在使用 ActiveState Python 3.6,它随 pywin32 库一起提供。
使用logging
报告程序的进度and do debug printing。这样,您将看到程序在做什么以及卡在什么地方。
(另请注意,ServiceFramework
有一个 "debug mode"。运行 <your_program> debug
从控制台指示 HandleCommandLine
到 运行 你的程序交互:你得到控制台输出,然后 Ctrl-C 终止程序。这使得 运行-fix 迭代花费更少的时间,但不会揭示仅在 运行ning 时才会出现的错误作为服务。因此它很有帮助,但只是初步步骤,运行作为服务并记录到文件是最终测试。)
以下是我如何安排 运行 unatteneded/as 服务的脚本。
尽快设置日志记录。
始终使用轮换处理程序,以便日志不会无限增长。
确保记录任何未处理的异常。
# 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(<...>)
一个问题是
# core logic of the service
def SvcDoRun(self):
有自己的异常处理程序。因此,即使您将 std out 和 err 重定向到一个文件,您也不会看到那里打印出未经处理的异常。
但是有一个解决办法:
# core logic of the service
def SvcDoRun(self):
try:
self.server = MyLauncher()
self.server.run()
except:
import traceback
print(traceback.format_exc())
另一个答案中提到的更改后的 excepthook 在我的情况下不起作用:
我有一个 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 的新手,我不知道我错过了什么。该服务已正确安装,也可以正常启动。我正在使用 ActiveState Python 3.6,它随 pywin32 库一起提供。
使用logging
报告程序的进度and do debug printing。这样,您将看到程序在做什么以及卡在什么地方。
(另请注意,ServiceFramework
有一个 "debug mode"。运行 <your_program> debug
从控制台指示 HandleCommandLine
到 运行 你的程序交互:你得到控制台输出,然后 Ctrl-C 终止程序。这使得 运行-fix 迭代花费更少的时间,但不会揭示仅在 运行ning 时才会出现的错误作为服务。因此它很有帮助,但只是初步步骤,运行作为服务并记录到文件是最终测试。)
以下是我如何安排 运行 unatteneded/as 服务的脚本。
尽快设置日志记录。
始终使用轮换处理程序,以便日志不会无限增长。
确保记录任何未处理的异常。# 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(<...>)
一个问题是
# core logic of the service
def SvcDoRun(self):
有自己的异常处理程序。因此,即使您将 std out 和 err 重定向到一个文件,您也不会看到那里打印出未经处理的异常。 但是有一个解决办法:
# core logic of the service
def SvcDoRun(self):
try:
self.server = MyLauncher()
self.server.run()
except:
import traceback
print(traceback.format_exc())
另一个答案中提到的更改后的 excepthook 在我的情况下不起作用: