Last active
March 22, 2016 19:47
-
-
Save stavxyz/4fdad1a8a4584a541f38 to your computer and use it in GitHub Desktop.
bottle, tornado and upgraded json logs using pythonjsonlogger
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import bottle | |
THUMBS_UP = u'\U0001F44D' | |
THUMBS_DOWN = u'\U0001F44E' | |
@bottle.route('/hello/<name>') | |
def index(name): | |
return bottle.template('<b>Hello {{name}}!</b>', name=name) | |
@bottle.route('/hello') | |
def helloworld(name): | |
return '<b>Hello World!</b>' | |
@bottle.route('/') | |
def ok(): | |
return u'\n {} \n\n'.format(random.choice([THUMBS_UP, THUMBS_DOWN])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bottle==0.12.9 | |
tornado==4.3 | |
python-json-logger==0.1.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
from __future__ import print_function | |
import datetime | |
import logging | |
import os | |
import sys | |
import bottle | |
from pythonjsonlogger import jsonlogger | |
from tornado.log import access_log | |
from tornado import wsgi | |
class WSGIContainer(wsgi.WSGIContainer): | |
"""Override log method to include more request data.""" | |
def _log(self, status_code, request): | |
if status_code < 400: | |
log_method = access_log.info | |
elif status_code < 500: | |
log_method = access_log.warning | |
else: | |
log_method = access_log.error | |
request_time = 1000.0 * request.request_time() | |
summary = request.method + " " + request.uri + " (" + \ | |
request.remote_ip + ")" | |
request_time_ms = '{number:.{digits}f}'.format( | |
number=request_time, digits=2) | |
extra = { | |
'status': status_code, | |
'method': request.method, | |
'path': request.uri, | |
'remote_ip': request.remote_ip, | |
'request_time_ms': request_time_ms, | |
} | |
log_method("%d %s %sms", status_code, summary, request_time_ms, | |
extra=extra) | |
class JsonFormatter(jsonlogger.JsonFormatter): | |
"""Extend the json formatter to include more fields. | |
Note: | |
It is possible to get a similar outcome by supplying a | |
format string with the desired parameters when initializing | |
the JsonFormatter, but that relies on regex, and the mapping | |
key ends up being the same. Here, we can have funcName->function, | |
where 'function' is a semantic improvement. | |
See http://bit.ly/1OaJylG | |
""" | |
def add_fields(self, log_record, record, message_dict): | |
"""Supply additional data to dict for logging.""" | |
super(JsonFormatter, self).add_fields( | |
log_record, record, message_dict) | |
log_record['name'] = 'my-application' | |
log_record['level'] = record.levelname | |
log_record['function'] = record.funcName | |
log_record['file'] = record.filename | |
log_record['module'] = record.module | |
log_record['time'] = datetime.datetime.fromtimestamp( | |
record.created) | |
class TornadoServer(bottle.ServerAdapter): | |
"""The super hyped asynchronous server by facebook.""" | |
def run(self, handler): | |
"""Use custom wsgi container and pass along server options.""" | |
import tornado.httpserver | |
import tornado.ioloop | |
if bottle.DEBUG: | |
handler.debug = True | |
container = WSGIContainer(handler) | |
tornado_server = tornado.httpserver.HTTPServer( | |
container, **self.options) | |
tornado_server.listen(port=self.port, address=self.host) | |
tornado.ioloop.IOLoop.instance().start() | |
# This overwrites the reference to the built-in-tornado server adapter! | |
bottle.server_names['tornado'] = TornadoServer | |
def setup_json_logging_to_stdout(): | |
streamhandler = logging.StreamHandler(stream=sys.stdout) | |
streamhandler.setLevel(logging.INFO) | |
root_logger = logging.getLogger() | |
root_logger.addHandler(streamhandler) | |
root_ll = root_logger.getEffectiveLevel() | |
if logging.INFO < root_ll or root_ll == logging.NOTSET: | |
root_logger.setLevel(logging.INFO) | |
jsonformatter = JsonFormatter(datefmt='%Y-%m-%dT%H:%M:%SZ') | |
streamhandler.setFormatter(jsonformatter) | |
def __bottle__main__(): | |
"""Copied from if __name__ == '__main__' in bottle.py""" | |
from optparse import OptionParser | |
_cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app") | |
_opt = _cmd_parser.add_option | |
_opt("--version", action="store_true", help="show version number.") | |
_opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.") | |
_opt("-s", "--server", default='tornado', help="use SERVER as backend.") | |
_opt("-p", "--plugin", action="append", help="install additional plugin/s.") | |
_opt("--debug", default=True, action="store_true", help="start server in debug mode.") | |
_opt("--reload", default=True, action="store_true", help="auto-reload on file changes.") | |
_cmd_options, _cmd_args = _cmd_parser.parse_args() | |
if _cmd_options.server and _cmd_options.server.startswith('gevent'): | |
import gevent.monkey; gevent.monkey.patch_all() | |
opt, args, parser = _cmd_options, _cmd_args, _cmd_parser | |
if opt.version: | |
bottle._stdout('Bottle %s\n'%__version__) | |
sys.exit(0) | |
sys.path.insert(0, '.') | |
sys.modules.setdefault('bottle', sys.modules['__main__']) | |
host, port = (opt.bind or 'localhost'), 8080 | |
if ':' in host and host.rfind(']') < host.rfind(':'): | |
host, port = host.rsplit(':', 1) | |
host = host.strip('[]') | |
app = args[0] if args else None | |
return app, { | |
'host': host, | |
'port': port, | |
'server': opt.server, | |
'reloader': opt.reload, | |
'plugins': opt.plugin, | |
'debug': opt.debug | |
} | |
def main(): | |
setup_json_logging_to_stdout() | |
app, kwargs = __bottle__main__() | |
if not app: | |
app = 'app' | |
if not os.getenv('BOTTLE_CHILD'): | |
logging.getLogger(__name__).info("Starting.......................") | |
print('\nTry it out: `curl {host}:{port}`\n'.format( | |
host=kwargs['host'], port=kwargs['port'])) | |
bottle.run(app, **kwargs) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment