~didrocks/ubuntuone-client/dont-suffer-zg-crash

« back to all changes in this revision

Viewing changes to ubuntuone/syncdaemon/logger.py

  • Committer: Bazaar Package Importer
  • Author(s): Rodney Dawes
  • Date: 2009-06-30 12:00:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090630120000-by806ovmw3193qe8
Tags: upstream-0.90.3
ImportĀ upstreamĀ versionĀ 0.90.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ubuntuone.syncdaemon.logger - logging utilities
 
2
#
 
3
# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
 
4
#
 
5
# Copyright 2009 Canonical Ltd.
 
6
#
 
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.
 
10
#
 
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.
 
15
#
 
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. """
 
19
 
 
20
from itertools import imap
 
21
import logging
 
22
import sys
 
23
import os
 
24
import re
 
25
import xdg.BaseDirectory
 
26
 
 
27
from logging.handlers import TimedRotatingFileHandler
 
28
 
 
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)
 
34
 
 
35
LOGFILENAME = os.path.join(LOGFOLDER, 'syncdaemon.log')
 
36
EXLOGFILENAME = os.path.join(LOGFOLDER, 'syncdaemon-exceptions.log')
 
37
 
 
38
LOGBACKUP = 5 # the number of log files to keep around
 
39
 
 
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")
 
44
# extra levels
 
45
TRACE = 5 # be more verbose than logging.DEBUG(10)
 
46
 
 
47
# a constant to change the default DEBUG level value
 
48
_DEBUG_LOG_LEVEL = logging.DEBUG
 
49
 
 
50
# check if we are in TRACE mode
 
51
if 'trace' in os.environ.get('DEBUG', ''):
 
52
    _DEBUG_LOG_LEVEL = TRACE
 
53
 
 
54
 
 
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.
 
59
    """
 
60
 
 
61
    def __init__(self, *args, **kwargs):
 
62
        """ create the instance and override the suffix and extMatch. """
 
63
        kwargs['when'] = 'D'
 
64
        kwargs['backupCount'] = LOGBACKUP
 
65
        # check if we are in 2.5, only for PQM
 
66
        if sys.version_info[:2] >= (2, 6):
 
67
            kwargs['delay'] = 1
 
68
        TimedRotatingFileHandler.__init__(self, *args, **kwargs)
 
69
        # override suffix
 
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}$")
 
72
 
 
73
 
 
74
# root logger
 
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)
 
83
# exception logs
 
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)
 
90
 
 
91
# hook twisted.python.log with standard logging
 
92
from twisted.python import log
 
93
observer = log.PythonLoggingObserver('twisted')
 
94
observer.start()
 
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)
 
105
 
 
106
 
 
107
class mklog(object):
 
108
    """
 
109
    Create a logger that keeps track of the method where it's being
 
110
    called from, in order to make more informative messages.
 
111
    """
 
112
    def __init__(self, _logger, _method, _share, _uid, *args, **kwargs):
 
113
        # args are _-prepended to lower the chances of them
 
114
        # conflicting with kwargs
 
115
 
 
116
        all_args = []
 
117
        for arg in args:
 
118
            all_args.append(
 
119
                repr(arg).decode('ascii', 'replace').encode('ascii', 'replace')
 
120
                )
 
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)
 
125
 
 
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):
 
130
        """
 
131
        Generalized form of the different logging functions.
 
132
        """
 
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)
 
149
 
 
150
 
 
151
    def callbacks(self, success_message='success', success_arg='',
 
152
                  failure_message='failure'):
 
153
        """
 
154
        Return a callback and an errback that log success or failure
 
155
        messages.
 
156
 
 
157
        The callback/errback pair are pass-throughs; they don't
 
158
        interfere in the callback/errback chain of the deferred you
 
159
        add them to.
 
160
        """
 
161
        def callback(arg, success_arg=success_arg):
 
162
            "it worked!"
 
163
            if callable(success_arg):
 
164
                success_arg = success_arg(arg)
 
165
            self.debug(success_message, success_arg)
 
166
            return arg
 
167
        def errback(failure):
 
168
            "it failed!"
 
169
            self.error(failure_message, failure.getErrorMessage())
 
170
            self.debug('traceback follows:\n\n' + failure.getTraceback(), '')
 
171
            return failure
 
172
        return callback, errback
 
173
 
 
174
 
 
175
def set_debug(dest):
 
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.
 
179
 
 
180
    @param dest: a string with a one or more of 'file', 'stdout', and 'stderr'
 
181
                 e.g. 'file stdout'
 
182
    """
 
183
    if not [ v for v in ['file', 'stdout', 'stderr'] if v in dest]:
 
184
        # invalid dest value, let the loggers alone
 
185
        return
 
186
    sd_filter = logging.Filter('ubuntuone.SyncDaemon')
 
187
    if 'file' in dest:
 
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)
 
197
        if 'stderr' in dest:
 
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)
 
203
        if 'stdout' in dest:
 
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)
 
209
 
 
210
 
 
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.
 
215
 
 
216
    @param dest: a string containing 'file' and/or 'stdout', e.g: 'file stdout'
 
217
    """
 
218
    logger = logging.getLogger("storage.server")
 
219
    logger.setLevel(5) # this shows server messages
 
220
    if 'file' in dest:
 
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)
 
226
    if 'stdout' in dest:
 
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)
 
236
 
 
237
 
 
238
# if we are in debug mode, replace/add the handlers
 
239
DEBUG = os.environ.get("DEBUG", None)
 
240
if DEBUG:
 
241
    set_debug(DEBUG)
 
242
 
 
243
# configure server logging if SERVER_DEBUG != None
 
244
SERVER_DEBUG = os.environ.get("SERVER_DEBUG", None)
 
245
if SERVER_DEBUG:
 
246
    set_server_debug(SERVER_DEBUG)
 
247
 
 
248
 
 
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
 
253
try:
 
254
    root_handler.doRollover()
 
255
except OSError:
 
256
    pass
 
257
try:
 
258
    exception_handler.doRollover()
 
259
except OSError:
 
260
    pass
 
261
 
 
262