153

I want to use pprint's output to show a complex data structure, but I would like to output it using the logging module rather than stdout.

ds = [{'hello': 'there'}]
logging.debug( pprint.pprint(ds) ) # outputs as STDOUT
vvvvv
  • 25,404
  • 19
  • 49
  • 81
yee379
  • 6,498
  • 10
  • 56
  • 101
  • i glanced through the docs and found `pprint( {}, stream )`, but found it rather awkward. i would have thought something like `spprint` might have been nicer than `pformat` (like in `c`). – yee379 Jun 19 '12 at 02:26
  • 8
    `pprint.pformat()` was on that page. – Gareth Latty Jun 19 '12 at 02:26
  • 31
    @Lattywayre - Not everyone who asks a question like this has skipped the docs. I read the same docs and also missed pformat. On stackoverflow you also sometimes get gems from other people's experience that weren't in the docs at all. Thanks yee379 for asking this. – Mnebuerquo Aug 08 '14 at 00:22

3 Answers3

310

Use pprint.pformat to get a string, and then send it to your logging framework.

from pprint import pformat
ds = [{'hello': 'there'}]
logging.debug(pformat(ds))
MarAja
  • 1,547
  • 4
  • 20
  • 36
robert
  • 33,242
  • 8
  • 53
  • 74
  • 14
    If you don't remove this code after you're done debugging, you should probably guard it with "if Logger.isEnabledFor(logging.DEBUG):" to avoid running pformat when you won't use its output: http://docs.python.org/2/library/logging.html#logging.Logger.isEnabledFor – Ed Brannin Aug 13 '13 at 16:58
  • 2
    @EdBrannin Does pformat add that much overhead that it's worth the trouble of adding the conditionals to all the DEBUG log statements? – undefinedvariable Sep 29 '15 at 13:04
  • 3
    @undefinedvariable Good question. Me-today wants to tell Me-2-years-ago to generate some A/B performance metrics. – Ed Brannin Sep 29 '15 at 17:10
  • 1
    I get `AttributeError: 'function' object has no attribute 'pformat'` any idea why? – JinSnow May 03 '17 at 20:53
  • 4
    solution: I needed `from pprint import pprint,pformat` then `logging.debug((pformat(stuff))` – JinSnow May 03 '17 at 21:02
  • 1
    I believe if you use `logging.debug("%s", pformat(ds))` you should avoid the overhead of `pformat` when the `logLevel` is > `DEBUG`. Here is a [link](https://reinout.vanrees.org/weblog/2015/06/05/logging-formatting.html) to good info on string formatting for logging. – RoHS4U Apr 05 '19 at 19:18
  • I was hoping you are right RoHS4U but I can't find any evidence in that link, the docs or anywhere else. – Charlie Gorichanaz Oct 08 '19 at 00:35
  • {'general': 'kenobi'} – mdornfe1 Mar 10 '20 at 20:23
  • 5
    `logging.debug("%s", pformat(ds))` won't avoid the `pformat` call. That format simply means that, if debug logging is disabled, then a new string won't be created by formatting the template string with the other arguments. The arguments themselves, if results of a function call, **will** be evaluated. – kevlarr Sep 11 '20 at 16:58
25

The solution above didn't quite cut it for me because I'm also using a formatter to add name and levelname when logging. It looks a little untidy:

__main__    : DEBUG   : ['aaaaaaaaaaaaaaaaaaaa',
'bbbbbbbbbbbbbbbbbbbb',
'cccccccccccccccccccc',
'dddddddddddddddddddd']
__main__    : DEBUG   : Some other logging text

There may be a more elegant solution, but this:

for line in pprint.pformat(ds).split('\n'):
    logging.debug(line)

produces something a little nicer:

__main__    : DEBUG   : ['aaaaaaaaaaaaaaaaaaaa',
__main__    : DEBUG   :  'bbbbbbbbbbbbbbbbbbbb',
__main__    : DEBUG   :  'cccccccccccccccccccc',
__main__    : DEBUG   :  'dddddddddddddddddddd']
__main__    : DEBUG   : Some other logging text
Hywel Thomas
  • 799
  • 9
  • 5
  • 19
    Nicer for human consumption. Not so great if you're shipping logs to logstash or similar tools, and want a single multiline message to be sent as, well, one message. – Charles Duffy Jan 18 '14 at 02:08
  • 8
    is there a way to pretty print at the handler/formatter level of the logger config? Seems like a valid use case to pretty print to console, but go unformatted to file – jon_darkstar Apr 02 '15 at 13:43
  • @CharlesDuffy Is there any easy way to handle both cases? – jtlz2 Nov 26 '18 at 07:14
  • 9
    Fwiw my solution has been to just add an extra `\n` character in the pformat. At least this way the block is together. – ricekab May 31 '19 at 07:37
  • @CharlesDuffy I think the proper solution would be to fix the default Python console logging handler, so knows about `pformat()` and prefixes every line in the output with the configured logger format. – Mikko Ohtamaa Jul 12 '21 at 14:50
11

An alternative is to use json.dumps with the indent arg. In some cases (depending on logging format, data size, etc) it might give you nicer output.

logging.error('Malformed input data!')
logging.error(pformat(foo))

ERROR:root:Malformed input data!
ERROR:root:{'a': 1, 'b': 2, 'c': 'womp rat', 'd': 'turd sandwich'}

vs.

logging.error('Malformed input data!') 
logging.error(json.dumps(foo, indent=4))

ERROR:root:Malformed input data!
ERROR:root:{
    "a": 1,
    "b": 2,
    "c": "womp rat",
    "d": "turd sandwich"
}
Brian Wylie
  • 2,347
  • 28
  • 29