aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/logger/_flatten.py
blob: a6f06ed60d7c4f14000a08f1fbc9369e63d9d4c3 (plain) (blame)
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# -*- test-case-name: twisted.logger.test.test_flatten -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Code related to "flattening" events; that is, extracting a description of all
relevant fields from the format string and persisting them for later
examination.
"""

from string import Formatter
from collections import defaultdict

from twisted.python.compat import unicode

aFormatter = Formatter()



class KeyFlattener(object):
    """
    A L{KeyFlattener} computes keys for the things within curly braces in
    PEP-3101-style format strings as parsed by L{string.Formatter.parse}.
    """

    def __init__(self):
        """
        Initialize a L{KeyFlattener}.
        """
        self.keys = defaultdict(lambda: 0)


    def flatKey(self, fieldName, formatSpec, conversion):
        """
        Compute a string key for a given field/format/conversion.

        @param fieldName: A format field name.
        @type fieldName: L{str}

        @param formatSpec: A format spec.
        @type formatSpec: L{str}

        @param conversion: A format field conversion type.
        @type conversion: L{str}

        @return: A key specific to the given field, format and conversion, as
            well as the occurrence of that combination within this
            L{KeyFlattener}'s lifetime.
        @rtype: L{str}
        """
        result = (
            "{fieldName}!{conversion}:{formatSpec}"
            .format(
                fieldName=fieldName,
                formatSpec=(formatSpec or ""),
                conversion=(conversion or ""),
            )
        )
        self.keys[result] += 1
        n = self.keys[result]
        if n != 1:
            result += "/" + str(self.keys[result])
        return result



def flattenEvent(event):
    """
    Flatten the given event by pre-associating format fields with specific
    objects and callable results in a L{dict} put into the C{"log_flattened"}
    key in the event.

    @param event: A logging event.
    @type event: L{dict}
    """
    if event.get("log_format", None) is None:
        return

    if "log_flattened" in event:
        fields = event["log_flattened"]
    else:
        fields = {}

    keyFlattener = KeyFlattener()

    for (literalText, fieldName, formatSpec, conversion) in (
        aFormatter.parse(event["log_format"])
    ):
        if fieldName is None:
            continue

        if conversion != "r":
            conversion = "s"

        flattenedKey = keyFlattener.flatKey(fieldName, formatSpec, conversion)
        structuredKey = keyFlattener.flatKey(fieldName, formatSpec, "")

        if flattenedKey in fields:
            # We've already seen and handled this key
            continue

        if fieldName.endswith(u"()"):
            fieldName = fieldName[:-2]
            callit = True
        else:
            callit = False

        field = aFormatter.get_field(fieldName, (), event)
        fieldValue = field[0]

        if conversion == "r":
            conversionFunction = repr
        else:  # Above: if conversion is not "r", it's "s"
            conversionFunction = unicode

        if callit:
            fieldValue = fieldValue()

        flattenedValue = conversionFunction(fieldValue)
        fields[flattenedKey] = flattenedValue
        fields[structuredKey] = fieldValue

    if fields:
        event["log_flattened"] = fields



def extractField(field, event):
    """
    Extract a given format field from the given event.

    @param field: A string describing a format field or log key.  This is the
        text that would normally fall between a pair of curly braces in a
        format string: for example, C{"key[2].attribute"}.  If a conversion is
        specified (the thing after the C{"!"} character in a format field) then
        the result will always be L{unicode}.
    @type field: L{str} (native string)

    @param event: A log event.
    @type event: L{dict}

    @return: A value extracted from the field.
    @rtype: L{object}

    @raise KeyError: if the field is not found in the given event.
    """
    keyFlattener = KeyFlattener()
    [[literalText, fieldName, formatSpec, conversion]] = aFormatter.parse(
        "{" + field + "}"
    )
    key = keyFlattener.flatKey(fieldName, formatSpec, conversion)
    if "log_flattened" not in event:
        flattenEvent(event)
    return event["log_flattened"][key]



def flatFormat(event):
    """
    Format an event which has been flattened with L{flattenEvent}.

    @param event: A logging event.
    @type event: L{dict}

    @return: A formatted string.
    @rtype: L{unicode}
    """
    fieldValues = event["log_flattened"]
    s = []
    keyFlattener = KeyFlattener()
    formatFields = aFormatter.parse(event["log_format"])
    for literalText, fieldName, formatSpec, conversion in formatFields:
        s.append(literalText)
        if fieldName is not None:
            key = keyFlattener.flatKey(
                    fieldName, formatSpec, conversion or "s")
            s.append(unicode(fieldValues[key]))
    return u"".join(s)