Skip to content

Commit 9e5893c

Browse files
authored
1 parent e975243 commit 9e5893c

File tree

4 files changed

+116
-2
lines changed

4 files changed

+116
-2
lines changed

opentelemetry-exporter-gcp-monitoring/src/opentelemetry/exporter/cloud_monitoring/__init__.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def _get_metric_descriptor(
169169
if key in seen_keys:
170170
continue
171171
seen_keys.add(key)
172-
descriptor["labels"].append(LabelDescriptor(key=key)) # type: ignore[union-attr] # TODO #56
172+
descriptor["labels"].append(LabelDescriptor(key=_normalize_label_key(key))) # type: ignore[union-attr] # TODO #56
173173

174174
if self.unique_identifier:
175175
descriptor["labels"].append( # type: ignore[union-attr] # TODO #56
@@ -312,7 +312,7 @@ def export(
312312

313313
for data_point in metric.data.data_points:
314314
labels = {
315-
key: str(value)
315+
_normalize_label_key(key): str(value)
316316
for key, value in (
317317
data_point.attributes or {}
318318
).items()
@@ -357,3 +357,17 @@ def _timestamp_from_nanos(nanos: int) -> Timestamp:
357357
ts = Timestamp()
358358
ts.FromNanoseconds(nanos)
359359
return ts
360+
361+
362+
def _normalize_label_key(key: str) -> str:
363+
"""Makes the key into a valid GCM label key
364+
365+
See reference impl
366+
https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/e955c204f4f2bfdc92ff0ad52786232b975efcc2/exporter/metric/metric.go#L595-L604
367+
"""
368+
sanitized = "".join(
369+
c if c.isalpha() or c.isnumeric() else "_" for c in key
370+
)
371+
if sanitized[0].isdigit():
372+
sanitized = "key_" + sanitized
373+
return sanitized
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"/google.monitoring.v3.MetricService/CreateMetricDescriptor": [
3+
{
4+
"metricDescriptor": {
5+
"description": "foo",
6+
"displayName": "mycounter",
7+
"labels": [
8+
{
9+
"key": "key_1some_invalid__key"
10+
}
11+
],
12+
"metricKind": "CUMULATIVE",
13+
"type": "workload.googleapis.com/mycounter",
14+
"valueType": "INT64"
15+
},
16+
"name": "projects/fakeproject"
17+
}
18+
],
19+
"/google.monitoring.v3.MetricService/CreateTimeSeries": [
20+
{
21+
"name": "projects/fakeproject",
22+
"timeSeries": [
23+
{
24+
"metric": {
25+
"labels": {
26+
"key_1some_invalid__key": "value"
27+
},
28+
"type": "workload.googleapis.com/mycounter"
29+
},
30+
"metricKind": "CUMULATIVE",
31+
"points": [
32+
{
33+
"interval": {
34+
"endTime": "str",
35+
"startTime": "str"
36+
},
37+
"value": {
38+
"int64Value": "12"
39+
}
40+
}
41+
],
42+
"resource": {
43+
"labels": {
44+
"location": "global",
45+
"namespace": "",
46+
"node_id": ""
47+
},
48+
"type": "generic_node"
49+
}
50+
}
51+
]
52+
}
53+
]
54+
}

opentelemetry-exporter-gcp-monitoring/tests/test_cloud_monitoring.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,17 @@ def callback(_: CallbackOptions) -> List[Observation]:
201201
)
202202
meter_provider.force_flush()
203203
assert gcmfake.get_calls() == snapshot_gcmcalls
204+
205+
206+
def test_invalid_label_keys(
207+
gcmfake_meter_provider: GcmFakeMeterProvider,
208+
gcmfake: GcmFake,
209+
snapshot_gcmcalls,
210+
) -> None:
211+
meter_provider = gcmfake_meter_provider()
212+
counter = meter_provider.get_meter(__name__).create_counter(
213+
"mycounter", description="foo", unit="{myunit}"
214+
)
215+
counter.add(12, {"1some.invalid$\\key": "value"})
216+
meter_provider.force_flush()
217+
assert gcmfake.get_calls() == snapshot_gcmcalls
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
from opentelemetry.exporter.cloud_monitoring import _normalize_label_key
17+
18+
19+
@pytest.mark.parametrize(
20+
("key", "expected"),
21+
[
22+
("valid_key_1", "valid_key_1"),
23+
("hellø", "hellø"),
24+
("123", "key_123"),
25+
("key!321", "key_321"),
26+
("key!321", "key_321"),
27+
("hyphens-dots.slashes/", "hyphens_dots_slashes_"),
28+
("non_letters_:£¢$∞", "non_letters______"),
29+
],
30+
)
31+
def test_normalize_label_key(key: str, expected: str) -> None:
32+
assert _normalize_label_key(key) == expected

0 commit comments

Comments
 (0)