.. _examples:

Examples
========

In the simplest case, you bind a unique request ID to every incoming request so you can easily see which log entries belong to which request.


``file1.py``

.. code-block:: python

   import logging
   import uuid

   from flask import request
   from structlog import BoundLogger, ThreadLocalDict

   from .file2 import some_function

   log = BoundLogger(logging.getLogger(__name__))

   @app.route('/login', methods=['POST', 'GET'])
   def some_route():
       log.new(
           request_id=str(uuid.uuid4()),
       )
       # do something
       # ...
       log.info('user logged in', user='test-user')
       # gives you:
       # request_id='ffcdc44f-b952-4b5f-95e6-0f1f3a9ee5fd' event='user logged in' user='test-user'
       # ...
       some_function()
       # ...

   if __name__ == "__main__":
      BoundLogger.configure(
         context_class=structlog.ThreadLocalDict(dict),
      )
      app.run()

``file2.py``

.. code-block:: python

   import logging

   from structlog import BoundLogger

   log = BoundLogger.wrap(logging.getLogger(__name__))

   def some_function():
       # later then:
       log.error('user did something')
       # gives you:
       # request_id='ffcdc44f-b952-4b5f-95e6-0f1f3a9ee5fd' event='user did something'

While wrapped loggers are *immutable* by default, this example shows how to circumvent that using thread local data for convenience.

If you prefer to log less but with more context in each entry, you can bind everything important to your logger and log it out with each log entry.


.. code-block:: python

   import sys
   import uuid

   import twisted

   from structlog import BoundLogger
   from structlog.twisted import LogAdapter
   from twisted.internet import protocol, reactor

   logger = BoundLogger.wrap(twisted.python.log, processors=[LogAdapter()])

   class Echo(protocol.Protocol):
      def connectionMade(self):
         self._log = logger.new(
               connection_id=str(uuid.uuid4()),
               peer=self.transport.getPeer().host,
         )
      def dataReceived(self, data):
         self._log.msg('got data', data=data)
         self.transport.write(data)

   twisted.python.log.startLogging(sys.stderr)
   reactor.listenTCP(1234, protocol.Factory.forProtocol(Echo))
   reactor.run()
   

Additionally, structlog offers you a simple but flexible way to filter and modify your log entries using so called processors once you actually log an event.


So you want timestamps as part of the structure of the log entry, censor passwords, filter out log entries below your log level before they even get rendered, and get your output as JSON for convenient parsing?
Here you go:

.. code-block:: pycon

   >>> import datetime
   >>> from structlog import JSONRenderer
   >>> from structlog.stdlib import filter_by_level
   >>> def add_timestamp(_, __, event_dict):
   ...     event_dict['timestamp'] = datetime.datetime.utcnow()
   ...     return event_dict
   >>> def censor_password(_, __, event_dict):
   ...     pw = event_dict.get('password')
   ...     if pw:
   ...         event_dict['password'] = '*CENSORED*'
   ...     return event_dict
   >>> log = BoundLogger.wrap(
   ...     logger,
   ...     processors=[
   ...         filter_by_level,
   ...         add_timestamp,
   ...         censor_password,
   ...         JSONRenderer(indent=1, sort_keys=True)
   ...     ]
   ... )
   >>> log.info('something.filtered')
   >>> log.warning('something.not_filtered', password='secret') # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
   {
    "event": "something.not_filtered",
    "password": "*CENSORED*",
    "timestamp": "datetime.datetime(..., ..., ..., ..., ...)"
   }

Of course you can set default processors and context classes once globally using :func:`structlog.loggers.BoundLogger.configure`.
