kiwicom / structlog-sentry

Sentry integration for structlog
MIT License
108 stars 28 forks source link

Structlog '.exception()' calls creating Sentry errors #166

Open paskalwolski opened 1 week ago

paskalwolski commented 1 week ago

I've run into an issue with an 'extra' exception from my django app being sent to Sentry through the sentry_sdk, after configuring structlog to use the SentryProcessor from this lib (v2.0.3)

When raising an unhandled exception, or when making a call to structlog.logger.exception:

import structlog
logger = structlog.getLogger()
logger.exception(Exception("Test Exception"))

I can get the original exception data appearing in sentry, but there is also a secondary exception (only in sentry, and not in my local logs):

ValueError: EXCEPTION is not a valid log level

Here's the traceback - note that I'm using a celery task so I can easily trigger the exception, but this happens with exceptions outside of celery, too:

AttributeError: module 'logging' has no attribute 'EXCEPTION'
  File "__init__.py", line 191, in _get_level_value
    return getattr(logging, level_name)
ValueError: EXCEPTION is not a valid log level
  File "myproject/celery.py", line 50, in debug_task
    logger.exception("Test Exception")
  File "structlog/stdlib.py", line 224, in exception
    return self._proxy_to_logger("exception", event, *args, **kw)
  File "structlog/stdlib.py", line 254, in _proxy_to_logger
    return super()._proxy_to_logger(method_name, event=event, **event_kw)
  File "structlog/_base.py", line 216, in _proxy_to_logger
    args, kw = self._process_event(method_name, event, event_kw)
  File "structlog/_base.py", line 167, in _process_event
    event_dict = proc(self._logger, method_name, event_dict)
  File "__init__.py", line 208, in __call__
    level = self._get_level_value(event_dict["level"].upper())
  File "__init__.py", line 199, in _get_level_value
    raise ValueError(f"{level_name} is not a valid log level") from e

I believe this is an issue with how structlog-sentry handles logs at level exception. The exception level/method name is hard-set in structlog after a call to .exception() ("structlog/stdlib.py", line 224) - this is in contrast to the standard logging library, which uses .exception() as convenience method - but I think that's inline with structlog's objective, as there they treat exceptions explicitly. Standard logging doesn't actually have an 'exception' level, but that's the only thing that's being searched by structlog-sentry here - thus the error.

The expected behaviour is that structlog-sentry should be able to handle logs raised from both a structlog logger and the standard logger, and allow for exception info, without any additional config - I don't believe I need to define a custom log level for what is a widely available logging function.

I might be doing something wrong before the errors arrive at structlog, but the structlog config we have is quite standard, according to the django/structlog implementation guidelines:

PROCESSORS = [
    structlog.contextvars.merge_contextvars,
    structlog.stdlib.filter_by_level,
    structlog.processors.TimeStamper(fmt="iso"),
    structlog.stdlib.add_logger_name,
    structlog.stdlib.add_log_level,
    SentryProcessor(level=logging.CRITICAL, event_level=logging.ERROR, verbose=True)
    structlog.stdlib.PositionalArgumentsFormatter(),
    structlog.processors.StackInfoRenderer(),
    structlog.processors.format_exc_info,
    structlog.processors.UnicodeDecoder(),
    structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
]
structlog.configure(
    processors=PROCESSORS,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

My caveat is that I didn't personally set up this logging config, and so I might be fundamentally misunderstanding something about the interactions between these libraries - sorry if that's the case !

fcabaud commented 4 days ago

I got the same issue.