-
-
Notifications
You must be signed in to change notification settings - Fork 223
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
[Feature-Request] Allow copying attrs from stdlib LogRecord into structlog's msg #253
Comments
Another stupid question, I tried digging through git history but still confused structlog/src/structlog/stdlib.py Lines 495 to 510 in 36eeb18
is the |
Is this related to #209? |
@hynek Not really, 209 wants to support the My request is to be able to copy some of these attributes https://github.com/python/cpython/blob/950c6795aa0ffa85e103a13e7a04e08cb34c66ad/Lib/logging/__init__.py#L290-L354 that stdlib's formatter uses on the format template given to it. Structlog makes a My use case is to be able to add module and line number to logs, for which I wrote a processor from typing import Optional, Dict, Any
import structlog
def add_module_and_lineno(logger: structlog.BoundLogger, name: str, event_dict: Dict[str, Any]) -> Dict[str, Any]:
# noinspection PyProtectedMember
frame, module_str = structlog._frames._find_first_app_frame_and_name(additional_ignores=[__name__])
# frame has filename, caller and line number
event_dict['module'] = module_str
event_dict['lineno'] = frame.f_lineno
return event_dict I was going to submit a PR to make this part of structlog's stdlib but then I thought about customizability, then did some code reading and realised structlog already has all relevant attributes already computed via stdlib's LogRecord init |
So it's loosely related to #246? I'm sorry but the stdlib code is a corner that I don't know that good myself and have to work myself into it every single time again. Generally speaking I'm happy to try to preserve as much data as possible, as long as it fits the overall design. |
I saw the PR you mentioned, but that seems it will only work for logs emitted from non-structlog Logger instances, that too only if that function is connected via The processor, I posted above, is a structlog processor and works only for logs emitted using On some further thinking, I think a better design would be to give a class ProcessorFormatter(logging.Formatter):
def __init__(self, ..., pre_render_hook: Optional[Callable] = None):
...
self.pre_render_hook = pre_render_hook
def format(self, record):
record = logging.makeLogRecord(record.__dict__)
...
if self.pre_render_hook is not None:
ed = self.pre_render_hook(record=record, event_dict=ed)
record.msg = self.processor(logger, meth_name, ed)
return super(ProcessorFormatter, self).format(record) Then I can easily do def add_module_and_lineno(record: LogRecord, event_dict: Dict[str, Any]) -> Dict[str, Any]:
event_dict['module'] = record.module
event_dict['lineno'] = record.lineno
return event_dict
LOGGING = {
'formatters': {
'json_formatter': {
'()': structlog.stdlib.ProcessorFormatter,
'processor': structlog.processors.JSONRenderer(),
'pre_render_hook': add_module_and_lineno,
},
}
} Well, this can also be achieved anywhere, even in the renderer (self.processor) if it can get access to the record.msg = self.processor(logger, meth_name, ed, record) I can easily inherit from I am not sure what design would work best here, thoughts on this? |
I'd also really like to exploit information included in Where/when in the processor chain does the LogRecord first becomes available? I've been trying to use def process_log_record(logger, method_name, event_dict, *args, **kwargs):
record: logging.LogRecord = event_dict.get("_record")
if record:
event_dict = dict(event_dict, **{
"module_name": record.module,
"function_name": record.funcName,
"line_number": record.lineno,
"process_name": record.processName,
"process_id": record.process,
"file_name": record.pathname,
"thread_name": record.threadName,
"thread_id": record.thread,
})
return event_dict
def configure():
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
process_log_record,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=LoggerClass
)
# formatter = structlog.stdlib.ProcessorFormatter(
# processor=structlog.dev.ConsoleRenderer
# )
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"dev": {
"()": structlog.stdlib.ProcessorFormatter,
"processor": structlog.dev.ConsoleRenderer(),
"foreign_pre_chain": [process_log_record]
}
},
"handlers": {
"default": {
"()": logging.StreamHandler,
"level": "DEBUG",
"formatter": "dev"
}
},
"loggers": {
"": {
"handlers": ["default"],
"level": "DEBUG",
"propagate": True
}
}
})
configure()
structlog.get_logger().log("Test") |
The first logging calls seemed to be handled by a |
@DrPyser structlog/src/structlog/stdlib.py Line 514 in db1e529
structlog/src/structlog/stdlib.py Lines 537 to 538 in db1e529
You don't get access to structlog/src/structlog/_base.py Lines 186 to 187 in db1e529
Hence, the feature request :) Test your example with a logging.get_logger().log("Test") |
Would absolutely love to have this feature.
But i would love to skip the manual extraction of these parameters and rely on std logging already doing the job. |
Well, TBH we'll probably add just something like that to structlog. :) The question here is how much control do we need to give the user? Is it OK to always dump all info into the context? I'd rather not have huge if-then-else trees. |
@hynek Any thoughts on the idea in #253 (comment) ? Personally I think, if the LogRecord was created before running structlog processors then maybe things would simpler because then processors can access to the LogRecord instance. But that level of changes would be massive. I am sure there must be reasons why things came to be the way they are
|
I think best case scenario, if it's possible, would be to provide access to Maybe easiest would be to add a This was the way I was thinking to solve this without touching the library. |
gestures at everything 😞 But mostly because I was preoccupied with attrs lately and couldn't justify spending significant FOSS time on structlog, since it's not part of Tidelift yet (= not enough companies would subscribe = no income). |
Hi everyone, sorry very much for for the long silence. I did keep having this on my mind and felt sufficiently guilty about it. Given structlog's modularity, I hope everyone was able to find a solution on their own anyways. Now, I've dived into extracting data from LogRecords and it's…adventurous but doable: #209 (comment) Now, after re-re-re-re-re-re-reading this thread, my understanding is that the problem is that LogRecords that have been created by While a Does any of this make sense? |
OK after looking at it, related but better idea:
This separates the pre_chain that has legit special stuff to deal with and "let's extract data from a LogRecord" that's useful for everybody. |
I have implemented this idea in #365 – please let me know what you think. |
When
._proxy_to_logger
is called, Python's built-in logging callsmakeRecord
that computes attributes that might be useful for some use cases (the most common example being filename/module and line number)All of that information is available in
ProcessorFormatter
's.format
at which structlog just copies themsg
attribute (event_dict) and drops the rest.My proposal is to allow copying attributes from LogRecord into the event_dict
EDIT: See #253 (comment) for a better idea
Roughly, that would look like
Here, I am assuming
LogRecord
Would love to hear your thoughts on this and if it's acceptable I can send a PR too.
EDIT: After studying the code a bit more, I realized that I can get those LogRecord attributes rendered simply by passing a custom format using
fmt
keyword arg, but that would mean losing the niceties of usingJSONRenderer
The text was updated successfully, but these errors were encountered: