1# Copyright (C) 2009 Bruno Tarquini <btarquini AT gmail.com>
2#
3# This file is part of Gajim.
4#
5# Gajim is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published
7# by the Free Software Foundation; version 3 only.
8#
9# Gajim is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18import os
19import sys
20import time
21from datetime import datetime
22
23from gajim.common import app
24from gajim.common import configpaths
25from gajim.common.i18n import _
26
27def parseLogLevel(arg):
28    """
29    Either numeric value or level name from logging module
30    """
31    if arg.isdigit():
32        return int(arg)
33    if arg.isupper() and hasattr(logging, arg):
34        return getattr(logging, arg)
35    print(_('%s is not a valid loglevel') % repr(arg), file=sys.stderr)
36    return 0
37
38def parseLogTarget(arg):
39    """
40    [gajim.]c.x.y  ->  gajim.c.x.y
41    .other_logger  ->  other_logger
42    <None>         ->  gajim
43    """
44    arg = arg.lower()
45    if not arg:
46        return 'gajim'
47    if arg.startswith('.'):
48        return arg[1:]
49    if arg.startswith('gajim'):
50        return arg
51    return 'gajim.' + arg
52
53def parseAndSetLogLevels(arg):
54    """
55    [=]LOGLEVEL     ->  gajim=LOGLEVEL
56    gajim=LOGLEVEL  ->  gajim=LOGLEVEL
57    .other=10       ->  other=10
58    .=10            ->  <nothing>
59    c.x.y=c.z=20    ->  gajim.c.x.y=20
60                        gajim.c.z=20
61    gajim=10,c.x=20 ->  gajim=10
62                        gajim.c.x=20
63    """
64    for directive in arg.split(','):
65        directive = directive.strip()
66        if not directive:
67            continue
68        if '=' not in directive:
69            directive = '=' + directive
70        targets, level = directive.rsplit('=', 1)
71        level = parseLogLevel(level.strip())
72        for target in targets.split('='):
73            target = parseLogTarget(target.strip())
74            if target:
75                logging.getLogger(target).setLevel(level)
76                print("Logger %s level set to %d" % (target, level),
77                      file=sys.stderr)
78
79
80class colors:
81    # pylint: disable=C0326
82    NONE         = chr(27) + "[0m"
83    BLACk        = chr(27) + "[30m"
84    RED          = chr(27) + "[31m"
85    GREEN        = chr(27) + "[32m"
86    BROWN        = chr(27) + "[33m"
87    BLUE         = chr(27) + "[34m"
88    MAGENTA      = chr(27) + "[35m"
89    CYAN         = chr(27) + "[36m"
90    LIGHT_GRAY   = chr(27) + "[37m"
91    DARK_GRAY    = chr(27) + "[30;1m"
92    BRIGHT_RED   = chr(27) + "[31;1m"
93    BRIGHT_GREEN = chr(27) + "[32;1m"
94    YELLOW       = chr(27) + "[33;1m"
95    BRIGHT_BLUE  = chr(27) + "[34;1m"
96    PURPLE       = chr(27) + "[35;1m"
97    BRIGHT_CYAN  = chr(27) + "[36;1m"
98    WHITE        = chr(27) + "[37;1m"
99
100def colorize(text, color):
101    return color + text + colors.NONE
102
103class FancyFormatter(logging.Formatter):
104    """
105    An eye-candy formatter with colors
106    """
107    colors_mapping = {
108        'DEBUG': colors.BLUE,
109        'INFO': colors.GREEN,
110        'WARNING': colors.BROWN,
111        'ERROR': colors.RED,
112        'CRITICAL': colors.BRIGHT_RED,
113    }
114
115    def __init__(self, fmt, datefmt=None, use_color=False):
116        logging.Formatter.__init__(self, fmt, datefmt)
117        self.use_color = use_color
118
119    def formatTime(self, record, datefmt=None):
120        f = logging.Formatter.formatTime(self, record, datefmt)
121        if self.use_color:
122            f = colorize(f, colors.DARK_GRAY)
123        return f
124
125    def format(self, record):
126        level = record.levelname
127        record.levelname = '(%s)' % level[0]
128
129        if self.use_color:
130            c = FancyFormatter.colors_mapping.get(level, '')
131            record.levelname = colorize(record.levelname, c)
132            record.name = '%-25s' % colorize(record.name, colors.CYAN)
133        else:
134            record.name = '%-25s|' % record.name
135
136        return logging.Formatter.format(self, record)
137
138
139def init():
140    """
141    Iinitialize the logging system
142    """
143
144    if app.get_debug_mode():
145        _cleanup_debug_logs()
146        _redirect_output()
147
148    use_color = False
149    if os.name != 'nt':
150        use_color = sys.stderr.isatty()
151
152    consoleloghandler = logging.StreamHandler()
153    consoleloghandler.setFormatter(
154        FancyFormatter(
155            '%(asctime)s %(levelname)s %(name)-35s %(message)s',
156            '%x %H:%M:%S',
157            use_color
158        )
159    )
160
161    root_log = logging.getLogger('gajim')
162    root_log.setLevel(logging.WARNING)
163    root_log.addHandler(consoleloghandler)
164    root_log.propagate = False
165
166    root_log = logging.getLogger('nbxmpp')
167    root_log.setLevel(logging.WARNING)
168    root_log.addHandler(consoleloghandler)
169    root_log.propagate = False
170
171    root_log = logging.getLogger('gnupg')
172    root_log.setLevel(logging.WARNING)
173    root_log.addHandler(consoleloghandler)
174    root_log.propagate = False
175
176    # GAJIM_DEBUG is set only on Windows when using Gajim-Debug.exe
177    # Gajim-Debug.exe shows a command line prompt and we want to redirect
178    # log output to it
179    if app.get_debug_mode() or os.environ.get('GAJIM_DEBUG', False):
180        set_verbose()
181
182def set_loglevels(loglevels_string):
183    parseAndSetLogLevels(loglevels_string)
184
185def set_verbose():
186    parseAndSetLogLevels('gajim=DEBUG')
187    parseAndSetLogLevels('.nbxmpp=INFO')
188
189def set_quiet():
190    parseAndSetLogLevels('gajim=CRITICAL')
191    parseAndSetLogLevels('.nbxmpp=CRITICAL')
192
193
194def _redirect_output():
195    debug_folder = configpaths.get('DEBUG')
196    date = datetime.today().strftime('%d%m%Y-%H%M%S')
197    filename = '%s-debug.log' % date
198    fd = open(debug_folder / filename, 'a')
199    sys.stderr = sys.stdout = fd
200
201
202def _cleanup_debug_logs():
203    debug_folder = configpaths.get('DEBUG')
204    debug_files = list(debug_folder.glob('*-debug.log*'))
205    now = time.time()
206    for file in debug_files:
207        # Delete everything older than 3 days
208        if file.stat().st_ctime < now - 259200:
209            file.unlink()
210
211
212
213# tests
214if __name__ == '__main__':
215    init()
216
217    set_loglevels('gajim.c=DEBUG,INFO')
218
219    log = logging.getLogger('gajim')
220    log.debug('debug')
221    log.info('info')
222    log.warning('warn')
223    log.error('error')
224    log.critical('critical')
225
226    log = logging.getLogger('gajim.c.x.dispatcher')
227    log.debug('debug')
228    log.info('info')
229    log.warning('warn')
230    log.error('error')
231    log.critical('critical')
232