structlog icon indicating copy to clipboard operation
structlog copied to clipboard

AttributeError: 'BytesLogger' object has no attribute 'name'

Open kxepal opened this issue 5 months ago • 1 comments

There is an example at https://www.structlog.org/en/25.4.0/performance.html about how to use structlog with orjson:

import logging
import orjson
import structlog

structlog.configure(
    cache_logger_on_first_use=True,
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.format_exc_info,
        structlog.processors.TimeStamper(fmt="iso", utc=True),
        structlog.processors.JSONRenderer(serializer=orjson.dumps),
    ],
    logger_factory=structlog.BytesLoggerFactory(),
)

And it works perfectly:

log = structlog.get_logger('my_logger')
log.info('test')  # prints {"event":"test","level":"info","timestamp":"2025-07-18T07:58:55.479135Z"}

However, if I add processor structlog.stdlib.add_logger_name to include logger name it stops working:

Traceback (most recent call last):
  File "/home/kxepal/projects/example.py", line 20, in <module>
    log.info('test')
  File "/home/kxepal/projects/venv/lib/python3.12/site-packages/structlog/_native.py", line 144, in meth
    return self._proxy_to_logger(name, event, **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kxepal/projects/venv/lib/python3.12/site-packages/structlog/_base.py", line 222, in _proxy_to_logger
    args, kw = self._process_event(method_name, event, event_kw)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kxepal/projects/venv/lib/python3.12/site-packages/structlog/_base.py", line 173, in _process_event
    event_dict = proc(self._logger, method_name, event_dict)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kxepal/projects/venv/lib/python3.12/site-packages/structlog/stdlib.py", line 832, in add_logger_name
    event_dict["logger"] = logger.name
                           ^^^^^^^^^^^
AttributeError: 'BytesLogger' object has no attribute 'name'

Full example to reproduce an issue:

import logging
import orjson
import structlog

structlog.configure(
    cache_logger_on_first_use=True,
    wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
    processors=[
        structlog.stdlib.add_logger_name,
        structlog.contextvars.merge_contextvars,
        structlog.processors.add_log_level,
        structlog.processors.format_exc_info,
        structlog.processors.TimeStamper(fmt="iso", utc=True),
        structlog.processors.JSONRenderer(serializer=orjson.dumps),
    ],
    logger_factory=structlog.BytesLoggerFactory(),
)

log = structlog.get_logger('my_logger')
log.info('test')

How would I get logger name into records using BytesLoggerFactory?

kxepal avatar Jul 18 '25 08:07 kxepal

Generally speaking, you can't use processors from the stdlib module if you don't use stdlib integration.

Looking at the code after years, the logic is a bit convoluted in order to stay consistent with stdlib: we currently pass the name of the logger to logging.Logger and subsequently look at the logger.

We probably should feed the name into initial_values instead, but I'm not sure how to do that in a backwards-compatible way, though. :(

So it's probably easier to do something like this, even tho it makes me wanna throw up:

diff --git src/structlog/_output.py src/structlog/_output.py
index b1612de..0be1eeb 100644
--- src/structlog/_output.py
+++ src/structlog/_output.py
@@ -256,12 +256,15 @@ class BytesLogger:
     .. versionadded:: 20.2.0
     """

-    __slots__ = ("_file", "_flush", "_lock", "_write")
+    __slots__ = ("_file", "_flush", "_lock", "_write", "name")

-    def __init__(self, file: BinaryIO | None = None):
+    def __init__(
+        self, file: BinaryIO | None = None, *, name: str | None = None
+    ):
         self._file = file or sys.stdout.buffer
         self._write = self._file.write
         self._flush = self._file.flush
+        self.name = name

         self._lock = _get_lock_for_file(self._file)

@@ -345,4 +348,4 @@ class BytesLoggerFactory:
         self._file = file

     def __call__(self, *args: Any) -> BytesLogger:
-        return BytesLogger(self._file)
+        return BytesLogger(self._file, name=args[0])

hynek avatar Jul 22 '25 09:07 hynek