codecov[bot]: # [Codecov](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=h1) Report
> Merging [#112](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=desc) into [master](https://codecov.io/gh/hynek/structlog/commit/5636314046792f1cc2510af27a379746bfe9dbf2?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/hynek/structlog/pull/112/graphs/tree.svg?width=650&height=150&src=pr&token=biZKjfuKn0)](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #112   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          13     13           
  Lines         774    777    +3     
  Branches       96     97    +1     
=====================================
+ Hits          774    777    +3
```


| [Impacted Files](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/structlog/stdlib.py](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=tree#diff-c3JjL3N0cnVjdGxvZy9zdGRsaWIucHk=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=footer). Last update [5636314...30777d0](https://codecov.io/gh/hynek/structlog/pull/112?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

hynek: I wonder if we should promote the `_record` field as an official API because I’m sure there’s a lot useful things that could be done with it. *ponder*
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 51e3966..0a95646 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -24,7 +24,8 @@ Deprecations:
 Changes:
 ^^^^^^^^
 
-*none*
+- ``structlog.stdlib.add_logger_name()`` now works in ``structlog.stdlib.ProcessorFormatter``'s ``foreign_pre_chain``.
+  `#112 <https://github.com/hynek/structlog/issues/112>`_
 
 
 ----
diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py
index 88c5735..4529e84 100644
--- a/src/structlog/stdlib.py
+++ b/src/structlog/stdlib.py
@@ -346,7 +346,11 @@ def add_logger_name(logger, method_name, event_dict):
     """
     Add the logger name to the event dict.
     """
-    event_dict['logger'] = logger.name
+    record = event_dict.get("_record")
+    if record is None:
+        event_dict["logger"] = logger.name
+    else:
+        event_dict["logger"] = record.name
     return event_dict
 
 
@@ -415,13 +419,15 @@ class ProcessorFormatter(logging.Formatter):
         else:
             logger = None
             meth_name = record.levelname.lower()
-            ed = {"event": record.getMessage()}
+            ed = {"event": record.getMessage(), "_record": record}
 
             # Non-structlog allows to run through a chain to prepare it for the
             # final processor (e.g. adding timestamps and log levels).
             for proc in self.foreign_pre_chain or ():
                 ed = proc(None, meth_name, ed)
 
+            del ed["_record"]
+
         record.msg = self.processor(logger, meth_name, ed)
         return super(ProcessorFormatter, self).format(record)
 

add_logger_name processor does not work in ProcessorFormatter's pre_chain
Hi,

### Steps to reproduce
1. Take [this](http://structlog.readthedocs.io/en/stable/standard-library.html?highlight=add_logger_name#rendering-using-structlog-based-formatters-within-logging) example from the doc.
2. Add `structlog.stdlib.add_logger_name` processor to pre_chain.
3. Run the example.

### Expected behaviour
The logger name (`root` in the doc example) should be added to the event dict with the key `logger`.

### Actual behaviour
Crashes as soon as you log anything using logging module. Here is the stacktrace:
```
>>> logging.getLogger('foo').warning('bar')
--- Logging error ---
Traceback (most recent call last):
  File "/usr/lib64/python3.6/logging/__init__.py", line 992, in emit
    msg = self.format(record)logging.getLogger('foo').warning("bar")
  File "/usr/lib64/python3.6/logging/__init__.py", line 838, in format
    return fmt.format(record)
  File "/home/gilbsgilbs/Projects/structlog/src/structlog/stdlib.py", line 423, in format
    ed = proc(None, meth_name, ed)
  File "/home/gilbsgilbs/Projects/structlog/src/structlog/stdlib.py", line 349, in add_logger_name
    event_dict['logger'] = logger.name
AttributeError: 'NoneType' object has no attribute 'name'
Call stack:
  File "test.py", line 66, in <module>
    logging.getLogger('foo').warning('bar')
Message: 'bar'
Arguments: ()
```

### Comments
This is because `ProcessorFormatter` passes `None` as the logger to pre_chain processors. From inside the processor, we have lost track of the record, which was our only hope to get the logger name (`record.name`).

How this should be fixed according to you? I can contribute to fix this faster. Probably won't use `ProcessorFormatter` for now because of this issue.
diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py
index 15aca38..423760e 100644
--- a/tests/test_stdlib.py
+++ b/tests/test_stdlib.py
@@ -294,6 +294,26 @@ class TestAddLogLevel(object):
         assert 'warning' == event_dict['level']
 
 
+@pytest.fixture
+def log_record():
+    """
+    A LogRecord factory.
+    """
+    def create_log_record(**kwargs):
+        defaults = {
+            "name": "sample-name",
+            "level": logging.INFO,
+            "pathname": None,
+            "lineno": None,
+            "msg": "sample-message",
+            "args": [],
+            "exc_info": None,
+        }
+        defaults.update(kwargs)
+        return logging.LogRecord(**defaults)
+    return create_log_record
+
+
 class TestAddLoggerName(object):
     def test_logger_name_added(self):
         """
@@ -304,6 +324,15 @@ class TestAddLoggerName(object):
         event_dict = add_logger_name(logger, None, {})
         assert name == event_dict["logger"]
 
+    def test_logger_name_added_with_record(self, log_record):
+        """
+        The logger name is deduced from the LogRecord if provided.
+        """
+        name = "sample-name"
+        record = log_record(name=name)
+        event_dict = add_logger_name(None, None, {"_record": record})
+        assert name == event_dict["logger"]
+
 
 class TestRenderToLogKW(object):
     def test_default(self):
@@ -424,6 +453,27 @@ class TestProcessorFormatter(object):
             "[warning  ] foo [in test_foreign_pre_chain]\n",
         ) == capsys.readouterr()
 
+    def test_foreign_pre_chain_add_logger_name(self, configure_for_pf, capsys):
+        """
+        foreign_pre_chain works with add_logger_name processor.
+        """
+        configure_logging((add_logger_name,))
+        configure(
+            processors=[
+                ProcessorFormatter.wrap_for_formatter,
+            ],
+            logger_factory=LoggerFactory(),
+            wrapper_class=BoundLogger,
+        )
+
+        logging.getLogger("sample-name").warning("foo")
+
+        assert (
+            "",
+            "foo                            [sample-name]  [in test_foreign_pr"
+            "e_chain_add_logger_name]\n",
+        ) == capsys.readouterr()
+
     def test_native(self, configure_for_pf, capsys):
         """
         If the log entry comes from structlog, it's unpackaged and processed.

pip install -e .[dev]
parse_log_pytest
requirements.txt
greenlet colorama coverage freezegun pretend pytest simplejson
apt-get update apt-get install -y gcc
3.6
dev-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
colorama==0.4.5
coverage==6.2
freezegun==1.2.2
greenlet==2.0.2
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
pretend==1.0.9
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.9.0.post0
simplejson==3.20.1
six==1.17.0
-e git+https://github.com/hynek/structlog.git@5636314046792f1cc2510af27a379746bfe9dbf2#egg=structlog
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: structlog
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - colorama==0.4.5
      - coverage==6.2
      - freezegun==1.2.2
      - greenlet==2.0.2
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - pretend==1.0.9
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - simplejson==3.20.1
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/structlog

tests/test_stdlib.py::TestAddLoggerName::test_logger_name_added_with_record tests/test_stdlib.py::TestProcessorFormatter::test_foreign_pre_chain_add_logger_name
tests/test_stdlib.py::TestLoggerFactory::test_ignores_frames
tests/test_stdlib.py::TestLoggerFactory::test_deduces_correct_name tests/test_stdlib.py::TestLoggerFactory::test_deduces_correct_caller tests/test_stdlib.py::TestLoggerFactory::test_stack_info tests/test_stdlib.py::TestLoggerFactory::test_no_stack_info_by_default tests/test_stdlib.py::TestLoggerFactory::test_find_caller tests/test_stdlib.py::TestLoggerFactory::test_sets_correct_logger tests/test_stdlib.py::TestLoggerFactory::test_positional_argument_avoids_guessing tests/test_stdlib.py::TestFilterByLevel::test_filters_lower_levels tests/test_stdlib.py::TestFilterByLevel::test_passes_higher_levels tests/test_stdlib.py::TestBoundLogger::test_proxies_to_correct_method[debug] tests/test_stdlib.py::TestBoundLogger::test_proxies_to_correct_method[info] tests/test_stdlib.py::TestBoundLogger::test_proxies_to_correct_method[warning] tests/test_stdlib.py::TestBoundLogger::test_proxies_to_correct_method[error] tests/test_stdlib.py::TestBoundLogger::test_proxies_to_correct_method[critical] tests/test_stdlib.py::TestBoundLogger::test_proxies_exception tests/test_stdlib.py::TestBoundLogger::test_proxies_log tests/test_stdlib.py::TestBoundLogger::test_positional_args_proxied tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[addHandler-method_args0] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[removeHandler-method_args1] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[hasHandlers-None] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[callHandlers-method_args3] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[handle-method_args4] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[setLevel-method_args5] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[getEffectiveLevel-None] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[isEnabledFor-method_args7] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[findCaller-None] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[makeRecord-method_args9] tests/test_stdlib.py::TestBoundLogger::test_stdlib_passthrough_methods[getChild-method_args10] tests/test_stdlib.py::TestBoundLogger::test_exception_exc_info tests/test_stdlib.py::TestBoundLogger::test_exception_maps_to_error tests/test_stdlib.py::TestPositionalArgumentsFormatter::test_formats_tuple tests/test_stdlib.py::TestPositionalArgumentsFormatter::test_formats_dict tests/test_stdlib.py::TestPositionalArgumentsFormatter::test_positional_args_retained tests/test_stdlib.py::TestPositionalArgumentsFormatter::test_nop_no_args tests/test_stdlib.py::TestPositionalArgumentsFormatter::test_args_removed_if_empty tests/test_stdlib.py::TestAddLogLevel::test_log_level_added tests/test_stdlib.py::TestAddLogLevel::test_log_level_alias_normalized tests/test_stdlib.py::TestAddLoggerName::test_logger_name_added tests/test_stdlib.py::TestRenderToLogKW::test_default tests/test_stdlib.py::TestRenderToLogKW::test_add_extra_event_dict tests/test_stdlib.py::TestProcessorFormatter::test_foreign_delegate tests/test_stdlib.py::TestProcessorFormatter::test_foreign_pre_chain tests/test_stdlib.py::TestProcessorFormatter::test_native
CHANGELOG.rst src/structlog/stdlib.py
CHANGELOG.rst src/structlog/stdlib.py