Repository navigation
feat(secretmanager): add OpenTelemetry distributed tracing samples #14646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Shared test fixtures for Secret Manager tracing snippets.""" | ||
|
|
||
| from opentelemetry import trace | ||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def reset_otel_global_tracer_provider(): | ||
| """Resets OpenTelemetry's global TracerProvider between tests.""" | ||
| yield | ||
| trace._TRACER_PROVIDER_SET_ONCE._done = False | ||
| trace._TRACER_PROVIDER = None |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| pytest==9.0.3 | ||
| pytest==9.0.3 | ||
| pytest-asyncio>=0.23.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Sample demonstrating OpenTelemetry distributed tracing with SecretManagerServiceClient.""" | ||
|
|
||
| # [START secretmanager_enable_tracing] | ||
| import os | ||
|
|
||
| from google.cloud import secretmanager | ||
| from opentelemetry import trace | ||
| from opentelemetry.sdk.trace import TracerProvider | ||
| from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor | ||
|
|
||
|
|
||
| def access_secret_version_with_tracing( | ||
| project_id: str, | ||
| secret_id: str, | ||
| version_id: str = "latest", | ||
| ) -> str: | ||
| """Access a secret version in Secret Manager with OpenTelemetry tracing enabled. | ||
|
|
||
| Args: | ||
| project_id: Google Cloud project ID. | ||
| secret_id: ID of the secret to access. | ||
| version_id: ID of the secret version to access (defaults to "latest"). | ||
|
|
||
| Returns: | ||
| The secret payload decoded as a UTF-8 string. | ||
| """ | ||
| # NOTE: In standalone scripts or CLI tools, initialize and shut down the | ||
| # TracerProvider within the script workflow. In long-running production | ||
| # applications (e.g., web servers), configure the TracerProvider once at | ||
| # application startup. | ||
| # | ||
| # 1. Initialize the OpenTelemetry TracerProvider with a Console exporter | ||
| provider = TracerProvider() | ||
| provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter())) | ||
| trace.set_tracer_provider(provider) | ||
|
|
||
| # 2. Enable Google Cloud client library tracing | ||
| os.environ["GOOGLE_SDK_PYTHON_TRACING_ENABLED"] = "true" | ||
|
|
||
| # 3. Instantiate the client (automatically discovers the active TracerProvider) | ||
| client = secretmanager.SecretManagerServiceClient() | ||
|
|
||
| # 4. Construct the resource name and invoke the RPC | ||
| name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}" | ||
|
|
||
| try: | ||
| response = client.access_secret_version(request={"name": name}) | ||
| payload = response.payload.data.decode("UTF-8") | ||
| return payload | ||
| finally: | ||
| # Flush buffered spans before program completion | ||
| provider.shutdown() | ||
|
|
||
|
|
||
| # [END secretmanager_enable_tracing] | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| import argparse | ||
|
|
||
| parser = argparse.ArgumentParser( | ||
| description="Access a Secret Manager secret version with OpenTelemetry tracing." | ||
| ) | ||
| parser.add_argument("project_id", help="id of the GCP project") | ||
| parser.add_argument("secret_id", help="id of the secret to access") | ||
| parser.add_argument( | ||
| "version_id", default="latest", nargs="?", help="version to access" | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
| secret_data = access_secret_version_with_tracing( | ||
| project_id=args.project_id, | ||
| secret_id=args.secret_id, | ||
| version_id=args.version_id, | ||
| ) | ||
| print(f"Retrieved secret payload: {secret_data}") | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (secret) Error loading related location Loading |
||
|
Comment on lines
+73
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Access a Secret Manager secret version with OpenTelemetry tracing."
)
parser.add_argument("--project-id", help="Google Cloud Project ID")
parser.add_argument("--secret-id", required=True, help="Secret ID")
parser.add_argument("--version-id", default="latest", help="Secret version ID")
args = parser.parse_args()
# Initialize the OpenTelemetry TracerProvider with a Console exporter
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
try:
secret_data = access_secret_version_with_tracing(
project_id=args.project_id,
secret_id=args.secret_id,
version_id=args.version_id,
)
print(f"Retrieved secret payload: {secret_data}")
finally:
# Flush buffered spans before program completion
provider.shutdown()References
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,94 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # -*- coding: utf-8 -*- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Copyright 2026 Google LLC | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # limitations under the License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Sample demonstrating async OpenTelemetry distributed tracing with SecretManagerServiceAsyncClient.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # [START secretmanager_enable_tracing_async] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from google.cloud import secretmanager_v1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from opentelemetry import trace | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from opentelemetry.sdk.trace import TracerProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def access_secret_version_with_tracing_async( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project_id: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret_id: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version_id: str = "latest", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Access a secret version using SecretManagerServiceAsyncClient with OpenTelemetry tracing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project_id: Google Cloud project ID. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret_id: ID of the secret to access. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version_id: ID of the secret version to access (defaults to "latest"). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The secret payload decoded as a UTF-8 string. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # NOTE: In standalone scripts or CLI tools, initialize and shut down the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # TracerProvider within the script workflow. In long-running production | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # applications (e.g., web servers), configure the TracerProvider once at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # application startup. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1. Initialize the OpenTelemetry TracerProvider with a Console exporter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider = TracerProvider() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trace.set_tracer_provider(provider) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2. Enable Google Cloud client library tracing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.environ["GOOGLE_SDK_PYTHON_TRACING_ENABLED"] = "true" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 3. Instantiate the async client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client = secretmanager_v1.SecretManagerServiceAsyncClient() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 4. Construct resource name and invoke the async RPC | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| response = await client.access_secret_version(request={"name": name}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload = response.payload.data.decode("UTF-8") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return payload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Flush buffered spans before program completion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| provider.shutdown() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the synchronous sample, initializing and shutting down the global Recommendation:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # [END secretmanager_enable_tracing_async] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description="Access a Secret Manager secret version asynchronously with OpenTelemetry tracing." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument("project_id", help="id of the GCP project") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument("secret_id", help="id of the secret to access") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "version_id", default="latest", nargs="?", help="version to access" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args = parser.parse_args() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret_data = asyncio.run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| access_secret_version_with_tracing_async( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| project_id=args.project_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret_id=args.secret_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version_id=args.version_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Retrieved secret payload asynchronously: {secret_data}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (secret) Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update the if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Access a Secret Manager secret version asynchronously with OpenTelemetry tracing."
)
parser.add_argument("--project-id", help="Google Cloud Project ID")
parser.add_argument("--secret-id", required=True, help="Secret ID")
parser.add_argument("--version-id", default="latest", help="Secret version ID")
args = parser.parse_args()
# Initialize the OpenTelemetry TracerProvider with a Console exporter
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
try:
secret_data = asyncio.run(
access_secret_version_with_tracing_async(
project_id=args.project_id,
secret_id=args.secret_id,
version_id=args.version_id,
)
)
print(f"Retrieved secret payload asynchronously: {secret_data}")
finally:
# Flush buffered spans before program completion
provider.shutdown()References
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,158 @@ | ||||||||||||||||||||||||||||||||||||||
| # -*- coding: utf-8 -*- | ||||||||||||||||||||||||||||||||||||||
| # Copyright 2026 Google LLC | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||||||||||||||
| # you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||||||||||||||
| # You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||||||
| # See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||||||
| # limitations under the License. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| """Unit tests for secretmanager_trace_async snippet.""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import concurrent.futures | ||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||
| from unittest import mock | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from google.api_core import _observability | ||||||||||||||||||||||||||||||||||||||
| from google.api_core.client_options import ClientOptions | ||||||||||||||||||||||||||||||||||||||
| from google.api_core.gapic_v1 import method_async | ||||||||||||||||||||||||||||||||||||||
| from google.cloud import secretmanager | ||||||||||||||||||||||||||||||||||||||
| from google.cloud.secretmanager_v1.services.secret_manager_service.transports.grpc_asyncio import ( | ||||||||||||||||||||||||||||||||||||||
| SecretManagerServiceGrpcAsyncIOTransport, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| import grpc | ||||||||||||||||||||||||||||||||||||||
| from opentelemetry.sdk.trace import TracerProvider | ||||||||||||||||||||||||||||||||||||||
| from opentelemetry.sdk.trace.export import SimpleSpanProcessor | ||||||||||||||||||||||||||||||||||||||
| from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( | ||||||||||||||||||||||||||||||||||||||
| InMemorySpanExporter, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from secretmanager_trace_async import ( | ||||||||||||||||||||||||||||||||||||||
| access_secret_version_with_tracing_async, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Ensure OTel tracing capabilities are enabled in the environment | ||||||||||||||||||||||||||||||||||||||
| os.environ["GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED"] = "true" | ||||||||||||||||||||||||||||||||||||||
| os.environ["GOOGLE_SDK_PYTHON_TRACING_ENABLED"] = "true" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @pytest.fixture | ||||||||||||||||||||||||||||||||||||||
| def mock_grpc_server(): | ||||||||||||||||||||||||||||||||||||||
| """Starts an in-memory gRPC server returning mock responses.""" | ||||||||||||||||||||||||||||||||||||||
| server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=2)) | ||||||||||||||||||||||||||||||||||||||
| port = server.add_insecure_port("127.0.0.1:0") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| mock_resp = secretmanager.AccessSecretVersionResponse( | ||||||||||||||||||||||||||||||||||||||
| name="projects/test-project/secrets/my-secret/versions/1", | ||||||||||||||||||||||||||||||||||||||
| payload=secretmanager.SecretPayload(data=b"mock-async-token-xyz"), | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| generic_handler = grpc.method_handlers_generic_handler( | ||||||||||||||||||||||||||||||||||||||
| "google.cloud.secretmanager.v1.SecretManagerService", | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| "AccessSecretVersion": grpc.unary_unary_rpc_method_handler( | ||||||||||||||||||||||||||||||||||||||
| lambda r, c: secretmanager.AccessSecretVersionResponse.serialize( | ||||||||||||||||||||||||||||||||||||||
| mock_resp | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| request_deserializer=lambda b: b, | ||||||||||||||||||||||||||||||||||||||
| response_serializer=lambda b: b, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| server.add_generic_rpc_handlers((generic_handler,)) | ||||||||||||||||||||||||||||||||||||||
| server.start() | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| yield port | ||||||||||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||||||||||
| server.stop(grace=None) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @pytest.fixture | ||||||||||||||||||||||||||||||||||||||
| def otel_setup(): | ||||||||||||||||||||||||||||||||||||||
| """Configures an in-memory OpenTelemetry tracer provider and exporter.""" | ||||||||||||||||||||||||||||||||||||||
| provider = TracerProvider() | ||||||||||||||||||||||||||||||||||||||
| exporter = InMemorySpanExporter() | ||||||||||||||||||||||||||||||||||||||
| provider.add_span_processor(SimpleSpanProcessor(exporter)) | ||||||||||||||||||||||||||||||||||||||
| return provider, exporter | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @pytest.mark.asyncio | ||||||||||||||||||||||||||||||||||||||
| async def test_access_secret_version_with_tracing_async_spans( | ||||||||||||||||||||||||||||||||||||||
| mock_grpc_server, otel_setup | ||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||
| """Verifies that access_secret_version_with_tracing_async emits T3 and T4 spans.""" | ||||||||||||||||||||||||||||||||||||||
| port = mock_grpc_server | ||||||||||||||||||||||||||||||||||||||
| provider, exporter = otel_setup | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| orig_init = SecretManagerServiceGrpcAsyncIOTransport.__init__ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def patched_init(self, *args, **kwargs): | ||||||||||||||||||||||||||||||||||||||
| opts = ClientOptions(api_endpoint=f"127.0.0.1:{port}", tracer_provider=provider) | ||||||||||||||||||||||||||||||||||||||
| interceptors = _observability.get_otel_async_interceptor(opts) | ||||||||||||||||||||||||||||||||||||||
| kwargs["channel"] = grpc.aio.insecure_channel( | ||||||||||||||||||||||||||||||||||||||
| f"127.0.0.1:{port}", interceptors=interceptors | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| orig_init(self, *args, **kwargs) | ||||||||||||||||||||||||||||||||||||||
| # Ensure modern GAPIC async tracing wrapper is attached | ||||||||||||||||||||||||||||||||||||||
| self._wrapped_methods[self.access_secret_version] = method_async.wrap_method( | ||||||||||||||||||||||||||||||||||||||
| self.access_secret_version, | ||||||||||||||||||||||||||||||||||||||
| method_name="/google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion", | ||||||||||||||||||||||||||||||||||||||
| client_options=opts, | ||||||||||||||||||||||||||||||||||||||
| kind="grpc_asyncio", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| with mock.patch("secretmanager_trace_async.TracerProvider", return_value=provider): | ||||||||||||||||||||||||||||||||||||||
| with mock.patch.object( | ||||||||||||||||||||||||||||||||||||||
| SecretManagerServiceGrpcAsyncIOTransport, "__init__", patched_init | ||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||
| payload = await access_secret_version_with_tracing_async( | ||||||||||||||||||||||||||||||||||||||
| project_id="test-project", | ||||||||||||||||||||||||||||||||||||||
| secret_id="my-secret", | ||||||||||||||||||||||||||||||||||||||
| version_id="1", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+111
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we removed the
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # 1. Verify returned payload | ||||||||||||||||||||||||||||||||||||||
| assert payload == "mock-async-token-xyz" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # 2. Inspect emitted spans | ||||||||||||||||||||||||||||||||||||||
| spans = exporter.get_finished_spans() | ||||||||||||||||||||||||||||||||||||||
| assert len(spans) == 2, f"Expected 2 spans (T3 + T4), but got {len(spans)}" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| t3_spans = [s for s in spans if s.parent is None] | ||||||||||||||||||||||||||||||||||||||
| t4_spans = [s for s in spans if s.parent is not None] | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| assert len(t3_spans) == 1, "Expected exactly 1 T3 (logical method) span" | ||||||||||||||||||||||||||||||||||||||
| assert len(t4_spans) == 1, "Expected exactly 1 T4 (wire attempt) span" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| t3_span = t3_spans[0] | ||||||||||||||||||||||||||||||||||||||
| t4_span = t4_spans[0] | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # 3. Verify T3 logical client span attributes | ||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||
| t3_span.name | ||||||||||||||||||||||||||||||||||||||
| == "google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| assert t3_span.attributes.get("rpc.system.name") == "grpc" | ||||||||||||||||||||||||||||||||||||||
| assert t3_span.attributes.get("rpc.response.status_code") == "OK" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # 4. Verify T4 wire attempt span attributes | ||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||
| t4_span.name | ||||||||||||||||||||||||||||||||||||||
| == "google.cloud.secretmanager.v1.SecretManagerService/AccessSecretVersion" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| assert ( | ||||||||||||||||||||||||||||||||||||||
| t4_span.attributes.get("rpc.service") | ||||||||||||||||||||||||||||||||||||||
| == "google.cloud.secretmanager.v1.SecretManagerService" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| assert t4_span.attributes.get("rpc.grpc.status_code") == 0 | ||||||||||||||||||||||||||||||||||||||
| assert t4_span.attributes.get("rpc.response.status_code") == "OK" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # 5. Verify span hierarchy / parent-child linkage | ||||||||||||||||||||||||||||||||||||||
| assert t4_span.parent.span_id == t3_span.context.span_id | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initializing and shutting down the global
TracerProviderinside a reusable utility function likeaccess_secret_version_with_tracingis a major anti-pattern and can cause bugs in production.trace.set_tracer_provider(provider)inside the function will log warnings or fail on subsequent calls because the global tracer provider can only be set once.provider.shutdown()in thefinallyblock will shut down the global tracer provider. Any subsequent client calls or other parts of the application trying to use the global tracer provider will fail to emit spans.Recommendation:
Move the OpenTelemetry configuration (TracerProvider initialization, global registration, and shutdown) to the application's entry point (the
__main__block). This keeps the utility function clean, reusable, and free of global side effects.