Passed
Push — master ( 6e8656...9792e6 )
by Dean
05:08 queued 02:39
created

LoggerManager.callback()   A

Complexity

Conditions 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
ccs 0
cts 5
cp 0
cc 2
crap 6
1 1
from plugin.core.constants import PLUGIN_IDENTIFIER
2 1
from plugin.core.environment import Environment
3 1
from plugin.core.helpers.variable import md5
4 1
from plugin.core.logger.filters import FrameworkFilter, AuthorizationFilter, RequestsLogFilter
5
6 1
from logging.handlers import RotatingFileHandler
7 1
from raven.utils import gethostname
8 1
import logging
9 1
import uuid
10 1
import warnings
11
12 1
LOG_FORMAT = '%(asctime)-15s - %(name)-32s (%(thread)x) :  %(levelname)s (%(name)s:%(lineno)d) - %(message)s'
13 1
LOG_OPTIONS = {
14
    'plex':                         'libraries',
15
    'plex_activity':                'libraries',
16
    'plex_metadata':                'libraries',
17
    'raven':                        'libraries',
18
    'requests':                     'libraries',
19
    'trakt':                        'libraries',
20
21
    'peewee':                       'peewee',
22
    'peewee_migrate':               'peewee',
23
24
    'com.plexapp.plugins.trakttv':  'plugin',
25
    'plugin':                       'plugin',
26
27
    'pyemitter':                    'pyemitter'
28
}
29
30 1
TRACE = 5
31
32 1
log = logging.getLogger(__name__)
33
34
35 1
class LogHandler(logging.Handler):
36 1
    def __init__(self, handler):
37
        super(LogHandler, self).__init__()
38
39
        self.handler = handler
40
41
        # Update formatter for log file
42
        self.handler.formatter._fmt = LOG_FORMAT
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _fmt was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
43
44 1
    @property
45
    def baseFilename(self):
46
        return self.handler.baseFilename
47
48 1
    def emit(self, record):
49
        return self.handler.emit(record)
50
51
52 1
class LoggerManager(object):
53 1
    @staticmethod
54
    def get_handler():
55 1
        logger = logging.getLogger(PLUGIN_IDENTIFIER)
56
57 1
        for h in logger.handlers:
58
            if type(h) is RotatingFileHandler:
59
                logger.handlers.remove(h)
60
                return LogHandler(h)
61
62 1
        return None
63
64 1
    @classmethod
65 1
    def setup(cls, report=True, storage=True):
66
        cls.setup_logging(report, storage)
67
        cls.setup_warnings()
68
69
        if report:
70
            cls.setup_raven()
71
72
        log.debug('Initialized logging (report: %r, storage: %r)', report, storage)
73
74 1
    @staticmethod
75 1
    def setup_logging(report=True, storage=True):
76
        # Initialize "root" logger
77
        rootLogger = logging.getLogger()
78
        rootLogger.setLevel(logging.DEBUG)
79
80
        rootLogger.filters = [
81
            FrameworkFilter()
82
        ]
83
84
        rootLogger.handlers = [
85
            LOG_HANDLER
86
        ]
87
88
        # Initialize "com.plexapp.plugins.trakttv" logger
89
        pluginLogger = logging.getLogger('com.plexapp.plugins.trakttv')
90
        pluginLogger.setLevel(logging.DEBUG)
91
92
        pluginLogger.filters = [
93
            FrameworkFilter()
94
        ]
95
96
        # Setup error reporting (if enabled)
97
        if report:
98
            from plugin.core.logger.handlers.error_reporter import PLUGIN_REPORTER_HANDLER, TRAKT_REPORTER_HANDLER
99
100
            rootLogger.handlers.append(PLUGIN_REPORTER_HANDLER)
101
            rootLogger.handlers.append(TRAKT_REPORTER_HANDLER)
102
103
        # Setup local error storage (if enabled)
104
        if storage:
105
            from plugin.core.logger.handlers.error_storage import ERROR_STORAGE_HANDLER
106
107
            rootLogger.handlers.append(ERROR_STORAGE_HANDLER)
108
109 1
    @classmethod
110
    def setup_raven(cls):
111
        from plugin.core.logger.handlers.error_reporter import ErrorReporter
112
113
        # Generate client identifier
114
        ErrorReporter.set_name(cls.generate_id())
115
116
        # Include server version in error reports
117
        try:
118
            ErrorReporter.set_tags({
119
                'server.version': Environment.platform.server_version
120
            })
121
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
122
            log.warn('Unable to retrieve server version - %s', ex, exc_info=True)
123
124 1
    @classmethod
125
    def setup_warnings(cls):
126
        logger = logging.getLogger('warnings')
127
128
        def callback(message, category, filename, lineno, file=None, line=None):
0 ignored issues
show
Unused Code introduced by
The argument line seems to be unused.
Loading history...
Unused Code introduced by
The argument filename seems to be unused.
Loading history...
Unused Code introduced by
The argument file seems to be unused.
Loading history...
Unused Code introduced by
The argument lineno seems to be unused.
Loading history...
129
            if not category:
130
                logger.warn(message)
131
                return
132
133
            logger.warn('[%s] %s' % (category.__name__, message))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
134
135
        warnings.showwarning = callback
136
137 1
    @staticmethod
138
    def generate_id():
139
        # Try use hashed machine identifier
140
        try:
141
            return md5(Environment.platform.machine_identifier)
142
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
143
            log.warn('Unable to generate id from machine identifier - %s', ex, exc_info=True)
144
145
        # Try use hashed hostname
146
        try:
147
            return md5(gethostname().encode('utf-8'))
148
        except Exception as ex:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
149
            log.warn('Unable to generate id from hostname - %s', ex, exc_info=True)
150
151
        # Fallback to generated identifier
152
        return 'generated-' + md5(str(uuid.uuid4()))
153
154 1
    @classmethod
155
    def refresh(cls):
156
        for name, option in LOG_OPTIONS.items():
157
            logger = logging.getLogger(name)
158
159
            # Retrieve logger level, check if it has changed
160
            level = cls.get_level(option)
161
162
            if level == logger.level:
163
                continue
164
165
            # Update logger level
166
            log.debug('Changed %r logger level to %s', name, logging.getLevelName(level))
167
168
            logger.setLevel(level)
169
170 1
    @staticmethod
171
    def get_level(option):
172
        # Try retrieve preference
173
        try:
174
            value = Environment.prefs['level_%s' % option]
175
        except KeyError:
176
            # Default to "plugin" preference
177
            value = Environment.prefs['level_plugin']
178
179
        # Parse labels into level attributes
180
        if value == 'ERROR':
181
            return logging.ERROR
182
183
        if value == 'WARN' or value == 'WARNING':
184
            return logging.WARNING
185
186
        if value == 'INFO':
187
            return logging.INFO
188
189
        if value == 'DEBUG':
190
            return logging.DEBUG
191
192
        if value == "TRACE":
193
            return TRACE
194
195
        log.warn('Unknown logging level "%s"', value)
196
        return logging.DEBUG
197
198
# Get the logging file handler
199 1
LOG_HANDLER = LoggerManager.get_handler()
200
201 1
if LOG_HANDLER:
202
    LOG_HANDLER.addFilter(AuthorizationFilter())
203
    LOG_HANDLER.addFilter(RequestsLogFilter())
204