Skip to content

Commit ec69ad1

Browse files
authored
Merge branch 'main' into export-nonmonotonic-sums-as-gauges-in-prometheus
2 parents 7bbc3f6 + adbcf82 commit ec69ad1

File tree

14 files changed

+194
-27
lines changed

14 files changed

+194
-27
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- Modify Prometheus exporter to translate non-monotonic Sums into Gauges
1111
([#3306](https://github.com/open-telemetry/opentelemetry-python/pull/3306))
12-
- Add max_scale option to Exponential Bucket Histogram Aggregation [#3323](https://github.com/open-telemetry/opentelemetry-python/pull/3323))
13-
- Use BoundedAttributes instead of raw dict to extract attributes from LogRecord and Support dropped_attributes_count in LogRecord ([#3310](https://github.com/open-telemetry/opentelemetry-python/pull/3310))
12+
- Add max_scale option to Exponential Bucket Histogram Aggregation
13+
([#3323](https://github.com/open-telemetry/opentelemetry-python/pull/3323))
14+
- Use BoundedAttributes instead of raw dict to extract attributes from LogRecord
15+
([#3310](https://github.com/open-telemetry/opentelemetry-python/pull/3310))
16+
- Support dropped_attributes_count in LogRecord and exporters
17+
([#3351](https://github.com/open-telemetry/opentelemetry-python/pull/3351))
18+
- Add unit to view instrument selection criteria
19+
([#3341](https://github.com/open-telemetry/opentelemetry-python/pull/3341))
1420
- Upgrade opentelemetry-proto to 0.20 and regen
1521
[#3355](https://github.com/open-telemetry/opentelemetry-python/pull/3355))
1622

@@ -31,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3137
- Add speced out environment variables and arguments for BatchLogRecordProcessor
3238
([#3237](https://github.com/open-telemetry/opentelemetry-python/pull/3237))
3339

40+
3441
## Version 1.17.0/0.38b0 (2023-03-22)
3542

3643
- Implement LowMemory temporality

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telem
101101
- [Aaron Abbott](https://github.com/aabmass), Google
102102
- [Jeremy Voss](https://github.com/jeremydvoss), Microsoft
103103
- [Sanket Mehta](https://github.com/sanketmehta28), Cisco
104+
- [Shalev Roda](https://github.com/shalevr), Cisco
104105

105106
Emeritus Approvers
106107

exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def _encode_log(log_data: LogData) -> PB2LogRecord:
4747
body=_encode_value(log_data.log_record.body),
4848
severity_text=log_data.log_record.severity_text,
4949
attributes=_encode_attributes(log_data.log_record.attributes),
50+
dropped_attributes_count=log_data.log_record.dropped_attributes,
5051
severity_number=log_data.log_record.severity_number.value,
5152
)
5253

exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from opentelemetry.proto.resource.v1.resource_pb2 import (
4040
Resource as PB2Resource,
4141
)
42-
from opentelemetry.sdk._logs import LogData
42+
from opentelemetry.sdk._logs import LogData, LogLimits
4343
from opentelemetry.sdk._logs import LogRecord as SDKLogRecord
4444
from opentelemetry.sdk.resources import Resource as SDKResource
4545
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
@@ -51,6 +51,19 @@ def test_encode(self):
5151
sdk_logs, expected_encoding = self.get_test_logs()
5252
self.assertEqual(encode_logs(sdk_logs), expected_encoding)
5353

54+
def test_dropped_attributes_count(self):
55+
sdk_logs = self._get_test_logs_dropped_attributes()
56+
encoded_logs = encode_logs(sdk_logs)
57+
self.assertTrue(hasattr(sdk_logs[0].log_record, "dropped_attributes"))
58+
self.assertEqual(
59+
# pylint:disable=no-member
60+
encoded_logs.resource_logs[0]
61+
.scope_logs[0]
62+
.log_records[0]
63+
.dropped_attributes_count,
64+
2,
65+
)
66+
5467
@staticmethod
5568
def _get_sdk_log_data() -> List[LogData]:
5669
log1 = LogData(
@@ -251,3 +264,42 @@ def get_test_logs(
251264
)
252265

253266
return sdk_logs, pb2_service_request
267+
268+
@staticmethod
269+
def _get_test_logs_dropped_attributes() -> List[LogData]:
270+
log1 = LogData(
271+
log_record=SDKLogRecord(
272+
timestamp=1644650195189786880,
273+
trace_id=89564621134313219400156819398935297684,
274+
span_id=1312458408527513268,
275+
trace_flags=TraceFlags(0x01),
276+
severity_text="WARN",
277+
severity_number=SeverityNumber.WARN,
278+
body="Do not go gentle into that good night. Rage, rage against the dying of the light",
279+
resource=SDKResource({"first_resource": "value"}),
280+
attributes={"a": 1, "b": "c", "user_id": "B121092"},
281+
limits=LogLimits(max_attributes=1),
282+
),
283+
instrumentation_scope=InstrumentationScope(
284+
"first_name", "first_version"
285+
),
286+
)
287+
288+
log2 = LogData(
289+
log_record=SDKLogRecord(
290+
timestamp=1644650249738562048,
291+
trace_id=0,
292+
span_id=0,
293+
trace_flags=TraceFlags.DEFAULT,
294+
severity_text="WARN",
295+
severity_number=SeverityNumber.WARN,
296+
body="Cooper, this is no time for caution!",
297+
resource=SDKResource({"second_resource": "CASE"}),
298+
attributes={},
299+
),
300+
instrumentation_scope=InstrumentationScope(
301+
"second_name", "second_version"
302+
),
303+
)
304+
305+
return [log1, log2]

exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
CounterMetricFamily,
7575
GaugeMetricFamily,
7676
HistogramMetricFamily,
77+
InfoMetricFamily,
7778
)
7879
from prometheus_client.core import Metric as PrometheusMetric
7980

@@ -97,6 +98,9 @@
9798

9899
_logger = getLogger(__name__)
99100

101+
_TARGET_INFO_NAME = "target"
102+
_TARGET_INFO_DESCRIPTION = "Target metadata"
103+
100104

101105
def _convert_buckets(
102106
bucket_counts: Sequence[int], explicit_bounds: Sequence[float]
@@ -116,8 +120,7 @@ def _convert_buckets(
116120
class PrometheusMetricReader(MetricReader):
117121
"""Prometheus metric exporter for OpenTelemetry."""
118122

119-
def __init__(self) -> None:
120-
123+
def __init__(self, disable_target_info: bool = False) -> None:
121124
super().__init__(
122125
preferred_temporality={
123126
Counter: AggregationTemporality.CUMULATIVE,
@@ -128,7 +131,7 @@ def __init__(self) -> None:
128131
ObservableGauge: AggregationTemporality.CUMULATIVE,
129132
}
130133
)
131-
self._collector = _CustomCollector()
134+
self._collector = _CustomCollector(disable_target_info)
132135
REGISTRY.register(self._collector)
133136
self._collector._callback = self.collect
134137

@@ -153,12 +156,14 @@ class _CustomCollector:
153156
https://github.com/prometheus/client_python#custom-collectors
154157
"""
155158

156-
def __init__(self):
159+
def __init__(self, disable_target_info: bool = False):
157160
self._callback = None
158161
self._metrics_datas = deque()
159162
self._non_letters_digits_underscore_re = compile(
160163
r"[^\w]", UNICODE | IGNORECASE
161164
)
165+
self._disable_target_info = disable_target_info
166+
self._target_info = None
162167

163168
def add_metrics_data(self, metrics_data: MetricsData) -> None:
164169
"""Add metrics to Prometheus data"""
@@ -175,6 +180,20 @@ def collect(self) -> None:
175180

176181
metric_family_id_metric_family = {}
177182

183+
if len(self._metrics_datas):
184+
if not self._disable_target_info:
185+
if self._target_info is None:
186+
attributes = {}
187+
for res in self._metrics_datas[0].resource_metrics:
188+
attributes = {**attributes, **res.resource.attributes}
189+
190+
self._target_info = self._create_info_metric(
191+
_TARGET_INFO_NAME, _TARGET_INFO_DESCRIPTION, attributes
192+
)
193+
metric_family_id_metric_family[
194+
_TARGET_INFO_NAME
195+
] = self._target_info
196+
178197
while self._metrics_datas:
179198
self._translate_to_prometheus(
180199
self._metrics_datas.popleft(), metric_family_id_metric_family
@@ -348,3 +367,11 @@ def _check_value(self, value: Union[int, float, str, Sequence]) -> str:
348367
if not isinstance(value, str):
349368
return dumps(value, default=str)
350369
return str(value)
370+
371+
def _create_info_metric(
372+
self, name: str, description: str, attributes: Dict[str, str]
373+
) -> InfoMetricFamily:
374+
"""Create an Info Metric Family with list of attributes"""
375+
info = InfoMetricFamily(name, description, labels=attributes)
376+
info.add_metric(labels=list(attributes.keys()), value=attributes)
377+
return info

exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
from unittest.mock import Mock, patch
1818

1919
from prometheus_client import generate_latest
20-
from prometheus_client.core import CounterMetricFamily, GaugeMetricFamily
20+
from prometheus_client.core import (
21+
CounterMetricFamily,
22+
GaugeMetricFamily,
23+
InfoMetricFamily,
24+
)
2125

2226
from opentelemetry.exporter.prometheus import (
2327
PrometheusMetricReader,
@@ -33,6 +37,7 @@
3337
ResourceMetrics,
3438
ScopeMetrics,
3539
)
40+
from opentelemetry.sdk.resources import Resource
3641
from opentelemetry.test.metrictestutil import (
3742
_generate_gauge,
3843
_generate_sum,
@@ -101,7 +106,7 @@ def test_histogram_to_prometheus(self):
101106
]
102107
)
103108

104-
collector = _CustomCollector()
109+
collector = _CustomCollector(disable_target_info=True)
105110
collector.add_metrics_data(metrics_data)
106111
result_bytes = generate_latest(collector)
107112
result = result_bytes.decode("utf-8")
@@ -146,7 +151,7 @@ def test_monotonic_sum_to_prometheus(self):
146151
]
147152
)
148153

149-
collector = _CustomCollector()
154+
collector = _CustomCollector(disable_target_info=True)
150155
collector.add_metrics_data(metrics_data)
151156

152157
for prometheus_metric in collector.collect():
@@ -233,7 +238,7 @@ def test_gauge_to_prometheus(self):
233238
]
234239
)
235240

236-
collector = _CustomCollector()
241+
collector = _CustomCollector(disable_target_info=True)
237242
collector.add_metrics_data(metrics_data)
238243

239244
for prometheus_metric in collector.collect():
@@ -295,7 +300,7 @@ def test_list_labels(self):
295300
)
296301
]
297302
)
298-
collector = _CustomCollector()
303+
collector = _CustomCollector(disable_target_info=True)
299304
collector.add_metrics_data(metrics_data)
300305

301306
for prometheus_metric in collector.collect():
@@ -337,3 +342,46 @@ def test_multiple_collection_calls(self):
337342
result_2 = list(metric_reader._collector.collect())
338343
self.assertEqual(result_0, result_1)
339344
self.assertEqual(result_1, result_2)
345+
346+
def test_target_info_enabled_by_default(self):
347+
metric_reader = PrometheusMetricReader()
348+
provider = MeterProvider(
349+
metric_readers=[metric_reader],
350+
resource=Resource({"os": "Unix", "histo": 1}),
351+
)
352+
meter = provider.get_meter("getting-started", "0.1.2")
353+
counter = meter.create_counter("counter")
354+
counter.add(1)
355+
result = list(metric_reader._collector.collect())
356+
357+
for prometheus_metric in result[:0]:
358+
self.assertEqual(type(prometheus_metric), InfoMetricFamily)
359+
self.assertEqual(prometheus_metric.name, "target")
360+
self.assertEqual(
361+
prometheus_metric.documentation, "Target metadata"
362+
)
363+
self.assertTrue(len(prometheus_metric.samples) == 1)
364+
self.assertEqual(prometheus_metric.samples[0].value, 1)
365+
self.assertTrue(len(prometheus_metric.samples[0].labels) == 2)
366+
self.assertEqual(prometheus_metric.samples[0].labels["os"], "Unix")
367+
self.assertEqual(prometheus_metric.samples[0].labels["histo"], "1")
368+
369+
def test_target_info_disabled(self):
370+
metric_reader = PrometheusMetricReader(disable_target_info=True)
371+
provider = MeterProvider(
372+
metric_readers=[metric_reader],
373+
resource=Resource({"os": "Unix", "histo": 1}),
374+
)
375+
meter = provider.get_meter("getting-started", "0.1.2")
376+
counter = meter.create_counter("counter")
377+
counter.add(1)
378+
result = list(metric_reader._collector.collect())
379+
380+
for prometheus_metric in result:
381+
self.assertNotEqual(type(prometheus_metric), InfoMetricFamily)
382+
self.assertNotEqual(prometheus_metric.name, "target")
383+
self.assertNotEqual(
384+
prometheus_metric.documentation, "Target metadata"
385+
)
386+
self.assertNotIn("os", prometheus_metric.samples[0].labels)
387+
self.assertNotIn("histo", prometheus_metric.samples[0].labels)

opentelemetry-api/src/opentelemetry/propagate/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,12 @@ def inject(
144144
)
145145
).load()()
146146
)
147-
148-
except Exception: # pylint: disable=broad-except
149-
logger.exception(
150-
"Failed to load configured propagator: %s", propagator
147+
except StopIteration:
148+
raise ValueError(
149+
f"Propagator {propagator} not found. It is either misspelled or not installed."
151150
)
151+
except Exception: # pylint: disable=broad-except
152+
logger.exception("Failed to load propagator: %s", propagator)
152153
raise
153154

154155

opentelemetry-api/tests/propagators/test_propagators.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# type: ignore
1616

1717
from importlib import reload
18-
from logging import ERROR
1918
from os import environ
2019
from unittest import TestCase
2120
from unittest.mock import Mock, patch
@@ -109,16 +108,16 @@ def test_propagators(propagators):
109108
)
110109
def test_composite_propagators_error(self):
111110

112-
# pylint: disable=import-outside-toplevel
113-
import opentelemetry.propagate
111+
with self.assertRaises(ValueError) as cm:
112+
# pylint: disable=import-outside-toplevel
113+
import opentelemetry.propagate
114+
115+
reload(opentelemetry.propagate)
114116

115-
with self.assertRaises(Exception):
116-
with self.assertLogs(level=ERROR) as err:
117-
reload(opentelemetry.propagate)
118-
self.assertIn(
119-
"Failed to load configured propagator `unknown`",
120-
err.output[0],
121-
)
117+
self.assertEqual(
118+
str(cm.exception),
119+
"Propagator unknown not found. It is either misspelled or not installed.",
120+
)
122121

123122

124123
class TestTraceContextTextMapPropagator(TestCase):

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def to_json(self, indent=4) -> str:
203203
"attributes": dict(self.attributes)
204204
if bool(self.attributes)
205205
else None,
206+
"dropped_attributes": self.dropped_attributes,
206207
"timestamp": ns_to_iso_str(self.timestamp),
207208
"trace_id": f"0x{format_trace_id(self.trace_id)}"
208209
if self.trace_id is not None

0 commit comments

Comments
 (0)