Чтобы добавить логирование в собственные исключения, нужно:
logging.__init__ класса исключения (или в методе, где оно вызывается) использовать logger.error() (или другой уровень) для записи информации об исключении. Можно передать сообщение об ошибке и данные, которые помогли определить исключение.
    
      import logging
      logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
      class MyCustomError(Exception):
          def __init__(self, message, data=None):
              super().__init__(message)
              self.data = data
              logging.error(f"MyCustomError: {message}, Data: {data}")
      try:
          # Код, который может вызвать исключение
          raise MyCustomError("Произошла ошибка", data={"value": 123})
      except MyCustomError as e:
          print(f"Обработано исключение: {e}")
    
  
Добавление логирования в собственные исключения позволяет отслеживать возникновение этих исключений, получать дополнительную информацию о контексте их возникновения и упрощает отладку.
Вот несколько способов реализации логирования в собственных исключениях:
Самый простой способ – добавить логирование непосредственно в конструктор класса исключения. В этом случае, при создании экземпляра исключения, будет сразу же записано соответствующее сообщение в лог.
 import logging
 logging.basicConfig(level=logging.ERROR, filename='exception.log', filemode='w', format='%(asctime)s - %(levelname)s - %(message)s')
 class CustomException(Exception):
  def __init__(self, message, details=None):
   super().__init__(message)
   self.message = message
   self.details = details
   logging.error(f"CustomException: {message}, Details: {details}") # Логирование при создании исключения
 try:
  # Код, который может вызвать исключение
  raise CustomException("Произошла ошибка", details={"user_id": 123, "operation": "delete"})
 except CustomException as e:
  print(f"Обработка исключения: {e.message}")
  # Дополнительная обработка исключения
   Плюсы: Простота реализации, логирование происходит непосредственно при создании исключения.
Минусы: Логирование происходит даже если исключение перехвачено и обработано без необходимости логирования. Захламление лога может быть нежелательным.
Вместо логирования в конструкторе исключения, можно логировать только в блоке `except`, когда исключение фактически поймано и обрабатывается. Это более контролируемый подход.
 import logging
 logging.basicConfig(level=logging.ERROR, filename='exception.log', filemode='w', format='%(asctime)s - %(levelname)s - %(message)s')
 class CustomException(Exception):
  def __init__(self, message, details=None):
   super().__init__(message)
   self.message = message
   self.details = details
 try:
  # Код, который может вызвать исключение
  raise CustomException("Произошла ошибка", details={"user_id": 123, "operation": "delete"})
 except CustomException as e:
  logging.error(f"CustomException: {e.message}, Details: {e.details}") # Логирование в блоке except
  print(f"Обработка исключения: {e.message}")
  # Дополнительная обработка исключения
   Плюсы: Логирование происходит только когда исключение обрабатывается, что позволяет избежать ненужных записей в логе.
Минусы: Требует добавления логирования в каждый блок `except`, где обрабатывается данное исключение.
Можно создать специальный метод в классе исключения, который будет отвечать за логирование. Это позволяет инкапсулировать логику логирования внутри класса исключения и повторно использовать ее.
 import logging
 logging.basicConfig(level=logging.ERROR, filename='exception.log', filemode='w', format='%(asctime)s - %(levelname)s - %(message)s')
 class CustomException(Exception):
  def __init__(self, message, details=None):
   super().__init__(message)
   self.message = message
   self.details = details
  def log_exception(self):
   logging.error(f"CustomException: {self.message}, Details: {self.details}")
 try:
  # Код, который может вызвать исключение
  raise CustomException("Произошла ошибка", details={"user_id": 123, "operation": "delete"})
 except CustomException as e:
  e.log_exception() # Вызов метода логирования
  print(f"Обработка исключения: {e.message}")
  # Дополнительная обработка исключения
   Плюсы: Инкапсуляция логики логирования, повторное использование, упрощение изменения логики логирования в будущем.
Минусы: Требует добавления вызова метода логирования в каждый блок `except`.
Дополнительные рекомендации:
Выбор конкретного подхода зависит от ваших потребностей и предпочтений. Важно, чтобы логирование было консистентным и предоставляло достаточно информации для диагностики проблем.