Как обнаружить и сделать отступ для 1000 * подстрок внутри длинного не json текста? - PullRequest
0 голосов
/ 23 апреля 2020

У меня есть существующее Python приложение, которое регистрирует как:

import logging
import json
logger = logging.getLogger()

some_var = 'abc'
data = {
   1: 2,
   'blah': {
      ['hello']
   }
}

logger.info(f"The value of some_var is {some_var} and data is {json.dumps(data)}")

Итак, функция logger.info задана:

The value of some_var is abc and data is {1: 2,"blah": {["hello"]}}

В настоящее время мои журналы go в AWS CloudWatch, который делает волшебные c и отображает это с отступом, например:

The value of some_var is abc and data is {
   1: 2,
   "blah": {
      ["hello"]
   }
}

Это делает журналы очень четкими для чтения.

Теперь я хочу внести некоторые изменения к моей записи в журнал, обрабатывая его самостоятельно с помощью другого python сценария, который оборачивает мой код и отправляет журналы по электронной почте при сбое.

Мне нужен какой-то способ получения каждой записи журнала (или потока / списка) записей и применения этого отступа.

Итак, я хочу функцию, которая принимает строку и определяет, какие подмножества этой строки json, затем вставляет \n и чтобы красиво распечатать это json.

пример ввода:

Здравствуйте, {"a": {"b": "c" }} это некоторые json данные, но также {"c": [1,2,3]} слишком

пример вывода

Hello, 
{
  "a": {
    "b": "c"
  }
} 
is some json data, but also 
{
  "c": [
    1,
    2,
    3
  ]
}
is too

У меня есть конс Деред разделил каждую запись на все до и после первого {. Оставьте левую половину как есть и передайте правую половину json.dumps(json.loads(x), indent=4).

Но что, если в файле журнала есть что-то после объекта json? Хорошо, мы можем просто выбрать все после первого { и до последнего }. Затем передайте средний бит в библиотеку JSON.

Но что, если в этой записи журнала есть два объекта JSON? (Как в приведенном выше примере.) Мы должны использовать стек, чтобы выяснить, появляется ли какой-либо { после того, как все предыдущие { были закрыты с соответствующим }.

Но что если есть что-то вроде {"a": "\}"}. Хм, хорошо, мы должны справиться с побегом. Теперь я чувствую, что мне нужно написать целый синтаксический анализатор json с нуля.

Есть ли простой способ сделать это?

Полагаю, я мог бы использовать регулярное выражение для замены каждого экземпляра json.dumps(x) во всем моем репо с json.dumps(x, indent=4). Но json.dumps иногда используется вне операторов логирования, и это просто удлиняет все мои строки логирования. Есть ли изящное элегантное решение?

(Бонусные баллы, если он может анализировать и отступать json -подобный вывод, который str(x) производит в python. Это в основном json с одинарными кавычками вместо двойных .)

1 Ответ

1 голос
/ 23 апреля 2020

Чтобы извлечь JSON объектов из строки, см. этот ответ . Функция extract_json_objects() из этого ответа будет обрабатывать JSON объекты и вложенные JSON объекты, но не более того. Если у вас есть список в вашем журнале за пределами JSON объекта , он не будет выбран.

В вашем случае измените функцию на также вернуть строки / текст вокруг всех JSON объектов, чтобы вы могли поместить их все в журнал вместе (или заменить строку журнала):

from json import JSONDecoder

def extract_json_objects(text, decoder=JSONDecoder()):
    pos = 0
    while True:
        match = text.find('{', pos)
        if match == -1:
            yield text[pos:]  # return the remaining text
            break
        yield text[pos:match]  # modification for the non-JSON parts
        try:
            result, index = decoder.raw_decode(text[match:])
            yield result
            pos = match + index
        except ValueError:
            pos = match + 1

Используйте эту функцию для обработки ваших строк журнала, добавьте их к списку строк , которые вы затем объединяете, чтобы получить единственную строку для вашего вывода, logger и т. д. c.:

def jsonify_logline(line):
    line_parts = []
    for result in extract_json_objects(line):
        if isinstance(result, dict):  # got a JSON obj
            line_parts.append(json.dumps(result, indent=4))
        else:                         # got text/non-JSON-obj
            line_parts.append(result)
    # (don't make that a list comprehension, quite un-readable)

    return ''.join(line_parts)

Пример:

>>> demo_text = """Hello, {"a": {"b": "c"}} is some json data, but also {"c": [1,2,3]} is too"""
>>> print(jsonify_logline(demo_text))
Hello, {
    "a": {
        "b": "c"
    }
} is some json data, but also {
    "c": [
        1,
        2,
        3
    ]
} is too
>>>

Другие вещи, не связанные напрямую, которые помогли бы:

  • Вместо использования json.dumps(x) для всех ваших строк журнала, следуя принципу DRY и создайте функцию, такую ​​как logdump(x), которая делает все, что вы хотите, например, json.dumps(x), или json.dumps(x, indent=4), или jsonify_logline(x). Таким образом, если вам нужно изменить формат JSON для всех ваших журналов, вы просто измените эту функцию; нет необходимости в массовом «поиске и замене», что связано со своими проблемами и крайними случаями.
    • Вы даже можете добавить к нему необязательный параметр pretty=True, чтобы решить, хотите ли вы, чтобы он был отступ или нет.
  • Вы можете выполнять массовый поиск и заменить все ваши существующие логи. сделать logger.blah(jsonify_logline(<previous log f-string or text>))
  • Если вы JSON -демпулируете пользовательские объекты / экземпляры классов, то используйте их метод __str__, чтобы всегда выводить симпатично напечатанное JSON. И __repr__, чтобы быть симпатичным / компактным.
    • Тогда вам вообще не нужно будет изменять логлайн. Выполнение logger.info(f'here is my object {x}') напрямую вызовет obj.__str__.
...