_flatten.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. # -*- test-case-name: twisted.logger.test.test_flatten -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Code related to "flattening" events; that is, extracting a description of all
  6. relevant fields from the format string and persisting them for later
  7. examination.
  8. """
  9. from string import Formatter
  10. from collections import defaultdict
  11. from twisted.python.compat import unicode
  12. aFormatter = Formatter()
  13. class KeyFlattener(object):
  14. """
  15. A L{KeyFlattener} computes keys for the things within curly braces in
  16. PEP-3101-style format strings as parsed by L{string.Formatter.parse}.
  17. """
  18. def __init__(self):
  19. """
  20. Initialize a L{KeyFlattener}.
  21. """
  22. self.keys = defaultdict(lambda: 0)
  23. def flatKey(self, fieldName, formatSpec, conversion):
  24. """
  25. Compute a string key for a given field/format/conversion.
  26. @param fieldName: A format field name.
  27. @type fieldName: L{str}
  28. @param formatSpec: A format spec.
  29. @type formatSpec: L{str}
  30. @param conversion: A format field conversion type.
  31. @type conversion: L{str}
  32. @return: A key specific to the given field, format and conversion, as
  33. well as the occurrence of that combination within this
  34. L{KeyFlattener}'s lifetime.
  35. @rtype: L{str}
  36. """
  37. result = (
  38. "{fieldName}!{conversion}:{formatSpec}"
  39. .format(
  40. fieldName=fieldName,
  41. formatSpec=(formatSpec or ""),
  42. conversion=(conversion or ""),
  43. )
  44. )
  45. self.keys[result] += 1
  46. n = self.keys[result]
  47. if n != 1:
  48. result += "/" + str(self.keys[result])
  49. return result
  50. def flattenEvent(event):
  51. """
  52. Flatten the given event by pre-associating format fields with specific
  53. objects and callable results in a L{dict} put into the C{"log_flattened"}
  54. key in the event.
  55. @param event: A logging event.
  56. @type event: L{dict}
  57. """
  58. if event.get("log_format", None) is None:
  59. return
  60. if "log_flattened" in event:
  61. fields = event["log_flattened"]
  62. else:
  63. fields = {}
  64. keyFlattener = KeyFlattener()
  65. for (literalText, fieldName, formatSpec, conversion) in (
  66. aFormatter.parse(event["log_format"])
  67. ):
  68. if fieldName is None:
  69. continue
  70. if conversion != "r":
  71. conversion = "s"
  72. flattenedKey = keyFlattener.flatKey(fieldName, formatSpec, conversion)
  73. structuredKey = keyFlattener.flatKey(fieldName, formatSpec, "")
  74. if flattenedKey in fields:
  75. # We've already seen and handled this key
  76. continue
  77. if fieldName.endswith(u"()"):
  78. fieldName = fieldName[:-2]
  79. callit = True
  80. else:
  81. callit = False
  82. field = aFormatter.get_field(fieldName, (), event)
  83. fieldValue = field[0]
  84. if conversion == "r":
  85. conversionFunction = repr
  86. else: # Above: if conversion is not "r", it's "s"
  87. conversionFunction = unicode
  88. if callit:
  89. fieldValue = fieldValue()
  90. flattenedValue = conversionFunction(fieldValue)
  91. fields[flattenedKey] = flattenedValue
  92. fields[structuredKey] = fieldValue
  93. if fields:
  94. event["log_flattened"] = fields
  95. def extractField(field, event):
  96. """
  97. Extract a given format field from the given event.
  98. @param field: A string describing a format field or log key. This is the
  99. text that would normally fall between a pair of curly braces in a
  100. format string: for example, C{"key[2].attribute"}. If a conversion is
  101. specified (the thing after the C{"!"} character in a format field) then
  102. the result will always be L{unicode}.
  103. @type field: L{str} (native string)
  104. @param event: A log event.
  105. @type event: L{dict}
  106. @return: A value extracted from the field.
  107. @rtype: L{object}
  108. @raise KeyError: if the field is not found in the given event.
  109. """
  110. keyFlattener = KeyFlattener()
  111. [[literalText, fieldName, formatSpec, conversion]] = aFormatter.parse(
  112. "{" + field + "}"
  113. )
  114. key = keyFlattener.flatKey(fieldName, formatSpec, conversion)
  115. if "log_flattened" not in event:
  116. flattenEvent(event)
  117. return event["log_flattened"][key]
  118. def flatFormat(event):
  119. """
  120. Format an event which has been flattened with L{flattenEvent}.
  121. @param event: A logging event.
  122. @type event: L{dict}
  123. @return: A formatted string.
  124. @rtype: L{unicode}
  125. """
  126. fieldValues = event["log_flattened"]
  127. s = []
  128. keyFlattener = KeyFlattener()
  129. formatFields = aFormatter.parse(event["log_format"])
  130. for literalText, fieldName, formatSpec, conversion in formatFields:
  131. s.append(literalText)
  132. if fieldName is not None:
  133. key = keyFlattener.flatKey(
  134. fieldName, formatSpec, conversion or "s")
  135. s.append(unicode(fieldValues[key]))
  136. return u"".join(s)