codecov[bot]: # [Codecov](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=h1) Report
> Merging [#117](https://codecov.io/gh/hynek/structlog/pull/117?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/117/graphs/tree.svg?width=650&height=150&src=pr&token=biZKjfuKn0)](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #117   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          13     13           
  Lines         774    775    +1     
  Branches       96     96           
=====================================
+ Hits          774    775    +1
```


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

------

[Continue to review full report at Codecov](https://codecov.io/gh/hynek/structlog/pull/117?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/117?src=pr&el=footer). Last update [5636314...161ede8](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=h1) Report
> Merging [#117](https://codecov.io/gh/hynek/structlog/pull/117?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/117/graphs/tree.svg?height=150&width=650&token=biZKjfuKn0&src=pr)](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #117   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          13     13           
  Lines         774    775    +1     
  Branches       96     96           
=====================================
+ Hits          774    775    +1
```


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

------

[Continue to review full report at Codecov](https://codecov.io/gh/hynek/structlog/pull/117?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/117?src=pr&el=footer). Last update [5636314...161ede8](https://codecov.io/gh/hynek/structlog/pull/117?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

if-fi: Hi @hynek I just found the same problem on Thursday, and I was looking for a non-hackish solution to send as a pull request. 
Yours is pretty smooth. I just tried it locally and it works fine in all scenarios I came up with.

P.S. I also found a problem when using `ProcessorFormatter` with `RotatingFileHandler`. I will send you a pull request that I hope will be included in the same bugfix release as this one
gilbsgilbs: Hi @if-fi, hi @hynek.

As far as I can see, this won't break anything and will fix the issue. Still, I have some remarks:

- You're clearing `record.args` too often. Clearing after calling `record.getMessage()` (which is the one actually consuming the args) should be sufficient I guess (untested),
- Not really fund of mutating `record.msg` and now `record.args`. You really shouldn't ever have to mutate any `record` attribute, appart from `record.message` (≠ from `record.msg`, yeah, ik, logging module is real crap, but `record.msg` should be the unformatted message and `record.message` the formatted one -_-"),
- You're actually formatting the message twice. First time in `record.getMessage()`, and another time calling super function, which puts the formatted message into `record.message` . The second time should be near-free since `record.args` is empty (provided that format operator is a bit smart, haven't checked that out). Yet, it still requires two format when one is most likely possible (calling super function sooner, and getting the formatted message in `record.message`).

Don't have much time to dig into this. I think the fix is ok.
hynek: 1. Good catch, I’ll move it up.
2. I sadly don’t know enough about this part.  But I guess I’d be hoping for too much if I thought I could format into `record.message` and it solves #119 too, right? :)
3. The reason is https://github.com/hynek/structlog/pull/105#discussion_r107939889 
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0a95646..b9dbfab 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -26,6 +26,10 @@ Changes:
 
 - ``structlog.stdlib.add_logger_name()`` now works in ``structlog.stdlib.ProcessorFormatter``'s ``foreign_pre_chain``.
   `#112 <https://github.com/hynek/structlog/issues/112>`_
+- Clear log record args in ``structlog.stdlib.ProcessorFormatter`` after rendering.
+  This fix is for you if you tried to use it and got ``TypeError: not all arguments converted during string formatting`` exceptions.
+  `#116 <https://github.com/hynek/structlog/issues/116>`_
+  `#117 <https://github.com/hynek/structlog/issues/117>`_
 
 
 ----
diff --git a/src/structlog/stdlib.py b/src/structlog/stdlib.py
index 4529e84..fb7ad04 100644
--- a/src/structlog/stdlib.py
+++ b/src/structlog/stdlib.py
@@ -420,6 +420,7 @@ class ProcessorFormatter(logging.Formatter):
             logger = None
             meth_name = record.levelname.lower()
             ed = {"event": record.getMessage(), "_record": record}
+            record.args = ()
 
             # Non-structlog allows to run through a chain to prepare it for the
             # final processor (e.g. adding timestamps and log levels).

Suggested configurations leads to exceptions for third party libs.
Hi,

Python 2.7.5
CentOS Linux release 7.2.1511 (Core)

I tried the suggestion named:

> Rendering Using structlog-based Formatters Within logging

From:
[http://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging](http://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging)

The only thing I added was:

```
import boto3  
ec2 = boto3.client('ec2')
response = ec2.describe_regions()
print('Regions:', response['Regions'])
response = ec2.describe_availability_zones()
print('Availability Zones:', response['AvailabilityZones'])
```
(This issue is not related to the third party lib used, I got the same thing with other third party libs, just needed one as a minimal repro case.)

This leads to a ton of exception that ends with:

> File "/usr/lib64/python2.7/logging/__init__.py", line 328, in getMessage
    msg = msg % self.args
TypeError: not all arguments converted during string formatting

My goal is to use structlog to get all my logs and those from imported third party libs to be formatted in JSON with a basic set of common keys. But I started with using the documentation as is first to understand how it works.

I was expecting boto3 logs to be formatted and it worked, somewhere in the console output (and the log file) there is:

> 2017-05-12 19:57:08 [info     ] Starting new HTTP connection (1): 169.254.169.254

What I don't understand are all the Exception thrown from inside the logging library that are in visible in the std err output. Should the suggested config work in this scenario?

Attached are the source code file used, the log file from the WatchedFileHandler and console output file where the exceptions are visible. It was generated with:
`python issue.py &> console_output.txt`

[console_output.txt](https://github.com/hynek/structlog/files/997889/console_output.txt)
[filehandler_output.txt](https://github.com/hynek/structlog/files/997891/filehandler_output.txt)
[source_code.txt](https://github.com/hynek/structlog/files/997890/source_code.txt)

Martin
diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py
index 423760e..548ccfa 100644
--- a/tests/test_stdlib.py
+++ b/tests/test_stdlib.py
@@ -432,6 +432,22 @@ class TestProcessorFormatter(object):
             "foo [in test_foreign_delegate]\n",
         ) == capsys.readouterr()
 
+    def test_clears_args(self, capsys, configure_for_pf):
+        """
+        We render our log records before sending it back to logging.  Therefore
+        we must clear `LogRecord.args` otherwise the user gets an
+        `TypeError: not all arguments converted during string formatting.` if
+        they use positional formatting in stdlib logging.
+        """
+        configure_logging(None)
+
+        logging.getLogger().warning("hello %s.", "world")
+
+        assert (
+            "",
+            "hello world. [in test_clears_args]\n",
+        ) == capsys.readouterr()
+
     def test_foreign_pre_chain(self, configure_for_pf, capsys):
         """
         If foreign_pre_chain is an iterable, it's used to pre-process

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@ff7cf4ce1ea725a66e93d7d5d51ed5155fc20c52#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::TestProcessorFormatter::test_clears_args
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::TestAddLoggerName::test_logger_name_added_with_record 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_foreign_pre_chain_add_logger_name tests/test_stdlib.py::TestProcessorFormatter::test_native
CHANGELOG.rst src/structlog/stdlib.py
CHANGELOG.rst src/structlog/stdlib.py