posts/unify-logging-for-a-gunicorn-uvicorn-app/ #36
Replies: 45 comments 5 replies
-
Very useful write up. I am using pretty much your set up to generate a consistent looking logs. Thank you. Couple of comments. 'error_log' should be 'error_logger' and the 'LOG_LEVEL' will stick.
And we can have different levels (APP_LOG_LEVEL & GUNICORN_LOG_LEVEL) in order to get better control... say 'debug' for app vs 'info' for gunicorn. First we comment out (in 'main')
just so we do not get unwanted extra logs from 3rd party packages...
|
Beta Was this translation helpful? Give feedback.
-
Hi @ashokc, thanks!
Ha, thank you very much!
Of course. In my case I did want the extra logs from all 3rd party packages 😄 |
Beta Was this translation helpful? Give feedback.
-
Your blogpost helped me a lot, thanks. Though it's interesting that for me the Uvicorn handlers all show up in [tool.poetry.dependencies]
python = "^3.9"
loguru = "^0.5.3"
prettyprinter = "^0.18.0"
fastapi = "^0.61.1"
requests = "^2.24.0"
dynaconf = {extras = ["yaml"], version = "^3.1.2"}
uvicorn = "^0.12.2" |
Beta Was this translation helpful? Give feedback.
-
*And I don't use Gunicorn workers |
Beta Was this translation helpful? Give feedback.
-
Oh, uvicorn 0.12.2, the latest version, I guess they have fixed their logging config so the loggers are children of the root one (I was following such an issue on their GitHub repo). Also yeah, I got rid of gunicorn myself, and the config is much lighter then 🙂 I think I'll update my post to reflect this. Maybe I'll also add the version without gunicorn! Anyway, glad it helped you! |
Beta Was this translation helpful? Give feedback.
-
Btw what did the trick for me in the end was a combination of setting the interception handler from your blog post as the only Händler in logging.basicConfig and passing / overriding the default LOGGING_CONFIG Uvicorn is using with a dict that contains all the loggers Uvicorn relies on (uvicorn.error and so on) while removing the handlers from it. So it's logs get propagated up to the root logger and the interception handler. Without the second part everything by Uvicorn was logged twice for me (once in Uvicorn default format and then again through the interception handler) even after looping through all loggers and setting the handlers to the interception handler like in your blog post. If of interest, here are the two relevant py files: https://github.com/trallnag/prometheus-webhook-proxy/blob/master/prometheus_webhook_proxy/main.py |
Beta Was this translation helpful? Give feedback.
-
To support more than 2 levels of nesting for loggers, I defined the following:
and changed the handler setting to this:
Note that |
Beta Was this translation helpful? Give feedback.
-
Thanks for sharing your solution @syk0saje! To everyone: I've added a Uvicorn-only version to the post. |
Beta Was this translation helpful? Give feedback.
-
Tiny bit optimisation in while loop use substring for comparison[11:15] than using whole string. |
Beta Was this translation helpful? Give feedback.
-
Hi @ketanpatil3, thanks! I'm not sure to get it though. Do you mean the while loop can be removed in favor of your snippet? In any case, you might want to chat with @Delgan as this code comes directly from his loguru project's documentation 🙂 |
Beta Was this translation helpful? Give feedback.
-
Tiny change in the code. You don't have to compare the whole string. Tiny portion is enough:
```python
substr = logging.__file__[11:15]
while frame.f_code.co_filename[11:15] == substr
```
|
Beta Was this translation helpful? Give feedback.
-
Arf, wanted to edit your comment to properly format the code, but email replies do not support MarkDown... sorry 😅 |
Beta Was this translation helpful? Give feedback.
-
Hi, Do you know how to contextualize information?
extra = {'app':'hello','name':'myname'}
logger.configure(extra=extra) #using loguru logger
mylog = logging.getLogger("gunicorn.error")
mylog.info('testing')
mylog.error('testing error')
I get the below output
2021-02-06 21:25:03.086 | INFO | __main__:run_app:125 - testing
2021-02-06 21:25:03.086 | ERROR | __main__:run_app:126 - testing error
How to make it extra information in the output? I need to change the format
for the above and add {extra}
…On Sat, Feb 6, 2021 at 6:22 PM Timothée Mazzucotelli < ***@***.***> wrote:
Arf, wanted to edit your comment to properly format the code, but email
replies do not support MarkDown... sorry 😅
And thanks for the explanation.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#17 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AEH6WJ2JKZ2KBZWJQXFBXKDS5U3PBANCNFSM4RKYCJQQ>
.
|
Beta Was this translation helpful? Give feedback.
-
Hi @pawamoy and @ketanpatil3. :) I'm not sure of what the It's safer to compare the whole path rather than trying to optimize comparison with a substring, because others unrelated files may actually have the same name, technically. Also, I think the slicing operation may actually be slower than comparing the whole string. |
Beta Was this translation helpful? Give feedback.
-
I was thinking the same thing when trying to understand @ketanpatil3's suggestion. Thanks for chiming in @Delgan 🙂 @ketanpatil3 about your question on how to contextualize information, you'll have better luck asking on loguru's issue tracker, I'm not an expert here 🙂 @Delgan do you have a gitter or something for your project? Maybe you could enable discussions as well. |
Beta Was this translation helpful? Give feedback.
-
@pawamoy you mention that you got rid of gunicorn. But in Uvicorn Deployment they say: |
Beta Was this translation helpful? Give feedback.
-
At work we are deploying our apps in a Kubernetes cluster. The scaling is done using pods, therefore I only need 1 uvicorn worker per pod/container. If you are deploying your app on a bare server, then gunicorn might help, but it is not required. |
Beta Was this translation helpful? Give feedback.
-
This code works for me for both Uvicorn(0.15) and Gunicorn(20.1)+Uvicorn. def init(lvl: str="INFO"):
logger.configure(handlers=[
{"sink": sys.stderr, "format": log_format, "level": lvl}
])
intercept_handler = InterceptHandler()
# level=NOTSET - loguru controls actual level
logging.basicConfig(handlers=[intercept_handler], level=logging.NOTSET)
for name in logging.root.manager.loggerDict.keys():
_logger = logging.getLogger(name)
if _logger.name.startswith("gunicorn"):
_logger.handlers = [intercept_handler]
else:
# By default uvicorn.access has a handler and doesn't propagate
# (uvicorn.access controls INFO messages on requests)
_logger.propagate = True
_logger.handlers = [] But it doesn't catch exceptions during main module loading. I have to use |
Beta Was this translation helpful? Give feedback.
-
I'm using the Uvicorn option only. This is a stupid question, but how do I actually log? |
Beta Was this translation helpful? Give feedback.
-
You can log things with standard logging. https://docs.python.org/3/howto/logging.html#logging-basic-tutorial In your library code: import logging
logger = logging.getLogger(__name__)
logger.debug(msg)
logger.info(msg)
logger.warning(msg)
logger.error(msg)
logger.critical(msg) Only, ONLY in the command line entry point (the highest possible): import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
# more logging configuration You can also log things with third-party libs, like loguru: https://loguru.readthedocs.io/en/stable/overview.html#suitable-for-scripts-and-libraries from loguru import logger
logger.debug(msg)
logger.info(msg)
logger.warning(msg)
logger.error(msg)
logger.critical(msg) |
Beta Was this translation helpful? Give feedback.
-
Hey everyone, I was having a lot of problems logging properly with gunicorn + uvicorn workers but this guide helped me out a ton. One problem I'm having currently is that after changing the errorlog and accesslog to direct to logfiles, the logs aren't being written to the logfiles, they just show up in the terminal. I don't mind having the logs in the terminal but having them stored in a logfile would be nice. Anyone have any solutions? I followed the guide above exactly (for gunicorn+uvicorn) |
Beta Was this translation helpful? Give feedback.
-
Nevermind, figured it out. Leaving it here for anyone else who faces the same issue. You must change the line |
Beta Was this translation helpful? Give feedback.
-
I figured out that I can combine your two solutions for class GunicornLogger(Logger):
def setup(self, cfg) -> None:
handler = InterceptHandler()
# Add log handler to logger and set log level
self.error_log.addHandler(handler)
self.error_log.setLevel(settings.LOG_LEVEL)
self.access_log.addHandler(handler)
self.access_log.setLevel(settings.LOG_LEVEL)
# Configure logger before gunicorn starts logging
logger.configure(handlers=[{"sink": sys.stdout, "level": settings.LOG_LEVEL}])
def configure_logger() -> None:
logging.root.handlers = [InterceptHandler()]
logging.root.setLevel(settings.LOG_LEVEL)
# Remove all log handlers and propagate to root logger
for name in logging.root.manager.loggerDict.keys():
logging.getLogger(name).handlers = []
logging.getLogger(name).propagate = True
# Configure logger (again) if gunicorn is not used
logger.configure(handlers=[{"sink": sys.stdout, "level": settings.LOG_LEVEL}]) Call configure_logger()
app = FastAPI() And use following command line to start it up: # gunicorn
gunicorn some.package.main:app -k uvicorn.workers.UvicornWorker --logger-class some.package.logger.GunicornLogger
# uvicorn
uvicorn some.package.main:app Thanks! |
Beta Was this translation helpful? Give feedback.
-
Nice, thanks for sharing! |
Beta Was this translation helpful? Give feedback.
-
🤔my example above worked for me without creating additional class and passing it to |
Beta Was this translation helpful? Give feedback.
-
Hi! I'm using the uvicorn-only version and I have a problem with reload. Any idea on how to make reload work? Thanks! |
Beta Was this translation helpful? Give feedback.
-
there is a related bug here -
benoitc/gunicorn#2339
…On Mon, 4 Jul 2022 at 12:04, Timothée Mazzucotelli ***@***.***> wrote:
I actually have the same problem 😕
I didn't get the time to investigate yet. Maybe an issue should be open on
unicorn's bugtracker (if there's not already one)
—
Reply to this email directly, view it on GitHub
<#36 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAASYUYS3CGT2WXITJUA2HDVSKAZDANCNFSM52RQLQGA>
.
You are receiving this because you commented.Message ID: <pawamoy/pawamoy.
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Thanks! I think you should also set the loguru log level explicitly so it honors the env var: # configure loguru
logger.configure(
handlers=[{"sink": sys.stdout, "serialize": JSON_LOGS, "level": LOG_LEVEL}]
) |
Beta Was this translation helpful? Give feedback.
-
If you're getting |
Beta Was this translation helpful? Give feedback.
-
This is my setup: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Unify Python logging for a Gunicorn/Uvicorn/FastAPI application - pawamoy's website
Findings, thoughts, tutorials, work. Pieces of my mind!
https://pawamoy.github.io/posts/unify-logging-for-a-gunicorn-uvicorn-app/
Beta Was this translation helpful? Give feedback.
All reactions