Skip to content

Commit

Permalink
Release 2.0.0b4
Browse files Browse the repository at this point in the history
  • Loading branch information
unknown authored and TheClockTwister committed Mar 14, 2021
2 parents 5835072 + 9db1fdb commit 28a0307
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 54 deletions.
File renamed without changes.
117 changes: 69 additions & 48 deletions Aeros/WebServer.py → aeros/WebServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import logging
import logging.handlers
from logging import INFO
import click
import sys

from .log import AccessLogFormatter, DefaultLogFormatter
from .caching.server import Cache
from .compression import Base
from .Request import EasyRequest
Expand Down Expand Up @@ -52,10 +54,6 @@ def __init__(self, import_name: str, host: str = "0.0.0.0", port: int = 80, incl
self._cache = cache
self._compression = compression

h = logging.StreamHandler(sys.stdout) # make own logging format the same as uvicorn uses
h.setFormatter(logging.Formatter(self.__log_format_std))
self.logger.handlers = [h]

self._global_headers = global_headers
if include_server_header:
self._global_headers['server'] = self._global_headers.get('server', 'Aeros 2.0.0 (uvicorn)') # change server header to Aeros
Expand Down Expand Up @@ -112,37 +110,21 @@ def _get_own_instance_path(self) -> str:
pass
except IndexError:
self.logger.critical(f"{self.__class__.__name__} is unable to find own instance file:name. Launching in single-thread mode!")
return None

def run_server(self,
host: str = None, port: int = None, log_level: int = None, # overrides for __init__
access_log_file: str = None, access_log_to_std: bool = True, traceback: bool = True,
soft_limit: int = 32, hard_limit: int = 42,
**kwargs
) -> None:

def run(self,
host: str = None, port: int = None, log_level: int = None, # overrides for __init__
log_to_std: bool = True, default_log_file: str = None, # default logging config
access_log_to_std: bool = True, access_log_file: str = None, # access log config
traceback: bool = True, # traceback for HTTP-500 exceptions in the quart application
color: bool = True, # CLI color mode
soft_limit: int = 32, hard_limit: int = 42, # uvicorn limits for memory limitations
**kwargs
) -> None:
""" Generates the necessary config and runs the server instance. Possible keyword arguments are the same as supported by the
`uvicorn.run()` method as they are passed into `Config(app, **kwargs)`. This method also configures features like caching
and compression that are not default in Quart or Flask and unique to Aeros or require third-party modules to be configured.
"""

if kwargs.get('suppress_deprecation_warning', False):
del kwargs['suppress_deprecation_warning']
else:
self.logger.warning('The usage of run_server() is deprecated. Use run() instead.') # delete, otherwise it gets into the config

# Initialize extra features just in case the user replaced them with their own instances
if issubclass(self._cache.__class__, Cache):
self._cache.init_app(self)
self.logger.info("Caching is enabled")
else:
self.logger.info("Caching is disabled")

if issubclass(self._compression.__class__, Base):
self._compression.init_app(self)
self.logger.info("Compression is enabled")
else:
self.logger.info("Compression is disabled")

# create uvicorn configuration ------------------------------------------------------------------------------------------------

config = {
Expand All @@ -160,51 +142,90 @@ def run_server(self,
**kwargs
}

# set logging information
# configure logging parameters ------------------------------------------------------------------------------------------------

self.logger.setLevel(log_level if log_level else self.log_level) # re-set log level for instance logger if changed

h = logging.StreamHandler(sys.stdout) # make own logging format the same as uvicorn uses
h.setFormatter(DefaultLogFormatter(color=color))
self.logger.handlers = [h]

if log_to_std:
print() # print empty line (makes reading the first CLI line easier)
msg = click.style('aeros.readthedocs.io', underline=True, fg='bright_blue') if color else 'aeros.readthedocs.io'
self.logger.info(f"Documentation & common fixes at {msg}.")

config['log_config'] = { # logging configuration for uvicorn.run()
'version': 1, 'disable_existing_loggers': True,
'formatters': {
'default': {'()': 'logging.Formatter', 'fmt': self.__log_format_std},
'access_file': {'()': 'uvicorn.logging.AccessFormatter', 'fmt': '[%(asctime)s] %(client_addr)s | "%(request_line)s" | %(status_code)s'},
'access_std': {'()': 'uvicorn.logging.AccessFormatter', 'fmt': '[%(asctime)s] ACCESS: %(client_addr)s | "%(request_line)s" | %(status_code)s'}
'default': {'()': DefaultLogFormatter, 'color': color}, # stdout output for generic logs
'default_file': {'()': DefaultLogFormatter, 'color': False}, # logfile for generic logs
'access': {'()': AccessLogFormatter, 'color': color}, # stdout output for access logs
'access_file': {'()': AccessLogFormatter, 'color': False} # logfile for access logs
},
'handlers': {
'error': {'formatter': 'default', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stderr'},
'default': {'formatter': 'default', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout'},
'error': {'formatter': 'default', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stderr'}
'access': {'formatter': 'access', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout'}
},
'loggers': {
'uvicorn': {'level': log_level if log_level else INFO, 'handlers': ['default']},
'uvicorn': {'level': log_level if log_level else INFO, 'handlers': []},
'uvicorn.access': {'level': 'DEBUG', 'propagate': False, 'handlers': []}
}
}

if traceback:
config['log_config']['loggers']['quart'] = {'handlers': ['error'], 'level': 'INFO'}
if access_log_to_std:
config['log_config']['handlers']['access_std'] = {'formatter': 'access_std', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout'}
config['log_config']['loggers']['uvicorn.access']['handlers'].append('access_std')
config['log_config']['loggers']['quart.app'] = {'level': 'ERROR', 'handlers': ['error']}

# these need to be first!
if default_log_file:
config['log_config']['handlers']['default_file'] = {'formatter': 'default_file', 'class': 'logging.FileHandler', 'filename': default_log_file}
config['log_config']['loggers']['uvicorn']['handlers'].append('default_file')
if access_log_file:
config['log_config']['handlers']['access_file'] = {'formatter': 'access_file', 'class': 'logging.FileHandler', 'filename': access_log_file}
config['log_config']['loggers']['uvicorn.access']['handlers'].append('access_file')

# try multi-core execution
if config.get('workers', None):
# these need to be after!
if log_to_std:
config['log_config']['loggers']['uvicorn']['handlers'].append('default')
if access_log_to_std:
config['log_config']['loggers']['uvicorn.access']['handlers'].append('access')

# initialize extra features ---------------------------------------------------------------------------------------------------

if issubclass(self._cache.__class__, Cache):
self._cache.init_app(self)
if log_to_std:
self.logger.info("Caching is enabled")
else:
if log_to_std:
self.logger.info("Caching is disabled")

if issubclass(self._compression.__class__, Base):
self._compression.init_app(self)
if log_to_std:
self.logger.info("Compression is enabled")
else:
if log_to_std:
self.logger.info("Compression is disabled")

# configure uvicorn execution parameters --------------------------------------------------------------------------------------

if config.get('workers', None): # try multi-core execution
self.logger.debug(f"{self.__class__.__name__}.run_server() multi-core execution is a beta feature. You can also use uvicorn CLI directly.")
instance_path = self._get_own_instance_path()
if instance_path is not None:
# if instance found, run multi-core by replacing instance with path
config['app'] = instance_path
config['app'] = instance_path # if instance found, run multi-core by replacing instance with path
self.logger.info(f"Starting in multi-thread mode...")
else:
# if instance not found, delete worker count and launch single-thread
del config['workers']
del config['workers'] # if instance not found, delete worker count and launch single-thread
self.logger.info(f"Starting in single-thread mode...")

uvicorn.run(**config)

def run(self, *args, **kwargs):
return self.run_server(*args, **kwargs, suppress_deprecation_warning=True)
def run_server(self, *args, **kwargs):
self.logger.warning('The usage of run_server() is deprecated. Use run() instead.') # delete, otherwise it gets into the config
return self.run(*args, **kwargs)

def route(self, *args, **kwargs):
def new_route_decorator(func):
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion Aeros/compression.py → aeros/compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def init_app(self, app: Quart):
app.config["COMPRESS_MIN_SIZE"] = self.min_size
app.config["COMPRESS_LEVEL"] = self.level
app.config["COMPRESS_MIMETYPES"] = self.mimetypes
print("Algorithm is", self.algorithm)
self.compressor.init_app(app)


Expand Down
52 changes: 52 additions & 0 deletions aeros/log/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from uvicorn.logging import AccessFormatter, DefaultFormatter
from colorama import init
import logging
import click

init()


def colorize_record(record: logging.LogRecord):
rules = {
"debug": "bright_black",
"info": "bright_blue",
"warning": "yellow",
"error": "red",
"critical": "red",
"access": 'bright_black'
}
levelname = record.levelname.lower()
if levelname in rules.keys():
record.levelname = click.style(levelname.upper(), bg=rules[levelname], fg='black')

record.asctime = click.style(f'[{record.asctime}]', fg='bright_black')
return record


class DefaultLogFormatter(DefaultFormatter):
def __init__(self, color: bool = True):
super().__init__(
fmt='%(asctime)s %(levelname)s: %(message)s',
use_colors=color
)

def formatMessage(self, record):
return DefaultFormatter.formatMessage(
self,
colorize_record(record) if self.use_colors else record
)


class AccessLogFormatter(AccessFormatter):
def __init__(self, color: bool = True):
super().__init__(
fmt='%(asctime)s %(levelname)s: %(client_addr)s | "%(request_line)s" | %(status_code)s',
use_colors=color
)

def formatMessage(self, record):
record.levelname = 'ACCESS'
return AccessFormatter.formatMessage(
self,
colorize_record(record) if self.use_colors else record
)
File renamed without changes.
File renamed without changes.
11 changes: 7 additions & 4 deletions docs/content/general/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ as described in the following example code snippet:
return "Hi, I am your new backend :)"
app.run(
log_level=DEBUG, # set your desired logging level
access_log_to_std=True, # Set False if you don't want the access log in stdout
access_log_file='access.log', # Specify if you want to log access to a file
traceback=True # Set False if you do not want tracebacks written to stderr
log_level=logging.DEBUG, # set logging level (default: INFO)
log_to_std=False, # print logs to console (default: True)
access_log_to_std=False, # print access logs to console (default: True)
default_log_file='log.log', # if specified, write server logs to file
access_log_file='access.log', # if specified, write access logs to file
traceback=False, # log tracebacks if exceptions occur (default: True)
color=True # colored terminal output (default: True) (doesn't affect files)
)
.. admonition:: Available since version 2.0.0
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ quart~=0.14.1
quart_compress~=0.2.1
flask_caching~=1.10.0
colorama~=0.4.4
click~=7.1.2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setuptools.setup(
name="Aeros",
version="2.0.0b3",
version="2.0.0b4",
author="TheClockTwister",
description="High-performance ASGI framework",
long_description=long_description,
Expand Down

0 comments on commit 28a0307

Please sign in to comment.