aboutsummaryrefslogtreecommitdiffstats
path: root/pylog/onaplogging/mdcformatter.py
blob: 01056a46831c0f07a6e267a2a51d4a40a56df382 (plain)
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
150
151
152
153
154
155
156
157
158
# Copyright (c) 2018 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
import sys
import logging


class MDCFormatter(logging.Formatter):
    """
    A custom MDC formatter to prepare Mapped Diagnostic Context
    to enrich log message.
    """

    def __init__(self, fmt=None, mdcfmt=None, datefmt=None, style="%"):
        """
        :param fmt: build-in format string contains standard
               Python %-style mapping keys
        :param mdcFmt: mdc format with '{}'-style mapping keys
        :param datefmt: Date format to use
        :param style: style mapping keys in python3
        """
        if sys.version_info > (3, 2):
            super(MDCFormatter, self).__init__(fmt=fmt, datefmt=datefmt,
                                               style=style)
        elif sys.version_info > (2, 7):
            super(MDCFormatter, self).__init__(fmt=fmt, datefmt=datefmt)
        else:
            logging.Formatter.__init__(self, fmt, datefmt)

        self.style = style
        self._mdc_tag = "%(mdc)s"
        if sys.version_info > (3, 2):
            if self.style not in logging._STYLES:
                raise ValueError('Style must be one of: %s' % ','.join(
                        logging._STYLES.keys()))
            if self.style == "{":
                self._mdc_tag = "{mdc}"
            elif self.style == "$":
                self._mdc_tag = "${mdc}"

        self._tmpfmt = self._fmt
        if mdcfmt:
            self._mdcFmt = mdcfmt
        else:
            self._mdcFmt = '{reqeustID}'

    def _mdcfmtKey(self):
        """
         maximum barce match algorithm to find the mdc key
        :return: key in brace  and key not in brace,such as ({key}, key)
        """

        left = '{'
        right = '}'
        target = self._mdcFmt
        st = []
        keys = []
        for index, v in enumerate(target):
            if v == left:
                st.append(index)
            elif v == right:

                if len(st) == 0:
                    continue

                elif len(st) == 1:
                    start = st.pop()
                    end = index
                    keys.append(target[start:end + 1])
                elif len(st) > 0:
                    st.pop()

        keys = list(filter(lambda x: x[1:-1].strip('\n \t  ') != "", keys))
        words = None
        if keys:
            words = map(lambda x: x[1:-1], keys)

        return keys, words

    def _replaceStr(self, keys):

        fmt = self._mdcFmt
        for i in keys:
            fmt = fmt.replace(i, i[1:-1] + "=" + i)

        return fmt

    def format(self, record):
        """
        Find mdcs in log record extra field, if key form mdcFmt dosen't
        contains mdcs, the values will be empty.
        :param record: the logging record instance
        :return:  string
        for example:
            the mdcs dict in logging record is
            {'key1':'value1','key2':'value2'}
            the mdcFmt is" '{key1} {key3}'
            the output of mdc message: 'key1=value1 key3='

        """
        mdcIndex = self._fmt.find(self._mdc_tag)
        if mdcIndex == -1:
            if sys.version_info > (2, 7):
                return super(MDCFormatter, self).format(record)
            else:
                return logging.Formatter.format(self, record)

        mdcFmtkeys, mdcFmtWords = self._mdcfmtKey()

        if mdcFmtWords is None:
            if sys.version_info > (3, 2):
                self._style = logging._STYLES[self.style][0](
                    self._fmt.replace(self._mdc_tag, ""))
            else:
                self._fmt = self._fmt.replace(self._mdc_tag, "")
            if sys.version_info > (2, 7):
                return super(MDCFormatter, self).format(record)
            else:
                return logging.Formatter.format(self, record)

        mdc = record.__dict__.get('mdc', None)
        res = {}
        for i in mdcFmtWords:
            if mdc and i in mdc:
                res[i] = mdc[i]
            else:
                res[i] = ""

        del mdc
        try:
            mdcstr = self._replaceStr(keys=mdcFmtkeys).format(**res)
            if sys.version_info > (3, 2):
                self._style = logging._STYLES[self.style][0](
                    self._fmt.replace(self._mdc_tag, mdcstr))
            else:
                self._fmt = self._fmt.replace(self._mdc_tag, mdcstr)
            if sys.version_info > (2, 7):
                s = super(MDCFormatter, self).format(record)
            else:
                s = logging.Formatter.format(self, record)
            return s

        except KeyError as e:
            print("The mdc key %s format is wrong" % str(e))
        except Exception:
            raise

        finally:
            # reset fmt format
            self._fmt = self._tmpfmt