@@ -1692,3 +1692,138 @@ When the above script is run, it prints::
16921692Note that the order of items might be different according to the version of
16931693Python used.
16941694
1695+ .. currentmodule :: logging.config
1696+
1697+ Customising handlers with :func: `dictConfig `
1698+ --------------------------------------------
1699+
1700+ There are times when you want to customise logging handlers in particular ways,
1701+ and if you use :func: `dictConfig ` you may be able to do this without
1702+ subclassing. As an example, consider that you may want to set the ownership of a
1703+ log file. On POSIX, this is easily done using :func: `shutil.chown `, but the file
1704+ handlers in the stdlib don't offer built-in support. You can customise handler
1705+ creation using a plain function such as::
1706+
1707+ def owned_file_handler(filename, mode='a', encoding=None, owner=None):
1708+ if owner:
1709+ if not os.path.exists(filename):
1710+ open(filename, 'a').close()
1711+ shutil.chown(filename, *owner)
1712+ return logging.FileHandler(filename, mode, encoding)
1713+
1714+ You can then specify, in a logging configuration passed to :func: `dictConfig `,
1715+ that a logging handler be created by calling this function::
1716+
1717+ LOGGING = {
1718+ 'version': 1,
1719+ 'disable_existing_loggers': False,
1720+ 'formatters': {
1721+ 'default': {
1722+ 'format': '%(asctime)s %(levelname)s %(name)s %(message)s'
1723+ },
1724+ },
1725+ 'handlers': {
1726+ 'file':{
1727+ # The values below are popped from this dictionary and
1728+ # used to create the handler, set the handler's level and
1729+ # its formatter.
1730+ '()': owned_file_handler,
1731+ 'level':'DEBUG',
1732+ 'formatter': 'default',
1733+ # The values below are passed to the handler creator callable
1734+ # as keyword arguments.
1735+ 'owner': ['pulse', 'pulse'],
1736+ 'filename': 'chowntest.log',
1737+ 'mode': 'w',
1738+ 'encoding': 'utf-8',
1739+ },
1740+ },
1741+ 'root': {
1742+ 'handlers': ['file'],
1743+ 'level': 'DEBUG',
1744+ },
1745+ }
1746+
1747+ In this example I am setting the ownership using the ``pulse `` user and group,
1748+ just for the purposes of illustration. Putting it together into a working
1749+ script, ``chowntest.py ``::
1750+
1751+ import logging, logging.config, os, shutil
1752+
1753+ def owned_file_handler(filename, mode='a', encoding=None, owner=None):
1754+ if owner:
1755+ if not os.path.exists(filename):
1756+ open(filename, 'a').close()
1757+ shutil.chown(filename, *owner)
1758+ return logging.FileHandler(filename, mode, encoding)
1759+
1760+ LOGGING = {
1761+ 'version': 1,
1762+ 'disable_existing_loggers': False,
1763+ 'formatters': {
1764+ 'default': {
1765+ 'format': '%(asctime)s %(levelname)s %(name)s %(message)s'
1766+ },
1767+ },
1768+ 'handlers': {
1769+ 'file':{
1770+ # The values below are popped from this dictionary and
1771+ # used to create the handler, set the handler's level and
1772+ # its formatter.
1773+ '()': owned_file_handler,
1774+ 'level':'DEBUG',
1775+ 'formatter': 'default',
1776+ # The values below are passed to the handler creator callable
1777+ # as keyword arguments.
1778+ 'owner': ['pulse', 'pulse'],
1779+ 'filename': 'chowntest.log',
1780+ 'mode': 'w',
1781+ 'encoding': 'utf-8',
1782+ },
1783+ },
1784+ 'root': {
1785+ 'handlers': ['file'],
1786+ 'level': 'DEBUG',
1787+ },
1788+ }
1789+
1790+ logging.config.dictConfig(LOGGING)
1791+ logger = logging.getLogger('mylogger')
1792+ logger.debug('A debug message')
1793+
1794+ To run this, you will probably need to run as ``root ``::
1795+
1796+ $ sudo python3.3 chowntest.py
1797+ $ cat chowntest.log
1798+ 2013-11-05 09:34:51,128 DEBUG mylogger A debug message
1799+ $ ls -l chowntest.log
1800+ -rw-r--r-- 1 pulse pulse 55 2013-11-05 09:34 chowntest.log
1801+
1802+ Note that this example uses Python 3.3 because that's where :func: `shutil.chown `
1803+ makes an appearance. This approach should work with any Python version that
1804+ supports :func: `dictConfig ` - namely, Python 2.7, 3.2 or later. With pre-3.3
1805+ versions, you would need to implement the actual ownership change using e.g.
1806+ :func: `os.chown `.
1807+
1808+ In practice, the handler-creating function may be in a utility module somewhere
1809+ in your project. Instead of the line in the configuration::
1810+
1811+ '()': owned_file_handler,
1812+
1813+ you could use e.g.::
1814+
1815+ '()': 'ext://project.util.owned_file_handler',
1816+
1817+ where ``project.util `` can be replaced with the actual name of the package
1818+ where the function resides. In the above working script, using
1819+ ``'ext://__main__.owned_file_handler' `` should work. Here, the actual callable
1820+ is resolved by :func: `dictConfig ` from the ``ext:// `` specification.
1821+
1822+ This example hopefully also points the way to how you could implement other
1823+ types of file change - e.g. setting specific POSIX permission bits - in the
1824+ same way, using :func: `os.chmod `.
1825+
1826+ Of course, the approach could also be extended to types of handler other than a
1827+ :class: `~logging.FileHandler ` - for example, one of the rotating file handlers,
1828+ or a different type of handler altogether.
1829+
0 commit comments