Skip to content

Commit a00c8bd

Browse files
committed
Improve DuplicateFilter behavior and log partial success unconditionally.
1 parent 11d3e09 commit a00c8bd

File tree

5 files changed

+29
-41
lines changed

5 files changed

+29
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Unreleased
1414

15-
- OTLP exporters now log partial success responses at `debug` level when `OTEL_LOG_LEVEL` is set to `debug` or `verbose`.
15+
- OTLP exporters now log partial success responses.
1616
([#4805](https://github.com/open-telemetry/opentelemetry-python/pull/4805))
1717
- docs: Added sqlcommenter example
1818
([#4734](https://github.com/open-telemetry/opentelemetry-python/pull/4734))

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313

14-
import sys
14+
1515
from os import environ
1616
from typing import Dict, Literal, Optional, Sequence, Tuple, Union
1717
from typing import Sequence as TypingSequence
@@ -113,11 +113,6 @@ def _translate_data(
113113
) -> ExportLogsServiceRequest:
114114
return encode_logs(data)
115115

116-
def _log_partial_success(self, partial_success):
117-
# Override that skips the "logging" module due to the possibility
118-
# of circular logic (logging -> OTLP logs export).
119-
sys.stderr.write(f"Partial success:\n{partial_success}\n")
120-
121116
def export( # type: ignore [reportIncompatibleMethodOverride]
122117
self,
123118
batch: Sequence[ReadableLogRecord],

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,9 @@ def _translate_data(
374374
) -> ExportServiceRequestT:
375375
pass
376376

377-
def _log_partial_success(self, partial_success):
378-
logger.debug("Partial success:\n%s", partial_success)
379-
380377
def _process_response(self, response):
381378
if response.HasField("partial_success"):
382-
self._log_partial_success(response.partial_success)
379+
logger.debug("Partial success:\n%s", response.partial_success)
383380

384381
def _export(
385382
self,

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# pylint: disable=too-many-lines
1616

1717
import time
18-
from io import StringIO
1918
from os.path import dirname
2019
from unittest import TestCase
2120
from unittest.mock import Mock, patch
@@ -29,9 +28,7 @@
2928
OTLPLogExporter,
3029
)
3130
from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import (
32-
ExportLogsPartialSuccess,
3331
ExportLogsServiceRequest,
34-
ExportLogsServiceResponse,
3532
)
3633
from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue
3734
from opentelemetry.proto.common.v1.common_pb2 import (
@@ -319,26 +316,6 @@ def export_log_and_deserialize(self, log_data):
319316
)
320317
return log_records
321318

322-
@patch("sys.stderr", new_callable=StringIO)
323-
def test_partial_success_recorded_directly_to_stderr(self, mock_stderr):
324-
# pylint: disable=protected-access
325-
exporter = OTLPLogExporter()
326-
exporter._client = Mock()
327-
exporter._client.Export.return_value = ExportLogsServiceResponse(
328-
partial_success=ExportLogsPartialSuccess(
329-
rejected_log_records=1,
330-
error_message="Log record dropped",
331-
)
332-
)
333-
334-
exporter.export([self.log_data_1])
335-
336-
self.assertIn("Partial success:\n", mock_stderr.getvalue())
337-
self.assertIn("rejected_log_records: 1\n", mock_stderr.getvalue())
338-
self.assertIn(
339-
'error_message: "Log record dropped"\n', mock_stderr.getvalue()
340-
)
341-
342319
def test_exported_log_without_trace_id(self):
343320
log_records = self.export_log_and_deserialize(self.log_data_4)
344321
if log_records:

opentelemetry-sdk/src/opentelemetry/sdk/_shared_internal/__init__.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,36 @@ class DuplicateFilter(logging.Filter):
4646
recursion depth exceeded issue in cases where logging itself results in an exception."""
4747

4848
def filter(self, record):
49-
current_log = (
49+
# We need to pick a time longer than the OTLP LogExporter timeout
50+
# which defaults to 10 seconds, but not pick something so long that
51+
# it filters out useful logs.
52+
time_boundary = time.time() // 20
53+
current_log_by_line = (
54+
record.module,
55+
record.pathname,
56+
record.lineno,
57+
time_boundary,
58+
)
59+
previous_log_by_line = getattr(self, "last_source", None)
60+
previous_log_by_line_count = getattr(self, "last_source_count", 1)
61+
if current_log_by_line == previous_log_by_line:
62+
recurring_count = previous_log_by_line_count + 1
63+
self.last_source_count = recurring_count # pylint: disable=attribute-defined-outside-init
64+
if recurring_count > 3:
65+
# Don't allow further processing, since the same line is repeatedly emitting logs.
66+
return False
67+
else:
68+
self.last_source = current_log_by_line # pylint: disable=attribute-defined-outside-init
69+
self.last_source_count = 1 # pylint: disable=attribute-defined-outside-init
70+
71+
current_log_by_content = (
5072
record.module,
5173
record.levelno,
5274
record.msg,
53-
# We need to pick a time longer than the OTLP LogExporter timeout
54-
# which defaults to 10 seconds, but not pick something so long that
55-
# it filters out useful logs.
56-
time.time() // 20,
75+
time_boundary,
5776
)
58-
if current_log != getattr(self, "last_log", None):
59-
self.last_log = current_log # pylint: disable=attribute-defined-outside-init
77+
if current_log_by_content != getattr(self, "last_log", None):
78+
self.last_log = current_log_by_content # pylint: disable=attribute-defined-outside-init
6079
return True
6180
# False means python's `logging` module will no longer process this log.
6281
return False

0 commit comments

Comments
 (0)