Expunge sensitive hostnames and IPs
[logging-analytics.git] / pylog / onaplogging / mdcformatter.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 from logging import LogRecord
17 from typing import Mapping, List, Dict, Callable
18 from deprecated import deprecated
19
20 from onaplogging.utils.system import is_above_python_2_7, is_above_python_3_2
21 from onaplogging.utils.styles import MDC_OPTIONS
22
23 from .markerFormatter import MarkerFormatter
24
25
26 class MDCFormatter(MarkerFormatter):
27     """A custom MDC formatter.
28
29     Prepares Mapped Diagnostic Context to enrich log message. If `fmt` is not
30     supplied, the `style` is used.
31
32     Extends:
33         MarkerFormatter
34     Args:
35         fmt             : Built-in format string containing standard Python
36                             %-style mapping keys in human-readable format.
37         mdcFmt          : MDC format with '{}'-style mapping keys.
38         datefmt         : Date format.
39         colorfmt        : colored output with an ANSI terminal escape code.
40         style           : style mapping keys in Python 3.x.
41     """
42
43     @property
44     def mdc_tag(self):
45         # type: () -> str
46         return self._mdc_tag
47
48     @property
49     def mdcfmt(self):
50         # type: () -> str
51         return self._mdcFmt
52
53     @mdc_tag.setter
54     def mdc_tag(self, value):
55         # type: (str) -> str
56         self._mdc_tag = value
57
58     @mdcfmt.setter
59     def mdcfmt(self, value):
60         # type: (str) -> str
61         self._mdc_tag = value
62
63     def __init__(self,
64                  fmt=None,          # type: str
65                  mdcfmt=None,       # type: str
66                  datefmt=None,      # type: str
67                  colorfmt=None,     # type: str
68                  style="%"):        # type: str
69
70         if is_above_python_3_2():
71             super(MDCFormatter, self).__init__(fmt=fmt,
72                                                datefmt=datefmt,
73                                                colorfmt=colorfmt,
74                                                style=style)
75         elif is_above_python_2_7():
76             super(MDCFormatter, self).__init__(fmt=fmt,
77                                                datefmt=datefmt,
78                                                colorfmt=colorfmt)
79         else:
80             MarkerFormatter.\
81             __init__(self, fmt, datefmt, colorfmt)  # noqa: E122
82
83         self._mdc_tag = MDC_OPTIONS[self.style]
84         self._mdcFmt = mdcfmt if mdcfmt else '{reqeustID}'
85
86     def format(self, record):
87         # type: (LogRecord) -> str
88         """
89         Find MDCs in a log record's extra field. If the key from mdcFmt
90         doesn't contain MDC, the values will be empty.
91
92         For example:
93         The MDC dict in a logging record is {'key1':'value1','key2':'value2'}.
94         The mdcFmt is '{key1} {key3}'.
95         The output of MDC message is 'key1=value1 key3='.
96
97         Args:
98             record  : an instance of a logged event.
99         Returns:
100             str     : "colored" text (formatted text).
101         """
102
103         mdc_index = self._fmt.find(self._mdc_tag)
104         if mdc_index == -1:
105             return self._parent_format(record)
106
107         mdc_format_keys, mdc_format_words = self._mdc_format_key()
108
109         if mdc_format_words is None:
110             self._fmt = self._replace_mdc_tag_str("")
111             self._apply_styling()
112
113             return self._parent_format(record)
114
115         res = self._apply_mdc(record, mdc_format_words)
116
117         try:
118             mdc_string = self._replaceStr(keys=mdc_format_keys).format(**res)
119             self._fmt = self._replace_mdc_tag_str(mdc_string)
120             self._apply_styling()
121
122             return self._parent_format(record)
123
124         except KeyError as e:
125             # is there a need for print?
126             print("The mdc key %s format is wrong" % str(e))
127
128         except Exception:
129             raise
130
131     def _mdc_format_key(self):
132         # type: () -> (List, Mapping[str, str])
133         """Maximum (balanced) parantehses matching algorithm for MDC keys.
134
135         Extracts and strips keys and words from a MDC format string. Use this
136         method to find the MDC key.
137
138         Returns:
139             list        : list of keys.
140             map object  : keys with and without brace, such as ({key}, key).
141         """
142
143         left = '{'
144         right = '}'
145         target = self._mdcFmt
146         stack = []
147         keys = []
148
149         for index, v in enumerate(target):
150             if v == left:
151                 stack.append(index)
152             elif v == right:
153
154                 if len(stack) == 0:
155                     continue
156
157                 elif len(stack) == 1:
158                     start = stack.pop()
159                     end = index
160                     keys.append(target[start:end + 1])
161                 elif len(stack) > 0:
162                     stack.pop()
163
164         keys = list(filter(lambda x: x[1:-1].strip('\n \t  ') != "", keys))
165         words = None
166
167         if keys:
168             words = map(lambda x: x[1:-1], keys)
169
170         return keys, words
171
172     def _replace_string(self, keys):
173         # type: (List[str]) -> str
174         """
175         Removes the first and last characters from each key and assigns not
176         stripped keys.
177         """
178         fmt = self._mdcFmt
179         for key in keys:
180             fmt = fmt.replace(key, key[1:-1] + "=" + key)
181         return fmt
182
183     def _parent_format(self, record):
184         # type: (LogRecord) -> str
185         """Call super class's format based on Python version."""
186         if is_above_python_2_7():
187             return super(MDCFormatter, self).format(record)
188         else:
189             return MarkerFormatter.format(self, record)
190
191     def _apply_mdc(self, record, mdc_format_words):
192         # type: (LogRecord, Mapping[Callable[[str], str], List]) -> Dict
193         """Apply MDC pamming to the LogRecord record."""
194         mdc = record.__dict__.get('mdc', None)
195         res = {}
196
197         for i in mdc_format_words:
198             if mdc and i in mdc:
199                 res[i] = mdc[i]
200             else:
201                 res[i] = ""
202         del mdc
203         return res
204
205     def _apply_styling(self):
206         # type: () -> None
207         """Apply styling to the formatter if using Python 3.2+"""
208         if is_above_python_3_2():
209             StylingClass = logging._STYLES[self.style][0](self._fmt)
210             self._style = StylingClass
211
212     def _replace_mdc_tag_str(self, replacement):
213         # type: (str) -> str
214         """Replace MDC tag in the format string."""
215         return self._fmt.replace(self._mdc_tag, replacement)
216
217     @deprecated(reason="Will be replaced. Use _mdc_format_key() instead.")
218     def _mdcfmtKey(self):
219         """See _mdc_format_key()."""
220         return self._mdc_format_key()
221
222     @deprecated(reason="Will be replaced. Use _replace_string(keys) instead.")
223     def _replaceStr(self, keys):
224         """See _replace_string(keys)."""
225         return self._replace_string(keys)