Skip to content

Instantly share code, notes, and snippets.

@stavxyz
Last active March 22, 2016 19:47
Show Gist options
  • Save stavxyz/4fdad1a8a4584a541f38 to your computer and use it in GitHub Desktop.
Save stavxyz/4fdad1a8a4584a541f38 to your computer and use it in GitHub Desktop.
bottle, tornado and upgraded json logs using pythonjsonlogger
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]))
bottle==0.12.9
tornado==4.3
python-json-logger==0.1.4
#! /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