Сохранить исключение Python в модели Django
Каков рекомендуемый способ хранения исключения Python - структурированным образом, который обеспечивает доступ к различным частям этого исключения - в модели Django?
Обычно проектируют модель Django, которая записывает "событие" или "попытку сделать foo"; часть информации, которая должна быть записана, - "… и результатом стала эта ошибка". Это тогда становится полем на модели.
Важным аспектом записи ошибки в базу данных является ее запрос различными структурированными способами: каков был тип исключения? Какое было конкретное сообщение? Каковы были другие аргументы к исключению? Каков был полный след?
(Мне известны службы, которые позволят мне вести потоковую регистрацию или сообщения об ошибках по сети; это не то, что задает этот вопрос. Мне нужны структурированные данные об исключениях в собственной базе данных проекта, для ссылки непосредственно из других моделей в той же база данных.)
Объект исключения Python - это структура данных, которая имеет все эти части - тип исключения, аргументы экземпляра исключения, "причину" и "контекст", трассировку - доступны отдельно как атрибуты объекта. Цель этого вопроса - сохранить информацию об исключениях Python в модели базы данных, чтобы их можно было запрашивать эквивалентным структурированным образом.
Поэтому недостаточно иметь TextField в свободной форме для записи строкового представления исключения. Требуется структурированное поле или набор полей; возможно, даже целая отдельная модель для хранения экземпляров исключений.
Конечно, я мог бы сам разработать такую вещь и провести время, совершая те же ошибки, которые бесчисленные люди до меня, несомненно, допустили, пытаясь реализовать нечто подобное. Вместо этого я хочу извлечь уроки из существующего искусства в решении этой проблемы.
Каков общий шаблон для хранения структурированных данных об исключениях Python в модели Django, предпочтительно в существующей зрелой библиотеке общего назначения в PyPI, которую мой проект может использовать для своих моделей?
1 ответ
Большинство людей делегируют подобные задачи сторонним сервисам, таким как RollBar или LogEntries, или сервисам промежуточного программного обеспечения, работающим в VPC, таком как Elastic LogStash.
Я бы предположил, что Django ORM не очень подходит для этого типа хранилища. Большим преимуществом ORM является возможность присоединения к реляционным таблицам без сложного SQL и управления изменениями схемы и целостности ссылок с помощью миграций. Это проблемы, с которыми сталкиваются приложения "CRUD", для которых предназначен Django - веб-приложения с учетными записями пользователей, предпочтениями, входящими почтовыми ящиками и т. Д. ORM предназначен для управления изменяемыми данными с большим количеством операций чтения, чем записи.
Ваша проблема, связанная с хранением исключений Python, которые произошли в производстве, совершенно иная. Ваши потребности в схеме почти никогда не изменятся. Данные, которые вы хотите сохранить, никогда не изменяются при записи, и все записи строго добавляются. Ваши данные не содержат внешних ключей или других полей, которые могли бы измениться при переносе. Вы почти всегда будете запрашивать последние данные по историческим данным, которые редко будут читаться вне автономной / массовой аналитики.
Если вы действительно хотите хранить эту информацию в Django, я бы рекомендовал хранить только скользящее окно, которое вы периодически поворачиваете в сжатые журналы на диске. В противном случае вы будете поддерживать дорогостоящие индексы в данных, которые почти никогда не нужны. В этом случае вам следует рассмотреть возможность создания собственной пользовательской модели Django, которая извлекает необходимые метаданные исключений. Вы также можете поместить эту информацию в поле JSON, которое вы храните в виде строки, как подсказывает @jsbueno, но это жертвует индексированным выбором.
(Обратите внимание, что исключения Python не могут быть напрямую сериализованы в JSON или зарезаны. Существует проект под названием tblib, который позволяет выполнять забор, который, в свою очередь, может храниться как поля BLOB в Django, но я не знаю, будет ли производительность приемлемой. это не стоило бы того.)
В последние годы появилось много альтернативных продуктов СУБД для хранения данных в виде журнала, только для добавления, с аналитическими шаблонами запросов. Но большая часть этого прогресса является слишком недавней и слишком "масштабируемой", чтобы иметь готовую интеграцию с Django, которая фокусируется на небольших, более традиционных приложениях CRUD. Вам следует искать решения, которые можно интегрировать с Python в более общем плане, поскольку сложное ведение журналов и хранение событий в большинстве случаев выходит за рамки Django.
Я не уверен, что многие люди искали собственный способ хранения данных об исключениях
В конечном итоге вы должны знать, что вам нужно. Во всех проектах, над которыми я работал до сих пор, текст трассировки содержал достаточно информации, чтобы найти источник ошибок. (иногда, даже наивно, так что ТБ несколько раз избегали, но все же было достаточно, чтобы "убежать назад" и исправить источник только с одним случаем ошибки).
Некоторые инструменты отладки предлагают интерактивный интерактивный анализ в каждом кадре выполнения, когда возникает исключение - но это должно быть "вживую", потому что вы не можете обычно сериализовать кадры выполнения Python и сохранять его в БД.
Тем не менее, если вам не достаточно текста трассировки, вы можете получить объект трассировки, вызвав sys.exc_info()[2]
, Это позволяет вам анализировать каждый кадр и знать в качестве словарей файл, номер строки, локальные и глобальные переменные. Если вы хотите, чтобы значения переменных были доступны в базе данных, вы должны их сериализовать, но не все значения переменных будут легко сериализуемы. Таким образом, вы должны знать, "когда достаточно, достаточно" в этом процессе.
Большинство современных баз данных допускают поля JSON, и, вероятно, достаточно сериализации информации об исключении в поле JSON, ограничивающее данные строками, числами, значениями типа bool и None.
Один из способов - вручную запустить каждый ключ в диктанте f_globals и f_locals для каждой славы и попытаться json.dumps для значения этого ключа, а для исключения из сериализации JSON использовать объект repr
вместо. Или вы можете настроить сериализатор JSON, который может хранить настроенные релевантные данные для даты и времени, открытых файлов, сокетов и т. Д. - только вы можете знать свои потребности.
TL; DR: использовать поле JSON, в предложении кроме получить объект трассировки, вызвав sys.last_traceback()
и иметь пользовательскую функцию для сериализации того, что вы хотите от него в поле JSON.
Или просто используйте traceback.format_tb
сохранить текст трассировки - в любом случае, этого, вероятно, будет достаточно.