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
12 changes: 12 additions & 0 deletions sentry_sdk/_log_batcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,21 @@ def add(

with self._lock:
if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_DROP:
# Construct log envelope item without sending it to report lost bytes
log_item = Item(
type="log",
content_type="application/vnd.sentry.items.log+json",
headers={
"item_count": 1,
},
payload=PayloadRef(
json={"items": [LogBatcher._log_to_transport_format(log)]}
),
)
self._record_lost_func(
reason="queue_overflow",
data_category="log_item",
item=log_item,
quantity=1,
)
return None
Expand Down
1 change: 1 addition & 0 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ class SDKInfo(TypedDict):
"monitor",
"span",
"log_item",
"log_byte",
"trace_metric",
]
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
Expand Down
4 changes: 3 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
from sentry_sdk.scope import Scope
from sentry_sdk.session import Session
from sentry_sdk.spotlight import SpotlightClient
from sentry_sdk.transport import Transport
from sentry_sdk.transport import Transport, Item
from sentry_sdk._log_batcher import LogBatcher
from sentry_sdk._metrics_batcher import MetricsBatcher

Expand Down Expand Up @@ -360,13 +360,15 @@ def _capture_envelope(envelope):
def _record_lost_event(
reason, # type: str
data_category, # type: EventDataCategory
item=None, # type: Optional[Item]
quantity=1, # type: int
):
# type: (...) -> None
if self.transport is not None:
self.transport.record_lost_event(
reason=reason,
data_category=data_category,
item=item,
quantity=quantity,
)

Expand Down
5 changes: 5 additions & 0 deletions sentry_sdk/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ def record_lost_event(
)
self.record_lost_event(reason, "span", quantity=span_count)

elif data_category == "log_item" and item:
# Also record size of lost logs in bytes
bytes_size = len(item.get_bytes())
self.record_lost_event(reason, "log_byte", quantity=bytes_size)

elif data_category == "attachment":
# quantity of 0 is actually 1 as we do not want to count
# empty attachments as actually empty.
Expand Down
63 changes: 60 additions & 3 deletions tests/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import time
from typing import List, Any, Mapping, Union
import pytest
from unittest import mock

import sentry_sdk
import sentry_sdk.logger
from sentry_sdk import get_client
from sentry_sdk.envelope import Envelope
from sentry_sdk.envelope import Envelope, Item, PayloadRef
from sentry_sdk.types import Log
from sentry_sdk.consts import SPANDATA, VERSION

Expand Down Expand Up @@ -450,7 +451,7 @@ def test_logs_with_literal_braces(

@minimum_python_37
def test_batcher_drops_logs(sentry_init, monkeypatch):
sentry_init(enable_logs=True)
sentry_init(enable_logs=True, server_name="test-server", release="1.0.0")
client = sentry_sdk.get_client()

def no_op_flush():
Expand All @@ -469,5 +470,61 @@ def record_lost_event(reason, data_category=None, item=None, *, quantity=1):
sentry_sdk.logger.info("This is a 'info' log...")

assert len(lost_event_calls) == 5

for lost_event_call in lost_event_calls:
assert lost_event_call == ("queue_overflow", "log_item", None, 1)
reason, data_category, item, quantity = lost_event_call

assert reason == "queue_overflow"
assert data_category == "log_item"
assert quantity == 1

assert item.type == "log"
assert item.headers == {
"type": "log",
"item_count": 1,
"content_type": "application/vnd.sentry.items.log+json",
}
assert item.payload.json == {
"items": [
{
"body": "This is a 'info' log...",
"level": "info",
"timestamp": mock.ANY,
"trace_id": mock.ANY,
"attributes": {
"sentry.environment": {
"type": "string",
"value": "production",
},
"sentry.release": {
"type": "string",
"value": "1.0.0",
},
"sentry.sdk.name": {
"type": "string",
"value": mock.ANY,
},
"sentry.sdk.version": {
"type": "string",
"value": VERSION,
},
"sentry.severity_number": {
"type": "integer",
"value": 9,
},
"sentry.severity_text": {
"type": "string",
"value": "info",
},
"sentry.trace.parent_span_id": {
"type": "string",
"value": mock.ANY,
},
"server.address": {
"type": "string",
"value": "test-server",
},
},
}
]
}
128 changes: 122 additions & 6 deletions tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
Hub,
)
from sentry_sdk._compat import PY37, PY38
from sentry_sdk.envelope import Envelope, Item, parse_json
from sentry_sdk.envelope import Envelope, Item, parse_json, PayloadRef
from sentry_sdk.transport import (
KEEP_ALIVE_SOCKET_OPTIONS,
_parse_rate_limits,
Expand Down Expand Up @@ -591,7 +591,110 @@ def test_complex_limits_without_data_category(


@pytest.mark.parametrize("response_code", [200, 429])
def test_log_item_limits(capturing_server, response_code, make_client):
@pytest.mark.parametrize(
"item",
[
Item(payload=b"{}", type="log"),
Item(
type="log",
content_type="application/vnd.sentry.items.log+json",
headers={
"item_count": 2,
},
payload=PayloadRef(
json={
"items": [
{
"body": "This is a 'info' log...",
"level": "info",
"timestamp": datetime(
2025, 1, 1, tzinfo=timezone.utc
).timestamp(),
"trace_id": "00000000-0000-0000-0000-000000000000",
"attributes": {
"sentry.environment": {
"value": "production",
"type": "string",
},
"sentry.release": {
"value": "1.0.0",
"type": "string",
},
"sentry.sdk.name": {
"value": "sentry.python",
"type": "string",
},
"sentry.sdk.version": {
"value": "2.45.0",
"type": "string",
},
"sentry.severity_number": {
"value": 9,
"type": "integer",
},
"sentry.severity_text": {
"value": "info",
"type": "string",
},
"server.address": {
"value": "test-server",
"type": "string",
},
},
},
{
"body": "The recorded value was '2.0'",
"level": "warn",
"timestamp": datetime(
2025, 1, 1, tzinfo=timezone.utc
).timestamp(),
"trace_id": "00000000-0000-0000-0000-000000000000",
"attributes": {
"sentry.message.parameter.float_var": {
"value": 2.0,
"type": "double",
},
"sentry.message.template": {
"value": "The recorded value was '{float_var}'",
"type": "string",
},
"sentry.sdk.name": {
"value": "sentry.python",
"type": "string",
},
"sentry.sdk.version": {
"value": "2.45.0",
"type": "string",
},
"server.address": {
"value": "test-server",
"type": "string",
},
"sentry.environment": {
"value": "production",
"type": "string",
},
"sentry.release": {
"value": "1.0.0",
"type": "string",
},
"sentry.severity_number": {
"value": 13,
"type": "integer",
},
"sentry.severity_text": {
"value": "warn",
"type": "string",
},
},
},
]
}
),
),
],
)
def test_log_item_limits(capturing_server, response_code, item, make_client):
client = make_client()
capturing_server.respond_with(
code=response_code,
Expand All @@ -601,7 +704,7 @@ def test_log_item_limits(capturing_server, response_code, make_client):
)

envelope = Envelope()
envelope.add_item(Item(payload=b"{}", type="log"))
envelope.add_item(item)
client.transport.capture_envelope(envelope)
client.flush()

Expand All @@ -622,9 +725,22 @@ def test_log_item_limits(capturing_server, response_code, make_client):
envelope = capturing_server.captured[1].envelope
assert envelope.items[0].type == "client_report"
report = parse_json(envelope.items[0].get_bytes())
assert report["discarded_events"] == [
{"category": "log_item", "reason": "ratelimit_backoff", "quantity": 1},
]

assert {
"category": "log_item",
"reason": "ratelimit_backoff",
"quantity": 1,
} in report["discarded_events"]

expected_lost_bytes = 1243
if item.payload.bytes == b"{}":
expected_lost_bytes = 2

assert {
"category": "log_byte",
"reason": "ratelimit_backoff",
"quantity": expected_lost_bytes,
} in report["discarded_events"]


def test_hub_cls_backwards_compat():
Expand Down