Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing import TYPE_CHECKING, Dict, List, cast, overload

from sentry_sdk._compat import check_uwsgi_thread_support
from sentry_sdk._log_batcher import LogBatcher
from sentry_sdk._metrics_batcher import MetricsBatcher
from sentry_sdk._span_batcher import SpanBatcher
from sentry_sdk.consts import (
Expand Down Expand Up @@ -64,8 +65,6 @@
get_type_name,
handle_in_app,
has_data_collection_enabled,
has_logs_enabled,
has_metrics_enabled,
logger,
)

Expand Down Expand Up @@ -649,22 +648,27 @@ def _record_lost_event(

self.session_flusher = SessionFlusher(capture_func=_capture_envelope)

self.log_batcher = None
if self.options.get("enable_logs", False) or self.options[
"_experiments"
].get("enable_logs", False):
logger.warning(
"The enable_logs option has no effect and will be removed in the next major."
)

if has_logs_enabled(self.options):
from sentry_sdk._log_batcher import LogBatcher
self.log_batcher = LogBatcher(
capture_func=_capture_envelope,
record_lost_func=_record_lost_event,
)

self.log_batcher = LogBatcher(
capture_func=_capture_envelope,
record_lost_func=_record_lost_event,
if self.options.get("enable_metrics", True) is False:
logger.warning(
"The enable_metrics option has no effect and will be removed in the next major."
)

self.metrics_batcher = None
if has_metrics_enabled(self.options):
self.metrics_batcher = MetricsBatcher(
capture_func=_capture_envelope,
record_lost_func=_record_lost_event,
Comment thread
cursor[bot] marked this conversation as resolved.
)
self.metrics_batcher = MetricsBatcher(
capture_func=_capture_envelope,
record_lost_func=_record_lost_event,
)

self.span_batcher = None
if has_span_streaming_enabled(self.options):
Expand Down
10 changes: 6 additions & 4 deletions sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
capture_internal_exceptions,
current_stacktrace,
event_from_exception,
has_logs_enabled,
safe_repr,
to_string,
)
Expand Down Expand Up @@ -115,13 +114,17 @@ def unignore_logger_for_sentry_logs(

class LoggingIntegration(Integration):
identifier = "logging"
capture_sentry_logs: "Optional[bool]" = False

def __init__(
self,
level: "Optional[int]" = DEFAULT_LEVEL,
event_level: "Optional[int]" = DEFAULT_EVENT_LEVEL,
sentry_logs_level: "Optional[int]" = DEFAULT_LEVEL,
capture_sentry_logs: "Optional[bool]" = False,
) -> None:
LoggingIntegration.capture_sentry_logs = capture_sentry_logs

self._handler = None
self._breadcrumb_handler = None
self._sentry_logs_handler = None
Expand Down Expand Up @@ -378,7 +381,7 @@ class SentryLogsHandler(_BaseHandler):
"""
A logging handler that records Sentry logs for each Python log record.

Note that you do not have to use this class if the logging integration is enabled, which it is by default.
Note that you do not have to use this class if the LoggingIntegration's capture_sentry_logs option is enabled.
"""

def _can_record(self, record: "LogRecord") -> bool:
Expand All @@ -398,7 +401,7 @@ def emit(self, record: "LogRecord") -> "Any":
if not client.is_active():
return

if not has_logs_enabled(client.options):
if not LoggingIntegration.capture_sentry_logs:
return

self._capture_log_from_record(client, record)
Expand Down Expand Up @@ -462,7 +465,6 @@ def _capture_log_from_record(
if record.name:
attrs["logger.name"] = record.name

# noinspection PyProtectedMember
sentry_sdk.get_current_scope()._capture_log(
{
"severity_text": otel_severity_text,
Expand Down
8 changes: 5 additions & 3 deletions sentry_sdk/integrations/loguru.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
_BaseHandler,
)
from sentry_sdk.logger import _log_level_to_otel
from sentry_sdk.utils import has_logs_enabled, safe_repr
from sentry_sdk.utils import safe_repr

if TYPE_CHECKING:
from logging import LogRecord
Expand Down Expand Up @@ -70,6 +70,7 @@ class LoguruIntegration(Integration):
breadcrumb_format = DEFAULT_FORMAT
event_format = DEFAULT_FORMAT
sentry_logs_level: "Optional[int]" = DEFAULT_LEVEL
capture_sentry_logs: "Optional[bool]" = False

def __init__(
self,
Expand All @@ -78,12 +79,14 @@ def __init__(
breadcrumb_format: "str | loguru.FormatFunction" = DEFAULT_FORMAT,
event_format: "str | loguru.FormatFunction" = DEFAULT_FORMAT,
sentry_logs_level: "Optional[int]" = DEFAULT_LEVEL,
capture_sentry_logs: "Optional[bool]" = False,
) -> None:
LoguruIntegration.level = level
LoguruIntegration.event_level = event_level
LoguruIntegration.breadcrumb_format = breadcrumb_format
LoguruIntegration.event_format = event_format
LoguruIntegration.sentry_logs_level = sentry_logs_level
LoguruIntegration.capture_sentry_logs = capture_sentry_logs

@staticmethod
def setup_once() -> None:
Expand Down Expand Up @@ -142,11 +145,10 @@ def loguru_sentry_logs_handler(message: "Message") -> None:
# This is intentionally a callable sink instead of a standard logging handler
# since otherwise we wouldn't get direct access to message.record
client = sentry_sdk.get_client()

if not client.is_active():
return

if not has_logs_enabled(client.options):
if not LoguruIntegration.capture_sentry_logs:
return

record = message.record
Expand Down
6 changes: 0 additions & 6 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@
exc_info_from_error,
format_attribute,
has_data_collection_enabled,
has_logs_enabled,
has_metrics_enabled,
logger,
)

Expand Down Expand Up @@ -1470,8 +1468,6 @@ def _capture_log(self, log: "Optional[Log]") -> None:
return

client = self.get_client()
if not has_logs_enabled(client.options):
return

merged_scope = self._merge_scopes()

Expand All @@ -1488,8 +1484,6 @@ def _capture_metric(self, metric: "Optional[Metric]") -> None:
return

client = self.get_client()
if not has_metrics_enabled(client.options):
return

merged_scope = self._merge_scopes()

Expand Down
17 changes: 0 additions & 17 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2070,16 +2070,6 @@ def serialize_item(
return str(data)


def has_logs_enabled(options: "Optional[dict[str, Any]]") -> bool:
if options is None:
return False

return bool(
options.get("enable_logs", False)
or options["_experiments"].get("enable_logs", False)
)


def has_data_collection_enabled(options: "Optional[dict[str, Any]]") -> bool:
if options is None:
return False
Expand All @@ -2098,13 +2088,6 @@ def get_before_send_log(
)


def has_metrics_enabled(options: "Optional[dict[str, Any]]") -> bool:
if options is None:
return False

return bool(options.get("enable_metrics", True))


def get_before_send_metric(
options: "Optional[dict[str, Any]]",
) -> "Optional[Callable[[Metric, Hint], Optional[Metric]]]":
Expand Down
58 changes: 46 additions & 12 deletions tests/integrations/logging/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,37 @@ def test_logging_captured_warnings(sentry_init, capture_events, recwarn):
assert len(third_warnings) == 1


def test_sentry_logs_collection_off_by_default(sentry_init, capture_items, request):
"""Automatic logs capture by Sentry logs needs explicit opt-in via capture_sentry_logs."""
sentry_init()
items = capture_items("log")

python_logger = logging.Logger("test-logger")
python_logger.warning("this is %s a template %s", "1", "2")

get_client().flush()

assert not items


def test_sentry_logs_collection_opt_in(sentry_init, capture_items, request):
"""Automatic logs capture by Sentry logs needs explicit opt-in via capture_sentry_logs."""
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
python_logger.warning("this is %s a template %s", "1", "2")

get_client().flush()

assert len(items) == 1

log = items[0].payload
assert log["attributes"]["sentry.message.template"] == "this is %s a template %s"
assert log["attributes"]["sentry.severity_number"] == 13
assert log["attributes"]["sentry.severity_text"] == "warn"


def test_ignore_logger(sentry_init, capture_events, request):
sentry_init(integrations=[LoggingIntegration()], default_integrations=False)
events = capture_events()
Expand Down Expand Up @@ -276,7 +307,7 @@ def test_ignore_logger_wildcard(sentry_init, capture_events, request):

def test_ignore_logger_does_not_affect_sentry_logs(sentry_init, capture_items, request):
"""ignore_logger should suppress events/breadcrumbs but not Sentry Logs."""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

ignore_logger("testfoo")
Expand All @@ -294,7 +325,7 @@ def test_ignore_logger_for_sentry_logs(
sentry_init, capture_envelopes, capture_items, request
):
"""ignore_logger_for_sentry_logs should suppress Sentry Logs but not events."""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
envelopes = capture_envelopes()
items = capture_items("log")

Expand Down Expand Up @@ -355,7 +386,7 @@ def test_sentry_logs_warning(sentry_init, capture_items):
"""
The python logger module should create 'warn' sentry logs if the flag is on.
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
Expand All @@ -380,7 +411,7 @@ def test_sentry_logs_debug(sentry_init, capture_envelopes):
"""
The python logger module should not create 'debug' sentry logs if the flag is on by default
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
envelopes = capture_envelopes()

python_logger = logging.Logger("test-logger")
Expand All @@ -395,8 +426,11 @@ def test_no_log_infinite_loop(sentry_init, capture_envelopes):
If 'debug' mode is true, and you set a low log level in the logging integration, there should be no infinite loops.
"""
sentry_init(
enable_logs=True,
integrations=[LoggingIntegration(sentry_logs_level=logging.DEBUG)],
integrations=[
LoggingIntegration(
capture_sentry_logs=True, sentry_logs_level=logging.DEBUG
)
],
debug=True,
)
envelopes = capture_envelopes()
Expand All @@ -412,7 +446,7 @@ def test_logging_errors(sentry_init, capture_envelopes, capture_items):
"""
The python logger module should be able to log errors without erroring
"""
sentry_init(enable_logs=True)
Comment thread
cursor[bot] marked this conversation as resolved.
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
envelopes = capture_envelopes()
items = capture_items("log")

Expand Down Expand Up @@ -448,8 +482,8 @@ def test_log_strips_project_root(sentry_init, capture_items):
The python logger should strip project roots from the log record path
"""
sentry_init(
enable_logs=True,
project_root="/custom/test",
integrations=[LoggingIntegration(capture_sentry_logs=True)],
)
items = capture_items("log")

Expand Down Expand Up @@ -477,7 +511,7 @@ def test_logger_with_all_attributes(sentry_init, capture_items):
"""
The python logger should be able to log all attributes, including extra data.
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
Expand Down Expand Up @@ -554,7 +588,7 @@ def test_sentry_logs_named_parameters(sentry_init, capture_items):
"""
The python logger module should capture named parameters from dictionary arguments in Sentry logs.
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
Expand Down Expand Up @@ -599,7 +633,7 @@ def test_sentry_logs_named_parameters_complex_values(sentry_init, capture_items)
"""
The python logger module should handle complex values in named parameters using safe_repr.
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
Expand Down Expand Up @@ -633,7 +667,7 @@ def test_sentry_logs_no_parameters_no_template(sentry_init, capture_items):
"""
There shouldn't be a template if there are no parameters.
"""
sentry_init(enable_logs=True)
sentry_init(integrations=[LoggingIntegration(capture_sentry_logs=True)])
items = capture_items("log")

python_logger = logging.Logger("test-logger")
Expand Down
Loading
Loading