179 lines
5.0 KiB
Python
179 lines
5.0 KiB
Python
# -*- 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)
|