text: import traceback
  from collections import namedtuple
  
  from dagster.serdes import whitelist_for_serdes
  
  
  @whitelist_for_serdes
  class SerializableErrorInfo(namedtuple("SerializableErrorInfo", "message stack cls_name cause")):
      # serdes log
      # * added cause - default to None in constructor to allow loading old entries
      #
      def __new__(cls, message, stack, cls_name, cause=None):
          return super(SerializableErrorInfo, cls).__new__(cls, message, stack, cls_name, cause)
  
      def to_string(self):
          stack_str = "\nStack Trace: \n" + "".join(self.stack) if self.stack else ""
          cause_str = (
              "\nThe above exception was the direct cause of the following exception:\n"
              + self.cause.to_string()
              if self.cause
              else ""
          )
          return "({err.cls_name}) - {err.message}{stack}{cause}".format(
              err=self, stack=stack_str, cause=cause_str
          )
  
  
  def _serializable_error_info_from_tb(tb):
      return SerializableErrorInfo(
          # usually one entry, last entry for syntax errors is the exception that occurred
          list(tb.format_exception_only())[-1],
          tb.stack.format(),
          tb.exc_type.__name__ if tb.exc_type is not None else None,
          _serializable_error_info_from_tb(tb.__cause__) if tb.__cause__ else None,
      )
  
  
  def serializable_error_info_from_exc_info(exc_info):
      if hasattr(traceback, "TracebackException"):
          return _serializable_error_info_from_tb(traceback.TracebackException(*exc_info))
  
      else:  # fallback for our old pal py27
          exc_type, exc_value, exc_tb = exc_info
  
          return SerializableErrorInfo(
              traceback.format_exception_only(exc_type, exc_value)[-1],
              traceback.format_tb(tb=exc_tb),
              exc_type.__name__,
          )
  