# HG changeset patch # User David Barts # Date 1559252649 25200 # Node ID 969f515b505b49260874c5b38a83c2553b0c0713 # Parent 7261459351fa0b39baef7ce4620f8d76e6d7b97d Make it easy to leave stdout and stderr alone (untested). diff -r 7261459351fa -r 969f515b505b tincan.py --- a/tincan.py Thu May 30 11:23:26 2019 -0700 +++ b/tincan.py Thu May 30 14:44:09 2019 -0700 @@ -13,14 +13,15 @@ import importlib from inspect import isclass import io +import logging import mimetypes import py_compile from stat import S_ISDIR, S_ISREG from string import whitespace from threading import Lock import time -import traceback import urllib +import uuid import bottle @@ -398,6 +399,7 @@ else: self.lock = _DummyLock() self.get_template = _get_template_cache + self.logger = launcher.logger def urljoin(self, *args): """ @@ -437,7 +439,7 @@ self._encoding = None def launch(self): - # print("adding static route:", self._urlpath) # debug + self.logger.info("adding static route: %s", self._urlpath) self._app.route(self._urlpath, 'GET', self) def _parse_date(self, ims): @@ -462,6 +464,7 @@ except PermissionError as e: return bottle.HTTPError(status=403, exception=e) except OSError as e: + self.logger.exception("unexpected exception reading %r", self._fspath) return bottle.HTTPError(status=500, exception=e) # Establish preliminary standard headers. headers = { @@ -488,12 +491,13 @@ custom code-behind, only two variables are available to your template: request (bottle.Request) and error (bottle.HTTPError). """ - def __init__(self, template, loads, klass, lock): + def __init__(self, template, loads, klass, lock, logger): self._template = template self._template.prepare() self._loads = loads self._class = klass self.lock = lock + self.logger = logger def __call__(self, e): bottle.request.environ[_FTYPE] = True @@ -507,7 +511,7 @@ except bottle.HTTPResponse as e: return e except Exception as e: - traceback.print_exc() + self.logger.exception("unexpected exception in error page") # Bottle doesn't allow error handlers to themselves cause # errors, most likely as a measure to prevent looping. So # this will cause a "Critical error while processing request" @@ -558,9 +562,9 @@ raise TinCanError("{0}: invalid #forward".format(self._origin)) if self._header.forward is None: break - # print("forwarding from:", self._urlpath) # debug + # self.logger.debug("forwarding from: %s", self._urlpath) # debug self._redirect() - # print("forwarded to:", self._urlpath) # debug + # self.logger.debug("forwarded to: %s", self._urlpath) # debug # If this is a #hidden page, we ignore it for now, since hidden pages # don't get routes made for them. if oheader.hidden and not oheader.errors: @@ -619,7 +623,7 @@ if not methods: raise TinCanError("{0}: no #methods specified".format(self._urlpath)) # Register this thing with Bottle - # print("adding route:", self._origin, '('+','.join(methods)+')') # debug + self.logger.info("adding route: %s (%s)", self._origin, ','.join(methods)) self._app.route(self._origin, methods, self) def _splitpath(self, unsplit): @@ -634,7 +638,7 @@ errors = range(_ERRMIN, _ERRMAX+1) route = _TinCanErrorRoute( ChameleonTemplate(source=self._template.body, encoding=self._encoding), - self._loads, self._class, self.lock) + self._loads, self._class, self.lock, self.logger) for error in errors: if error < _ERRMIN or error > _ERRMAX: raise TinCanError("{0}: bad #errors code".format(self._urlpath)) @@ -750,11 +754,10 @@ except bottle.HTTPResponse as e: return e except Exception as e: - traceback.print_exc() + self.logger.exception("%s: unexpected exception", self._urlpath) raise bottle.HTTPError(status=500, exception=e) if target is None: - message = "{0}: unexpected null target".format(self._urlpath) - sys.stderr.write(message + '\n') + self.logger.error("%s: unexpected null target", self._urlpath) raise bottle.HTTPError(status=500, exception=TinCanError(message)) # We get here if we are doing a server-side programmatic # forward. @@ -764,8 +767,7 @@ if _FLOOP not in environ: environ[_FLOOP] = set([self._urlpath]) elif target in environ[_FLOOP]: - message = "{0}: forward loop detected".format(environ[_FORIG]) - sys.stderr.write(message + '\n') + self.logger.error("%s: forward loop detected", environ[_FORIG]) raise bottle.HTTPError(status=500, exception=TinCanError(message)) environ[_FLOOP].add(target) environ['bottle.raw_path'] = target @@ -805,19 +807,19 @@ """ Helper class for launching webapps. """ - def __init__(self, fsroot, urlroot, logger, multithread=True): + def __init__(self, fsroot, urlroot, multithread): """ Lightweight constructor. The real action happens in .launch() below. """ self.fsroot = fsroot self.urlroot = urlroot - self.logger = logger self.app = None self.errors = 0 self.debug = False self.encoding = ENCODING self.static = False self.multithread = multithread + self.logger = None def launch(self): """ @@ -873,29 +875,28 @@ try: route.launch() except TinCanError as e: - self.logger(str(e)) - if self.debug: - while e.__cause__ != None: - e = e.__cause__ - self.logger("\t{0}: {1!s}".format(e.__class__.__name__, e)) + if self.logger.getEffectiveLevel() <= logging.DEBUG: + self.logger.exception(str(e)) + else: + self.logger.error(str(e)) self.errors += 1 elif S_ISDIR(etype): self._launch(subdir + [entry]) -def _logger(message): - sys.stderr.write(message) - sys.stderr.write('\n') - -def launch(fsroot=None, urlroot='/', logger=_logger, debug=False, +def launch(fsroot=None, urlroot='/', logger=None, debug=False, encoding=ENCODING, static=False, multithread=True): """ Launch and return a TinCan webapp. Does not run the app; it is the caller's responsibility to call app.run() """ + if logger is None: + logger = logging.getLogger("{0!s}-{1!s}".format(__name__, uuid.uuid1())) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.DEBUG if debug else logging.INFO) if fsroot is None: fsroot = os.getcwd() - launcher = _Launcher(fsroot, urlroot, logger, multithread=multithread) - launcher.debug = debug + launcher = _Launcher(fsroot, urlroot, multithread) + launcher.logger = logger launcher.encoding = encoding launcher.static = static launcher.launch()