Submit python logging library seed code
[logging-analytics.git] / pylog / onaplogging / mdcContext.py
1 # Copyright (c) 2018 VMware, Inc.
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
13
14 import logging
15 import threading
16 import os
17 import sys
18 import functools
19
20
21 __all__ = ['patch_loggingMDC', 'MDC']
22
23 _replace_func_name = ['info', 'critical', 'fatal', 'debug',
24                       'error', 'warn', 'warning', 'findCaller']
25
26
27 class MDCContext(threading.local):
28     """
29     A Thread local instance to storage mdc values
30     """
31     def __init__(self):
32
33         super(MDCContext, self).__init__()
34         self._localDict = {}
35
36     def get(self, key):
37
38         return self._localDict.get(key, None)
39
40     def put(self, key, value):
41
42         self._localDict[key] = value
43
44     def remove(self, key):
45
46         if key in self.localDict:
47             del self._localDict[key]
48
49     def clear(self):
50
51         self._localDict.clear()
52
53     def result(self):
54
55         return self._localDict
56
57     def isEmpty(self):
58
59         return self._localDict == {} or self._localDict is None
60
61
62 MDC = MDCContext()
63
64
65 def fetchkeys(func):
66
67     @functools.wraps(func)
68     def replace(*args, **kwargs):
69         kwargs['extra'] = _getmdcs(extra=kwargs.get('extra', None))
70         func(*args, **kwargs)
71     return replace
72
73
74 def _getmdcs(extra=None):
75     """
76     Put mdc dict in logging record extra filed with key 'mdc'
77     :param extra: dict
78     :return: mdc dict
79     """
80     if MDC.isEmpty():
81         return
82
83     mdc = MDC.result()
84
85     if extra is not None:
86         for key in extra:
87             #  make sure extra key dosen't override mdckey
88             if key in mdc or key == 'mdc':
89                     raise KeyError("Attempt to overwrite %r in MDC" % key)
90     else:
91         extra = {}
92
93     extra['mdc'] = mdc
94     del mdc
95     return extra
96
97
98 @fetchkeys
99 def info(self, msg, *args, **kwargs):
100
101     if self.isEnabledFor(logging.INFO):
102         self._log(logging.INFO, msg, args, **kwargs)
103
104
105 @fetchkeys
106 def debug(self, msg, *args, **kwargs):
107
108     if self.isEnabledFor(logging.DEBUG):
109         self._log(logging.DEBUG, msg, args, **kwargs)
110
111
112 @fetchkeys
113 def warning(self, msg, *args, **kwargs):
114     if self.isEnabledFor(logging.WARNING):
115         self._log(logging.WARNING, msg, args, **kwargs)
116
117
118 @fetchkeys
119 def exception(self, msg, *args, **kwargs):
120
121     kwargs['exc_info'] = 1
122     self.error(msg, *args, **kwargs)
123
124
125 @fetchkeys
126 def critical(self, msg, *args, **kwargs):
127
128     if self.isEnabledFor(logging.CRITICAL):
129         self._log(logging.CRITICAL, msg, args, **kwargs)
130
131
132 @fetchkeys
133 def error(self, msg, *args, **kwargs):
134     if self.isEnabledFor(logging.ERROR):
135         self._log(logging.ERROR, msg, args, **kwargs)
136
137
138 def findCaller(self):
139
140     f = logging.currentframe()
141     if f is not None:
142         f = f.f_back
143     rv = "(unkown file)", 0, "(unknow function)"
144     while hasattr(f, "f_code"):
145         co = f.f_code
146         filename = os.path.normcase(co.co_filename)
147         # jump through local 'replace' func frame
148         if filename == logging._srcfile or co.co_name == "replace":
149             f = f.f_back
150             continue
151         rv = (co.co_filename, f.f_lineno, co.co_name)
152         break
153
154     return rv
155
156
157 def patch_loggingMDC():
158     """
159     The patch to add MDC ability in logging Record instance at runtime
160     """
161     localModule = sys.modules[__name__]
162     for attr in dir(logging.Logger):
163         if attr in _replace_func_name:
164             newfunc = getattr(localModule, attr, None)
165             if newfunc:
166                 setattr(logging.Logger, attr, newfunc)