@@ -1136,33 +1136,113 @@ level of granularity you want to use in logging an application, it could
11361136be hard to manage if the number of :class: `Logger ` instances becomes
11371137effectively unbounded.
11381138
1139- There are a number of other ways you can pass contextual information to be
1140- output along with logging event information.
1141-
1142- * Use an adapter class which has access to the contextual information and
1143- which defines methods :meth: `debug `, :meth: `info ` etc. with the same
1144- signatures as used by :class: `Logger `. You instantiate the adapter with a
1145- name, which will be used to create an underlying :class: `Logger ` with that
1146- name. In each adpater method, the passed-in message is modified to include
1147- whatever contextual information you want.
1148-
1149- * Use something other than a string to pass the message. Although normally
1150- the first argument to a logger method such as :meth: `debug `, :meth: `info `
1151- etc. is usually a string, it can in fact be any object. This object is the
1152- argument to a :func: `str() ` call which is made, in
1153- :meth: `LogRecord.getMessage `, to obtain the actual message string. You can
1154- use this behavior to pass an instance which may be initialized with a
1155- logging message, which redefines :meth: __str__ to return a modified version
1156- of that message with the contextual information added.
1157-
1158- * Use a specialized :class: `Formatter ` subclass to add additional information
1159- to the formatted output. The subclass could, for instance, merge some thread
1160- local contextual information (or contextual information obtained in some
1161- other way) with the output generated by the base :class: `Formatter `.
1162-
1163- In each of these three approaches, thread locals can sometimes be a useful way
1164- of passing contextual information without undue coupling between different
1165- parts of your code.
1139+ An easy way in which you can pass contextual information to be output along
1140+ with logging event information is to use the :class: `LoggerAdapter ` class.
1141+ This class is designed to look like a :class: `Logger `, so that you can call
1142+ :meth: `debug `, :meth: `info `, :meth: `warning `, :meth: `error `,
1143+ :meth: `exception `, :meth: `critical ` and :meth: `log `. These methods have the
1144+ same signatures as their counterparts in :class: `Logger `, so you can use the
1145+ two types of instances interchangeably.
1146+
1147+ When you create an instance of :class: `LoggerAdapter `, you pass it a
1148+ :class: `Logger ` instance and a dict-like object which contains your contextual
1149+ information. When you call one of the logging methods on an instance of
1150+ :class: `LoggerAdapter `, it delegates the call to the underlying instance of
1151+ :class: `Logger ` passed to its constructor, and arranges to pass the contextual
1152+ information in the delegated call. Here's a snippet from the code of
1153+ :class: `LoggerAdapter `::
1154+
1155+ def debug(self, msg, *args, **kwargs):
1156+ """
1157+ Delegate a debug call to the underlying logger, after adding
1158+ contextual information from this adapter instance.
1159+ """
1160+ msg, kwargs = self.process(msg, kwargs)
1161+ self.logger.debug(msg, *args, **kwargs)
1162+
1163+ The :meth: `process ` method of :class: `LoggerAdapter ` is where the contextual
1164+ information is added to the logging output. It's passed the message and
1165+ keyword arguments of the logging call, and it passes back (potentially)
1166+ modified versions of these to use in the call to the underlying logger. The
1167+ default implementation of this method leaves the message alone, but inserts
1168+ an "extra" key in the keyword argument whose value is the dict-like object
1169+ passed to the constructor. Of course, if you had passed an "extra" keyword
1170+ argument in the call to the adapter, it will be silently overwritten.
1171+
1172+ The advantage of using "extra" is that the values in the dict-like object are
1173+ merged into the :class: `LogRecord ` instance's __dict__, allowing you to use
1174+ customized strings with your :class: `Formatter ` instances which know about
1175+ the keys of the dict-like object. If you need a different method, e.g. if you
1176+ want to prepend or append the contextual information to the message string,
1177+ you just need to subclass :class: `LoggerAdapter ` and override :meth: `process `
1178+ to do what you need. Here's an example script which uses this class, which
1179+ also illustrates what dict-like behaviour is needed from an arbitrary
1180+ "dict-like" object for use in the constructor::
1181+
1182+ import logging
1183+
1184+ class ConnInfo:
1185+ """
1186+ An example class which shows how an arbitrary class can be used as
1187+ the 'extra' context information repository passed to a LoggerAdapter.
1188+ """
1189+
1190+ def __getitem__(self, name):
1191+ """
1192+ To allow this instance to look like a dict.
1193+ """
1194+ from random import choice
1195+ if name == "ip":
1196+ result = choice(["127.0.0.1", "192.168.0.1"])
1197+ elif name == "user":
1198+ result = choice(["jim", "fred", "sheila"])
1199+ else:
1200+ result = self.__dict__.get(name, "?")
1201+ return result
1202+
1203+ def __iter__(self):
1204+ """
1205+ To allow iteration over keys, which will be merged into
1206+ the LogRecord dict before formatting and output.
1207+ """
1208+ keys = ["ip", "user"]
1209+ keys.extend(self.__dict__.keys())
1210+ return keys.__iter__()
1211+
1212+ if __name__ == "__main__":
1213+ from random import choice
1214+ levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
1215+ a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"),
1216+ { "ip" : "123.231.231.123", "user" : "sheila" })
1217+ logging.basicConfig(level=logging.DEBUG,
1218+ format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s")
1219+ a1.debug("A debug message")
1220+ a1.info("An info message with %s", "some parameters")
1221+ a2 = logging.LoggerAdapter(logging.getLogger("d.e.f"), ConnInfo())
1222+ for x in range(10):
1223+ lvl = choice(levels)
1224+ lvlname = logging.getLevelName(lvl)
1225+ a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters")
1226+
1227+ When this script is run, the output should look something like this::
1228+
1229+ 2008-01-18 14:49:54,023 a.b.c DEBUG IP: 123.231.231.123 User: sheila A debug message
1230+ 2008-01-18 14:49:54,023 a.b.c INFO IP: 123.231.231.123 User: sheila An info message with some parameters
1231+ 2008-01-18 14:49:54,023 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters
1232+ 2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: jim A message at INFO level with 2 parameters
1233+ 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters
1234+ 2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: fred A message at ERROR level with 2 parameters
1235+ 2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters
1236+ 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters
1237+ 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: jim A message at WARNING level with 2 parameters
1238+ 2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: fred A message at INFO level with 2 parameters
1239+ 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters
1240+ 2008-01-18 14:49:54,033 d.e.f WARNING IP: 127.0.0.1 User: jim A message at WARNING level with 2 parameters
1241+
1242+ .. versionadded :: 2.6
1243+
1244+ The :class: `LoggerAdapter ` class was not present in previous versions.
1245+
11661246
11671247.. _network-logging :
11681248
@@ -2047,6 +2127,33 @@ made, and any exception information to be logged.
20472127 Returns the message for this :class: `LogRecord ` instance after merging any
20482128 user-supplied arguments with the message.
20492129
2130+ LoggerAdapter Objects
2131+ ---------------------
2132+
2133+ .. versionadded :: 2.6
2134+
2135+ :class: `LoggerAdapter ` instances are used to conveniently pass contextual
2136+ information into logging calls. For a usage example , see context-info _.
2137+
2138+ .. class :: LoggerAdapter(logger, extra)
2139+
2140+ Returns an instance of :class: `LoggerAdapter ` initialized with an
2141+ underlying :class: `Logger ` instance and a dict-like object.
2142+
2143+ .. method :: LoggerAdapter.process(msg, kwargs)
2144+
2145+ Modifies the message and/or keyword arguments passed to a logging call in
2146+ order to insert contextual information. This implementation takes the
2147+ object passed as *extra * to the constructor and adds it to *kwargs * using
2148+ key 'extra'. The return value is a (*msg *, *kwargs *) tuple which has the
2149+ (possibly modified) versions of the arguments passed in.
2150+
2151+ In addition to the above, :class: `LoggerAdapter ` supports all the logging
2152+ methods of :class: `Logger `, i.e. :meth: `debug `, :meth: `info `, :meth: `warning `,
2153+ :meth: `error `, :meth: `exception `, :meth: `critical ` and :meth: `log `. These
2154+ methods have the same signatures as their counterparts in :class: `Logger `, so
2155+ you can use the two types of instances interchangeably.
2156+
20502157
20512158Thread Safety
20522159-------------
0 commit comments