From 8bb3be53e06f39cb69f7aa3d29876610cda0062d Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Wed, 24 Aug 2022 09:46:38 +0900 Subject: [PATCH 01/34] fix typo in example codes --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index f6de2a2d76..177bfe67b5 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -43,7 +43,8 @@ SimpleSpanProcessor(ConsoleSpanExporter()) ) - instrumentor = GrpcInstrumentorClient().instrument() + grpc_client_instrumentor = GrpcInstrumentorClient() + grpc_client_instrumentor.instrument() def run(): with grpc.insecure_channel("localhost:50051") as channel: @@ -180,7 +181,7 @@ class GrpcInstrumentorClient(BaseInstrumentor): Usage:: grpc_client_instrumentor = GrpcInstrumentorClient() - grpc.client_instrumentor.instrument() + grpc_client_instrumentor.instrument() """ From c960d2218582923837e79afd8259364fb4edbbfa Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Wed, 24 Aug 2022 17:09:26 +0900 Subject: [PATCH 02/34] add initial implementation of filters --- .../instrumentation/grpc/_client.py | 8 +- .../instrumentation/grpc/_server.py | 9 ++- .../instrumentation/grpc/filters/__init__.py | 78 +++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index b73b822b14..f2bba7f1a2 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -62,8 +62,9 @@ def callback(response_future): class OpenTelemetryClientInterceptor( grpcext.UnaryClientInterceptor, grpcext.StreamClientInterceptor ): - def __init__(self, tracer): + def __init__(self, tracer, filter=None): self._tracer = tracer + self._filter = filter def _start_span(self, method, **kwargs): service, meth = method.lstrip("/").split("/", 1) @@ -148,6 +149,8 @@ def _intercept(self, request, metadata, client_info, invoker): return self._trace_result(span, rpc_info, result) def intercept_unary(self, request, metadata, client_info, invoker): + if self._filter is not None and not self._filter(client_info): + return invoker(request, metadata) return self._intercept(request, metadata, client_info, invoker) # For RPCs that stream responses, the result can be a generator. To record @@ -188,6 +191,9 @@ def intercept_stream( if context.get_value(_SUPPRESS_INSTRUMENTATION_KEY): return invoker(request_or_iterator, metadata) + if self._filter is not None and not self._filter(client_info): + return invoker(request_or_iterator, metadata) + if client_info.is_server_stream: return self._intercept_server_stream( request_or_iterator, metadata, client_info, invoker diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index 82e192622c..44d24513b4 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -172,9 +172,10 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): Usage:: tracer = some OpenTelemetry tracer + filter = not filters.method_name("service.Foo") interceptors = [ - OpenTelemetryServerInterceptor(tracer), + OpenTelemetryServerInterceptor(tracer, filter), ] server = grpc.server( @@ -183,8 +184,9 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): """ - def __init__(self, tracer): + def __init__(self, tracer, filter=None): self._tracer = tracer + self._filter = filter @contextmanager def _set_remote_context(self, servicer_context): @@ -259,6 +261,9 @@ def _start_span( ) def intercept_service(self, continuation, handler_call_details): + if self._filter is not None and not self._filter(handler_call_details): + return continuation(handler_call_details) + def telemetry_wrapper(behavior, request_streaming, response_streaming): def telemetry_interceptor(request_or_iterator, context): diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py new file mode 100644 index 0000000000..1add922425 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -0,0 +1,78 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +import os + +import grpc + +from opentelemetry.instrumentation.grpc import grpcext + + +def _full_method(metadata): + name = "" + if isinstance(metadata, grpc.HandlerCallDetails): + name = metadata.method + elif isinstance(metadata, grpcext.UnaryClientInfo): + name = metadata.full_method + elif isinstance(metadata, grpcext.StreamClientInfo): + name = metadata.full_method + return name + + +def _split_full_method(metadata): + name = _full_method(metadata) + s, m = os.path.split(name) + if s != "": + s = os.path.normpath(s) + s.lstrip("/") + return (s, m) + + +def method_name(name): + def fn(metadata): + _, method = _split_full_method(metadata) + return method == name + return fn + + +def method_prefix(prefix): + def fn(metadata): + _, method = _split_full_method(metadata) + return method.startswith(prefix) + return fn + + +def full_method_name(name): + def fn(metadata): + fm = _full_method(metadata) + return fm == name + return fn + + +def service_name(name): + def fn(metadata): + service, _ = _split_full_method(metadata) + return service == name + return fn + + +def service_prefix(prefix): + def fn(metadata): + service, _ = _split_full_method(metadata) + return service.startswith(prefix) + return fn + + +def health_check(): + return service_prefix("grpc.health.v1.Health") From 380d63e5e358dd04cf782869826cc971d006b146 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Wed, 24 Aug 2022 18:12:07 +0900 Subject: [PATCH 03/34] add docstring to filter functions --- .../instrumentation/grpc/filters/__init__.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 1add922425..e566bc8471 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -40,6 +40,16 @@ def _split_full_method(metadata): def method_name(name): + """Returns a filter function that return True if + request's gRPC method name matches name. + + Args: + name (str): method name to match + + Returns: + A filter function that returns True if request's gRPC method + name matches name + """ def fn(metadata): _, method = _split_full_method(metadata) return method == name @@ -47,6 +57,16 @@ def fn(metadata): def method_prefix(prefix): + """Returns a filter function that return True if + request's gRPC method name starts with prefix. + + Args: + prefix (str): method prefix to match + + Returns: + A filter function that returns True if request's gRPC method + name starts with prefix + """ def fn(metadata): _, method = _split_full_method(metadata) return method.startswith(prefix) @@ -54,6 +74,16 @@ def fn(metadata): def full_method_name(name): + """Returns a filter function that return True if + request's gRPC full method name matches name. + + Args: + name (str): full method name to match + + Returns: + A filter function that returns True if request's gRPC full + method name matches name + """ def fn(metadata): fm = _full_method(metadata) return fm == name @@ -61,6 +91,16 @@ def fn(metadata): def service_name(name): + """Returns a filter function that return True if + request's gRPC service name matches name. + + Args: + name (str): service name to match + + Returns: + A filter function that returns True if request's gRPC service + name matches name + """ def fn(metadata): service, _ = _split_full_method(metadata) return service == name @@ -68,6 +108,16 @@ def fn(metadata): def service_prefix(prefix): + """Returns a filter function that return True if + request's gRPC service name starts with prefix. + + Args: + prefix (str): method prefix to match + + Returns: + A filter function that returns True if request's gRPC method + name starts with prefix + """ def fn(metadata): service, _ = _split_full_method(metadata) return service.startswith(prefix) @@ -75,4 +125,18 @@ def fn(metadata): def health_check(): + """Returns a Filter that returns true if the request's + service name is health check defined by gRPC Health Checking Protocol. + https://github.com/grpc/grpc/blob/master/doc/health-checking.md + """ return service_prefix("grpc.health.v1.Health") + + +__all__ = [ + "method_name", + "method_prefix", + "full_method_name", + "service_name", + "service_prefix", + "health_check", +] From 059b076f44ffd1d8362c79d11d875f6c17b6f72a Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Wed, 24 Aug 2022 18:15:55 +0900 Subject: [PATCH 04/34] update changelog to mention this pull request --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd7011afa..9d81409a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `opentelemetry-instrumentation-grpc` add supports to filter requests to instrument. ([#1241](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1241)) + ### Fixed - `opentelemetry-instrumentation-boto3sqs` Make propagation compatible with other SQS instrumentations, add 'messaging.url' span attribute, and fix missing package dependencies. @@ -24,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#1203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1203)) ### Added + - `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients ([#1177](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1177)) - `opentelemetry-instrumentation-sqlalchemy` Added span for the connection phase ([#1133](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1133)) @@ -34,11 +39,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.12.0rc2-0.32b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc2-0.32b0) - 2022-07-01 - - Pyramid: Only categorize 500s server exceptions as errors ([#1037](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1037)) ### Fixed + - Fix bug in system metrics by checking their configuration ([#1129](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1129)) - Adding escape call to fix [auto-instrumentation not producing spans on Windows](https://github.com/open-telemetry/opentelemetry-python/issues/2703). @@ -51,8 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fixed typo in `system.network.io` metric configuration ([#1135](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1135)) - ### Added + - `opentelemetry-instrumentation-aiohttp-client` Add support for optional custom trace_configs argument. ([1079](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1079)) - `opentelemetry-instrumentation-sqlalchemy` add support to instrument multiple engines @@ -76,10 +81,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Integrated sqlcommenter plugin into opentelemetry-instrumentation-django ([#896](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/896)) - ## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17 ### Fixed + - `opentelemetry-instrumentation-aiohttp-client` make span attributes available to sampler ([#1072](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1072)) - `opentelemetry-instrumentation-aws-lambda` Fixed an issue - in some rare cases (API GW proxy integration test) @@ -92,6 +97,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-sdk-extension-aws` change timeout for AWS EC2 and EKS metadata requests from 1000 seconds and 2000 seconds to 1 second ### Added + - `opentelemetry-instrument` and `opentelemetry-bootstrap` now include a `--version` flag ([#1065](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1065)) - `opentelemetry-instrumentation-redis` now instruments asynchronous Redis clients, if the installed redis-py includes async support (>=4.2.0). @@ -102,16 +108,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.11.1-0.30b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.1-0.30b1) - 2022-04-21 ### Added + - `opentelemetry-instrumentation-starlette` Capture custom request/response headers in span attributes - ([#1046])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1046) + ([#1046])() ### Fixed + - Prune autoinstrumentation sitecustomize module directory from PYTHONPATH immediately ([#1066](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1066)) ## [1.11.0-0.30b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.0-0.30b0) - 2022-04-18 ### Fixed + - `opentelemetry-instrumentation-pyramid` Fixed which package is the correct caller in _traced_init. ([#830](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/830)) - `opentelemetry-instrumentation-tornado` Fix Tornado errors mapping to 500 @@ -126,17 +135,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `opentelemetry-instrumentation-fastapi` Capture custom request/response headers in span attributes - ([#1032])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1032) + ([#1032])() - `opentelemetry-instrumentation-django` Capture custom request/response headers in span attributes - ([#1024])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1024) + ([#1024])() - `opentelemetry-instrumentation-asgi` Capture custom request/response headers in span attributes - ([#1004])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1004) + ([#1004])() - `opentelemetry-instrumentation-psycopg2` extended the sql commenter support of dbapi into psycopg2 ([#940](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/940)) - `opentelemetry-instrumentation-falcon` Add support for falcon==1.4.1 - ([#1000])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1000) + ([#1000])() - `opentelemetry-instrumentation-falcon` Falcon: Capture custom request/response headers in span attributes - ([#1003])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1003) + ([#1003])() - `opentelemetry-instrumentation-elasticsearch` no longer creates unique span names by including search target, replaces them with `` and puts the value in attribute `elasticsearch.target` ([#1018](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1018)) - `opentelemetry-instrumentation-pyramid` Handle non-HTTPException exceptions @@ -144,17 +153,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-system-metrics` restore `SystemMetrics` instrumentation as `SystemMetricsInstrumentor` ([#1012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1012)) - `opentelemetry-instrumentation-pyramid` Pyramid: Capture custom request/response headers in span attributes - ([#1022])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1022) - + ([#1022])() ## [1.10.0-0.29b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.10.0-0.29b0) - 2022-03-10 - `opentelemetry-instrumentation-wsgi` Capture custom request/response headers in span attributes - ([#925])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/925) + ([#925])() - `opentelemetry-instrumentation-flask` Flask: Capture custom request/response headers in span attributes - ([#952])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/952) + ([#952])() - `opentelemetry-instrumentation-tornado` Tornado: Capture custom request/response headers in span attributes - ([#950])(https://github.com/open-telemetry/opentelemetry-python-contrib/pull/950) + ([#950])() ### Added @@ -199,7 +207,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.0-0.28b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.9.0-0.28b0) - 2022-01-26 - ### Added - `opentelemetry-instrumentation-pyramid` Pyramid: Conditionally create SERVER spans @@ -238,7 +245,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-aiohttp-client` aiohttp: Remove `span_name` from docs ([#857](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/857)) - ## [1.8.0-0.27b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.8.0-0.27b0) - 2021-12-17 ### Added @@ -313,13 +319,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#755](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/755)) ### Added + - `opentelemetry-instrumentation-pika` Add `publish_hook` and `consume_hook` callbacks passed as arguments to the instrument method ([#763](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/763)) - ## [1.6.1-0.25b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.6.1-0.25b1) - 2021-10-18 ### Changed + - `opentelemetry-util-http` no longer contains an instrumentation entrypoint and will not be loaded automatically by the auto instrumentor. ([#745](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/745)) @@ -333,7 +340,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#760](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/760)) ## [1.6.0-0.25b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.6.0-0.25b0) - 2021-10-13 + ### Added + - `opentelemetry-sdk-extension-aws` Release AWS Python SDK Extension as 1.0.0 ([#667](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/667)) - `opentelemetry-instrumentation-urllib3`, `opentelemetry-instrumentation-requests` @@ -360,6 +369,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#391](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/391)) ### Changed + - `opentelemetry-instrumentation-flask` Fix `RuntimeError: Working outside of request context` ([#734](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/734)) - `opentelemetry-propagators-aws-xray` Rename `AwsXRayFormat` to `AwsXRayPropagator` @@ -390,6 +400,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26 ### Added + - `opentelemetry-sdk-extension-aws` Add AWS resource detectors to extension package ([#586](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/586)) - `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-aiohttp-client`, `openetelemetry-instrumentation-fastapi`, @@ -408,10 +419,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.4.0-0.23b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.4.0-0.23b0) - 2021-07-21 ### Removed + - Move `opentelemetry-instrumentation` to the core repo. ([#595](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/595)) ### Changed + - `opentelemetry-instrumentation-falcon` added support for Falcon 3. ([#607](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/607)) - `opentelemetry-instrumentation-tornado` properly instrument work done in tornado on_finish method. @@ -459,12 +472,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#568](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/568)) ### Added + - `opentelemetry-instrumentation-httpx` Add `httpx` instrumentation ([#461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/461)) ## [0.22b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.3.0-0.22b0) - 2021-06-01 ### Changed + - `opentelemetry-bootstrap` not longer forcibly removes and re-installs libraries and their instrumentations. This means running bootstrap will not auto-upgrade existing dependencies and as a result not cause dependency conflicts. @@ -481,6 +496,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#488](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/488)) ### Added + - `opentelemetry-instrumentation-botocore` now supports context propagation for lambda invoke via Payload embedded headers. ([#458](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/458)) @@ -490,6 +506,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.21b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.2.0-0.21b0) - 2021-05-11 ### Changed + - Instrumentation packages don't specify the libraries they instrument as dependencies anymore. Instead, they verify the correct version of libraries are installed at runtime. ([#475](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/475)) @@ -939,7 +956,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-ext-boto` Initial release - `opentelemetry-ext-botocore` Initial release - `opentelemetry-ext-system-metrics` Initial release - (https://github.com/open-telemetry/opentelemetry-python/pull/652) + () ## [0.8b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v0.8.0) - 2020-05-27 @@ -949,7 +966,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#572](https://github.com/open-telemetry/opentelemetry-python/pull/572)) - `opentelemetry-ext-sqlite3` Initial release - `opentelemetry-ext-psycopg2` Implement instrumentor interface, enabling auto-instrumentation - ([#694]https://github.com/open-telemetry/opentelemetry-python/pull/694) + ([#694]) - `opentelemetry-ext-asgi` Add ASGI middleware ([#716](https://github.com/open-telemetry/opentelemetry-python/pull/716)) - `opentelemetry-ext-django` Add exclude list for paths and hosts to prevent from tracing From ead1f61b745115eea4ee271d656ee3aaef3d5b8e Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 09:24:58 +0900 Subject: [PATCH 05/34] revert unnecessary change and fix typos in changelog --- CHANGELOG.md | 51 +++++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d81409a46..12d66091aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#1203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1203)) ### Added - - `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients ([#1177](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1177)) - `opentelemetry-instrumentation-sqlalchemy` Added span for the connection phase ([#1133](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1133)) @@ -39,11 +38,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.12.0rc2-0.32b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc2-0.32b0) - 2022-07-01 + - Pyramid: Only categorize 500s server exceptions as errors ([#1037](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1037)) ### Fixed - - Fix bug in system metrics by checking their configuration ([#1129](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1129)) - Adding escape call to fix [auto-instrumentation not producing spans on Windows](https://github.com/open-telemetry/opentelemetry-python/issues/2703). @@ -56,8 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fixed typo in `system.network.io` metric configuration ([#1135](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1135)) -### Added +### Added - `opentelemetry-instrumentation-aiohttp-client` Add support for optional custom trace_configs argument. ([1079](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1079)) - `opentelemetry-instrumentation-sqlalchemy` add support to instrument multiple engines @@ -81,10 +80,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Integrated sqlcommenter plugin into opentelemetry-instrumentation-django ([#896](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/896)) + ## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17 ### Fixed - - `opentelemetry-instrumentation-aiohttp-client` make span attributes available to sampler ([#1072](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1072)) - `opentelemetry-instrumentation-aws-lambda` Fixed an issue - in some rare cases (API GW proxy integration test) @@ -97,7 +96,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-sdk-extension-aws` change timeout for AWS EC2 and EKS metadata requests from 1000 seconds and 2000 seconds to 1 second ### Added - - `opentelemetry-instrument` and `opentelemetry-bootstrap` now include a `--version` flag ([#1065](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1065)) - `opentelemetry-instrumentation-redis` now instruments asynchronous Redis clients, if the installed redis-py includes async support (>=4.2.0). @@ -108,19 +106,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.11.1-0.30b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.1-0.30b1) - 2022-04-21 ### Added - - `opentelemetry-instrumentation-starlette` Capture custom request/response headers in span attributes - ([#1046])() + ([#1046](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1046)) ### Fixed - - Prune autoinstrumentation sitecustomize module directory from PYTHONPATH immediately ([#1066](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1066)) ## [1.11.0-0.30b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.0-0.30b0) - 2022-04-18 ### Fixed - - `opentelemetry-instrumentation-pyramid` Fixed which package is the correct caller in _traced_init. ([#830](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/830)) - `opentelemetry-instrumentation-tornado` Fix Tornado errors mapping to 500 @@ -135,17 +130,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `opentelemetry-instrumentation-fastapi` Capture custom request/response headers in span attributes - ([#1032])() + ([#1032](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1032)) - `opentelemetry-instrumentation-django` Capture custom request/response headers in span attributes - ([#1024])() + ([#1024](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1024)) - `opentelemetry-instrumentation-asgi` Capture custom request/response headers in span attributes - ([#1004])() + ([#1004](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1004)) - `opentelemetry-instrumentation-psycopg2` extended the sql commenter support of dbapi into psycopg2 ([#940](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/940)) - `opentelemetry-instrumentation-falcon` Add support for falcon==1.4.1 - ([#1000])() + ([#1000](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1000)) - `opentelemetry-instrumentation-falcon` Falcon: Capture custom request/response headers in span attributes - ([#1003])() + ([#1003](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1003)) - `opentelemetry-instrumentation-elasticsearch` no longer creates unique span names by including search target, replaces them with `` and puts the value in attribute `elasticsearch.target` ([#1018](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1018)) - `opentelemetry-instrumentation-pyramid` Handle non-HTTPException exceptions @@ -153,16 +148,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-system-metrics` restore `SystemMetrics` instrumentation as `SystemMetricsInstrumentor` ([#1012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1012)) - `opentelemetry-instrumentation-pyramid` Pyramid: Capture custom request/response headers in span attributes - ([#1022])() + ([#1022](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1022)) + ## [1.10.0-0.29b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.10.0-0.29b0) - 2022-03-10 - `opentelemetry-instrumentation-wsgi` Capture custom request/response headers in span attributes - ([#925])() + ([#925](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/925)) - `opentelemetry-instrumentation-flask` Flask: Capture custom request/response headers in span attributes - ([#952])() + ([#952](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/952)) - `opentelemetry-instrumentation-tornado` Tornado: Capture custom request/response headers in span attributes - ([#950])() + ([#950](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/950)) ### Added @@ -207,6 +203,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.9.0-0.28b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.9.0-0.28b0) - 2022-01-26 + ### Added - `opentelemetry-instrumentation-pyramid` Pyramid: Conditionally create SERVER spans @@ -245,6 +242,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-aiohttp-client` aiohttp: Remove `span_name` from docs ([#857](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/857)) + ## [1.8.0-0.27b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.8.0-0.27b0) - 2021-12-17 ### Added @@ -319,14 +317,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#755](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/755)) ### Added - - `opentelemetry-instrumentation-pika` Add `publish_hook` and `consume_hook` callbacks passed as arguments to the instrument method ([#763](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/763)) + ## [1.6.1-0.25b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.6.1-0.25b1) - 2021-10-18 ### Changed - - `opentelemetry-util-http` no longer contains an instrumentation entrypoint and will not be loaded automatically by the auto instrumentor. ([#745](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/745)) @@ -340,9 +337,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#760](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/760)) ## [1.6.0-0.25b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.6.0-0.25b0) - 2021-10-13 - ### Added - - `opentelemetry-sdk-extension-aws` Release AWS Python SDK Extension as 1.0.0 ([#667](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/667)) - `opentelemetry-instrumentation-urllib3`, `opentelemetry-instrumentation-requests` @@ -369,7 +364,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#391](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/391)) ### Changed - - `opentelemetry-instrumentation-flask` Fix `RuntimeError: Working outside of request context` ([#734](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/734)) - `opentelemetry-propagators-aws-xray` Rename `AwsXRayFormat` to `AwsXRayPropagator` @@ -400,7 +394,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26 ### Added - - `opentelemetry-sdk-extension-aws` Add AWS resource detectors to extension package ([#586](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/586)) - `opentelemetry-instrumentation-asgi`, `opentelemetry-instrumentation-aiohttp-client`, `openetelemetry-instrumentation-fastapi`, @@ -419,12 +412,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.4.0-0.23b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.4.0-0.23b0) - 2021-07-21 ### Removed - - Move `opentelemetry-instrumentation` to the core repo. ([#595](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/595)) ### Changed - - `opentelemetry-instrumentation-falcon` added support for Falcon 3. ([#607](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/607)) - `opentelemetry-instrumentation-tornado` properly instrument work done in tornado on_finish method. @@ -472,14 +463,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#568](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/568)) ### Added - - `opentelemetry-instrumentation-httpx` Add `httpx` instrumentation ([#461](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/461)) ## [0.22b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.3.0-0.22b0) - 2021-06-01 ### Changed - - `opentelemetry-bootstrap` not longer forcibly removes and re-installs libraries and their instrumentations. This means running bootstrap will not auto-upgrade existing dependencies and as a result not cause dependency conflicts. @@ -496,7 +485,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#488](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/488)) ### Added - - `opentelemetry-instrumentation-botocore` now supports context propagation for lambda invoke via Payload embedded headers. ([#458](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/458)) @@ -506,7 +494,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.21b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.2.0-0.21b0) - 2021-05-11 ### Changed - - Instrumentation packages don't specify the libraries they instrument as dependencies anymore. Instead, they verify the correct version of libraries are installed at runtime. ([#475](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/475)) @@ -956,7 +943,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-ext-boto` Initial release - `opentelemetry-ext-botocore` Initial release - `opentelemetry-ext-system-metrics` Initial release - () + (https://github.com/open-telemetry/opentelemetry-python/pull/652) ## [0.8b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v0.8.0) - 2020-05-27 @@ -966,7 +953,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#572](https://github.com/open-telemetry/opentelemetry-python/pull/572)) - `opentelemetry-ext-sqlite3` Initial release - `opentelemetry-ext-psycopg2` Implement instrumentor interface, enabling auto-instrumentation - ([#694]) + ([#694](https://github.com/open-telemetry/opentelemetry-python/pull/694)) - `opentelemetry-ext-asgi` Add ASGI middleware ([#716](https://github.com/open-telemetry/opentelemetry-python/pull/716)) - `opentelemetry-ext-django` Add exclude list for paths and hosts to prevent from tracing From d1aee361f039056c5d37e46e009c90716183ecaf Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 10:41:24 +0900 Subject: [PATCH 06/34] ran tox -e generate --- .../instrumentation/grpc/filters/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index e566bc8471..5bfbe27bb2 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -50,9 +50,11 @@ def method_name(name): A filter function that returns True if request's gRPC method name matches name """ + def fn(metadata): _, method = _split_full_method(metadata) return method == name + return fn @@ -67,9 +69,11 @@ def method_prefix(prefix): A filter function that returns True if request's gRPC method name starts with prefix """ + def fn(metadata): _, method = _split_full_method(metadata) return method.startswith(prefix) + return fn @@ -84,9 +88,11 @@ def full_method_name(name): A filter function that returns True if request's gRPC full method name matches name """ + def fn(metadata): fm = _full_method(metadata) return fm == name + return fn @@ -101,9 +107,11 @@ def service_name(name): A filter function that returns True if request's gRPC service name matches name """ + def fn(metadata): service, _ = _split_full_method(metadata) return service == name + return fn @@ -118,9 +126,11 @@ def service_prefix(prefix): A filter function that returns True if request's gRPC method name starts with prefix """ + def fn(metadata): service, _ = _split_full_method(metadata) return service.startswith(prefix) + return fn From 782213f8c336269abea9c0ed4fd82f372f45c0d4 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 11:18:34 +0900 Subject: [PATCH 07/34] add filter option to auto instrumentors --- .../instrumentation/grpc/__init__.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 177bfe67b5..ab1d875250 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -155,16 +155,22 @@ def instrumentation_dependencies(self) -> Collection[str]: def _instrument(self, **kwargs): self._original_func = grpc.server tracer_provider = kwargs.get("tracer_provider") + _filter = kwargs.get("filter") def server(*args, **kwargs): if "interceptors" in kwargs: # add our interceptor as the first kwargs["interceptors"].insert( - 0, server_interceptor(tracer_provider=tracer_provider) + 0, + server_interceptor( + tracer_provider=tracer_provider, filter=_filter + ), ) else: kwargs["interceptors"] = [ - server_interceptor(tracer_provider=tracer_provider) + server_interceptor( + tracer_provider=tracer_provider, filter=_filter + ) ] return self._original_func(*args, **kwargs) @@ -219,13 +225,16 @@ def _uninstrument(self, **kwargs): def wrapper_fn(self, original_func, instance, args, kwargs): channel = original_func(*args, **kwargs) tracer_provider = kwargs.get("tracer_provider") + _filter = kwargs.get("filter") return intercept_channel( channel, - client_interceptor(tracer_provider=tracer_provider), + client_interceptor( + tracer_provider=tracer_provider, filter=_filter + ), ) -def client_interceptor(tracer_provider=None): +def client_interceptor(tracer_provider=None, filter=None): """Create a gRPC client channel interceptor. Args: @@ -238,10 +247,10 @@ def client_interceptor(tracer_provider=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _client.OpenTelemetryClientInterceptor(tracer) + return _client.OpenTelemetryClientInterceptor(tracer, filter=filter) -def server_interceptor(tracer_provider=None): +def server_interceptor(tracer_provider=None, filter=None): """Create a gRPC server interceptor. Args: @@ -254,4 +263,4 @@ def server_interceptor(tracer_provider=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _server.OpenTelemetryServerInterceptor(tracer) + return _server.OpenTelemetryServerInterceptor(tracer, filter=filter) From a66b21fa2e7c34ef736358d328c9a4c82fd08a48 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 14:47:55 +0900 Subject: [PATCH 08/34] add filter tests and fix filters --- .../instrumentation/grpc/filters/__init__.py | 8 +- .../tests/test_filters.py | 194 ++++++++++++++++++ 2 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 5bfbe27bb2..f314a9bc56 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -23,9 +23,9 @@ def _full_method(metadata): name = "" if isinstance(metadata, grpc.HandlerCallDetails): name = metadata.method - elif isinstance(metadata, grpcext.UnaryClientInfo): - name = metadata.full_method - elif isinstance(metadata, grpcext.StreamClientInfo): + # NOTE: replace here if there's better way to match cases to handle + # grpcext._interceptor._UnaryClientInfo/_StreamClientInfo + elif hasattr(metadata, "full_method"): name = metadata.full_method return name @@ -35,7 +35,7 @@ def _split_full_method(metadata): s, m = os.path.split(name) if s != "": s = os.path.normpath(s) - s.lstrip("/") + s = s.lstrip("/") return (s, m) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py new file mode 100644 index 0000000000..99c20a4579 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -0,0 +1,194 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +import collections + +import grpc +import pytest + +from opentelemetry.instrumentation.grpc import filters + + +class _HandlerCallDetails( + collections.namedtuple('_HanlderCallDetails', ( + 'method', + 'invocation_metadata', + )), grpc.HandlerCallDetails): + pass + + +class _UnaryClientInfo( + collections.namedtuple("_UnaryClientInfo", ("full_method", "timeout")) +): + pass + + +class _StreamClientInfo( + collections.namedtuple( + "_StreamClientInfo", + ("full_method", "is_client_stream", "is_server_stream", "timeout"), + ) +): + pass + + +@pytest.mark.parametrize('tc', [ + (True, "SimpleMethod", _HandlerCallDetails( + method="SimpleMethod", + invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] + )), + (False, "SimpleMethod", _HandlerCallDetails( + method="NotSimpleMethod", + invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] + )), + (True, "SimpleMethod", _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + )), + (False, "SimpleMethod", _UnaryClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + timeout=3000, + )), + (True, "SimpleMethod", _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + (False, "SimpleMethod", _StreamClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + ]) +def test_method_name(tc): + fn = filters.method_name(tc[1]) + assert tc[0] == fn(tc[2]) + + +@pytest.mark.parametrize('tc', [ + (True, "Simple", _HandlerCallDetails( + method="SimpleMethod", + invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] + )), + (False, "Simple", _HandlerCallDetails( + method="NotSimpleMethod", + invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] + )), + (True, "Simple", _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + )), + (False, "Simple", _UnaryClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + timeout=3000, + )), + (True, "Simple", _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + (False, "Simple", _StreamClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + ]) +def test_method_prefix(tc): + fn = filters.method_prefix(tc[1]) + assert tc[0] == fn(tc[2]) + + +@pytest.mark.parametrize('tc', [ + (True, "GRPCTestServer", _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + )), + (False, "GRPCTestServer", _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + )), + (True, "GRPCTestServer", _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + (False, "GRPCTestServer", _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), +]) +def test_service_name(tc): + fn = filters.service_name(tc[1]) + assert tc[0] == fn(tc[2]) + + +@pytest.mark.parametrize('tc', [ + (True, "GRPCTest", _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + )), + (False, "GRPCTest", _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + )), + (True, "GRPCTest", _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + (False, "GRPCTest", _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), +]) +def test_service_prefix(tc): + fn = filters.service_prefix(tc[1]) + assert tc[0] == fn(tc[2]) + + +@pytest.mark.parametrize('tc', [ + (True, _UnaryClientInfo( + full_method="/grpc.health.v1.Health/Check", + timeout=3000, + )), + (False, _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + )), + (True, _StreamClientInfo( + full_method="/grpc.health.v1.Health/Check", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), + (False, _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + )), +]) +def test_health_check(tc): + fn = filters.health_check() + assert tc[0] == fn(tc[1]) From d7a15883759bdb85859aa8e776977bbd972bd7a9 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 14:52:07 +0900 Subject: [PATCH 09/34] ran tox -e generate --- .../tests/test_filters.py | 372 ++++++++++++------ 1 file changed, 242 insertions(+), 130 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index 99c20a4579..adeb72d469 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -21,10 +21,15 @@ class _HandlerCallDetails( - collections.namedtuple('_HanlderCallDetails', ( - 'method', - 'invocation_metadata', - )), grpc.HandlerCallDetails): + collections.namedtuple( + "_HanlderCallDetails", + ( + "method", + "invocation_metadata", + ), + ), + grpc.HandlerCallDetails, +): pass @@ -43,152 +48,259 @@ class _StreamClientInfo( pass -@pytest.mark.parametrize('tc', [ - (True, "SimpleMethod", _HandlerCallDetails( - method="SimpleMethod", - invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] - )), - (False, "SimpleMethod", _HandlerCallDetails( - method="NotSimpleMethod", - invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] - )), - (True, "SimpleMethod", _UnaryClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - timeout=3000, - )), - (False, "SimpleMethod", _UnaryClientInfo( - full_method="/GRPCTestServer/NotSimpleMethod", - timeout=3000, - )), - (True, "SimpleMethod", _StreamClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - (False, "SimpleMethod", _StreamClientInfo( - full_method="/GRPCTestServer/NotSimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - ]) +@pytest.mark.parametrize( + "tc", + [ + ( + True, + "SimpleMethod", + _HandlerCallDetails( + method="SimpleMethod", + invocation_metadata=[("tracer", "foo"), ("caller", "bar")], + ), + ), + ( + False, + "SimpleMethod", + _HandlerCallDetails( + method="NotSimpleMethod", + invocation_metadata=[("tracer", "foo"), ("caller", "bar")], + ), + ), + ( + True, + "SimpleMethod", + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + False, + "SimpleMethod", + _UnaryClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + timeout=3000, + ), + ), + ( + True, + "SimpleMethod", + _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ( + False, + "SimpleMethod", + _StreamClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ], +) def test_method_name(tc): fn = filters.method_name(tc[1]) assert tc[0] == fn(tc[2]) -@pytest.mark.parametrize('tc', [ - (True, "Simple", _HandlerCallDetails( - method="SimpleMethod", - invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] - )), - (False, "Simple", _HandlerCallDetails( - method="NotSimpleMethod", - invocation_metadata=[('tracer', 'foo'), ('caller', 'bar')] - )), - (True, "Simple", _UnaryClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - timeout=3000, - )), - (False, "Simple", _UnaryClientInfo( - full_method="/GRPCTestServer/NotSimpleMethod", - timeout=3000, - )), - (True, "Simple", _StreamClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - (False, "Simple", _StreamClientInfo( - full_method="/GRPCTestServer/NotSimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - ]) +@pytest.mark.parametrize( + "tc", + [ + ( + True, + "Simple", + _HandlerCallDetails( + method="SimpleMethod", + invocation_metadata=[("tracer", "foo"), ("caller", "bar")], + ), + ), + ( + False, + "Simple", + _HandlerCallDetails( + method="NotSimpleMethod", + invocation_metadata=[("tracer", "foo"), ("caller", "bar")], + ), + ), + ( + True, + "Simple", + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + False, + "Simple", + _UnaryClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + timeout=3000, + ), + ), + ( + True, + "Simple", + _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ( + False, + "Simple", + _StreamClientInfo( + full_method="/GRPCTestServer/NotSimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ], +) def test_method_prefix(tc): fn = filters.method_prefix(tc[1]) assert tc[0] == fn(tc[2]) -@pytest.mark.parametrize('tc', [ - (True, "GRPCTestServer", _UnaryClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - timeout=3000, - )), - (False, "GRPCTestServer", _UnaryClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - timeout=3000, - )), - (True, "GRPCTestServer", _StreamClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - (False, "GRPCTestServer", _StreamClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), -]) +@pytest.mark.parametrize( + "tc", + [ + ( + True, + "GRPCTestServer", + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + False, + "GRPCTestServer", + _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + ), + ), + ( + True, + "GRPCTestServer", + _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ( + False, + "GRPCTestServer", + _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ], +) def test_service_name(tc): fn = filters.service_name(tc[1]) assert tc[0] == fn(tc[2]) -@pytest.mark.parametrize('tc', [ - (True, "GRPCTest", _UnaryClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - timeout=3000, - )), - (False, "GRPCTest", _UnaryClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - timeout=3000, - )), - (True, "GRPCTest", _StreamClientInfo( - full_method="/GRPCTestServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - (False, "GRPCTest", _StreamClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), -]) +@pytest.mark.parametrize( + "tc", + [ + ( + True, + "GRPCTest", + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + False, + "GRPCTest", + _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + ), + ), + ( + True, + "GRPCTest", + _StreamClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ( + False, + "GRPCTest", + _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ], +) def test_service_prefix(tc): fn = filters.service_prefix(tc[1]) assert tc[0] == fn(tc[2]) -@pytest.mark.parametrize('tc', [ - (True, _UnaryClientInfo( - full_method="/grpc.health.v1.Health/Check", - timeout=3000, - )), - (False, _UnaryClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - timeout=3000, - )), - (True, _StreamClientInfo( - full_method="/grpc.health.v1.Health/Check", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), - (False, _StreamClientInfo( - full_method="/GRPCRealServer/SimpleMethod", - is_client_stream=True, - is_server_stream=False, - timeout=3000, - )), -]) +@pytest.mark.parametrize( + "tc", + [ + ( + True, + _UnaryClientInfo( + full_method="/grpc.health.v1.Health/Check", + timeout=3000, + ), + ), + ( + False, + _UnaryClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + timeout=3000, + ), + ), + ( + True, + _StreamClientInfo( + full_method="/grpc.health.v1.Health/Check", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ( + False, + _StreamClientInfo( + full_method="/GRPCRealServer/SimpleMethod", + is_client_stream=True, + is_server_stream=False, + timeout=3000, + ), + ), + ], +) def test_health_check(tc): fn = filters.health_check() assert tc[0] == fn(tc[1]) From 48baf119ff5c266df191fc34e473c726cad88c09 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 15:07:01 +0900 Subject: [PATCH 10/34] remove unused package --- .../src/opentelemetry/instrumentation/grpc/filters/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index f314a9bc56..bfa9b82cdc 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -16,8 +16,6 @@ import grpc -from opentelemetry.instrumentation.grpc import grpcext - def _full_method(metadata): name = "" From 840f15236c0f634900df8faa044c7a1401ea46b3 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 15:31:15 +0900 Subject: [PATCH 11/34] rename to meet lint conditions --- .../instrumentation/grpc/__init__.py | 14 ++++----- .../instrumentation/grpc/_client.py | 4 +-- .../instrumentation/grpc/_server.py | 4 +-- .../instrumentation/grpc/filters/__init__.py | 30 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index ab1d875250..5b81c8f5a1 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -163,13 +163,13 @@ def server(*args, **kwargs): kwargs["interceptors"].insert( 0, server_interceptor( - tracer_provider=tracer_provider, filter=_filter + tracer_provider=tracer_provider, filters=_filter ), ) else: kwargs["interceptors"] = [ server_interceptor( - tracer_provider=tracer_provider, filter=_filter + tracer_provider=tracer_provider, filters=_filter ) ] return self._original_func(*args, **kwargs) @@ -229,12 +229,12 @@ def wrapper_fn(self, original_func, instance, args, kwargs): return intercept_channel( channel, client_interceptor( - tracer_provider=tracer_provider, filter=_filter + tracer_provider=tracer_provider, filters=_filter ), ) -def client_interceptor(tracer_provider=None, filter=None): +def client_interceptor(tracer_provider=None, filters=None): """Create a gRPC client channel interceptor. Args: @@ -247,10 +247,10 @@ def client_interceptor(tracer_provider=None, filter=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _client.OpenTelemetryClientInterceptor(tracer, filter=filter) + return _client.OpenTelemetryClientInterceptor(tracer, filters=filters) -def server_interceptor(tracer_provider=None, filter=None): +def server_interceptor(tracer_provider=None, filters=None): """Create a gRPC server interceptor. Args: @@ -263,4 +263,4 @@ def server_interceptor(tracer_provider=None, filter=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _server.OpenTelemetryServerInterceptor(tracer, filter=filter) + return _server.OpenTelemetryServerInterceptor(tracer, filters=filters) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index f2bba7f1a2..116c50849d 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -62,9 +62,9 @@ def callback(response_future): class OpenTelemetryClientInterceptor( grpcext.UnaryClientInterceptor, grpcext.StreamClientInterceptor ): - def __init__(self, tracer, filter=None): + def __init__(self, tracer, filters=None): self._tracer = tracer - self._filter = filter + self._filter = filters def _start_span(self, method, **kwargs): service, meth = method.lstrip("/").split("/", 1) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index 44d24513b4..0e1883063a 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -184,9 +184,9 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): """ - def __init__(self, tracer, filter=None): + def __init__(self, tracer, filters=None): self._tracer = tracer - self._filter = filter + self._filter = filters @contextmanager def _set_remote_context(self, servicer_context): diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index bfa9b82cdc..b2821eff01 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -30,11 +30,11 @@ def _full_method(metadata): def _split_full_method(metadata): name = _full_method(metadata) - s, m = os.path.split(name) - if s != "": - s = os.path.normpath(s) - s = s.lstrip("/") - return (s, m) + service, method = os.path.split(name) + if service != "": + service = os.path.normpath(service) + service = service.lstrip("/") + return (service, method) def method_name(name): @@ -49,11 +49,11 @@ def method_name(name): name matches name """ - def fn(metadata): + def filter_fn(metadata): _, method = _split_full_method(metadata) return method == name - return fn + return filter_fn def method_prefix(prefix): @@ -68,11 +68,11 @@ def method_prefix(prefix): name starts with prefix """ - def fn(metadata): + def filter_fn(metadata): _, method = _split_full_method(metadata) return method.startswith(prefix) - return fn + return filter_fn def full_method_name(name): @@ -87,11 +87,11 @@ def full_method_name(name): method name matches name """ - def fn(metadata): + def filter_fn(metadata): fm = _full_method(metadata) return fm == name - return fn + return filter_fn def service_name(name): @@ -106,11 +106,11 @@ def service_name(name): name matches name """ - def fn(metadata): + def filter_fn(metadata): service, _ = _split_full_method(metadata) return service == name - return fn + return filter_fn def service_prefix(prefix): @@ -125,11 +125,11 @@ def service_prefix(prefix): name starts with prefix """ - def fn(metadata): + def filter_fn(metadata): service, _ = _split_full_method(metadata) return service.startswith(prefix) - return fn + return filter_fn def health_check(): From 98252887641115d28cbbf24995c322325aec9109 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 15:41:34 +0900 Subject: [PATCH 12/34] fix variable names to meet lint criteria --- .../tests/test_filters.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index adeb72d469..5859ba21d3 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -49,7 +49,7 @@ class _StreamClientInfo( @pytest.mark.parametrize( - "tc", + "test_case", [ ( True, @@ -105,13 +105,13 @@ class _StreamClientInfo( ), ], ) -def test_method_name(tc): - fn = filters.method_name(tc[1]) - assert tc[0] == fn(tc[2]) +def test_method_name(test_case): + fn = filters.method_name(test_case[1]) + assert test_case[0] == fn(test_case[2]) @pytest.mark.parametrize( - "tc", + "test_case", [ ( True, @@ -167,13 +167,13 @@ def test_method_name(tc): ), ], ) -def test_method_prefix(tc): - fn = filters.method_prefix(tc[1]) - assert tc[0] == fn(tc[2]) +def test_method_prefix(test_case): + fn = filters.method_prefix(test_case[1]) + assert test_case[0] == fn(test_case[2]) @pytest.mark.parametrize( - "tc", + "test_case", [ ( True, @@ -213,13 +213,13 @@ def test_method_prefix(tc): ), ], ) -def test_service_name(tc): - fn = filters.service_name(tc[1]) - assert tc[0] == fn(tc[2]) +def test_service_name(test_case): + fn = filters.service_name(test_case[1]) + assert test_case[0] == fn(test_case[2]) @pytest.mark.parametrize( - "tc", + "test_case", [ ( True, @@ -259,13 +259,13 @@ def test_service_name(tc): ), ], ) -def test_service_prefix(tc): - fn = filters.service_prefix(tc[1]) - assert tc[0] == fn(tc[2]) +def test_service_prefix(test_case): + fn = filters.service_prefix(test_case[1]) + assert test_case[0] == fn(test_case[2]) @pytest.mark.parametrize( - "tc", + "test_case", [ ( True, @@ -301,6 +301,6 @@ def test_service_prefix(tc): ), ], ) -def test_health_check(tc): +def test_health_check(test_case): fn = filters.health_check() - assert tc[0] == fn(tc[1]) + assert test_case[0] == fn(test_case[1]) From de75b244fc34c21ec8f167da2047d2850c0ac38d Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 20:02:17 +0900 Subject: [PATCH 13/34] fix filter propagation and add tests --- .../instrumentation/grpc/__init__.py | 17 +- .../instrumentation/grpc/_client.py | 1 + .../instrumentation/grpc/_server.py | 2 + .../tests/test_client_interceptor_filter.py | 372 ++++++++++++++++++ .../tests/test_server_interceptor_filter.py | 218 ++++++++++ 5 files changed, 604 insertions(+), 6 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py create mode 100644 instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 5b81c8f5a1..cd40a77179 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -149,13 +149,15 @@ class GrpcInstrumentorServer(BaseInstrumentor): # pylint:disable=attribute-defined-outside-init, redefined-outer-name + def __init__(self, filters=None): + self._filters = filters + def instrumentation_dependencies(self) -> Collection[str]: return _instruments def _instrument(self, **kwargs): self._original_func = grpc.server tracer_provider = kwargs.get("tracer_provider") - _filter = kwargs.get("filter") def server(*args, **kwargs): if "interceptors" in kwargs: @@ -163,13 +165,13 @@ def server(*args, **kwargs): kwargs["interceptors"].insert( 0, server_interceptor( - tracer_provider=tracer_provider, filters=_filter + tracer_provider=tracer_provider, filters=self._filters ), ) else: kwargs["interceptors"] = [ server_interceptor( - tracer_provider=tracer_provider, filters=_filter + tracer_provider=tracer_provider, filters=self._filters ) ] return self._original_func(*args, **kwargs) @@ -191,6 +193,9 @@ class GrpcInstrumentorClient(BaseInstrumentor): """ + def __init__(self, filters=None): + self._filters = filters + # Figures out which channel type we need to wrap def _which_channel(self, kwargs): # handle legacy argument @@ -225,11 +230,11 @@ def _uninstrument(self, **kwargs): def wrapper_fn(self, original_func, instance, args, kwargs): channel = original_func(*args, **kwargs) tracer_provider = kwargs.get("tracer_provider") - _filter = kwargs.get("filter") return intercept_channel( channel, client_interceptor( - tracer_provider=tracer_provider, filters=_filter + tracer_provider=tracer_provider, + filters=self._filters, ), ) @@ -246,7 +251,7 @@ def client_interceptor(tracer_provider=None, filters=None): from . import _client tracer = trace.get_tracer(__name__, __version__, tracer_provider) - + print(f"client.client_interceptor filters: {filters}\n") return _client.OpenTelemetryClientInterceptor(tracer, filters=filters) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index 116c50849d..656d6a0a18 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -150,6 +150,7 @@ def _intercept(self, request, metadata, client_info, invoker): def intercept_unary(self, request, metadata, client_info, invoker): if self._filter is not None and not self._filter(client_info): + print("not intercepted") return invoker(request, metadata) return self._intercept(request, metadata, client_info, invoker) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index 0e1883063a..bd428188b1 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -261,7 +261,9 @@ def _start_span( ) def intercept_service(self, continuation, handler_call_details): + print(f"server filters: {self._filter}") if self._filter is not None and not self._filter(handler_call_details): + print("not intercepted") return continuation(handler_call_details) def telemetry_wrapper(behavior, request_streaming, response_streaming): diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py new file mode 100644 index 0000000000..aab52780ef --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -0,0 +1,372 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +import grpc +from tests.protobuf import ( # pylint: disable=no-name-in-module + test_server_pb2_grpc, +) + +import opentelemetry.instrumentation.grpc +from opentelemetry import context, trace +from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, filters +from opentelemetry.instrumentation.grpc._client import ( + OpenTelemetryClientInterceptor, +) +from opentelemetry.instrumentation.grpc.grpcext._interceptor import ( + _UnaryClientInfo, +) +from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY +from opentelemetry.propagate import get_global_textmap, set_global_textmap +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.test.mock_textmap import MockTextMapPropagator +from opentelemetry.test.test_base import TestBase + +from ._client import ( + bidirectional_streaming_method, + client_streaming_method, + server_streaming_method, + simple_method, + simple_method_future, +) +from ._server import create_test_server +from .protobuf.test_server_pb2 import Request + + +# User defined interceptor. Is used in the tests along with the opentelemetry client interceptor. +class Interceptor( + grpc.UnaryUnaryClientInterceptor, + grpc.UnaryStreamClientInterceptor, + grpc.StreamUnaryClientInterceptor, + grpc.StreamStreamClientInterceptor, +): + def __init__(self): + pass + + def intercept_unary_unary( + self, continuation, client_call_details, request + ): + return self._intercept_call(continuation, client_call_details, request) + + def intercept_unary_stream( + self, continuation, client_call_details, request + ): + return self._intercept_call(continuation, client_call_details, request) + + def intercept_stream_unary( + self, continuation, client_call_details, request_iterator + ): + return self._intercept_call( + continuation, client_call_details, request_iterator + ) + + def intercept_stream_stream( + self, continuation, client_call_details, request_iterator + ): + return self._intercept_call( + continuation, client_call_details, request_iterator + ) + + @staticmethod + def _intercept_call( + continuation, client_call_details, request_or_iterator + ): + return continuation(client_call_details, request_or_iterator) + + +class TestClientProtoFilterMethodName(TestBase): + def setUp(self): + super().setUp() + GrpcInstrumentorClient( + filters=filters.method_name("SimpleMethod") + ).instrument() + self.server = create_test_server(25565) + self.server.start() + # use a user defined interceptor along with the opentelemetry client interceptor + interceptors = [Interceptor()] + self.channel = grpc.insecure_channel("localhost:25565") + self.channel = grpc.intercept_channel(self.channel, *interceptors) + self._stub = test_server_pb2_grpc.GRPCTestServerStub(self.channel) + + def tearDown(self): + super().tearDown() + GrpcInstrumentorClient().uninstrument() + self.server.stop(None) + self.channel.close() + + def test_unary_unary_future(self): + simple_method_future(self._stub).result() + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + def test_unary_unary(self): + simple_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "SimpleMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_unary_stream(self): + server_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_stream_unary(self): + client_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_stream_stream(self): + bidirectional_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_simple(self): + with self.assertRaises(grpc.RpcError): + simple_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_error_stream_unary(self): + with self.assertRaises(grpc.RpcError): + client_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_unary_stream(self): + with self.assertRaises(grpc.RpcError): + server_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_stream_stream(self): + with self.assertRaises(grpc.RpcError): + bidirectional_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_client_interceptor_trace_context_propagation( + self, + ): # pylint: disable=no-self-use + """ensure that client interceptor correctly inject trace context into all outgoing requests.""" + previous_propagator = get_global_textmap() + try: + set_global_textmap(MockTextMapPropagator()) + interceptor = OpenTelemetryClientInterceptor(trace.NoOpTracer()) + + carrier = tuple() + + def invoker(request, metadata): + nonlocal carrier + carrier = metadata + return {} + + request = Request(client_id=1, request_data="data") + interceptor.intercept_unary( + request, + {}, + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", timeout=None + ), + invoker=invoker, + ) + + assert len(carrier) == 2 + assert carrier[0][0] == "mock-traceid" + assert carrier[0][1] == "0" + assert carrier[1][0] == "mock-spanid" + assert carrier[1][1] == "0" + + finally: + set_global_textmap(previous_propagator) + + +class TestClientProtoFilterMethodPrefix(TestBase): + def setUp(self): + super().setUp() + GrpcInstrumentorClient( + filters=filters.method_prefix("Simple") + ).instrument() + self.server = create_test_server(25565) + self.server.start() + # use a user defined interceptor along with the opentelemetry client interceptor + interceptors = [Interceptor()] + self.channel = grpc.insecure_channel("localhost:25565") + self.channel = grpc.intercept_channel(self.channel, *interceptors) + self._stub = test_server_pb2_grpc.GRPCTestServerStub(self.channel) + + def tearDown(self): + super().tearDown() + GrpcInstrumentorClient().uninstrument() + self.server.stop(None) + self.channel.close() + + def test_unary_unary_future(self): + simple_method_future(self._stub).result() + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + def test_unary_unary(self): + simple_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "SimpleMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_unary_stream(self): + server_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_stream_unary(self): + client_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_stream_stream(self): + bidirectional_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_simple(self): + with self.assertRaises(grpc.RpcError): + simple_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_error_stream_unary(self): + with self.assertRaises(grpc.RpcError): + client_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_unary_stream(self): + with self.assertRaises(grpc.RpcError): + server_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_error_stream_stream(self): + with self.assertRaises(grpc.RpcError): + bidirectional_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_client_interceptor_trace_context_propagation( + self, + ): # pylint: disable=no-self-use + """ensure that client interceptor correctly inject trace context into all outgoing requests.""" + previous_propagator = get_global_textmap() + try: + set_global_textmap(MockTextMapPropagator()) + interceptor = OpenTelemetryClientInterceptor(trace.NoOpTracer()) + + carrier = tuple() + + def invoker(request, metadata): + nonlocal carrier + carrier = metadata + return {} + + request = Request(client_id=1, request_data="data") + interceptor.intercept_unary( + request, + {}, + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", timeout=None + ), + invoker=invoker, + ) + + assert len(carrier) == 2 + assert carrier[0][0] == "mock-traceid" + assert carrier[0][1] == "0" + assert carrier[1][0] == "mock-spanid" + assert carrier[1][1] == "0" + + finally: + set_global_textmap(previous_propagator) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py new file mode 100644 index 0000000000..7b2da3d6c2 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py @@ -0,0 +1,218 @@ +# Copyright The OpenTelemetry Authors +# +# 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. + +# pylint:disable=unused-argument +# pylint:disable=no-self-use + +import threading +from concurrent import futures + +import grpc + +import opentelemetry.instrumentation.grpc +from opentelemetry import trace +from opentelemetry.instrumentation.grpc import ( + GrpcInstrumentorServer, + filters, + server_interceptor, +) +from opentelemetry.sdk import trace as trace_sdk +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace import StatusCode + +from .protobuf.test_server_pb2 import Request, Response +from .protobuf.test_server_pb2_grpc import ( + GRPCTestServerServicer, + add_GRPCTestServerServicer_to_server, +) + + +class UnaryUnaryMethodHandler(grpc.RpcMethodHandler): + def __init__(self, handler): + self.request_streaming = False + self.response_streaming = False + self.request_deserializer = None + self.response_serializer = None + self.unary_unary = handler + self.unary_stream = None + self.stream_unary = None + self.stream_stream = None + + +class UnaryUnaryRpcHandler(grpc.GenericRpcHandler): + def __init__(self, handler): + self._unary_unary_handler = handler + + def service(self, handler_call_details): + return UnaryUnaryMethodHandler(self._unary_unary_handler) + + +class Servicer(GRPCTestServerServicer): + """Our test servicer""" + + # pylint:disable=C0103 + def SimpleMethod(self, request, context): + return Response( + server_id=request.client_id, + response_data=request.request_data, + ) + + # pylint:disable=C0103 + def ServerStreamingMethod(self, request, context): + for data in ("one", "two", "three"): + yield Response( + server_id=request.client_id, + response_data=data, + ) + + +class TestOpenTelemetryServerInterceptorFilterMethodName(TestBase): + def test_instrumentor(self): + def handler(request, context): + return b"" + + grpc_server_instrumentor = GrpcInstrumentorServer( + filters=filters.method_name("handler") + ) + grpc_server_instrumentor.instrument() + with futures.ThreadPoolExecutor(max_workers=1) as executor: + server = grpc.server( + executor, + options=(("grpc.so_reuseport", 0),), + ) + + server.add_generic_rpc_handlers((UnaryUnaryRpcHandler(handler),)) + + port = server.add_insecure_port("[::]:0") + channel = grpc.insecure_channel(f"localhost:{port:d}") + + rpc_call = "TestServicer/handler" + try: + server.start() + channel.unary_unary(rpc_call)(b"test") + finally: + server.stop(None) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + self.assertEqual(span.name, rpc_call) + self.assertIs(span.kind, trace.SpanKind.SERVER) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + # Check attributes + self.assertSpanHasAttributes( + span, + { + SpanAttributes.NET_PEER_IP: "[::1]", + SpanAttributes.NET_PEER_NAME: "localhost", + SpanAttributes.RPC_METHOD: "handler", + SpanAttributes.RPC_SERVICE: "TestServicer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + grpc_server_instrumentor.uninstrument() + + def test_uninstrument(self): + def handler(request, context): + return b"" + + grpc_server_instrumentor = GrpcInstrumentorServer( + filters=filters.method_name("SimpleMethod") + ) + grpc_server_instrumentor.instrument() + grpc_server_instrumentor.uninstrument() + with futures.ThreadPoolExecutor(max_workers=1) as executor: + server = grpc.server( + executor, + options=(("grpc.so_reuseport", 0),), + ) + + server.add_generic_rpc_handlers((UnaryUnaryRpcHandler(handler),)) + + port = server.add_insecure_port("[::]:0") + channel = grpc.insecure_channel(f"localhost:{port:d}") + + rpc_call = "TestServicer/test" + try: + server.start() + channel.unary_unary(rpc_call)(b"test") + finally: + server.stop(None) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 0) + + def test_create_span(self): + """Check that the interceptor wraps calls with spans server-side.""" + + # Intercept gRPC calls... + interceptor = server_interceptor( + filters=filters.method_name("SimpleMethod") + ) + + with futures.ThreadPoolExecutor(max_workers=1) as executor: + server = grpc.server( + executor, + options=(("grpc.so_reuseport", 0),), + interceptors=[interceptor], + ) + add_GRPCTestServerServicer_to_server(Servicer(), server) + port = server.add_insecure_port("[::]:0") + channel = grpc.insecure_channel(f"localhost:{port:d}") + + rpc_call = "/GRPCTestServer/SimpleMethod" + request = Request(client_id=1, request_data="test") + msg = request.SerializeToString() + try: + server.start() + channel.unary_unary(rpc_call)(msg) + finally: + server.stop(None) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + + self.assertEqual(span.name, rpc_call) + self.assertIs(span.kind, trace.SpanKind.SERVER) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + # Check attributes + self.assertSpanHasAttributes( + span, + { + SpanAttributes.NET_PEER_IP: "[::1]", + SpanAttributes.NET_PEER_NAME: "localhost", + SpanAttributes.RPC_METHOD: "SimpleMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) From e23d77d794292ad74f6f34cc882a53bfb3f01cc2 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 20:17:23 +0900 Subject: [PATCH 14/34] fix lint --- .../tests/test_client_interceptor_filter.py | 3 +-- .../tests/test_server_interceptor_filter.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py index aab52780ef..ab3c6e3883 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -18,7 +18,7 @@ ) import opentelemetry.instrumentation.grpc -from opentelemetry import context, trace +from opentelemetry import trace from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, filters from opentelemetry.instrumentation.grpc._client import ( OpenTelemetryClientInterceptor, @@ -26,7 +26,6 @@ from opentelemetry.instrumentation.grpc.grpcext._interceptor import ( _UnaryClientInfo, ) -from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY from opentelemetry.propagate import get_global_textmap, set_global_textmap from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.test.mock_textmap import MockTextMapPropagator diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py index 7b2da3d6c2..6fb7d96974 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py @@ -15,7 +15,6 @@ # pylint:disable=unused-argument # pylint:disable=no-self-use -import threading from concurrent import futures import grpc @@ -27,10 +26,8 @@ filters, server_interceptor, ) -from opentelemetry.sdk import trace as trace_sdk from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.test.test_base import TestBase -from opentelemetry.trace import StatusCode from .protobuf.test_server_pb2 import Request, Response from .protobuf.test_server_pb2_grpc import ( From 98a84f90f1b0ecb5033e4356fbf3c318ee5aa51c Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 20:59:00 +0900 Subject: [PATCH 15/34] add composition filters and tests --- .../instrumentation/grpc/__init__.py | 22 ++++++- .../instrumentation/grpc/_client.py | 1 - .../instrumentation/grpc/_server.py | 4 +- .../instrumentation/grpc/filters/__init__.py | 60 +++++++++++++++++++ .../tests/test_filters.py | 40 +++++++++++++ 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index cd40a77179..e1230283be 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -145,6 +145,13 @@ class GrpcInstrumentorServer(BaseInstrumentor): grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() + If you want to add filters that only intercept requests + to match the condition, assign filters option to GrpcInstrumentorServer. + + grpc_server_instrumentor = GrpcInstrumentorServer( + filters=filters.method_prefix("SimpleMethod")) + grpc_server_instrumentor.instrument() + """ # pylint:disable=attribute-defined-outside-init, redefined-outer-name @@ -191,6 +198,13 @@ class GrpcInstrumentorClient(BaseInstrumentor): grpc_client_instrumentor = GrpcInstrumentorClient() grpc_client_instrumentor.instrument() + If you want to add filters that only intercept requests + to match the condition, assign filters option to GrpcInstrumentorClient. + + grpc_client_instrumentor = GrpcInstrumentorClient( + filter=not filters.health_check()) + grpc_client_instrumentor.instrument() + """ def __init__(self, filters=None): @@ -245,13 +259,16 @@ def client_interceptor(tracer_provider=None, filters=None): Args: tracer: The tracer to use to create client-side spans. + filters: filter function that returns True if gRPC requests + matches the condition. + Returns: An invocation-side interceptor object. """ from . import _client tracer = trace.get_tracer(__name__, __version__, tracer_provider) - print(f"client.client_interceptor filters: {filters}\n") + return _client.OpenTelemetryClientInterceptor(tracer, filters=filters) @@ -261,6 +278,9 @@ def server_interceptor(tracer_provider=None, filters=None): Args: tracer: The tracer to use to create server-side spans. + filters: filter function that returns True if gRPC requests + matches the condition. + Returns: A service-side interceptor object. """ diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index 656d6a0a18..116c50849d 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -150,7 +150,6 @@ def _intercept(self, request, metadata, client_info, invoker): def intercept_unary(self, request, metadata, client_info, invoker): if self._filter is not None and not self._filter(client_info): - print("not intercepted") return invoker(request, metadata) return self._intercept(request, metadata, client_info, invoker) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index bd428188b1..90c5582adc 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -172,7 +172,7 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): Usage:: tracer = some OpenTelemetry tracer - filter = not filters.method_name("service.Foo") + filter = filters.reverse(filters.method_name("service.Foo")) interceptors = [ OpenTelemetryServerInterceptor(tracer, filter), @@ -261,9 +261,7 @@ def _start_span( ) def intercept_service(self, continuation, handler_call_details): - print(f"server filters: {self._filter}") if self._filter is not None and not self._filter(handler_call_details): - print("not intercepted") return continuation(handler_call_details) def telemetry_wrapper(behavior, request_streaming, response_streaming): diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index b2821eff01..a1f6e52a8d 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -37,6 +37,66 @@ def _split_full_method(metadata): return (service, method) +def all(*args): + """Returns a filter function that returns True if all filter functions + assigned matches conditions. + + Args: + args (function): a list of filter function + + Returns: + A filter function that returns True if all filter functions + assigned matches conditions. + """ + + def filter_fn(metadata): + for func in args: + if not func(metadata): + return False + return True + + return filter_fn + + +def any(*args): + """Returns a filter function that returns True if either of filter functions + assigned matches conditions. + + Args: + args (function): a list of filter function + + Returns: + A filter function that returns True if either of filter functions + assigned matches conditions. + """ + + def filter_fn(metadata): + for func in args: + if func(metadata): + return True + return False + + return filter_fn + + +def reverse(func): + """Returns a filter function that reverse the result of func + + Args: + func (function): filter function that returns True if + request's gRPC method name matches name + + Returns: + A filter function that returns False if request's gRPC method + name matches name + """ + + def filter_fn(metadata): + return not func(metadata) + + return filter_fn + + def method_name(name): """Returns a filter function that return True if request's gRPC method name matches name. diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index 5859ba21d3..e65d064e8e 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -304,3 +304,43 @@ def test_service_prefix(test_case): def test_health_check(test_case): fn = filters.health_check() assert test_case[0] == fn(test_case[1]) + + +@pytest.mark.parametrize( + "test_case", + [ + ( + True, + filters.all( + filters.method_name("SimpleMethod"), + filters.service_name("GRPCTestServer"), + ), + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + True, + filters.any( + filters.method_name("NotSimpleMethod"), + filters.service_name("GRPCTestServer"), + ), + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ( + False, + filters.reverse(filters.method_name("SimpleMethod")), + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), + ], +) +def test_all_any_reverse(test_case): + fn = test_case[1] + assert test_case[0] == fn(test_case[2]) From 7aa93c8e2f0eb4ff28bfac3e53f44dd408477a5e Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 21:05:50 +0900 Subject: [PATCH 16/34] add comments about default filter value and its behavior --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index e1230283be..791c8a0e27 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -202,7 +202,8 @@ class GrpcInstrumentorClient(BaseInstrumentor): to match the condition, assign filters option to GrpcInstrumentorClient. grpc_client_instrumentor = GrpcInstrumentorClient( - filter=not filters.health_check()) + filter=filters.reverse(filters.health_check()) + ) grpc_client_instrumentor.instrument() """ @@ -260,7 +261,7 @@ def client_interceptor(tracer_provider=None, filters=None): tracer: The tracer to use to create client-side spans. filters: filter function that returns True if gRPC requests - matches the condition. + matches the condition. Default is None and intercept all requests. Returns: An invocation-side interceptor object. @@ -279,7 +280,7 @@ def server_interceptor(tracer_provider=None, filters=None): tracer: The tracer to use to create server-side spans. filters: filter function that returns True if gRPC requests - matches the condition. + matches the condition. Default is None and intercept all requests. Returns: A service-side interceptor object. From 97faf49522fcf24a16f51cbe0fbd1c499350ef6c Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Thu, 25 Aug 2022 21:16:28 +0900 Subject: [PATCH 17/34] fix function names --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 6 ++++-- .../opentelemetry/instrumentation/grpc/filters/__init__.py | 4 ++-- .../tests/test_filters.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 791c8a0e27..854daf4541 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -261,7 +261,8 @@ def client_interceptor(tracer_provider=None, filters=None): tracer: The tracer to use to create client-side spans. filters: filter function that returns True if gRPC requests - matches the condition. Default is None and intercept all requests. + matches the condition. Default is None and intercept + all requests. Returns: An invocation-side interceptor object. @@ -280,7 +281,8 @@ def server_interceptor(tracer_provider=None, filters=None): tracer: The tracer to use to create server-side spans. filters: filter function that returns True if gRPC requests - matches the condition. Default is None and intercept all requests. + matches the condition. Default is None and intercept + all requests. Returns: A service-side interceptor object. diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index a1f6e52a8d..6becc9a4de 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -37,7 +37,7 @@ def _split_full_method(metadata): return (service, method) -def all(*args): +def all_of(*args): """Returns a filter function that returns True if all filter functions assigned matches conditions. @@ -58,7 +58,7 @@ def filter_fn(metadata): return filter_fn -def any(*args): +def any_of(*args): """Returns a filter function that returns True if either of filter functions assigned matches conditions. diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index e65d064e8e..bec0dc0c72 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -311,7 +311,7 @@ def test_health_check(test_case): [ ( True, - filters.all( + filters.all_of( filters.method_name("SimpleMethod"), filters.service_name("GRPCTestServer"), ), @@ -322,7 +322,7 @@ def test_health_check(test_case): ), ( True, - filters.any( + filters.any_of( filters.method_name("NotSimpleMethod"), filters.service_name("GRPCTestServer"), ), From 49ab02eccb4a00d04a1b570cceaa1ab68d159e15 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 26 Aug 2022 18:00:50 +0900 Subject: [PATCH 18/34] add OTEL_PYTHON_GRPC_EXCLUDED_SERVICES support in global instrumentor --- .../instrumentation/grpc/__init__.py | 86 ++++++++++++++++++- .../tests/test_client_interceptor_filter.py | 38 ++++++++ .../tests/test_filters.py | 11 +++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 854daf4541..6e70325d2c 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -118,13 +118,63 @@ def serve(): server = grpc.server(futures.ThreadPoolExecutor(), interceptors = [server_interceptor()]) +Filters +------- + +If you prefer to filter specific requests to be instrumented, you can specify +the condition by assigning filters to instrumentors. + +You can write a global server instrumentor as follows: + +.. code-block:: + + from opentelemetry.instrumentation.grpc import filters, GrpcInstrumentorServer + + grpc_server_instrumentor = GrpcInstrumentorServer( + filters = filters.any_of( + filters.method_name("SimpleMethod"), + filters.method_name("ComplexMethod"), + ) + ) + grpc_server_instrumentor.instrument() + +You can also use the filter to manual instrumentors: + +.. code-block:: + + my_interceptor = server_interceptor( + filters = filters.reverse(filters.method_name("TestMethod")) + ) + server = grpc.server(futures.ThreadPoolExecutor(), + interceptors = [my_interceptor]) + +``filters`` option also applies to both global and manual client intrumentors. + + +Environment variable +-------------------- + +If you'd like to exclude specific services for the instrumentations, you can use +``OTEL_PYTHON_GRPC_EXCLUDED_SERVICES`` environment variables. + +For example, if you assign ``"GRPCTestServer,GRPCHealthServer"`` to the variable, +then the global interceptor automatically add the filter to exclude requests to +services ``GRPCTestServer`` and ``GRPCHealthServer``. + """ -from typing import Collection +import os +from typing import Callable, Collection, List, Union import grpc # pylint:disable=import-self from wrapt import wrap_function_wrapper as _wrap from opentelemetry import trace +from opentelemetry.instrumentation.grpc.filters import ( + all_of, + any_of, + reverse, + service_name, +) from opentelemetry.instrumentation.grpc.grpcext import intercept_channel from opentelemetry.instrumentation.grpc.package import _instruments from opentelemetry.instrumentation.grpc.version import __version__ @@ -157,6 +207,12 @@ class GrpcInstrumentorServer(BaseInstrumentor): # pylint:disable=attribute-defined-outside-init, redefined-outer-name def __init__(self, filters=None): + excluded_service_filter = _excluded_service_filter() + if excluded_service_filter is not None: + if filters is None: + filters = excluded_service_filter + else: + filters = all_of(filters, excluded_service_filter) self._filters = filters def instrumentation_dependencies(self) -> Collection[str]: @@ -209,6 +265,12 @@ class GrpcInstrumentorClient(BaseInstrumentor): """ def __init__(self, filters=None): + excluded_service_filter = _excluded_service_filter() + if excluded_service_filter is not None: + if filters is None: + filters = excluded_service_filter + else: + filters = all_of(filters, excluded_service_filter) self._filters = filters # Figures out which channel type we need to wrap @@ -292,3 +354,25 @@ def server_interceptor(tracer_provider=None, filters=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) return _server.OpenTelemetryServerInterceptor(tracer, filters=filters) + + +def _excluded_service_filter() -> Union[Callable[[object], bool], None]: + services = _parse_services( + os.environ.get("OTEL_PYTHON_GRPC_EXCLUDED_SERVICES", "") + ) + if len(services) == 0: + return None + filters = [] + for s in services: + filters.append(service_name(s)) + return reverse(any_of(*filters)) + + +def _parse_services(excluded_services: str) -> List[str]: + if excluded_services != "": + excluded_service_list = [ + s.strip() for s in excluded_services.split(",") + ] + else: + excluded_service_list = [] + return excluded_service_list diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py index ab3c6e3883..2f165d2421 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +from unittest import mock + import grpc from tests.protobuf import ( # pylint: disable=no-name-in-module test_server_pb2_grpc, @@ -369,3 +372,38 @@ def invoker(request, metadata): finally: set_global_textmap(previous_propagator) + + +class TestClientProtoFilterByEnv(TestBase): + def setUp(self): + with mock.patch.dict( + os.environ, + { + "OTEL_PYTHON_GRPC_EXCLUDED_SERVICES": "GRPCMockServer,GRPCTestServer" + }, + ): + super().setUp() + GrpcInstrumentorClient().instrument() + self.server = create_test_server(25565) + self.server.start() + # use a user defined interceptor along with the opentelemetry client interceptor + interceptors = [Interceptor()] + self.channel = grpc.insecure_channel("localhost:25565") + self.channel = grpc.intercept_channel(self.channel, *interceptors) + self._stub = test_server_pb2_grpc.GRPCTestServerStub(self.channel) + + def tearDown(self): + super().tearDown() + GrpcInstrumentorClient().uninstrument() + self.server.stop(None) + self.channel.close() + + def test_unary_unary_future(self): + simple_method_future(self._stub).result() + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + def test_unary_unary(self): + simple_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index bec0dc0c72..285507c709 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -331,6 +331,17 @@ def test_health_check(test_case): timeout=3000, ), ), + ( + True, + filters.any_of( + filters.service_name("GRPCMockServer"), + filters.service_name("GRPCTestServer"), + ), + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", + timeout=3000, + ), + ), ( False, filters.reverse(filters.method_name("SimpleMethod")), From 3e058cbb5d5f95366d1225270380f0808c2f763c Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 26 Aug 2022 18:10:57 +0900 Subject: [PATCH 19/34] fix lint --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 6e70325d2c..af1f1a4e51 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -363,8 +363,8 @@ def _excluded_service_filter() -> Union[Callable[[object], bool], None]: if len(services) == 0: return None filters = [] - for s in services: - filters.append(service_name(s)) + for srv in services: + filters.append(service_name(srv)) return reverse(any_of(*filters)) From f7de43fed88eb0c606fa6aceb20423dc786e7d3e Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:07:46 +0900 Subject: [PATCH 20/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index af1f1a4e51..c7a9ffe48f 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -138,7 +138,7 @@ def serve(): ) grpc_server_instrumentor.instrument() -You can also use the filter to manual instrumentors: +You can also use the filters directly on the provided interceptors: .. code-block:: From 8f808443232545eb9615cc3bfbddab4e5f2330b6 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:07:51 +0900 Subject: [PATCH 21/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index c7a9ffe48f..dab3798085 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -158,7 +158,7 @@ def serve(): ``OTEL_PYTHON_GRPC_EXCLUDED_SERVICES`` environment variables. For example, if you assign ``"GRPCTestServer,GRPCHealthServer"`` to the variable, -then the global interceptor automatically add the filter to exclude requests to +then the global interceptor automatically adds the filters to exclude requests to services ``GRPCTestServer`` and ``GRPCHealthServer``. """ From b064a13e257d6c8e55fe1ed5bbe49450ac2c0ce6 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:07:58 +0900 Subject: [PATCH 22/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index dab3798085..cfd6b97749 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -195,8 +195,8 @@ class GrpcInstrumentorServer(BaseInstrumentor): grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() - If you want to add filters that only intercept requests - to match the condition, assign filters option to GrpcInstrumentorServer. + If you want to add filters that skip requests which match + the condition, pass ``filters`` option to GrpcInstrumentorServer. grpc_server_instrumentor = GrpcInstrumentorServer( filters=filters.method_prefix("SimpleMethod")) From c2b62d1a53e5a82fc3fa7241a136e0bda78a39b8 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:08:16 +0900 Subject: [PATCH 23/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index cfd6b97749..46b8a2aed3 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -362,9 +362,7 @@ def _excluded_service_filter() -> Union[Callable[[object], bool], None]: ) if len(services) == 0: return None - filters = [] - for srv in services: - filters.append(service_name(srv)) + filters = (service_name(srv) for srv in services) return reverse(any_of(*filters)) From 3a5466e3e5520848d921243fd96d612b57b1ec10 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:10:55 +0900 Subject: [PATCH 24/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py Co-authored-by: Aaron Abbott --- .../opentelemetry/instrumentation/grpc/filters/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 6becc9a4de..696a7591c6 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -50,10 +50,7 @@ def all_of(*args): """ def filter_fn(metadata): - for func in args: - if not func(metadata): - return False - return True + return all(func(metadata) for func in args) return filter_fn From 56609693d8af1ea68174fc462d06e1334db5fe57 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:11:48 +0900 Subject: [PATCH 25/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/filters/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 696a7591c6..a939cfcc0d 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -56,7 +56,7 @@ def filter_fn(metadata): def any_of(*args): - """Returns a filter function that returns True if either of filter functions + """Returns a filter function that returns True if any of filter functions assigned matches conditions. Args: From d72156bca499527480a4ab4e273917f3d6af8e55 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 09:12:02 +0900 Subject: [PATCH 26/34] Update instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py Co-authored-by: Aaron Abbott --- .../src/opentelemetry/instrumentation/grpc/filters/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index a939cfcc0d..7fd583190c 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -63,7 +63,7 @@ def any_of(*args): args (function): a list of filter function Returns: - A filter function that returns True if either of filter functions + A filter function that returns True if any of filter functions assigned matches conditions. """ From 39bc5302aa020fdd75e9cd9abe14dd818ca25e75 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 13:57:00 +1000 Subject: [PATCH 27/34] fix comments, function name and add type annotations --- .../instrumentation/grpc/__init__.py | 14 +++---- .../instrumentation/grpc/_server.py | 2 +- .../instrumentation/grpc/filters/__init__.py | 38 +++++++++---------- .../tests/test_filters.py | 4 +- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 46b8a2aed3..4203123756 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -143,7 +143,7 @@ def serve(): .. code-block:: my_interceptor = server_interceptor( - filters = filters.reverse(filters.method_name("TestMethod")) + filters = filters.negate(filters.method_name("TestMethod")) ) server = grpc.server(futures.ThreadPoolExecutor(), interceptors = [my_interceptor]) @@ -172,7 +172,7 @@ def serve(): from opentelemetry.instrumentation.grpc.filters import ( all_of, any_of, - reverse, + negate, service_name, ) from opentelemetry.instrumentation.grpc.grpcext import intercept_channel @@ -195,8 +195,8 @@ class GrpcInstrumentorServer(BaseInstrumentor): grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() - If you want to add filters that skip requests which match - the condition, pass ``filters`` option to GrpcInstrumentorServer. + If you want to add filters that only intercept requests + to match the condition, pass ``filters`` to GrpcInstrumentorServer. grpc_server_instrumentor = GrpcInstrumentorServer( filters=filters.method_prefix("SimpleMethod")) @@ -255,10 +255,10 @@ class GrpcInstrumentorClient(BaseInstrumentor): grpc_client_instrumentor.instrument() If you want to add filters that only intercept requests - to match the condition, assign filters option to GrpcInstrumentorClient. + to match the condition, pass ``filters`` option to GrpcInstrumentorClient. grpc_client_instrumentor = GrpcInstrumentorClient( - filter=filters.reverse(filters.health_check()) + filter=filters.negate(filters.health_check()) ) grpc_client_instrumentor.instrument() @@ -363,7 +363,7 @@ def _excluded_service_filter() -> Union[Callable[[object], bool], None]: if len(services) == 0: return None filters = (service_name(srv) for srv in services) - return reverse(any_of(*filters)) + return negate(any_of(*filters)) def _parse_services(excluded_services: str) -> List[str]: diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index 0a3dc91f50..7b84d80432 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -173,7 +173,7 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): Usage:: tracer = some OpenTelemetry tracer - filter = filters.reverse(filters.method_name("service.Foo")) + filter = filters.negate(filters.method_name("service.Foo")) interceptors = [ OpenTelemetryServerInterceptor(tracer, filter), diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 7fd583190c..750a6a1848 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -13,9 +13,12 @@ # limitations under the License. import os +from typing import Callable, TypeVar, Union import grpc +TCallDetails = TypeVar('TCallDetails', grpc.HandlerCallDetails, grpc.ClientCallDetails) +Condition = Callable[[TCallDetails], bool] def _full_method(metadata): name = "" @@ -37,7 +40,7 @@ def _split_full_method(metadata): return (service, method) -def all_of(*args): +def all_of(*args: Condition[TCallDetails]) -> Condition[TCallDetails]: """Returns a filter function that returns True if all filter functions assigned matches conditions. @@ -55,7 +58,7 @@ def filter_fn(metadata): return filter_fn -def any_of(*args): +def any_of(*args: Condition[TCallDetails]) -> Condition[TCallDetails]: """Returns a filter function that returns True if any of filter functions assigned matches conditions. @@ -68,24 +71,19 @@ def any_of(*args): """ def filter_fn(metadata): - for func in args: - if func(metadata): - return True - return False + return any(func(metadata) for func in args) return filter_fn -def reverse(func): - """Returns a filter function that reverse the result of func +def negate(func: Condition[TCallDetails]) -> Condition[TCallDetails]: + """Returns a filter function that negate the result of func Args: - func (function): filter function that returns True if - request's gRPC method name matches name + func (function): filter function to negate the result Returns: - A filter function that returns False if request's gRPC method - name matches name + A filter function that negate the result of func """ def filter_fn(metadata): @@ -94,7 +92,7 @@ def filter_fn(metadata): return filter_fn -def method_name(name): +def method_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name matches name. @@ -113,7 +111,7 @@ def filter_fn(metadata): return filter_fn -def method_prefix(prefix): +def method_prefix(prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name starts with prefix. @@ -132,7 +130,7 @@ def filter_fn(metadata): return filter_fn -def full_method_name(name): +def full_method_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC full method name matches name. @@ -151,7 +149,7 @@ def filter_fn(metadata): return filter_fn -def service_name(name): +def service_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name matches name. @@ -170,15 +168,15 @@ def filter_fn(metadata): return filter_fn -def service_prefix(prefix): +def service_prefix(prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name starts with prefix. Args: - prefix (str): method prefix to match + prefix (str): service prefix to match Returns: - A filter function that returns True if request's gRPC method + A filter function that returns True if request's gRPC service name starts with prefix """ @@ -189,7 +187,7 @@ def filter_fn(metadata): return filter_fn -def health_check(): +def health_check() -> Condition[TCallDetails]: """Returns a Filter that returns true if the request's service name is health check defined by gRPC Health Checking Protocol. https://github.com/grpc/grpc/blob/master/doc/health-checking.md diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py index 285507c709..f7d69074ac 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_filters.py @@ -344,7 +344,7 @@ def test_health_check(test_case): ), ( False, - filters.reverse(filters.method_name("SimpleMethod")), + filters.negate(filters.method_name("SimpleMethod")), _UnaryClientInfo( full_method="/GRPCTestServer/SimpleMethod", timeout=3000, @@ -352,6 +352,6 @@ def test_health_check(test_case): ), ], ) -def test_all_any_reverse(test_case): +def test_all_any_negate(test_case): fn = test_case[1] assert test_case[0] == fn(test_case[2]) From 91649ba31c9c89a296cc815814a8849fc756dafb Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 14:52:01 +1000 Subject: [PATCH 28/34] tox -e lint and generate --- .../instrumentation/grpc/filters/__init__.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 750a6a1848..910ee0d0d0 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -17,9 +17,12 @@ import grpc -TCallDetails = TypeVar('TCallDetails', grpc.HandlerCallDetails, grpc.ClientCallDetails) +TCallDetails = TypeVar( + "TCallDetails", grpc.HandlerCallDetails, grpc.ClientCallDetails +) Condition = Callable[[TCallDetails], bool] + def _full_method(metadata): name = "" if isinstance(metadata, grpc.HandlerCallDetails): @@ -92,7 +95,9 @@ def filter_fn(metadata): return filter_fn -def method_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: +def method_name( + name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] +) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name matches name. @@ -111,7 +116,9 @@ def filter_fn(metadata): return filter_fn -def method_prefix(prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: +def method_prefix( + prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] +) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name starts with prefix. @@ -130,7 +137,9 @@ def filter_fn(metadata): return filter_fn -def full_method_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: +def full_method_name( + name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] +) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC full method name matches name. @@ -149,7 +158,9 @@ def filter_fn(metadata): return filter_fn -def service_name(name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: +def service_name( + name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] +) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name matches name. @@ -168,7 +179,9 @@ def filter_fn(metadata): return filter_fn -def service_prefix(prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]]) -> Condition[TCallDetails]: +def service_prefix( + prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] +) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name starts with prefix. From fc9b0e68c3cc03de337e11893f80f2916137f46a Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 15:49:02 +1000 Subject: [PATCH 29/34] fix filter condition to handle both environment variables and options --- .../instrumentation/grpc/__init__.py | 4 +- .../tests/test_client_interceptor_filter.py | 277 +++++++++++++++++- 2 files changed, 278 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 4203123756..43a437c54d 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -212,7 +212,7 @@ def __init__(self, filters=None): if filters is None: filters = excluded_service_filter else: - filters = all_of(filters, excluded_service_filter) + filters = any_of(filters, excluded_service_filter) self._filters = filters def instrumentation_dependencies(self) -> Collection[str]: @@ -270,7 +270,7 @@ def __init__(self, filters=None): if filters is None: filters = excluded_service_filter else: - filters = all_of(filters, excluded_service_filter) + filters = any_of(filters, excluded_service_filter) self._filters = filters # Figures out which channel type we need to wrap diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py index 2f165d2421..a8b4a750d9 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -21,7 +21,7 @@ ) import opentelemetry.instrumentation.grpc -from opentelemetry import trace +from opentelemetry import context, trace from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient, filters from opentelemetry.instrumentation.grpc._client import ( OpenTelemetryClientInterceptor, @@ -29,6 +29,7 @@ from opentelemetry.instrumentation.grpc.grpcext._interceptor import ( _UnaryClientInfo, ) +from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY from opentelemetry.propagate import get_global_textmap, set_global_textmap from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.test.mock_textmap import MockTextMapPropagator @@ -407,3 +408,277 @@ def test_unary_unary(self): simple_method(self._stub) spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) + + +class TestClientProtoFilterByEnvAndOption(TestBase): + def setUp(self): + with mock.patch.dict( + os.environ, + { + "OTEL_PYTHON_GRPC_EXCLUDED_SERVICES": "GRPCMockServer" + }, + ): + super().setUp() + GrpcInstrumentorClient( + filters=filters.service_prefix("GRPCTestServer") + ).instrument() + self.server = create_test_server(25565) + self.server.start() + # use a user defined interceptor along with the opentelemetry client interceptor + interceptors = [Interceptor()] + self.channel = grpc.insecure_channel("localhost:25565") + self.channel = grpc.intercept_channel(self.channel, *interceptors) + self._stub = test_server_pb2_grpc.GRPCTestServerStub(self.channel) + + def tearDown(self): + super().tearDown() + GrpcInstrumentorClient().uninstrument() + self.server.stop(None) + self.channel.close() + + def test_unary_unary_future(self): + simple_method_future(self._stub).result() + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + def test_unary_unary(self): + simple_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/SimpleMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "SimpleMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_unary_stream(self): + server_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/ServerStreamingMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "ServerStreamingMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_stream_unary(self): + client_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.name, "/GRPCTestServer/ClientStreamingMethod") + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "ClientStreamingMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_stream_stream(self): + bidirectional_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual( + span.name, "/GRPCTestServer/BidirectionalStreamingMethod" + ) + self.assertIs(span.kind, trace.SpanKind.CLIENT) + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.grpc + ) + + self.assertSpanHasAttributes( + span, + { + SpanAttributes.RPC_METHOD: "BidirectionalStreamingMethod", + SpanAttributes.RPC_SERVICE: "GRPCTestServer", + SpanAttributes.RPC_SYSTEM: "grpc", + SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.OK.value[ + 0 + ], + }, + ) + + def test_error_simple(self): + with self.assertRaises(grpc.RpcError): + simple_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_error_stream_unary(self): + with self.assertRaises(grpc.RpcError): + client_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_error_unary_stream(self): + with self.assertRaises(grpc.RpcError): + server_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_error_stream_stream(self): + with self.assertRaises(grpc.RpcError): + bidirectional_streaming_method(self._stub, error=True) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self.assertIs( + span.status.status_code, + trace.StatusCode.ERROR, + ) + + def test_client_interceptor_trace_context_propagation( + self, + ): # pylint: disable=no-self-use + """ensure that client interceptor correctly inject trace context into all outgoing requests.""" + previous_propagator = get_global_textmap() + try: + set_global_textmap(MockTextMapPropagator()) + interceptor = OpenTelemetryClientInterceptor(trace.NoOpTracer()) + + carrier = tuple() + + def invoker(request, metadata): + nonlocal carrier + carrier = metadata + return {} + + request = Request(client_id=1, request_data="data") + interceptor.intercept_unary( + request, + {}, + _UnaryClientInfo( + full_method="/GRPCTestServer/SimpleMethod", timeout=None + ), + invoker=invoker, + ) + + assert len(carrier) == 2 + assert carrier[0][0] == "mock-traceid" + assert carrier[0][1] == "0" + assert carrier[1][0] == "mock-spanid" + assert carrier[1][1] == "0" + + finally: + set_global_textmap(previous_propagator) + + def test_unary_unary_with_suppress_key(self): + token = context.attach( + context.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True) + ) + try: + simple_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + finally: + context.detach(token) + self.assertEqual(len(spans), 0) + + def test_unary_stream_with_suppress_key(self): + token = context.attach( + context.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True) + ) + try: + server_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + finally: + context.detach(token) + self.assertEqual(len(spans), 0) + + def test_stream_unary_with_suppress_key(self): + token = context.attach( + context.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True) + ) + try: + client_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + finally: + context.detach(token) + self.assertEqual(len(spans), 0) + + def test_stream_stream_with_suppress_key(self): + token = context.attach( + context.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True) + ) + try: + bidirectional_streaming_method(self._stub) + spans = self.memory_exporter.get_finished_spans() + finally: + context.detach(token) + self.assertEqual(len(spans), 0) From 21c287c1491d9007748bc032bd990368dcfdb0ca Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 15:49:44 +1000 Subject: [PATCH 30/34] tox -e generate --- .../tests/test_client_interceptor_filter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py index a8b4a750d9..b2bf3130d1 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -414,9 +414,7 @@ class TestClientProtoFilterByEnvAndOption(TestBase): def setUp(self): with mock.patch.dict( os.environ, - { - "OTEL_PYTHON_GRPC_EXCLUDED_SERVICES": "GRPCMockServer" - }, + {"OTEL_PYTHON_GRPC_EXCLUDED_SERVICES": "GRPCMockServer"}, ): super().setUp() GrpcInstrumentorClient( From 55a17f9ebca4458f6c0c48cbc7d8991f1998b58e Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Fri, 9 Sep 2022 15:55:47 +1000 Subject: [PATCH 31/34] tox -e lint --- .../src/opentelemetry/instrumentation/grpc/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 43a437c54d..427a951342 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -170,7 +170,6 @@ def serve(): from opentelemetry import trace from opentelemetry.instrumentation.grpc.filters import ( - all_of, any_of, negate, service_name, From 4ad8547464cebc8828448f87801024097ddebce0 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Wed, 14 Sep 2022 13:44:21 +0900 Subject: [PATCH 32/34] change option name from filters to filter_ --- .../instrumentation/grpc/__init__.py | 56 +++++++++---------- .../instrumentation/grpc/_client.py | 4 +- .../instrumentation/grpc/_server.py | 4 +- .../tests/test_client_interceptor_filter.py | 6 +- .../tests/test_server_interceptor_filter.py | 6 +- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py index 427a951342..9b4b0c61fd 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/__init__.py @@ -131,7 +131,7 @@ def serve(): from opentelemetry.instrumentation.grpc import filters, GrpcInstrumentorServer grpc_server_instrumentor = GrpcInstrumentorServer( - filters = filters.any_of( + filter_ = filters.any_of( filters.method_name("SimpleMethod"), filters.method_name("ComplexMethod"), ) @@ -143,12 +143,12 @@ def serve(): .. code-block:: my_interceptor = server_interceptor( - filters = filters.negate(filters.method_name("TestMethod")) + filter_ = filters.negate(filters.method_name("TestMethod")) ) server = grpc.server(futures.ThreadPoolExecutor(), interceptors = [my_interceptor]) -``filters`` option also applies to both global and manual client intrumentors. +``filter_`` option also applies to both global and manual client intrumentors. Environment variable @@ -194,25 +194,25 @@ class GrpcInstrumentorServer(BaseInstrumentor): grpc_server_instrumentor = GrpcInstrumentorServer() grpc_server_instrumentor.instrument() - If you want to add filters that only intercept requests - to match the condition, pass ``filters`` to GrpcInstrumentorServer. + If you want to add a filter that only intercept requests + to match the condition, pass ``filter_`` to GrpcInstrumentorServer. grpc_server_instrumentor = GrpcInstrumentorServer( - filters=filters.method_prefix("SimpleMethod")) + filter_=filters.method_prefix("SimpleMethod")) grpc_server_instrumentor.instrument() """ # pylint:disable=attribute-defined-outside-init, redefined-outer-name - def __init__(self, filters=None): + def __init__(self, filter_=None): excluded_service_filter = _excluded_service_filter() if excluded_service_filter is not None: - if filters is None: - filters = excluded_service_filter + if filter_ is None: + filter_ = excluded_service_filter else: - filters = any_of(filters, excluded_service_filter) - self._filters = filters + filter_ = any_of(filter_, excluded_service_filter) + self._filter = filter_ def instrumentation_dependencies(self) -> Collection[str]: return _instruments @@ -227,13 +227,13 @@ def server(*args, **kwargs): kwargs["interceptors"].insert( 0, server_interceptor( - tracer_provider=tracer_provider, filters=self._filters + tracer_provider=tracer_provider, filter_=self._filter ), ) else: kwargs["interceptors"] = [ server_interceptor( - tracer_provider=tracer_provider, filters=self._filters + tracer_provider=tracer_provider, filter_=self._filter ) ] return self._original_func(*args, **kwargs) @@ -253,24 +253,24 @@ class GrpcInstrumentorClient(BaseInstrumentor): grpc_client_instrumentor = GrpcInstrumentorClient() grpc_client_instrumentor.instrument() - If you want to add filters that only intercept requests - to match the condition, pass ``filters`` option to GrpcInstrumentorClient. + If you want to add a filter that only intercept requests + to match the condition, pass ``filter_`` option to GrpcInstrumentorClient. grpc_client_instrumentor = GrpcInstrumentorClient( - filter=filters.negate(filters.health_check()) + filter_=filters.negate(filters.health_check()) ) grpc_client_instrumentor.instrument() """ - def __init__(self, filters=None): + def __init__(self, filter_=None): excluded_service_filter = _excluded_service_filter() if excluded_service_filter is not None: - if filters is None: - filters = excluded_service_filter + if filter_ is None: + filter_ = excluded_service_filter else: - filters = any_of(filters, excluded_service_filter) - self._filters = filters + filter_ = any_of(filter_, excluded_service_filter) + self._filter = filter_ # Figures out which channel type we need to wrap def _which_channel(self, kwargs): @@ -310,18 +310,18 @@ def wrapper_fn(self, original_func, instance, args, kwargs): channel, client_interceptor( tracer_provider=tracer_provider, - filters=self._filters, + filter_=self._filter, ), ) -def client_interceptor(tracer_provider=None, filters=None): +def client_interceptor(tracer_provider=None, filter_=None): """Create a gRPC client channel interceptor. Args: tracer: The tracer to use to create client-side spans. - filters: filter function that returns True if gRPC requests + filter_: filter function that returns True if gRPC requests matches the condition. Default is None and intercept all requests. @@ -332,16 +332,16 @@ def client_interceptor(tracer_provider=None, filters=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _client.OpenTelemetryClientInterceptor(tracer, filters=filters) + return _client.OpenTelemetryClientInterceptor(tracer, filter_=filter_) -def server_interceptor(tracer_provider=None, filters=None): +def server_interceptor(tracer_provider=None, filter_=None): """Create a gRPC server interceptor. Args: tracer: The tracer to use to create server-side spans. - filters: filter function that returns True if gRPC requests + filter_: filter function that returns True if gRPC requests matches the condition. Default is None and intercept all requests. @@ -352,7 +352,7 @@ def server_interceptor(tracer_provider=None, filters=None): tracer = trace.get_tracer(__name__, __version__, tracer_provider) - return _server.OpenTelemetryServerInterceptor(tracer, filters=filters) + return _server.OpenTelemetryServerInterceptor(tracer, filter_=filter_) def _excluded_service_filter() -> Union[Callable[[object], bool], None]: diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py index 116c50849d..55a46d4a49 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_client.py @@ -62,9 +62,9 @@ def callback(response_future): class OpenTelemetryClientInterceptor( grpcext.UnaryClientInterceptor, grpcext.StreamClientInterceptor ): - def __init__(self, tracer, filters=None): + def __init__(self, tracer, filter_=None): self._tracer = tracer - self._filter = filters + self._filter = filter_ def _start_span(self, method, **kwargs): service, meth = method.lstrip("/").split("/", 1) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py index 7b84d80432..6a8c1ef58b 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_server.py @@ -185,9 +185,9 @@ class OpenTelemetryServerInterceptor(grpc.ServerInterceptor): """ - def __init__(self, tracer, filters=None): + def __init__(self, tracer, filter_=None): self._tracer = tracer - self._filter = filters + self._filter = filter_ @contextmanager def _set_remote_context(self, servicer_context): diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py index b2bf3130d1..a15268464b 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_client_interceptor_filter.py @@ -91,7 +91,7 @@ class TestClientProtoFilterMethodName(TestBase): def setUp(self): super().setUp() GrpcInstrumentorClient( - filters=filters.method_name("SimpleMethod") + filter_=filters.method_name("SimpleMethod") ).instrument() self.server = create_test_server(25565) self.server.start() @@ -235,7 +235,7 @@ class TestClientProtoFilterMethodPrefix(TestBase): def setUp(self): super().setUp() GrpcInstrumentorClient( - filters=filters.method_prefix("Simple") + filter_=filters.method_prefix("Simple") ).instrument() self.server = create_test_server(25565) self.server.start() @@ -418,7 +418,7 @@ def setUp(self): ): super().setUp() GrpcInstrumentorClient( - filters=filters.service_prefix("GRPCTestServer") + filter_=filters.service_prefix("GRPCTestServer") ).instrument() self.server = create_test_server(25565) self.server.start() diff --git a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py index 6fb7d96974..95e70236cb 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/tests/test_server_interceptor_filter.py @@ -81,7 +81,7 @@ def handler(request, context): return b"" grpc_server_instrumentor = GrpcInstrumentorServer( - filters=filters.method_name("handler") + filter_=filters.method_name("handler") ) grpc_server_instrumentor.instrument() with futures.ThreadPoolExecutor(max_workers=1) as executor: @@ -135,7 +135,7 @@ def handler(request, context): return b"" grpc_server_instrumentor = GrpcInstrumentorServer( - filters=filters.method_name("SimpleMethod") + filter_=filters.method_name("SimpleMethod") ) grpc_server_instrumentor.instrument() grpc_server_instrumentor.uninstrument() @@ -165,7 +165,7 @@ def test_create_span(self): # Intercept gRPC calls... interceptor = server_interceptor( - filters=filters.method_name("SimpleMethod") + filter_=filters.method_name("SimpleMethod") ) with futures.ThreadPoolExecutor(max_workers=1) as executor: From 3dd81ced8f07a3fcbadeed68bf18392a6f00fca4 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Tue, 20 Sep 2022 15:08:06 +0900 Subject: [PATCH 33/34] fix filters' type annotations --- .../instrumentation/grpc/filters/__init__.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index 910ee0d0d0..b398db1b53 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -95,9 +95,7 @@ def filter_fn(metadata): return filter_fn -def method_name( - name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] -) -> Condition[TCallDetails]: +def method_name(name: str) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name matches name. @@ -116,9 +114,7 @@ def filter_fn(metadata): return filter_fn -def method_prefix( - prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] -) -> Condition[TCallDetails]: +def method_prefix(prefix: str) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC method name starts with prefix. @@ -137,9 +133,7 @@ def filter_fn(metadata): return filter_fn -def full_method_name( - name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] -) -> Condition[TCallDetails]: +def full_method_name(name: str) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC full method name matches name. @@ -158,9 +152,7 @@ def filter_fn(metadata): return filter_fn -def service_name( - name: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] -) -> Condition[TCallDetails]: +def service_name(name: str) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name matches name. @@ -179,9 +171,7 @@ def filter_fn(metadata): return filter_fn -def service_prefix( - prefix: Condition[Union[grpc.HandlerCallDetails, grpc.ClientCallDetails]] -) -> Condition[TCallDetails]: +def service_prefix(prefix: str) -> Condition[TCallDetails]: """Returns a filter function that return True if request's gRPC service name starts with prefix. From ddbf0bd29ded66f3d55b1e69470e28c984874f95 Mon Sep 17 00:00:00 2001 From: Yoshi Yamaguchi Date: Tue, 20 Sep 2022 15:18:18 +0900 Subject: [PATCH 34/34] lint: remove unused class --- .../src/opentelemetry/instrumentation/grpc/filters/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py index b398db1b53..905bb8d696 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/filters/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. import os -from typing import Callable, TypeVar, Union +from typing import Callable, TypeVar import grpc