Skip to content

Commit 369a9f2

Browse files
Include more request configuration options into the span attributes for the Google GenAI SDK instrumentation (#3374)
* Create a utility to simplify recording request attributes. * Update recording mechanism to record more request options. * Improve the recording of span request attributes. * Reformat with ruff. * Update TODOs to reflect change made here. * Update changelog now that PR has been created and can be referenced. * Fix lint issues. * Reformat with ruff. * Add more documentation comments requested in the pull request. * Add tests and comments that provide some additional clarity regarding the flattening logic in response to PR comments. * Add tests and comments that provide some additional clarity regarding the flattening logic in response to PR comments. * Handle corner case where flatten function returns compound output. * Update prefix to match currently proposed SemConv. * Update to specify attributes from SemConv constants per PR feedback. * Use an allowlist for dynamic keys per PR feedback. * Reformat with ruff. * Fix lint issues. * Reformat with ruff. * Handle flattening errors more gracefully. * Add support for more wildcards in the allowlist. * Add a clearer type for the flatten functions. * Simplify 'exclude_keys' initialization per PR feedback. * Simplify AllowList constructor type annotation per PR feedback. * Reformat with ruff. * Resolve lint error concerning too many returns. * Reformat with ruff. * Update name to reflect requested changes in Semantic Conventions pull request #2125. * Add test to verify correct handling of Unicode. * Reformat with ruff. * Remove deuplicated test.
1 parent c54292f commit 369a9f2

File tree

15 files changed

+1257
-91
lines changed

15 files changed

+1257
-91
lines changed

instrumentation-genai/opentelemetry-instrumentation-google-genai/CHANGELOG.md

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

88
## Unreleased
99

10+
- Add more request configuration options to the span attributes ([#3374](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3374))
1011
- Restructure tests to keep in line with repository conventions ([#3344](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3344))
1112

1213
- Fix [bug](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/3416) where

instrumentation-genai/opentelemetry-instrumentation-google-genai/TODOS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
Here are some TODO items required to achieve stability for this package:
66

7-
- Add more span-level attributes for request configuration
87
- Add more span-level attributes for response information
98
- Verify and correct formatting of events:
109
- Including the 'role' field for message events

instrumentation-genai/opentelemetry-instrumentation-google-genai/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ classifiers = [
3737
"Programming Language :: Python :: 3.12"
3838
]
3939
dependencies = [
40-
"opentelemetry-api >=1.30.0, <2",
41-
"opentelemetry-instrumentation >=0.51b0, <2",
42-
"opentelemetry-semantic-conventions >=0.51b0, <2"
40+
"opentelemetry-api >=1.31.1, <2",
41+
"opentelemetry-instrumentation >=0.52b1, <2",
42+
"opentelemetry-semantic-conventions >=0.52b1, <2"
4343
]
4444

4545
[project.optional-dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright The OpenTelemetry Authors
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+
# http://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 os
16+
import re
17+
from typing import Iterable, Optional, Set
18+
19+
ALLOWED = True
20+
DENIED = False
21+
22+
23+
def _parse_env_list(s: str) -> Set[str]:
24+
result = set()
25+
for entry in s.split(","):
26+
stripped_entry = entry.strip()
27+
if not stripped_entry:
28+
continue
29+
result.add(stripped_entry)
30+
return result
31+
32+
33+
class _CompoundMatcher:
34+
def __init__(self, entries: Set[str]):
35+
self._match_all = "*" in entries
36+
self._entries = entries
37+
self._regex_matcher = None
38+
regex_entries = []
39+
for entry in entries:
40+
if "*" not in entry:
41+
continue
42+
if entry == "*":
43+
continue
44+
entry = entry.replace("[", "\\[")
45+
entry = entry.replace("]", "\\]")
46+
entry = entry.replace(".", "\\.")
47+
entry = entry.replace("*", ".*")
48+
regex_entries.append(f"({entry})")
49+
if regex_entries:
50+
joined_regex = "|".join(regex_entries)
51+
regex_str = f"^({joined_regex})$"
52+
self._regex_matcher = re.compile(regex_str)
53+
54+
@property
55+
def match_all(self):
56+
return self._match_all
57+
58+
def matches(self, x):
59+
if self._match_all:
60+
return True
61+
if x in self._entries:
62+
return True
63+
if (self._regex_matcher is not None) and (
64+
self._regex_matcher.fullmatch(x)
65+
):
66+
return True
67+
return False
68+
69+
70+
class AllowList:
71+
def __init__(
72+
self,
73+
includes: Optional[Iterable[str]] = None,
74+
excludes: Optional[Iterable[str]] = None,
75+
):
76+
self._includes = _CompoundMatcher(set(includes or []))
77+
self._excludes = _CompoundMatcher(set(excludes or []))
78+
assert (not self._includes.match_all) or (
79+
not self._excludes.match_all
80+
), "Can't have '*' in both includes and excludes."
81+
82+
def allowed(self, x: str):
83+
if self._excludes.match_all:
84+
return self._includes.matches(x)
85+
if self._includes.match_all:
86+
return not self._excludes.matches(x)
87+
return self._includes.matches(x) and not self._excludes.matches(x)
88+
89+
@staticmethod
90+
def from_env(
91+
includes_env_var: str, excludes_env_var: Optional[str] = None
92+
):
93+
includes = _parse_env_list(os.getenv(includes_env_var) or "")
94+
excludes = set()
95+
if excludes_env_var:
96+
excludes = _parse_env_list(os.getenv(excludes_env_var) or "")
97+
return AllowList(includes=includes, excludes=excludes)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright The OpenTelemetry Authors
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+
# http://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+
16+
# Semantic Convention still being defined in:
17+
# https://github.com/open-telemetry/semantic-conventions/pull/2125
18+
GCP_GENAI_OPERATION_CONFIG = "gcp.gen_ai.operation.config"

0 commit comments

Comments
 (0)