14 |
14 |
# You should have received a copy of the GNU Affero General Public License
|
15 |
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 |
16 |
|
17 |
|
import logging
|
18 |
|
import datetime
|
19 |
17 |
import random
|
20 |
18 |
import struct
|
21 |
19 |
import time
|
... | ... | |
36 |
34 |
from . import app_settings, utils, plugins
|
37 |
35 |
|
38 |
36 |
|
39 |
|
class ThreadCollector(object):
|
40 |
|
def __init__(self):
|
41 |
|
if threading is None:
|
42 |
|
raise NotImplementedError(
|
43 |
|
"threading module is not available, "
|
44 |
|
"this panel cannot be used without it")
|
45 |
|
self.collections = {} # a dictionary that maps threads to collections
|
46 |
|
|
47 |
|
def get_collection(self, thread=None):
|
48 |
|
"""
|
49 |
|
Returns a list of collected items for the provided thread, of if none
|
50 |
|
is provided, returns a list for the current thread.
|
51 |
|
"""
|
52 |
|
if thread is None:
|
53 |
|
thread = threading.currentThread()
|
54 |
|
if thread not in self.collections:
|
55 |
|
self.collections[thread] = []
|
56 |
|
return self.collections[thread]
|
57 |
|
|
58 |
|
def clear_collection(self, thread=None):
|
59 |
|
if thread is None:
|
60 |
|
thread = threading.currentThread()
|
61 |
|
if thread in self.collections:
|
62 |
|
del self.collections[thread]
|
63 |
|
|
64 |
|
def collect(self, item, thread=None):
|
65 |
|
self.get_collection(thread).append(item)
|
66 |
|
|
67 |
|
MESSAGE_IF_STRING_REPRESENTATION_INVALID = '[Could not get log message]'
|
68 |
|
|
69 |
|
|
70 |
|
class ThreadTrackingHandler(logging.Handler):
|
71 |
|
def __init__(self, collector):
|
72 |
|
logging.Handler.__init__(self)
|
73 |
|
self.collector = collector
|
74 |
|
|
75 |
|
def emit(self, record):
|
76 |
|
try:
|
77 |
|
message = self.format(record)
|
78 |
|
except Exception:
|
79 |
|
message = MESSAGE_IF_STRING_REPRESENTATION_INVALID
|
80 |
|
|
81 |
|
record = {
|
82 |
|
'message': message,
|
83 |
|
'time': datetime.datetime.fromtimestamp(record.created),
|
84 |
|
'level': record.levelname,
|
85 |
|
'file': record.pathname,
|
86 |
|
'line': record.lineno,
|
87 |
|
'channel': record.name,
|
88 |
|
}
|
89 |
|
self.collector.collect(record)
|
90 |
|
|
91 |
|
|
92 |
|
# We don't use enable/disable_instrumentation because logging is global.
|
93 |
|
# We can't add thread-local logging handlers. Hopefully logging is cheap.
|
94 |
|
|
95 |
|
collector = ThreadCollector()
|
96 |
|
logging_handler = ThreadTrackingHandler(collector)
|
97 |
|
logging.root.addHandler(logging_handler)
|
98 |
|
|
99 |
|
|
100 |
|
class LoggingCollectorMiddleware(MiddlewareMixin):
|
101 |
|
def process_request(self, request):
|
102 |
|
collector.clear_collection()
|
103 |
|
|
104 |
|
def show_logs(self, request):
|
105 |
|
if request.META.get('REMOTE_ADDR', None) in settings.INTERNAL_IPS:
|
106 |
|
return True
|
107 |
|
|
108 |
|
def process_exception(self, request, exception):
|
109 |
|
if self.show_logs(request):
|
110 |
|
request.logs = collector.get_collection()
|
111 |
|
request.exception = exception
|
112 |
|
|
113 |
|
|
114 |
37 |
class CollectIPMiddleware(MiddlewareMixin):
|
115 |
38 |
def process_response(self, request, response):
|
116 |
39 |
# only collect IP if session is used
|