Merge branch 'logging'

This commit is contained in:
zenix 2017-04-11 19:03:35 +09:00
commit bf46bcf376
9 changed files with 115 additions and 7 deletions

View File

@ -16,4 +16,5 @@ dependencies:
- ujson>=1.35 - ujson>=1.35
- aiofiles>=0.3.0 - aiofiles>=0.3.0
- websockets>=3.2 - websockets>=3.2
- pyyaml>=3.12
- https://github.com/channelcat/docutils-fork/zipball/master - https://github.com/channelcat/docutils-fork/zipball/master

View File

@ -8,3 +8,4 @@ pytest
tox tox
ujson ujson
uvloop uvloop
pyyaml

View File

@ -3,3 +3,4 @@ httptools
ujson ujson
uvloop uvloop
websockets websockets
pyyaml

View File

@ -1,6 +1,9 @@
import os
from argparse import ArgumentParser from argparse import ArgumentParser
from importlib import import_module from importlib import import_module
from sanic import __path__ as lib_path
from sanic.log import log from sanic.log import log
from sanic.app import Sanic from sanic.app import Sanic
@ -14,6 +17,8 @@ if __name__ == "__main__":
help='location of keyfile for SSL.') help='location of keyfile for SSL.')
parser.add_argument('--workers', dest='workers', type=int, default=1, ) parser.add_argument('--workers', dest='workers', type=int, default=1, )
parser.add_argument('--debug', dest='debug', action="store_true") parser.add_argument('--debug', dest='debug', action="store_true")
parser.add_argument('--log', dest='log_config_path', type=str,
default=os.path.join(lib_path[0], 'default.conf'))
parser.add_argument('module') parser.add_argument('module')
args = parser.parse_args() args = parser.parse_args()
@ -34,7 +39,8 @@ if __name__ == "__main__":
ssl = None ssl = None
app.run(host=args.host, port=args.port, app.run(host=args.host, port=args.port,
workers=args.workers, debug=args.debug, ssl=ssl) workers=args.workers, debug=args.debug, ssl=ssl,
log_config_path=args.log_config_path)
except ImportError: except ImportError:
log.error("No module named {} found.\n" log.error("No module named {} found.\n"
" Example File: project/sanic_server.py -> app\n" " Example File: project/sanic_server.py -> app\n"

View File

@ -1,6 +1,9 @@
import logging import logging
import logging.config
import re import re
import warnings import warnings
import os
import yaml
from asyncio import get_event_loop, ensure_future, CancelledError from asyncio import get_event_loop, ensure_future, CancelledError
from collections import deque, defaultdict from collections import deque, defaultdict
from functools import partial from functools import partial
@ -9,11 +12,12 @@ from traceback import format_exc
from urllib.parse import urlencode, urlunparse from urllib.parse import urlencode, urlunparse
from ssl import create_default_context, Purpose from ssl import create_default_context, Purpose
from sanic import __path__ as lib_path
from sanic.config import Config from sanic.config import Config
from sanic.constants import HTTP_METHODS from sanic.constants import HTTP_METHODS
from sanic.exceptions import ServerError, URLBuildError, SanicException from sanic.exceptions import ServerError, URLBuildError, SanicException
from sanic.handlers import ErrorHandler from sanic.handlers import ErrorHandler
from sanic.log import log from sanic.log import log, netlog
from sanic.response import HTTPResponse, StreamingHTTPResponse from sanic.response import HTTPResponse, StreamingHTTPResponse
from sanic.router import Router from sanic.router import Router
from sanic.server import serve, serve_multiple, HttpProtocol from sanic.server import serve, serve_multiple, HttpProtocol
@ -26,10 +30,17 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed
class Sanic: class Sanic:
def __init__(self, name=None, router=None, error_handler=None, def __init__(self, name=None, router=None, error_handler=None,
load_env=True): load_env=True,
log_config_path=os.path.join(lib_path[0], "default.conf")):
conf = None
if log_config_path and os.path.exists(log_config_path):
with open(log_config_path) as f:
conf = yaml.load(f)
conf.setdefault('version', 1)
logging.config.dictConfig(conf)
# Only set up a default log handler if the # Only set up a default log handler if the
# end-user application didn't set anything up. # end-user application didn't set anything up.
if not logging.root.handlers and log.level == logging.NOTSET: if not conf and log.level == logging.NOTSET:
formatter = logging.Formatter( formatter = logging.Formatter(
"%(asctime)s: %(levelname)s: %(message)s") "%(asctime)s: %(levelname)s: %(message)s")
handler = logging.StreamHandler() handler = logging.StreamHandler()
@ -46,6 +57,7 @@ class Sanic:
self.router = router or Router() self.router = router or Router()
self.error_handler = error_handler or ErrorHandler() self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env) self.config = Config(load_env=load_env)
self.log_config_path = log_config_path
self.request_middleware = deque() self.request_middleware = deque()
self.response_middleware = deque() self.response_middleware = deque()
self.blueprints = {} self.blueprints = {}
@ -442,7 +454,6 @@ class Sanic:
# -------------------------------------------- # # -------------------------------------------- #
# Request Middleware # Request Middleware
# -------------------------------------------- # # -------------------------------------------- #
request.app = self request.app = self
response = await self._run_request_middleware(request) response = await self._run_request_middleware(request)
# No middleware results # No middleware results
@ -490,6 +501,13 @@ class Sanic:
log.exception( log.exception(
'Exception occured in one of response middleware handlers' 'Exception occured in one of response middleware handlers'
) )
if self.log_config_path:
netlog.info('', extra={
"host": "%s:%d" % request.ip,
"request": "%s %s" % (request.method, request.query_string),
"status": response.status,
"byte": len(response.body)
})
# pass the response to the correct callback # pass the response to the correct callback
if isinstance(response, StreamingHTTPResponse): if isinstance(response, StreamingHTTPResponse):
@ -512,7 +530,8 @@ class Sanic:
def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None, def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None,
after_start=None, before_stop=None, after_stop=None, ssl=None, after_start=None, before_stop=None, after_stop=None, ssl=None,
sock=None, workers=1, loop=None, protocol=None, sock=None, workers=1, loop=None, protocol=None,
backlog=100, stop_event=None, register_sys_signals=True): backlog=100, stop_event=None, register_sys_signals=True,
log_config_path=os.path.join(lib_path[0], "default.yml")):
"""Run the HTTP Server and listen until keyboard interrupt or term """Run the HTTP Server and listen until keyboard interrupt or term
signal. On termination, drain connections before closing. signal. On termination, drain connections before closing.
@ -539,6 +558,11 @@ class Sanic:
:param protocol: Subclass of asyncio protocol class :param protocol: Subclass of asyncio protocol class
:return: Nothing :return: Nothing
""" """
if log_config_path and os.path.exists(log_config_path):
with open(log_config_path) as f:
conf = yaml.load(f)
conf.setdefault('version', 1)
logging.config.dictConfig(conf)
if protocol is None: if protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol) else HttpProtocol)
@ -579,12 +603,19 @@ class Sanic:
before_start=None, after_start=None, before_start=None, after_start=None,
before_stop=None, after_stop=None, ssl=None, before_stop=None, after_stop=None, ssl=None,
sock=None, loop=None, protocol=None, sock=None, loop=None, protocol=None,
backlog=100, stop_event=None): backlog=100, stop_event=None,
log_config_path=os.path.join(lib_path[0],
"default.yml")):
"""Asynchronous version of `run`. """Asynchronous version of `run`.
NOTE: This does not support multiprocessing and is not the preferred NOTE: This does not support multiprocessing and is not the preferred
way to run a Sanic application. way to run a Sanic application.
""" """
if log_config_path and os.path.exists(log_config_path):
with open(log_config_path) as f:
conf = yaml.load(f)
conf.setdefault('version', 1)
logging.config.dictConfig(conf)
if protocol is None: if protocol is None:
protocol = (WebSocketProtocol if self.websocket_enabled protocol = (WebSocketProtocol if self.websocket_enabled
else HttpProtocol) else HttpProtocol)

52
sanic/default.yml Normal file
View File

@ -0,0 +1,52 @@
version: 1
filters:
access_filter:
(): sanic.defaultFilter.DefaultFilter
param: [0, 10, 20]
error_filter:
(): sanic.defaultFilter.DefaultFilter
param: [30, 40, 50]
formatters:
simple:
format: '%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s'
datefmt: '%Y-%m-%d %H:%M:%S'
access:
format: '%(asctime)s - [%(levelname)s][%(host)s]: %(request)s %(message)s %(status)s %(byte)d'
datefmt: '%Y-%m-%d %H:%M:%S'
handlers:
internal:
class: logging.handlers.TimedRotatingFileHandler
filters: [access_filter]
formatter: simple
when: 'D'
interval: 1
backupCount: 7
filename: 'access.log'
access:
class: logging.handlers.TimedRotatingFileHandler
filters: [access_filter]
formatter: access
when: 'D'
interval: 1
backupCount: 7
filename: 'access.log'
error:
class: logging.handlers.TimedRotatingFileHandler
filters: [error_filter]
filename: 'error.log'
when: 'D'
interval: 1
backupCount: 7
formatter: simple
loggers:
sanic:
level: DEBUG
handlers: [internal, error]
network:
level: DEBUG
handlers: [access, error]

13
sanic/defaultFilter.py Normal file
View File

@ -0,0 +1,13 @@
import logging
class DefaultFilter(logging.Filter):
def __init__(self, param=None):
self.param = param
def filter(self, record):
if self.param is None:
return True
if record.levelno in self.param:
return True
return False

View File

@ -1,3 +1,4 @@
import logging import logging
log = logging.getLogger('sanic') log = logging.getLogger('sanic')
netlog = logging.getLogger('network')

View File

@ -27,6 +27,7 @@ setup_kwargs = {
'description': ( 'description': (
'A microframework based on uvloop, httptools, and learnings of flask'), 'A microframework based on uvloop, httptools, and learnings of flask'),
'packages': ['sanic'], 'packages': ['sanic'],
'package_data': {'':['default.yml']},
'platforms': 'any', 'platforms': 'any',
'classifiers': [ 'classifiers': [
'Development Status :: 2 - Pre-Alpha', 'Development Status :: 2 - Pre-Alpha',
@ -46,6 +47,7 @@ requirements = [
ujson, ujson,
'aiofiles>=0.3.0', 'aiofiles>=0.3.0',
'websockets>=3.2', 'websockets>=3.2',
'pyyaml>=3.12'
] ]
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")): if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
print("Installing without uJSON") print("Installing without uJSON")