-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
python: allow to override the UvicornServer's default log config #2782
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,10 @@ | |
help="Enable docs url '/docs' to display Swagger UI.") | ||
parser.add_argument("--enable_latency_logging", default=True, type=lambda x: bool(strtobool(x)), | ||
help="Output a log per request with latency metrics.") | ||
parser.add_argument("--log_config_file", default=None, type=str, | ||
help="File path containing UvicornServer's log config. Needs to be a yaml or json file.") | ||
parser.add_argument("--access_log_format", default=None, type=str, | ||
help="Format to set for the access log (provided by asgi-logger).") | ||
|
||
args, _ = parser.parse_known_args() | ||
|
||
|
@@ -76,6 +80,8 @@ class ModelServer: | |
enable_grpc (bool): Whether to turn on grpc server. Default: ``True`` | ||
enable_docs_url (bool): Whether to turn on ``/docs`` Swagger UI. Default: ``False``. | ||
enable_latency_logging (bool): Whether to log latency metric. Default: ``True``. | ||
log_config_file (dict): File path containing UvicornServer's log config. Default: ``None``. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added :) |
||
access_log_format (string): Format to set for the access log (provided by asgi-logger). Default: ``None`` | ||
""" | ||
|
||
def __init__(self, http_port: int = args.http_port, | ||
|
@@ -86,7 +92,9 @@ def __init__(self, http_port: int = args.http_port, | |
registered_models: ModelRepository = ModelRepository(), | ||
enable_grpc: bool = args.enable_grpc, | ||
enable_docs_url: bool = args.enable_docs_url, | ||
enable_latency_logging: bool = args.enable_latency_logging): | ||
enable_latency_logging: bool = args.enable_latency_logging, | ||
log_config_file: str = args.log_config_file, | ||
access_log_format: str = args.access_log_format): | ||
self.registered_models = registered_models | ||
self.http_port = http_port | ||
self.grpc_port = grpc_port | ||
|
@@ -100,6 +108,8 @@ def __init__(self, http_port: int = args.http_port, | |
self.model_repository_extension = ModelRepositoryExtension( | ||
model_registry=self.registered_models) | ||
self._grpc_server = GRPCServer(grpc_port, self.dataplane, self.model_repository_extension) | ||
self.log_config_file = log_config_file | ||
self.access_log_format = access_log_format | ||
|
||
def start(self, models: Union[List[Model], Dict[str, Deployment]]) -> None: | ||
if isinstance(models, list): | ||
|
@@ -143,7 +153,9 @@ async def serve(): | |
sig, lambda s=sig: asyncio.create_task(self.stop(sig=s)) | ||
) | ||
self._rest_server = UvicornServer(self.http_port, [serversocket], | ||
self.dataplane, self.model_repository_extension, self.enable_docs_url) | ||
self.dataplane, self.model_repository_extension, | ||
self.enable_docs_url, log_config_file=self.log_config_file, | ||
access_log_format=self.access_log_format) | ||
if self.workers == 1: | ||
await self._rest_server.run() | ||
else: | ||
|
@@ -153,7 +165,8 @@ async def serve(): | |
# https://github.com/tiangolo/fastapi/issues/1586 | ||
multiprocessing.set_start_method('fork') | ||
server = UvicornServer(self.http_port, [serversocket], | ||
self.dataplane, self.model_repository_extension, self.enable_docs_url) | ||
self.dataplane, self.model_repository_extension, | ||
self.enable_docs_url, custom_log_config=self.log_config) | ||
for _ in range(self.workers): | ||
p = Process(target=server.run_sync) | ||
p.start() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,7 +126,8 @@ def create_application(self) -> FastAPI: | |
|
||
class UvicornServer: | ||
def __init__(self, http_port: int, sockets: List[socket.socket], | ||
data_plane: DataPlane, model_repository_extension, enable_docs_url): | ||
data_plane: DataPlane, model_repository_extension, enable_docs_url, | ||
log_config_file: str = None, access_log_format: str = None): | ||
super().__init__() | ||
self.sockets = sockets | ||
rest_server = RESTServer(data_plane, model_repository_extension, enable_docs_url) | ||
|
@@ -136,46 +137,71 @@ def __init__(self, http_port: int, sockets: List[socket.socket], | |
client=PrintTimings(), | ||
metric_namer=StarletteScopeToName(prefix="kserve.io", starlette_app=app) | ||
) | ||
log_config = { | ||
"version": 1, | ||
"formatters": { | ||
"default": { | ||
"()": "uvicorn.logging.DefaultFormatter", | ||
"datefmt": DATE_FMT, | ||
"fmt": "%(asctime)s.%(msecs)03d %(name)s %(levelprefix)s %(message)s", | ||
"use_colors": None, | ||
}, | ||
"access": { | ||
"()": "uvicorn.logging.AccessFormatter", | ||
"datefmt": DATE_FMT, | ||
"fmt": '%(asctime)s.%(msecs)03d %(name)s %(levelprefix)s %(client_addr)s %(process)s - ' | ||
'"%(request_line)s" %(status_code)s', | ||
# noqa: E501 | ||
}, | ||
}, | ||
"handlers": { | ||
"default": { | ||
"formatter": "default", | ||
"class": "logging.StreamHandler", | ||
"stream": "ext://sys.stderr", | ||
}, | ||
"access": { | ||
"formatter": "access", | ||
"class": "logging.StreamHandler", | ||
"stream": "ext://sys.stdout", | ||
}, | ||
}, | ||
"loggers": { | ||
"uvicorn": {"handlers": ["default"], "level": "INFO"}, | ||
"uvicorn.error": {"level": "INFO"}, | ||
"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, | ||
}, | ||
} | ||
|
||
# If the log_config value is a string ending up with ".json" | ||
# or ".yaml", it is interpreted as file path and the log configuration | ||
# is loaded from disk. | ||
if log_config_file: | ||
log_config = log_config_file | ||
|
||
self.cfg = uvicorn.Config( | ||
app=app, | ||
host="0.0.0.0", | ||
log_config=log_config, | ||
port=http_port, | ||
log_config={ | ||
"version": 1, | ||
"formatters": { | ||
"default": { | ||
"()": "uvicorn.logging.DefaultFormatter", | ||
"datefmt": DATE_FMT, | ||
"fmt": "%(asctime)s.%(msecs)03d %(name)s %(levelprefix)s %(message)s", | ||
"use_colors": None, | ||
}, | ||
"access": { | ||
"()": "uvicorn.logging.AccessFormatter", | ||
"datefmt": DATE_FMT, | ||
"fmt": '%(asctime)s.%(msecs)03d %(name)s %(levelprefix)s %(client_addr)s %(process)s - ' | ||
'"%(request_line)s" %(status_code)s', | ||
# noqa: E501 | ||
}, | ||
}, | ||
"rest": { | ||
"default": { | ||
"formatter": "default", | ||
"class": "logging.StreamHandler", | ||
"stream": "ext://sys.stderr", | ||
}, | ||
"access": { | ||
"formatter": "access", | ||
"class": "logging.StreamHandler", | ||
"stream": "ext://sys.stdout", | ||
}, | ||
}, | ||
"loggers": { | ||
"uvicorn": {"rest": ["default"], "level": "INFO"}, | ||
"uvicorn.error": {"level": "INFO"}, | ||
"uvicorn.access": {"rest": ["access"], "level": "INFO", "propagate": False}, | ||
}, | ||
} | ||
) | ||
|
||
# More context in https://github.com/encode/uvicorn/pull/947 | ||
# At the time of writing the ASGI specs are not clear when it comes | ||
# to change the access log format, and hence the Uvicorn upstream devs | ||
# chose to create a custom middleware for this. | ||
# The allowed log format is specified in https://github.com/Kludex/asgi-logger#usage | ||
if access_log_format: | ||
from asgi_logger import AccessLoggerMiddleware | ||
# As indicated by the asgi-logger docs, we need to clear/unset | ||
# any setting for uvicorn.access to avoid log duplicates. | ||
logging.getLogger("uvicorn.access").handlers = [] | ||
app.add_middleware( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How this is different from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding from
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea of the patch is to have several type of access logs:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @elukey thanks for the explanation. We also have starlette metrics logging for printing the time spent on the endpoints, do you think we still need it with the uvicorn access logging? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yuzisun I'd keep it for the moment, so we can figure out with the community what's needed and what not. I didn't check but starlette shouldn't have its own access log right? Everything should go through uvicorn, but the precise relationship between the two is not 100% clear to me (if you want to shed some clarity I'd be very happy). If you want we can create a documentation page somewhere with all the details discussed in here, so folks in the community can refer to it and report use cases etc.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And also third option, custom config file (note the
and logs:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Almost! Sigh :(
In theory it shouldn't happen, maybe it is due how to asgi-logger sets the logger + propagation. Will investigate :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok should be fixed now, I added an extra setting for asgi-logger (to avoid propagation). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Asked in Kludex/asgi-logger#6 some guidance about what I have written, maybe there is another/better way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yuzisun the change should be ready for a review, I checked and the output looks good now (no more repetitions). |
||
AccessLoggerMiddleware, format=access_log_format) | ||
# The asgi-logger settings don't set propagate to False, | ||
# so we get duplicates if we don't set it explicitly. | ||
logging.getLogger("access").propagate = False | ||
|
||
self.server = _NoSignalUvicornServer(config=self.cfg) | ||
|
||
def run_sync(self): | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elukey Do you have an example how to pass this on the inference service yaml? I guess you need to mount this log file as ConfigMap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yuzisun Yes exactly something similar, it just need a file to be present on the pod, it shouldn't be too difficult. I can try to come up with an example, lemme know if it is needed :)