fix access_log in run server and fix bool in env variables

This commit is contained in:
Sergey Fedoruk 2018-12-30 19:37:30 +01:00 committed by Sergey Fedoruk
parent d76d5e2c5f
commit 391fcdc83d
10 changed files with 143 additions and 78 deletions

View File

@ -72,10 +72,16 @@ To improve the performance add `debug=False` and `access_log=False` in the `run`
app.run(host='0.0.0.0', port=1337, workers=4, debug=False, access_log=False) app.run(host='0.0.0.0', port=1337, workers=4, debug=False, access_log=False)
``` ```
Running via Gunicorn you can set `--log-level` higher than `info` to not get any access logs anymore. Running via Gunicorn you can set Environment variable `SANIC_ACCESS_LOG="False"`
``` ```
gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker --log-level warning env SANIC_ACCESS_LOG="False" gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class sanic.worker.GunicornWorker --log-level warning
```
Or you can rewrite app config directly
```python
app.config.ACCESS_LOG = False
``` ```
## Asynchronous support ## Asynchronous support

View File

@ -977,7 +977,7 @@ class Sanic:
backlog=100, backlog=100,
stop_event=None, stop_event=None,
register_sys_signals=True, register_sys_signals=True,
access_log=True, access_log=None,
**kwargs **kwargs
): ):
"""Run the HTTP Server and listen until keyboard interrupt or term """Run the HTTP Server and listen until keyboard interrupt or term
@ -1027,8 +1027,10 @@ class Sanic:
"stop_event will be removed from future versions.", "stop_event will be removed from future versions.",
DeprecationWarning, DeprecationWarning,
) )
# compatibility old access_log params # if access_log is passed explicitly change config.ACCESS_LOG
self.config.ACCESS_LOG = access_log if access_log is not None:
self.config.ACCESS_LOG = access_log
server_settings = self._helper( server_settings = self._helper(
host=host, host=host,
port=port, port=port,
@ -1086,7 +1088,7 @@ class Sanic:
protocol=None, protocol=None,
backlog=100, backlog=100,
stop_event=None, stop_event=None,
access_log=True, access_log=None,
): ):
""" """
Asynchronous version of :func:`run`. Asynchronous version of :func:`run`.
@ -1114,8 +1116,10 @@ class Sanic:
"stop_event will be removed from future versions.", "stop_event will be removed from future versions.",
DeprecationWarning, DeprecationWarning,
) )
# compatibility old access_log params # if access_log is passed explicitly change config.ACCESS_LOG
self.config.ACCESS_LOG = access_log if access_log is not None:
self.config.ACCESS_LOG = access_log
server_settings = self._helper( server_settings = self._helper(
host=host, host=host,
port=port, port=port,

View File

@ -12,23 +12,31 @@ BASE_LOGO = """
""" """
DEFAULT_CONFIG = {
"REQUEST_MAX_SIZE": 100000000, # 100 megabytes
"REQUEST_BUFFER_QUEUE_SIZE": 100,
"REQUEST_TIMEOUT": 60, # 60 seconds
"RESPONSE_TIMEOUT": 60, # 60 seconds
"KEEP_ALIVE": True,
"KEEP_ALIVE_TIMEOUT": 5, # 5 seconds
"WEBSOCKET_MAX_SIZE": 2 ** 20, # 1 megabytes
"WEBSOCKET_MAX_QUEUE": 32,
"WEBSOCKET_READ_LIMIT": 2 ** 16,
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
"ACCESS_LOG": True,
}
class Config(dict): class Config(dict):
def __init__(self, defaults=None, load_env=True, keep_alive=True): def __init__(self, defaults=None, load_env=True, keep_alive=None):
super().__init__(DEFAULT_CONFIG)
super().__init__(defaults or {}) super().__init__(defaults or {})
self.LOGO = BASE_LOGO self.LOGO = BASE_LOGO
self.REQUEST_MAX_SIZE = 100000000 # 100 megabytes
self.REQUEST_BUFFER_QUEUE_SIZE = 100 if keep_alive is not None:
self.REQUEST_TIMEOUT = 60 # 60 seconds self.KEEP_ALIVE = keep_alive
self.RESPONSE_TIMEOUT = 60 # 60 seconds
self.KEEP_ALIVE = keep_alive
self.KEEP_ALIVE_TIMEOUT = 5 # 5 seconds
self.WEBSOCKET_MAX_SIZE = 2 ** 20 # 1 megabytes
self.WEBSOCKET_MAX_QUEUE = 32
self.WEBSOCKET_READ_LIMIT = 2 ** 16
self.WEBSOCKET_WRITE_LIMIT = 2 ** 16
self.GRACEFUL_SHUTDOWN_TIMEOUT = 15.0 # 15 sec
self.ACCESS_LOG = True
if load_env: if load_env:
prefix = SANIC_PREFIX if load_env is True else load_env prefix = SANIC_PREFIX if load_env is True else load_env
@ -116,4 +124,7 @@ class Config(dict):
try: try:
self[config_key] = float(v) self[config_key] = float(v)
except ValueError: except ValueError:
self[config_key] = v if v in ['True', 'False']:
self[config_key] = v == 'True'
else:
self[config_key] = v

View File

@ -58,9 +58,6 @@ class GunicornWorker(base.Worker):
else self.http_protocol else self.http_protocol
) )
# set ACCESS_LOG on base of logging level
self.app.callable.config.ACCESS_LOG = self.log.loglevel <= logging.INFO
self._server_settings = self.app.callable._helper( self._server_settings = self.app.callable._helper(
loop=self.loop, loop=self.loop,
debug=is_debug, debug=is_debug,

View File

@ -6,6 +6,7 @@ from textwrap import dedent
import pytest import pytest
from sanic import Sanic from sanic import Sanic
from sanic.config import Config, DEFAULT_CONFIG
from sanic.exceptions import PyFileError from sanic.exceptions import PyFileError
@ -34,6 +35,13 @@ def test_auto_load_env():
del environ["SANIC_TEST_ANSWER"] del environ["SANIC_TEST_ANSWER"]
def test_auto_load_bool_env():
environ["SANIC_TEST_ANSWER"] = "True"
app = Sanic()
assert app.config.TEST_ANSWER == True
del environ["SANIC_TEST_ANSWER"]
def test_dont_load_env(): def test_dont_load_env():
environ["SANIC_TEST_ANSWER"] = "42" environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(load_env=False) app = Sanic(load_env=False)
@ -139,3 +147,64 @@ def test_missing_config(app):
with pytest.raises(AttributeError) as e: with pytest.raises(AttributeError) as e:
app.config.NON_EXISTENT app.config.NON_EXISTENT
assert str(e.value) == ("Config has no 'NON_EXISTENT'") assert str(e.value) == ("Config has no 'NON_EXISTENT'")
def test_config_defaults():
"""
load DEFAULT_CONFIG
"""
conf = Config()
for key, value in DEFAULT_CONFIG.items():
assert getattr(conf, key) == value
def test_config_custom_defaults():
"""
we should have all the variables from defaults rewriting them with custom defaults passed in
Config
"""
custom_defaults = {
"REQUEST_MAX_SIZE": 1,
"KEEP_ALIVE": False,
"ACCESS_LOG": False
}
conf = Config(defaults=custom_defaults)
for key, value in DEFAULT_CONFIG.items():
if key in custom_defaults.keys():
value = custom_defaults[key]
assert getattr(conf, key) == value
def test_config_custom_defaults_with_env():
"""
test that environment variables has higher priority than DEFAULT_CONFIG and passed defaults dict
"""
custom_defaults = {
"REQUEST_MAX_SIZE123": 1,
"KEEP_ALIVE123": False,
"ACCESS_LOG123": False
}
environ_defaults = {
"SANIC_REQUEST_MAX_SIZE123": "2",
"SANIC_KEEP_ALIVE123": "True",
"SANIC_ACCESS_LOG123": "False"
}
for key, value in environ_defaults.items():
environ[key] = value
conf = Config(defaults=custom_defaults)
for key, value in DEFAULT_CONFIG.items():
if "SANIC_" + key in environ_defaults.keys():
value = environ_defaults["SANIC_" + key]
try:
value = int(value)
except ValueError:
if value in ['True', 'False']:
value = value == 'True'
assert getattr(conf, key) == value
for key, value in environ_defaults.items():
del environ[key]

View File

@ -3,13 +3,17 @@ from sanic import Sanic
import asyncio import asyncio
from asyncio import sleep as aio_sleep from asyncio import sleep as aio_sleep
from sanic.response import text from sanic.response import text
from sanic.config import Config
from sanic import server from sanic import server
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST, PORT from sanic.testing import SanicTestClient, HOST, PORT
CONFIG_FOR_TESTS = {
"KEEP_ALIVE_TIMEOUT": 2,
"KEEP_ALIVE": True
}
class ReuseableTCPConnector(TCPConnector): class ReuseableTCPConnector(TCPConnector):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReuseableTCPConnector, self).__init__(*args, **kwargs) super(ReuseableTCPConnector, self).__init__(*args, **kwargs)
@ -141,7 +145,7 @@ class ReuseableSanicTestClient(SanicTestClient):
# loop, so the changes above are required too. # loop, so the changes above are required too.
async def _local_request(self, method, uri, cookies=None, *args, **kwargs): async def _local_request(self, method, uri, cookies=None, *args, **kwargs):
request_keepalive = kwargs.pop( request_keepalive = kwargs.pop(
"request_keepalive", Config.KEEP_ALIVE_TIMEOUT "request_keepalive", CONFIG_FOR_TESTS['KEEP_ALIVE_TIMEOUT']
) )
if uri.startswith(("http:", "https:", "ftp:", "ftps://" "//")): if uri.startswith(("http:", "https:", "ftp:", "ftps://" "//")):
url = uri url = uri
@ -191,12 +195,14 @@ class ReuseableSanicTestClient(SanicTestClient):
return response return response
Config.KEEP_ALIVE_TIMEOUT = 2
Config.KEEP_ALIVE = True
keep_alive_timeout_app_reuse = Sanic("test_ka_timeout_reuse") keep_alive_timeout_app_reuse = Sanic("test_ka_timeout_reuse")
keep_alive_app_client_timeout = Sanic("test_ka_client_timeout") keep_alive_app_client_timeout = Sanic("test_ka_client_timeout")
keep_alive_app_server_timeout = Sanic("test_ka_server_timeout") keep_alive_app_server_timeout = Sanic("test_ka_server_timeout")
keep_alive_timeout_app_reuse.config.update(CONFIG_FOR_TESTS)
keep_alive_app_client_timeout.config.update(CONFIG_FOR_TESTS)
keep_alive_app_server_timeout.config.update(CONFIG_FOR_TESTS)
@keep_alive_timeout_app_reuse.route("/1") @keep_alive_timeout_app_reuse.route("/1")
async def handler1(request): async def handler1(request):

View File

@ -73,6 +73,8 @@ def test_middleware_response_exception(app):
def test_middleware_response_raise_cancelled_error(app, caplog): def test_middleware_response_raise_cancelled_error(app, caplog):
app.config.RESPONSE_TIMEOUT = 1
@app.middleware("response") @app.middleware("response")
async def process_response(request, response): async def process_response(request, response):
raise CancelledError("CancelledError at response middleware") raise CancelledError("CancelledError at response middleware")

View File

@ -3,7 +3,6 @@ from json import JSONDecodeError
from sanic import Sanic from sanic import Sanic
import asyncio import asyncio
from sanic.response import text from sanic.response import text
from sanic.config import Config
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST from sanic.testing import SanicTestClient, HOST
@ -183,9 +182,10 @@ class DelayableSanicTestClient(SanicTestClient):
return response return response
Config.REQUEST_TIMEOUT = 0.6
request_timeout_default_app = Sanic("test_request_timeout_default") request_timeout_default_app = Sanic("test_request_timeout_default")
request_no_timeout_app = Sanic("test_request_no_timeout") request_no_timeout_app = Sanic("test_request_no_timeout")
request_timeout_default_app.config.REQUEST_TIMEOUT = 0.6
request_no_timeout_app.config.REQUEST_TIMEOUT = 0.6
@request_timeout_default_app.route("/1") @request_timeout_default_app.route("/1")

View File

@ -2,13 +2,15 @@ from sanic import Sanic
import asyncio import asyncio
from sanic.response import text from sanic.response import text
from sanic.exceptions import ServiceUnavailable from sanic.exceptions import ServiceUnavailable
from sanic.config import Config
Config.RESPONSE_TIMEOUT = 1
response_timeout_app = Sanic("test_response_timeout") response_timeout_app = Sanic("test_response_timeout")
response_timeout_default_app = Sanic("test_response_timeout_default") response_timeout_default_app = Sanic("test_response_timeout_default")
response_handler_cancelled_app = Sanic("test_response_handler_cancelled") response_handler_cancelled_app = Sanic("test_response_handler_cancelled")
response_timeout_app.config.RESPONSE_TIMEOUT = 1
response_timeout_default_app.config.RESPONSE_TIMEOUT = 1
response_handler_cancelled_app.config.RESPONSE_TIMEOUT = 1
@response_timeout_app.route("/1") @response_timeout_app.route("/1")
async def handler_1(request): async def handler_1(request):

View File

@ -4,7 +4,6 @@ import shlex
import subprocess import subprocess
import urllib.request import urllib.request
from unittest import mock from unittest import mock
from urllib.error import HTTPError
from sanic.worker import GunicornWorker from sanic.worker import GunicornWorker
from sanic.app import Sanic from sanic.app import Sanic
import asyncio import asyncio
@ -26,12 +25,11 @@ def gunicorn_worker():
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def gunicorn_worker_log_level_info(): def gunicorn_worker_with_access_logs():
command = ( command = (
'gunicorn ' 'gunicorn '
'--bind 127.0.0.1:1338 ' '--bind 127.0.0.1:1338 '
'--worker-class sanic.worker.GunicornWorker ' '--worker-class sanic.worker.GunicornWorker '
'--log-level info '
'examples.simple_server:app' 'examples.simple_server:app'
) )
worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
@ -40,12 +38,13 @@ def gunicorn_worker_log_level_info():
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def gunicorn_worker_log_level_warning(): def gunicorn_worker_with_env_var():
command = ( command = (
'env SANIC_ACCESS_LOG="False" '
'gunicorn ' 'gunicorn '
'--bind 127.0.0.1:1339 ' '--bind 127.0.0.1:1339 '
'--worker-class sanic.worker.GunicornWorker ' '--worker-class sanic.worker.GunicornWorker '
'--log-level warning ' '--log-level info '
'examples.simple_server:app' 'examples.simple_server:app'
) )
worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
@ -53,59 +52,28 @@ def gunicorn_worker_log_level_warning():
return worker return worker
@pytest.fixture(scope='module')
def gunicorn_worker_log_level_warning2():
command = (
'gunicorn '
'--bind 127.0.0.1:1340 '
'--worker-class sanic.worker.GunicornWorker '
'--log-level warning '
'examples.exception_monitoring:app'
)
worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
time.sleep(2)
return worker
def test_gunicorn_worker(gunicorn_worker): def test_gunicorn_worker(gunicorn_worker):
with urllib.request.urlopen("http://localhost:1337/") as f: with urllib.request.urlopen("http://localhost:1337/") as f:
res = json.loads(f.read(100).decode()) res = json.loads(f.read(100).decode())
assert res["test"] assert res["test"]
def test_gunicorn_worker_logs_info(gunicorn_worker_log_level_info): def test_gunicorn_worker_no_logs(gunicorn_worker_with_env_var):
""" """
on base of our log-level we get an access message if SANIC_ACCESS_LOG was set to False do not show access logs
"""
with urllib.request.urlopen('http://localhost:1338/') as _:
gunicorn_worker_log_level_info.kill()
assert b"(sanic.access)[INFO][127.0.0.1" in gunicorn_worker_log_level_info.stdout.read()
def test_gunicorn_worker_logs_warning(gunicorn_worker_log_level_warning):
"""
with log-level warning we are not getting an access messages anymore
""" """
with urllib.request.urlopen('http://localhost:1339/') as _: with urllib.request.urlopen('http://localhost:1339/') as _:
gunicorn_worker_log_level_warning.kill() gunicorn_worker_with_env_var.kill()
assert not gunicorn_worker_log_level_warning.stdout.read() assert not gunicorn_worker_with_env_var.stdout.read()
def test_gunicorn_worker_logs_warning_on_error(gunicorn_worker_log_level_warning2): def test_gunicorn_worker_with_logs(gunicorn_worker_with_access_logs):
""" """
with log-level warning we get an error log but don't get an access log default - show access logs
""" """
try: with urllib.request.urlopen('http://localhost:1338/') as _:
url = urllib.request.urlopen('http://localhost:1340/') gunicorn_worker_with_access_logs.kill()
except HTTPError: assert b"(sanic.access)[INFO][127.0.0.1" in gunicorn_worker_with_access_logs.stdout.read()
pass
else:
url.close()
gunicorn_worker_log_level_warning2.kill()
log_message = gunicorn_worker_log_level_warning2.stdout.read()
assert b"(sanic.access)[INFO][127.0.0.1" not in log_message
assert b"[ERROR] Exception occurred while handling uri" in log_message
class GunicornTestWorker(GunicornWorker): class GunicornTestWorker(GunicornWorker):