forked from Flexget/Flexget
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogger.py
149 lines (117 loc) · 4.81 KB
/
logger.py
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from __future__ import absolute_import, division, unicode_literals
import contextlib
import logging
import logging.handlers
import sys
import threading
import uuid
import warnings
# A level more detailed than DEBUG
TRACE = 5
# A level more detailed than INFO
VERBOSE = 15
@contextlib.contextmanager
def task_logging(task):
"""Context manager which adds task information to log messages."""
old_task = getattr(FlexGetLogger.local, 'task', '')
FlexGetLogger.local.task = task
try:
yield
finally:
FlexGetLogger.local.task = old_task
@contextlib.contextmanager
def command_logging(command, command_id=None):
"""Context manager which adds command information to log messages."""
old_command = getattr(FlexGetLogger.local, 'command', '')
old_id = getattr(FlexGetLogger.local, 'command_id', '')
# If command id not given, keep using current, or create one if none already set
FlexGetLogger.local.command_id = command_id or old_id or uuid.uuid4()
FlexGetLogger.local.command = command
try:
yield
finally:
FlexGetLogger.local.command = old_command
FlexGetLogger.local.command_id = old_id
class FlexGetLogger(logging.Logger):
"""Custom logger that adds trace and verbose logging methods, and contextual information to log records."""
local = threading.local()
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
extra = extra or {}
extra.update(
task=getattr(self.local, 'task', ''),
command=getattr(self.local, 'command', ''),
command_id=getattr(self.local, 'command_id', ''))
# Replace newlines in log messages with \n
msg = msg.replace('\n', '\\n')
return super(FlexGetLogger, self).makeRecord(name, level, fn, lno, msg, args, exc_info, func, extra)
def trace(self, msg, *args, **kwargs):
"""Log at TRACE level (more detailed than DEBUG)."""
self.log(TRACE, msg, *args, **kwargs)
def verbose(self, msg, *args, **kwargs):
"""Log at VERBOSE level (displayed when FlexGet is run interactively.)"""
self.log(VERBOSE, msg, *args, **kwargs)
class FlexGetFormatter(logging.Formatter):
"""Custom formatter that can handle both regular log records and those created by FlexGetLogger"""
plain_fmt = '%(asctime)-15s %(levelname)-8s %(name)-29s %(message)s'
flexget_fmt = '%(asctime)-15s %(levelname)-8s %(name)-13s %(task)-15s %(message)s'
def __init__(self):
logging.Formatter.__init__(self, self.plain_fmt, '%Y-%m-%d %H:%M')
def format(self, record):
self._fmt = self.flexget_fmt if hasattr(record, 'task') else self.plain_fmt
return super(FlexGetFormatter, self).format(record)
_logging_configured = False
_buff_handler = None
_logging_started = False
def initialize(unit_test=False):
"""Prepare logging.
"""
global _logging_configured, _logging_started, _buff_handler
if _logging_configured:
return
warnings.simplefilter('once')
logging.addLevelName(TRACE, 'TRACE')
logging.addLevelName(VERBOSE, 'VERBOSE')
_logging_configured = True
# with unit test we want a bit simpler setup
if unit_test:
logging.basicConfig()
_logging_started = True
return
# Store any log messages in a buffer until we `start` function is run
logger = logging.getLogger()
_buff_handler = logging.handlers.BufferingHandler(1000 * 1000)
logger.addHandler(_buff_handler)
logger.setLevel(logging.NOTSET)
def start(filename=None, level=logging.INFO, to_console=True, to_file=True):
"""After initialization, start file logging.
"""
global _logging_started
assert _logging_configured
if _logging_started:
return
# root logger
logger = logging.getLogger()
if not isinstance(level, int):
# Python logging api is horrible. This is getting the level number, which is required on python 2.6.
level = logging.getLevelName(level)
logger.setLevel(level)
formatter = FlexGetFormatter()
if to_file:
file_handler = logging.handlers.RotatingFileHandler(filename, maxBytes=1000 * 1024, backupCount=9)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# without --cron we log to console
if to_console:
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# flush what we have stored from the plugin initialization
logger.removeHandler(_buff_handler)
if _buff_handler:
for record in _buff_handler.buffer:
if logger.isEnabledFor(record.levelno):
logger.handle(record)
_buff_handler.flush()
_logging_started = True
# Set our custom logger class as default
logging.setLoggerClass(FlexGetLogger)