1
# ubuntuone.syncdaemon.logger - logging utilities
3
# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
5
# Copyright 2009 Canonical Ltd.
7
# This program is free software: you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License version 3, as published
9
# by the Free Software Foundation.
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
# PURPOSE. See the GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License along
17
# with this program. If not, see <http://www.gnu.org/licenses/>.
18
""" SyncDaemon logging utilities and config. """
20
from itertools import imap
25
import xdg.BaseDirectory
27
from logging.handlers import TimedRotatingFileHandler
29
# define the location of the log folder
30
home = xdg.BaseDirectory.xdg_cache_home
31
LOGFOLDER = os.path.join(home, 'ubuntuone/log')
32
if not os.path.exists(LOGFOLDER):
33
os.makedirs(LOGFOLDER)
35
LOGFILENAME = os.path.join(LOGFOLDER, 'syncdaemon.log')
36
EXLOGFILENAME = os.path.join(LOGFOLDER, 'syncdaemon-exceptions.log')
38
LOGBACKUP = 5 # the number of log files to keep around
40
basic_formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - " \
41
"%(levelname)s - %(message)s")
42
debug_formatter = logging.Formatter(fmt="%(asctime)s %(name)s %(module)s " \
43
"%(lineno)s %(funcName)s %(message)s")
45
TRACE = 5 # be more verbose than logging.DEBUG(10)
47
# a constant to change the default DEBUG level value
48
_DEBUG_LOG_LEVEL = logging.DEBUG
50
# check if we are in TRACE mode
51
if 'trace' in os.environ.get('DEBUG', ''):
52
_DEBUG_LOG_LEVEL = TRACE
55
class DayRotatingFileHandler(TimedRotatingFileHandler):
56
""" A TimedRotatingFileHandler configured for Day rotation but that uses
57
the suffix and extMatch of Hourly rotation, in order to allow seconds based
58
rotation on each startup.
61
def __init__(self, *args, **kwargs):
62
""" create the instance and override the suffix and extMatch. """
64
kwargs['backupCount'] = LOGBACKUP
65
# check if we are in 2.5, only for PQM
66
if sys.version_info[:2] >= (2, 6):
68
TimedRotatingFileHandler.__init__(self, *args, **kwargs)
70
self.suffix = "%Y-%m-%d_%H-%M-%S"
71
self.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$")
75
root_logger = logging.getLogger("ubuntuone.SyncDaemon")
76
root_logger.propagate = False
77
root_logger.setLevel(_DEBUG_LOG_LEVEL)
78
root_handler = DayRotatingFileHandler(filename=LOGFILENAME)
79
root_handler.addFilter(logging.Filter("ubuntuone.SyncDaemon"))
80
root_handler.setFormatter(basic_formatter)
81
root_handler.setLevel(_DEBUG_LOG_LEVEL)
82
root_logger.addHandler(root_handler)
84
exception_handler = DayRotatingFileHandler(filename=EXLOGFILENAME)
85
exception_handler.setFormatter(basic_formatter)
86
exception_handler.setLevel(logging.ERROR)
87
# add the exception handler to the root logger
88
logging.getLogger('').addHandler(exception_handler)
89
root_logger.addHandler(exception_handler)
91
# hook twisted.python.log with standard logging
92
from twisted.python import log
93
observer = log.PythonLoggingObserver('twisted')
95
# configure the logger to only show errors
96
twisted_logger = logging.getLogger('twisted')
97
twisted_logger.propagate = False
98
twisted_logger.setLevel(logging.ERROR)
99
twisted_handler = DayRotatingFileHandler(filename=LOGFILENAME)
100
twisted_handler.addFilter(logging.Filter("twisted"))
101
twisted_handler.setFormatter(basic_formatter)
102
twisted_handler.setLevel(logging.ERROR)
103
twisted_logger.addHandler(twisted_handler)
104
twisted_logger.addHandler(exception_handler)
109
Create a logger that keeps track of the method where it's being
110
called from, in order to make more informative messages.
112
def __init__(self, _logger, _method, _share, _uid, *args, **kwargs):
113
# args are _-prepended to lower the chances of them
114
# conflicting with kwargs
119
repr(arg).decode('ascii', 'replace').encode('ascii', 'replace')
121
for k, v in kwargs.items():
122
v = repr(v).decode('ascii', 'replace').encode('ascii', 'replace')
123
all_args.append("%s=%r" % (k, v))
124
args = ", ".join(all_args)
126
self.desc = "%-28s share:%-40r node:%-40r %s(%s) " \
127
% (_method, _share, _uid, _method, args)
128
self.logger = _logger
129
def _log(self, logger_func, *args):
131
Generalized form of the different logging functions.
133
logger_func(self.desc + " ".join(imap(str, args)))
134
def debug(self, *args):
135
"""Log at level DEBUG"""
136
self._log(self.logger.debug, *args)
137
def info(self, *args):
138
"""Log at level INFO"""
139
self._log(self.logger.info, *args)
140
def warn(self, *args):
141
"""Log at level WARN"""
142
self._log(self.logger.warn, *args)
143
def error(self, *args):
144
"""Log at level ERROR"""
145
self._log(self.logger.error, *args)
146
def exception(self, *args):
147
"""Log an exception"""
148
self._log(self.logger.exception, *args)
151
def callbacks(self, success_message='success', success_arg='',
152
failure_message='failure'):
154
Return a callback and an errback that log success or failure
157
The callback/errback pair are pass-throughs; they don't
158
interfere in the callback/errback chain of the deferred you
161
def callback(arg, success_arg=success_arg):
163
if callable(success_arg):
164
success_arg = success_arg(arg)
165
self.debug(success_message, success_arg)
167
def errback(failure):
169
self.error(failure_message, failure.getErrorMessage())
170
self.debug('traceback follows:\n\n' + failure.getTraceback(), '')
172
return callback, errback
176
""" Set the level to debug of all registered loggers, and replace their
177
handlers. if debug_level is file, syncdaemon-debug.log is used. If it's
178
stdout, all the logging is redirected to stdout. If it's stderr, to stderr.
180
@param dest: a string with a one or more of 'file', 'stdout', and 'stderr'
183
if not [ v for v in ['file', 'stdout', 'stderr'] if v in dest]:
184
# invalid dest value, let the loggers alone
186
sd_filter = logging.Filter('ubuntuone.SyncDaemon')
188
# setup the existing loggers in debug
189
root_handler.setLevel(_DEBUG_LOG_LEVEL)
190
twisted_handler.setLevel(_DEBUG_LOG_LEVEL)
191
logfile = os.path.join(LOGFOLDER, 'syncdaemon-debug.log')
192
root_handler.baseFilename = os.path.abspath(logfile)
193
twisted_handler.baseFilename = os.path.abspath(logfile)
194
for name in ['ubuntuone.SyncDaemon', 'twisted']:
195
logger = logging.getLogger(name)
196
logger.setLevel(_DEBUG_LOG_LEVEL)
198
stderr_handler = logging.StreamHandler()
199
stderr_handler.setFormatter(basic_formatter)
200
stderr_handler.setLevel(_DEBUG_LOG_LEVEL)
201
stderr_handler.addFilter(sd_filter)
202
logger.addHandler(stderr_handler)
204
stdout_handler = logging.StreamHandler(sys.stdout)
205
stdout_handler.setFormatter(basic_formatter)
206
stdout_handler.setLevel(_DEBUG_LOG_LEVEL)
207
stdout_handler.addFilter(sd_filter)
208
logger.addHandler(stdout_handler)
211
def set_server_debug(dest):
212
""" Set the level to debug of all registered loggers, and replace their
213
handlers. if debug_level is file, syncdaemon-debug.log is used. If it's
214
stdout, all the logging is redirected to stdout.
216
@param dest: a string containing 'file' and/or 'stdout', e.g: 'file stdout'
218
logger = logging.getLogger("storage.server")
219
logger.setLevel(5) # this shows server messages
221
handler = DayRotatingFileHandler(filename=os.path.join(LOGFOLDER,
222
'syncdaemon-debug.log'))
223
handler.setFormatter(basic_formatter)
224
handler.setLevel(5) # this shows server messages
225
logger.addHandler(handler)
227
stdout_handler = logging.StreamHandler(sys.stdout)
228
stdout_handler.setFormatter(basic_formatter)
229
stdout_handler.setLevel(5) # this shows server messages
230
logger.addHandler(stdout_handler)
231
if 'stderrt' in dest:
232
stdout_handler = logging.StreamHandler(sys.stdout)
233
stdout_handler.setFormatter(basic_formatter)
234
stdout_handler.setLevel(5) # this shows server messages
235
logger.addHandler(stdout_handler)
238
# if we are in debug mode, replace/add the handlers
239
DEBUG = os.environ.get("DEBUG", None)
243
# configure server logging if SERVER_DEBUG != None
244
SERVER_DEBUG = os.environ.get("SERVER_DEBUG", None)
246
set_server_debug(SERVER_DEBUG)
249
# do a rollover to get an empty log file on each run
250
twisted_handler.close()
251
# ignore the missing file error on a failed rollover
252
# pylint: disable-msg=W0704
254
root_handler.doRollover()
258
exception_handler.doRollover()