[Checkins] SVN: z3c.recipe.winservice/trunk/ - Added debug option to log exceptions for totally misbehaving scripts

Adam Groszer agroszer at gmail.com
Mon Mar 23 06:07:51 EDT 2009


Log message for revision 98304:
  - Added debug option to log exceptions for totally misbehaving scripts
  - Slight changes to service manager

Changed:
  U   z3c.recipe.winservice/trunk/CHANGES.txt
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py
  U   z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in

-=-
Modified: z3c.recipe.winservice/trunk/CHANGES.txt
===================================================================
--- z3c.recipe.winservice/trunk/CHANGES.txt	2009-03-23 09:19:29 UTC (rev 98303)
+++ z3c.recipe.winservice/trunk/CHANGES.txt	2009-03-23 10:07:51 UTC (rev 98304)
@@ -7,6 +7,10 @@
 
 - ...
 
+- Added debug option to log exceptions for totally misbehaving scripts
+
+- Slight changes to service manager
+
 Version 0.5.0 (2008-04-12)
 --------------------------
 

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt	2009-03-23 09:19:29 UTC (rev 98303)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/README.txt	2009-03-23 10:07:51 UTC (rev 98304)
@@ -7,7 +7,7 @@
 
 This Zope 3 recipes offers windows service installation support.
 
-The 'service' recipe installes the required scripts and files whihc can be 
+The 'service' recipe installes the required scripts and files whihc can be
 used for install a windwos service.
 
 
@@ -23,8 +23,8 @@
   The windows service description option.
 
 runzope
-  The script name which get used by the winservice. This script must exist in 
-  the bin folder and before we run this recipe exist. This script can get setup 
+  The script name which get used by the winservice. This script must exist in
+  the bin folder and before we run this recipe exist. This script can get setup
   with the z3c.recipe.dev.app recipe.
 
 
@@ -96,7 +96,7 @@
   -  buildout.exe
   -  winservice.py
 
-The winservice-scrip.py contains the service setup for our zope windows service:
+The winservice.py contains the service setup for our zope windows service:
 
   >>> cat('bin', 'winservice.py')
   ##############################################################################
@@ -186,10 +186,11 @@
   import win32process
   <BLANKLINE>
   # these are replacements from winservice recipe
-  PYTHON = r'C:\Python24\python.exe'
+  PYTHON = r'U:\Python24\python.exe'
   PYTHONDIR = os.path.split(PYTHON)[0]
   PYTHONW = os.path.join(PYTHONDIR, 'pythonw.exe')
   PYTHONSERVICE_EXE = r'%s\Lib\site-packages\win32\pythonservice.exe' % PYTHONDIR
+  TOSTART = r'/sample-buildout/bin/app-script.py'
   <BLANKLINE>
   <BLANKLINE>
   # the max seconds we're allowed to spend backing off
@@ -241,14 +242,20 @@
       # each instance.  The below-defined start_cmd (and _svc_display_name_
       # and _svc_name_) are just examples.
   <BLANKLINE>
-      _svc_name_ = '870810267'
+      _svc_name_ = '..._bin_app_script_py'
       _svc_display_name_ = r'Zope 3 Windows Service'
       _svc_description_ = r'Zope 3 Windows Service description'
   <BLANKLINE>
       _exe_name_ = PYTHONSERVICE_EXE
-      start_cmd = '"%s" "%s"' % (PYTHONW, r'/sample-buildout/bin/app-script.py')
+      start_cmd = '"%s" "%s"' % (PYTHONW, TOSTART)
   <BLANKLINE>
       def __init__(self, args):
+          if not os.path.exists(PYTHONW):
+              raise OSError("%s does not exist" % PYTHON)
+  <BLANKLINE>
+          if not os.path.exists(TOSTART):
+              raise OSError("%s does not exist" % TOSTART)
+  <BLANKLINE>
           win32serviceutil.ServiceFramework.__init__(self, args)
           # Create an event which we will use to wait on.
           # The "service stop" request will set this event.
@@ -398,3 +405,77 @@
           # This ensures that pythonservice.exe is registered...
           os.system('"%s" -register' % PYTHONSERVICE_EXE)
       win32serviceutil.HandleCommandLine(Zope3Service)
+
+
+Debug
+-----
+
+This option is for service scripts having fundamental problems.
+The problem with those scripts is that they are starting and stopping at once.
+Seemingly there is no output, no sing that the script did something.
+And because they run in a subprocess until those scripts have logging established
+they won't have any chance to report the error.
+For this we'll setup a bare catch-all around the whole script and log any
+exceptions to the windows event log.
+CAUTION: this takes a copy of the app-script.py but does not update the patched
+result when it changes!
+
+We can enable the ``debug`` option:
+
+  >>> write('buildout.cfg',
+  ... '''
+  ... [buildout]
+  ... develop = demo1 demo2
+  ... parts = winservice
+  ...
+  ... [winservice]
+  ... recipe = z3c.recipe.winservice:service
+  ... name = Zope 3 Windows Service
+  ... description = Zope 3 Windows Service description
+  ... runzope = app
+  ... debug = true
+  ...
+  ... ''' % globals())
+
+Now, Let's run the buildout and see what we get:
+
+  >>> print system(join('bin', 'buildout')),
+  Develop: '/sample-buildout/demo1'
+  Develop: '/sample-buildout/demo2'
+  Uninstalling winservice.
+  Installing winservice.
+
+
+The bin folder contains the windows service installer script:
+
+  >>> ls('bin')
+  -  app-script.py
+  -  app-servicedebug.py
+  -  buildout-script.py
+  -  buildout.exe
+  -  winservice.py
+
+The winservice.py file gets changed according to the new script name:
+
+  >>> cat('bin', 'winservice.py')
+  ##############################################################################
+  ...TOSTART = r'/sample-buildout/bin/app-servicedebug.py'...
+  ..._svc_name_ = '...bin_app_script_py'...
+
+The debug script contains a bare catch-all and a logger:
+
+  >>> cat('bin', 'app-servicedebug.py')
+  <BLANKLINE>
+  def exceptionlogger():
+      import servicemanager
+      import traceback
+      servicemanager.LogErrorMsg("Script %s had an exception: %s" % (
+        __file__, traceback.format_exc()
+      ))
+  <BLANKLINE>
+  try:
+  <BLANKLINE>
+      dummy start script
+  <BLANKLINE>
+  except Exception, e:
+      exceptionlogger()

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py	2009-03-23 09:19:29 UTC (rev 98303)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/service.py	2009-03-23 10:07:51 UTC (rev 98304)
@@ -17,42 +17,82 @@
 """
 
 import os
+import string
 import shutil
 import zc.buildout
 
 
 def installScript(srcFile, script, replacements):
     generated = []
-    
+
     # generate the winservice script
     text = open(srcFile, "rU").read()
     # perform replacements
     for var, string in replacements:
         text = text.replace(var, string)
 
-    # If the file exists, keep the old file.  This is a
-    # hopefully temporary hack to get around distutils
-    # stripping the permissions on the server skeletin files.
-    # We reuse the original default files, which have the
-    # right permissions.
-    old = os.path.exists(script)
-    if old:
-        f = open(script, "r+")
-        f.truncate(0)
-    else:
-        f = open(script, "w")
+    changed = not (os.path.exists(script) and open(script).read() == text)
 
-    f.write(text)
-    f.close()
+    if changed:
+        # If the file exists, keep the old file.  This is a
+        # hopefully temporary hack to get around distutils
+        # stripping the permissions on the server skeleton files.
+        # We reuse the original default files, which have the
+        # right permissions.
+        old = os.path.exists(script)
+        if old:
+            f = open(script, "r+")
+            f.truncate(0)
+        else:
+            f = open(script, "w")
 
-    if not old:
-        shutil.copymode(srcFile, script)
-        shutil.copystat(srcFile, script)
+        f.write(text)
+        f.close()
+
+        if not old:
+            shutil.copymode(srcFile, script)
+            shutil.copystat(srcFile, script)
+
     generated.append(script)
 
     return generated
 
+def patchScript(srcFile, dstFile):
+    generated = []
 
+    src = open(srcFile, 'rU').read()
+
+    src = '\n'.join(['    '+line for line in src.split('\n')])
+
+    dest = PATCH_TEMPLATE % dict(script=src,
+                                 srcFile=srcFile)
+
+    changed = not (os.path.exists(dstFile) and open(dstFile).read() == dest)
+
+    if changed:
+        open(dstFile, 'w').write(dest)
+        shutil.copymode(srcFile, dstFile)
+        shutil.copystat(srcFile, dstFile)
+
+    generated.append(dstFile)
+
+    return generated
+
+
+PATCH_TEMPLATE = '''
+def exceptionlogger():
+    import servicemanager
+    import traceback
+    servicemanager.LogErrorMsg("Script %%s had an exception: %%s" %% (
+      __file__, traceback.format_exc()
+    ))
+
+try:
+%(script)s
+except Exception, e:
+    exceptionlogger()
+'''
+
 class ServiceSetup:
 
     def __init__(self, buildout, name, options):
@@ -77,38 +117,65 @@
             runzope = '%s-script.py' % runzope
 
         self.runScript = os.path.join(binDir, runzope)
+        options['serviceName'] = self.getServiceName()
 
+    def getServiceName(self):
+        try:
+            serviceName = self.options['serviceName']
+        except KeyError:
+            if len(self.runScript) < 128:
+                #make a meaningful name in case it fits
+                serviceName = ''
+                for c in self.runScript:
+                    if c in string.letters + string.digits:
+                        serviceName += c
+                    else:
+                        serviceName += '_'
+            else:
+                #otherwise a dumb hash
+                serviceName = str(hash(self.runScript))
+        return serviceName
+
+    def install(self):
+        options = self.options
+
         # setup service name
         defaultName = 'Zope3 %s' % self.name
-        defaultDescription = 'Zope3 windows service for %s' % self.name
+        defaultDescription = 'Zope3 windows service for %s, using %s' % (
+            self.name, self.runScript)
         displayName = options.get('name', defaultName)
-        serviceName = str(hash(displayName))
+        serviceName = options['serviceName']
         description = options.get('description', defaultDescription)
+
+        generated = []
+
+        if options.get('debug'):
+            serviceScript = self.runScript.replace(
+                '-script.py', '-servicedebug.py')
+            generated += patchScript(self.runScript, serviceScript)
+        else:
+            serviceScript = self.runScript
+
         self.winServiceVars = [
             ("<<PYTHON>>", self.executable),
-            ("<<RUNZOPE>>", self.runScript),
+            ("<<RUNZOPE>>", serviceScript),
             ("<<SERVICE_NAME>>", serviceName),
             ("<<SERVICE_DISPLAY_NAME>>", displayName),
             ("<<SERVICE_DESCRIPTION>>", description),
             ]
-        self.runZopeVars = []
 
-    def install(self):
-        options = self.options
-        generated = []
-
         # raise exeption if the app script is not here now
-        if not os.path.exists(self.runScript):
+        if not os.path.exists(serviceScript):
             raise zc.buildout.UserError(
-                'App start script %s does not exist in bin folder.'  % 
+                'App start script %s does not exist in bin folder.'  %
                     self.runScript)
 
         # get templates
-        binDir = self.buildout['buildout']['bin-directory']
         winServiceTemplate = os.path.join(os.path.dirname(__file__),
             'winservice.in')
 
         # setup winservice file paths
+        binDir = self.buildout['buildout']['bin-directory']
         script = os.path.join(binDir, 'winservice.py')
 
         generated += installScript(winServiceTemplate, script,
@@ -117,4 +184,4 @@
         # return list of generated files
         return generated
 
-    update = install
+    update = install
\ No newline at end of file

Modified: z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in
===================================================================
--- z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in	2009-03-23 09:19:29 UTC (rev 98303)
+++ z3c.recipe.winservice/trunk/src/z3c/recipe/winservice/winservice.in	2009-03-23 10:07:51 UTC (rev 98304)
@@ -37,7 +37,7 @@
       install : Installs the service
 
       update : Updates the service, use this when you change
-               the service class implementation 
+               the service class implementation
 
       remove : Removes the service
 
@@ -89,6 +89,7 @@
 PYTHONDIR = os.path.split(PYTHON)[0]
 PYTHONW = os.path.join(PYTHONDIR, 'pythonw.exe')
 PYTHONSERVICE_EXE = r'%s\Lib\site-packages\win32\pythonservice.exe' % PYTHONDIR
+TOSTART = r'<<RUNZOPE>>'
 
 
 # the max seconds we're allowed to spend backing off
@@ -145,9 +146,15 @@
     _svc_description_ = r'<<SERVICE_DESCRIPTION>>'
 
     _exe_name_ = PYTHONSERVICE_EXE
-    start_cmd = '"%s" "%s"' % (PYTHONW, r'<<RUNZOPE>>')
+    start_cmd = '"%s" "%s"' % (PYTHONW, TOSTART)
 
     def __init__(self, args):
+        if not os.path.exists(PYTHONW):
+            raise OSError("%s does not exist" % PYTHON)
+
+        if not os.path.exists(TOSTART):
+            raise OSError("%s does not exist" % TOSTART)
+
         win32serviceutil.ServiceFramework.__init__(self, args)
         # Create an event which we will use to wait on.
         # The "service stop" request will set this event.



More information about the Checkins mailing list