-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
generic logging options #1572
Comments
How about moving away from individual options all together and just having optional sane defaults, then only allowing the python logging to be configured through |
I think I like what @Code0x58 is saying.
|
If it's really necessary for people who love to 12factor these things and feel a strong need for it, we could treat some environment variable as a JSON dictConfig, if that's reasonable. |
There also appears to be |
By default gunicorn already return the logs on stdout/stderr so it's already "compliant" with twelve-factor. Logging to logs/syslogs/other logger is a convenience offered to the users that want a minimal installation without having to pipe or capture them by using an external tool. What I proposed is exactly about that, but instead of having different options, the command line arguments should be used to choose one or more logging routers with different handlers that can be used for it. We already support the logconfig or dictconfig parameter for those who want to override the default logging. Gunicorn logging is about its own log, the application shouldn't try to override the `gunicorn." handlers and write to stdout or anything using ts own entry point, its own log file, its own syslog config. I can understand anyway that some users want to log at the same place, so maybe we could provide a way to define the name of log endpoint that gunicorn should use for logging inside python? That should be optional though since some including myself are also using gunicorn with apps not in python. |
If I may give a tangible use case: the code bases I work on usually have many different entrypoints. Gunicorn for the API, Celery workers for background tasks, some standalone processes for statefull work and so on. Of course each of these processes need to log things. In my dreams I would configure logging myself once before letting the rest of the code (Gunicorn, Celery, my own code) do its work. At this point I don't want anything to touch the logging configuration I set. (Of course libraries creating log records to their own loggers is completely fine) Doing that is surprisingly difficult.
It would be amazing it there was a way of completely bypassing the configuration that Gunicorn does to the logging module. (Oh and by the way, Gunicorn only reads the kind of deprecated |
I have that exact same situation, after looking at it for too long I subclassed* I am not 100% on this approach yet, but it is better than what I have seen. I or one of my colleagues** may be willing to put in patches to change this, but only if it is released with Python2 support (considering it would likely just be adding things, it could still be in 19.x). I think just disabling gunicorn's logging config all together would be a better option, then it could be configured wherever without concerns of the changes getting squashed. * having your config file named |
actually there was a PR that was adding dictConfig: #1110 but it has been closed. I think we all agree we should support it. Anyway Gunicorn is a server and will continue to provide its own way to use the logs. It can be improved and I would like to discuss more the way we can improve the interface to configure them as suggested in the issue introduction. Maybe we could also have a way to remove any loging by configuring the logger class to |
I suppose you could do something like accumulate I think generic configuration like this is hindered by the current use of the I've been thinking about this recently as it would let the user configure logging entirely through dictConfig/fileConfig (at least because of access log formatting) and also leave the underlying application to decide what non-gunicorn derived logging class to use, helping make gunicorn more transparent. |
As @Code0x58, I also find this topic hard to follow because Gunicorn and Python Logging have very different definitions of what a That being said, we have to live with it, as there is no changing that without breaking things that Gunicorn users rely on.
(quote edited to lift ambiguity) That sounds good to me. Gunicorn would only be responsible for creating its own |
Just wondering if any progress has been made on this? I'm running into similar issues, my use case is that I want to log json formatted logs so I can pass them to something like kibana (or any other log aggregator service). I think this and most of the other stuff discussed in this thread can be solved by allowing a LoggerAdapter, which basically lets you bypass all the crazyness of the logging module formatter-that-can't-really-format. I.e. you set the format to just I can't figure out how to use a LoggerAdapter with gunicorn though - as far as I can figure out, you can't specify it in any of the static log definition formats like ini config file or dict, and it's not a logger class I can give gunicorn the path to, because the constructor of a LoggerAdapter is different than a python Logger or glogger Logger. I can wrap the gunicorn logger via Is this possible? Or does it make sense to add a new option for specifying an already-configured logger object in a python gunicorn config file? |
In case it's useful to anyone else, I did get something working (though an easier & documented way to do this seems like it would still be useful).
|
It is absolutely useful @dcosson. Thank you so much for sharing this. I've been banging my head for 10 full-focus hours trying to unify logging for a Gunicorn/Uvicorn/FastAPI application (and learning how Python logging works). Your solution is... well, the solution! I intercept everything in a loguru sink and every log line is now beautifully formatted 😄 And if I ever need to format the logs as JSON for Kibana or else, I now know how! (edit: loguru has a serialize option that does that just fine!) Thanks again! |
I wrote a short blog post about this at http://pawamoy.github.io/2020/06/02/unify-logging-for-a-gunicorn-uvicorn-app/ I setup everything in a single file: import os
import logging
import sys
from gunicorn.app.base import BaseApplication
from gunicorn.glogging import Logger
from loguru import logger
from my_app.app import app
LOG_LEVEL = logging.getLevelName(os.environ.get("LOG_LEVEL", "DEBUG"))
JSON_LOGS = True if os.environ.get("JSON_LOGS", "0") == "1" else False
WORKERS = int(os.environ.get("GUNICORN_WORKERS", "5"))
class InterceptHandler(logging.Handler):
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
class StubbedGunicornLogger(Logger):
def setup(self, cfg):
handler = logging.NullHandler()
self.error_logger = logging.getLogger("gunicorn.error")
self.error_logger.addHandler(handler)
self.access_logger = logging.getLogger("gunicorn.access")
self.access_logger.addHandler(handler)
self.error_log.setLevel(LOG_LEVEL)
self.access_log.setLevel(LOG_LEVEL)
class StandaloneApplication(BaseApplication):
"""Our Gunicorn application."""
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self):
config = {
key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
for key, value in config.items():
self.cfg.set(key.lower(), value)
def load(self):
return self.application
if __name__ == '__main__':
intercept_handler = InterceptHandler()
# logging.basicConfig(handlers=[intercept_handler], level=LOG_LEVEL)
# logging.root.handlers = [intercept_handler]
logging.root.setLevel(LOG_LEVEL)
seen = set()
for name in [
*logging.root.manager.loggerDict.keys(),
"gunicorn",
"gunicorn.access",
"gunicorn.error",
"uvicorn",
"uvicorn.access",
"uvicorn.error",
]:
if name not in seen:
seen.add(name.split(".")[0])
logging.getLogger(name).handlers = [intercept_handler]
logger.configure(handlers=[{"sink": sys.stdout, "serialize": JSON_LOGS}])
options = {
"bind": "0.0.0.0",
"workers": WORKERS,
"accesslog": "-",
"errorlog": "-",
"worker_class": "uvicorn.workers.UvicornWorker",
"logger_class": StubbedGunicornLogger
}
StandaloneApplication(app, options).run() |
i agree this should be better documented. Can you open a ticket related to it? |
thanks for the blog. It’s useful :) |
no activity since awhile. closing feel free to create a new ticket if needed. |
beside the changein #1528 , I think we could introduce in a later release a more generic logging handling to prevent the addition of more options each time someone wants to introduce its own log handler. Smth like
--access-log=syslog: ....
and so on. Thoughts?The text was updated successfully, but these errors were encountered: