Merge "Pylog test suite"
[logging-analytics.git] / pylog / onaplogging / mdcContext.py
1 # Copyright 2018 ke liang <lokyse@163.com>.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import logging
16 import threading
17 import io
18 import os
19 import traceback
20 import sys
21 import functools
22 from .marker import Marker
23 from .marker import MARKER_TAG
24
25 __all__ = ['patch_loggingMDC', 'MDC']
26
27 _replace_func_name = ['info', 'critical', 'fatal', 'debug',
28                       'error', 'warn', 'warning', 'log',
29                       'handle', 'findCaller']
30
31
32 class MDCContext(threading.local):
33     """
34     A Thread local instance to storage mdc values
35     """
36     def __init__(self):
37
38         super(MDCContext, self).__init__()
39         self._localDict = {}
40
41     def get(self, key):
42
43         return self._localDict.get(key, None)
44
45     def put(self, key, value):
46
47         self._localDict[key] = value
48
49     def remove(self, key):
50
51         if key in self._localDict:
52             del self._localDict[key]
53
54     def clear(self):
55
56         self._localDict.clear()
57
58     def result(self):
59
60         return self._localDict
61
62     def isEmpty(self):
63
64         return self._localDict == {} or self._localDict is None
65
66
67 MDC = MDCContext()
68
69
70 def fetchkeys(func):
71
72     @functools.wraps(func)
73     def replace(*args, **kwargs):
74         kwargs['extra'] = _getmdcs(extra=kwargs.get('extra', None))
75         func(*args, **kwargs)
76     return replace
77
78
79 def _getmdcs(extra=None):
80     """
81     Put mdc dict in logging record extra filed with key 'mdc'
82     :param extra: dict
83     :return: mdc dict
84     """
85     if MDC.isEmpty():
86         return extra
87
88     mdc = MDC.result()
89
90     if extra is not None:
91         for key in extra:
92             #  make sure extra key dosen't override mdckey
93             if key in mdc or key == 'mdc':
94                 raise KeyError("Attempt to overwrite %r in MDC" % key)
95     else:
96         extra = {}
97
98     extra['mdc'] = mdc
99
100     del mdc
101     return extra
102
103
104 @fetchkeys
105 def info(self, msg, *args, **kwargs):
106
107     if self.isEnabledFor(logging.INFO):
108         self._log(logging.INFO, msg, args, **kwargs)
109
110
111 @fetchkeys
112 def debug(self, msg, *args, **kwargs):
113     if self.isEnabledFor(logging.DEBUG):
114         self._log(logging.DEBUG, msg, args, **kwargs)
115
116
117 @fetchkeys
118 def warning(self, msg, *args, **kwargs):
119     if self.isEnabledFor(logging.WARNING):
120         self._log(logging.WARNING, msg, args, **kwargs)
121
122
123 @fetchkeys
124 def exception(self, msg, *args, **kwargs):
125
126     kwargs['exc_info'] = 1
127     self.error(msg, *args, **kwargs)
128
129
130 @fetchkeys
131 def critical(self, msg, *args, **kwargs):
132
133     if self.isEnabledFor(logging.CRITICAL):
134         self._log(logging.CRITICAL, msg, args, **kwargs)
135
136
137 @fetchkeys
138 def error(self, msg, *args, **kwargs):
139     if self.isEnabledFor(logging.ERROR):
140         self._log(logging.ERROR, msg, args, **kwargs)
141
142
143 @fetchkeys
144 def log(self, level, msg, *args, **kwargs):
145
146     if not isinstance(level, int):
147         if logging.raiseExceptions:
148             raise TypeError("level must be an integer")
149         else:
150             return
151
152     if self.isEnabledFor(level):
153         self._log(level, msg, args, **kwargs)
154
155
156 def handle(self, record):
157
158     cmarker = getattr(self, MARKER_TAG, None)
159
160     if isinstance(cmarker, Marker):
161         setattr(record, MARKER_TAG, cmarker)
162
163     if (not self.disabled) and self.filter(record):
164         self.callHandlers(record)
165
166
167 def findCaller(self, stack_info=False):
168
169     f = logging.currentframe()
170     if f is not None:
171         f = f.f_back
172     rv = "(unkown file)", 0, "(unknow function)"
173     while hasattr(f, "f_code"):
174         co = f.f_code
175         filename = os.path.normcase(co.co_filename)
176         # jump through local 'replace' func frame
177         if filename == logging._srcfile or co.co_name == "replace":
178             f = f.f_back
179             continue
180         if sys.version_info > (3, 2):
181             sinfo = None
182             if stack_info:
183                 sio = io.StringIO()
184                 sio.write("Stack (most recent call last):\n")
185                 traceback.print_stack(f, file=sio)
186                 sinfo = sio.getvalue()
187                 if sinfo[-1] == '\n':
188                     sinfo = sinfo[:-1]
189                 sio.close()
190             rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
191         else:
192             rv = (co.co_filename, f.f_lineno, co.co_name)
193
194         break
195
196     return rv
197
198
199 def patch_loggingMDC():
200     """
201     The patch to add MDC ability in logging Record instance at runtime
202     """
203     localModule = sys.modules[__name__]
204     for attr in dir(logging.Logger):
205         if attr in _replace_func_name:
206             newfunc = getattr(localModule, attr, None)
207             if newfunc:
208                 setattr(logging.Logger, attr, newfunc)