diff -r 87d49e2cdd34 Doc/library/logging.handlers.rst --- a/Doc/library/logging.handlers.rst Sun Nov 03 17:00:51 2013 +1000 +++ b/Doc/library/logging.handlers.rst Sun Nov 03 13:11:30 2013 +0200 @@ -77,13 +77,16 @@ :class:`StreamHandler`. -.. class:: FileHandler(filename, mode='a', encoding=None, delay=False) +.. class:: FileHandler(filename, mode='a', encoding=None, delay=False, chown=None) Returns a new instance of the :class:`FileHandler` class. The specified file is opened and used as the stream for logging. If *mode* is not specified, :const:`'a'` is used. If *encoding* is not *None*, it is used to open the file with that encoding. If *delay* is true, then file opening is deferred until the - first call to :meth:`emit`. By default, the file grows indefinitely. + first call to :meth:`emit`. + If *chown* is not *None*, it is used to change the owner and the group of the + specified file. *chown* argument must be a (``username``, ``groupname``) tuple. + By default, the file grows indefinitely. .. method:: close() @@ -95,6 +98,9 @@ Outputs the record to the file. + .. versionchanged:: 3.4 + *chown* parameter was added. + .. _null-handler: @@ -153,7 +159,7 @@ for this value. -.. class:: WatchedFileHandler(filename[,mode[, encoding[, delay]]]) +.. class:: WatchedFileHandler(filename[,mode[, encoding[, delay[, chown]]]]) Returns a new instance of the :class:`WatchedFileHandler` class. The specified file is opened and used as the stream for logging. If *mode* is not specified, @@ -168,6 +174,10 @@ changed. If it has, the existing stream is flushed and closed and the file opened again, before outputting the record to the file. + .. versionchanged:: 3.4 + *chown* parameter was added. + + .. _base-rotating-handler: BaseRotatingHandler @@ -179,7 +189,7 @@ not need to instantiate this class, but it has attributes and methods you may need to override. -.. class:: BaseRotatingHandler(filename, mode, encoding=None, delay=False) +.. class:: BaseRotatingHandler(filename, mode, encoding=None, delay=False, chown=None) The parameters are as for :class:`FileHandler`. The attributes are: @@ -236,6 +246,9 @@ .. versionadded:: 3.3 + .. versionchanged:: 3.4 + *chown* parameter was added. + The reason the attributes exist is to save you having to subclass - you can use the same callables for instances of :class:`RotatingFileHandler` and :class:`TimedRotatingFileHandler`. If either the namer or rotator callable @@ -258,7 +271,7 @@ module, supports rotation of disk log files. -.. class:: RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0) +.. class:: RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0, chown=None) Returns a new instance of the :class:`RotatingFileHandler` class. The specified file is opened and used as the stream for logging. If *mode* is not specified, @@ -290,6 +303,9 @@ Outputs the record to the file, catering for rollover as described previously. + .. versionchanged:: 3.4 + *chown* parameter was added. + .. _timed-rotating-file-handler: TimedRotatingFileHandler @@ -300,7 +316,7 @@ timed intervals. -.. class:: TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None) +.. class:: TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, chown=None) Returns a new instance of the :class:`TimedRotatingFileHandler` class. The specified file is opened and used as the stream for logging. On rotating it also @@ -357,6 +373,10 @@ .. versionchanged:: 3.4 *atTime* parameter was added. + .. versionchanged:: 3.4 + *chown* parameter was added. + + .. method:: doRollover() Does a rollover, as described above. diff -r 87d49e2cdd34 Lib/logging/__init__.py --- a/Lib/logging/__init__.py Sun Nov 03 17:00:51 2013 +1000 +++ b/Lib/logging/__init__.py Sun Nov 03 13:11:30 2013 +0200 @@ -25,6 +25,7 @@ import sys, os, time, io, traceback, warnings, weakref from string import Template +import shutil __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO', @@ -962,7 +963,8 @@ """ A handler class which writes formatted logging records to disk files. """ - def __init__(self, filename, mode='a', encoding=None, delay=False): + def __init__(self, filename, mode='a', encoding=None, + delay=False, chown=None): """ Open the specified file and use it as the stream for logging. """ @@ -972,6 +974,7 @@ self.mode = mode self.encoding = encoding self.delay = delay + self.chown = chown if delay: #We don't open the stream, but we still need to call the #Handler constructor to set level, formatter, lock etc. @@ -1000,6 +1003,10 @@ Open the current base file with the (original) mode and encoding. Return the resulting stream. """ + if self.chown is not None: + shutil.chown(self.baseFilename, + self.chown[0], + self.chown[1]) return open(self.baseFilename, self.mode, encoding=self.encoding) def emit(self, record): diff -r 87d49e2cdd34 Lib/logging/handlers.py --- a/Lib/logging/handlers.py Sun Nov 03 17:00:51 2013 +1000 +++ b/Lib/logging/handlers.py Sun Nov 03 13:11:30 2013 +0200 @@ -51,11 +51,12 @@ Not meant to be instantiated directly. Instead, use RotatingFileHandler or TimedRotatingFileHandler. """ - def __init__(self, filename, mode, encoding=None, delay=False): + def __init__(self, filename, mode, encoding=None, delay=False, chown=None): """ Use the specified filename for streamed logging """ - logging.FileHandler.__init__(self, filename, mode, encoding, delay) + logging.FileHandler.__init__(self, filename, mode, encoding, + delay, chown=chown) self.mode = mode self.encoding = encoding self.namer = None @@ -120,7 +121,8 @@ Handler for logging to a set of files, which switches from one file to the next when the current file reaches a certain size. """ - def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False): + def __init__(self, filename, mode='a', maxBytes=0, + backupCount=0, encoding=None, delay=False, chown=None): """ Open the specified file and use it as the stream for logging. @@ -148,7 +150,8 @@ # on each run. if maxBytes > 0: mode = 'a' - BaseRotatingHandler.__init__(self, filename, mode, encoding, delay) + BaseRotatingHandler.__init__(self, filename, mode, + encoding, delay, chown=chown) self.maxBytes = maxBytes self.backupCount = backupCount @@ -199,8 +202,11 @@ If backupCount is > 0, when rollover is done, no more than backupCount files are kept - the oldest ones are deleted. """ - def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None): - BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay) + def __init__(self, filename, when='h', interval=1, backupCount=0, + encoding=None, delay=False, utc=False, + atTime=None, chown=None): + BaseRotatingHandler.__init__(self, filename, 'a', + encoding, delay, chown=chown) self.when = when.upper() self.backupCount = backupCount self.utc = utc @@ -431,8 +437,10 @@ This handler is based on a suggestion and patch by Chad J. Schroeder. """ - def __init__(self, filename, mode='a', encoding=None, delay=False): - logging.FileHandler.__init__(self, filename, mode, encoding, delay) + def __init__(self, filename, mode='a', encoding=None, + delay=False, chown=None): + logging.FileHandler.__init__(self, filename, mode, + encoding, delay, chown=chown) self.dev, self.ino = -1, -1 self._statstream() diff -r 87d49e2cdd34 Lib/test/test_logging.py --- a/Lib/test/test_logging.py Sun Nov 03 17:00:51 2013 +1000 +++ b/Lib/test/test_logging.py Sun Nov 03 13:11:30 2013 +0200 @@ -77,6 +77,12 @@ import zlib except ImportError: pass +try: + import grp + import pwd + UID_GID_SUPPORT = True +except ImportError: + UID_GID_SUPPORT = False class BaseTest(unittest.TestCase): @@ -3833,6 +3839,24 @@ self.assertTrue(os.path.exists(self.fn)) fh.close() + @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") + @unittest.skipUnless(hasattr(os, 'chown'), 'requires os.chown') + def test_chown(self): + def check_chown(path, uid=None, gid=None): + s = os.stat(path) + if uid is not None: + self.assertEqual(uid, s.st_uid) + if gid is not None: + self.assertEqual(gid, s.st_gid) + + uid = os.getuid() + gid = os.getgid() + user = pwd.getpwuid(uid)[0] + group = grp.getgrgid(gid)[0] + fh = logging.FileHandler(self.fn, chown=(user, group)) + self.addCleanup(fh.close) + check_chown(self.fn, uid, gid) + class RotatingFileHandlerTest(BaseFileTest): def next_rec(self): return logging.LogRecord('n', logging.DEBUG, 'p', 1,