123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- # -*- 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 collections import defaultdict
- from string import Formatter
- from typing import Any, Dict, Optional
-
- from ._interfaces import LogEvent
-
- aFormatter = Formatter()
-
-
- class KeyFlattener:
- """
- 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) -> None:
- """
- Initialize a L{KeyFlattener}.
- """
- self.keys: Dict[str, int] = defaultdict(lambda: 0)
-
- def flatKey(
- self, fieldName: str, formatSpec: Optional[str], conversion: Optional[str]
- ) -> str:
- """
- Compute a string key for a given field/format/conversion.
-
- @param fieldName: A format field name.
- @param formatSpec: A format spec.
- @param conversion: A format field conversion type.
-
- @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.
- """
- if formatSpec is None:
- formatSpec = ""
-
- if conversion is None:
- conversion = ""
-
- result = "{fieldName}!{conversion}:{formatSpec}".format(
- fieldName=fieldName,
- formatSpec=formatSpec,
- conversion=conversion,
- )
- self.keys[result] += 1
- n = self.keys[result]
- if n != 1:
- result += "/" + str(self.keys[result])
- return result
-
-
- def flattenEvent(event: LogEvent) -> None:
- """
- 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.
- """
- 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("()"):
- 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 = str
-
- if callit:
- fieldValue = fieldValue()
-
- flattenedValue = conversionFunction(fieldValue)
- fields[flattenedKey] = flattenedValue
- fields[structuredKey] = fieldValue
-
- if fields:
- event["log_flattened"] = fields
-
-
- def extractField(field: str, event: LogEvent) -> Any:
- """
- 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 str.
- @param event: A log event.
-
- @return: A value extracted from the field.
-
- @raise KeyError: if the field is not found in the given event.
- """
- keyFlattener = KeyFlattener()
-
- [[literalText, fieldName, formatSpec, conversion]] = aFormatter.parse(
- "{" + field + "}"
- )
-
- assert fieldName is not None
-
- key = keyFlattener.flatKey(fieldName, formatSpec, conversion)
-
- if "log_flattened" not in event:
- flattenEvent(event)
-
- return event["log_flattened"][key]
-
-
- def flatFormat(event: LogEvent) -> str:
- """
- Format an event which has been flattened with L{flattenEvent}.
-
- @param event: A logging event.
-
- @return: A formatted string.
- """
- fieldValues = event["log_flattened"]
- keyFlattener = KeyFlattener()
- s = []
-
- for literalText, fieldName, formatSpec, conversion in aFormatter.parse(
- event["log_format"]
- ):
- s.append(literalText)
-
- if fieldName is not None:
- key = keyFlattener.flatKey(fieldName, formatSpec, conversion or "s")
- s.append(str(fieldValues[key]))
-
- return "".join(s)
|