-
-
Notifications
You must be signed in to change notification settings - Fork 225
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
Add ProcessorFormatter #105
Conversation
Based on the work of @fabianbuechler, @insolite, and @if-fi. Fixes #79
Codecov Report
@@ Coverage Diff @@
## master #105 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 13 13
Lines 754 774 +20
Branches 94 96 +2
=====================================
+ Hits 754 774 +20
Continue to review full report at Codecov.
|
@hynek Yes, that is exactly the thing I was thinking of. The idea with ProcessorFormatter: class ProcessorFormatter(logging.Formatter):
def __init__(self, processors=None, *args, **kwargs):
fmt = kwargs.pop("fmt", "%(message)s")
super(ProcessorFormatter, self).__init__(*args, fmt=fmt, **kwargs)
self.processors = processors
def format(self, record):
"""
Extract ``structlog``'s `event_dict` from ``record.msg`` and format it.
"""
if isinstance(record.msg, dict):
meth_name = record._name
# We need to copy because it's possible that the same record gets
# processed by multiple logging formatters.
ed = record.msg.copy()
else:
if self.processors is None:
# Let logging format the message.
return super(ProcessorFormatter, self).format(record)
# Non-structlog allows to run through a chain to prepare it for the
# final processor (e.g. adding timestamps and log levels).
ed = {"event": record.getMessage()}
meth_name = record.levelname.lower()
for proc in self.processors:
ed = proc(None, meth_name, ed)
return ed Config: logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'plain': {
'()': structlog.stdlib.ProcessorFormatter,
'processors': (structlog.dev.ConsoleRenderer(colors=False),),
},
'colored': {
'()': structlog.stdlib.ProcessorFormatter,
'processors': (structlog.dev.ConsoleRenderer(colors=True),),
},
},
'handlers': {
'default': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'colored',
},
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.WatchedFileHandler',
'filename': 'test.log',
'formatter': 'plain',
},
},
'loggers': {
'': {
'handlers': ['default', 'file'],
'level': 'DEBUG',
'propagate': True,
},
}
})
structlog.configure(
processors=[
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt='%Y-%m-%d %H:%M:%S'),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
) I did some simple tests locally, but I could miss something. If it's ok, I can prepare a PR soon if you want. |
@hynek works like a charm! Thank you! My use case is the following: |
@insolite I think your approach doesn’t fix my problem (and the problem ten people complain each week :)) though: the point of Or am I missing something here? Just came back from a vacation, so my memory may be clouded. :) |
@hynek I have tried logging with the following configuration 'plain_struct': {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.dev.ConsoleRenderer(colors=False),
"foreign_pre_chain": pre_chain,
'format': '\n%(message)s [in %(pathname)s:%(lineno)d]',
}, but for me the If I use |
src/structlog/stdlib.py
Outdated
for proc in self.foreign_pre_chain: | ||
ed = proc(None, meth_name, ed) | ||
|
||
return self.processor(logger, meth_name, ed) |
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.
You are not actually formatting the message anywhere in this method.
So if you have a logging configuration like 'format': u'\n%(message)s [in %(pathname)s:%(lineno)d]',
you are not respecting that. And the end format is always the plain message only.
@hynek I left you a comment in the code where I think the problem occurs. class ProcessorFormatterFix(ProcessorFormatter):
def format(self, record):
record.msg = super(ProcessorFormatterFix, self).format(record)
return super(ProcessorFormatter, self).format(record) |
Good catch! Do you think formatting should be unconditional like you do? Because I’d tend to only do that for the stdlib-case. E.g. replace |
ping @if-fi (sorry, I’m not comfortable to make decisions w/o double checking with competent people here :)) |
Hey @hynek, sorry for missing your previous comment. In my current usecase, I do the additional formatting for the structlog messages themselves. I could not find a processor that adds Moreover, the other processors respect the external formatting configuration, so it will be a great inconsistency if |
Hey @if-fi, how about now? Happy Easter everyone 🐥🙈. Thank you for indulging me, we can do it. :) |
Happy easter :) |
Thank you so much! Looking forward to your feedback! |
@hynek now the formatting works fine :) Could this be caused by structlog? |
Uhhhh the actual output is done by |
I can't think of a way how this can be caused by structlog as well... |
FYI, 17.1 is on PyPI. Have fun with it and thanks once more everyone! |
Thank you, @hynek! |
First off, I’m terribly sorry I didn’t get around it any sooner. However given how long it took me to dive back into
logging
, I guess my procrastination was warranted. :(Anyhow, @insolite, and @if-fi I’d like your feedback on what I’ve molded based on your PR and comments. Does this solve your problem? I’ve tried to make it more useful by adding the possibility to run stdlib entries through a separate chain, does that make sense to you?
I hope we’ll be able to close these PRs/issues soon.
Again: sorry.