1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
# Copyright 2009 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Backport of Python 2.6 logging handlers.
Do not import this module directly, instead import from
`lp.services.scripts`.
Given that most of the contents of this file are derived from the Python 2.6
distribution, the above license statement is probably wrong.
"""
# Comments starting with 'BACKPORT' have been added to indicate changes from
# the original.
__metaclass__ = type
__all__ = [
'WatchedFileHandler',
]
import codecs
import logging
import os
from stat import (
ST_DEV,
ST_INO,
)
class WatchedFileHandler(logging.FileHandler):
"""
A handler for logging to a file, which watches the file
to see if it has changed while in use. This can happen because of
usage of programs such as newsyslog and logrotate which perform
log file rotation. This handler, intended for use under Unix,
watches the file to see if it has changed since the last emit.
(A file has changed if its device or inode have changed.)
If it has changed, the old file stream is closed, and the file
opened to get a new stream.
This handler is not appropriate for use under Windows, because
under Windows open files cannot be moved or renamed - logging
opens the files with exclusive locks - and so there is no need
for such a handler. Furthermore, ST_INO is not supported under
Windows; stat always returns zero for this value.
This handler is based on a suggestion and patch by Chad J.
Schroeder.
"""
def __init__(self, filename, mode='a', encoding=None):
# BACKPORT: The 'delay' parameter has been removed, since Python 2.4
# logging doesn't have the delay feature.
logging.FileHandler.__init__(self, filename, mode, encoding)
# BACKPORT: In Python 2.6, the constructor stores the encoding
# parameter. Here we save it so we can use it in the _open method.
self.encoding = encoding
if not os.path.exists(self.baseFilename):
self.dev, self.ino = -1, -1
else:
stat = os.stat(self.baseFilename)
self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
def _open(self):
"""
Open the current base file with the (original) mode and encoding.
Return the resulting stream.
"""
# BACKPORT: Copied from the 2.6 implementation so that emit() can call
# it. In the Python 2.6 version, this is also called by the
# constructor.
if self.encoding is None:
stream = open(self.baseFilename, self.mode)
else:
stream = codecs.open(self.baseFilename, self.mode, self.encoding)
return stream
def emit(self, record):
"""
Emit a record.
First check if the underlying file has changed, and if it
has, close the old stream and reopen the file to get the
current stream.
"""
if not os.path.exists(self.baseFilename):
stat = None
changed = 1
else:
# BACKPORT: Doing a stat on every log seems unwise. A signal
# handling log handler might be better.
stat = os.stat(self.baseFilename)
changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
if changed and self.stream is not None:
self.stream.flush()
self.stream.close()
self.stream = self._open()
if stat is None:
stat = os.stat(self.baseFilename)
self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
logging.FileHandler.emit(self, record)
|