Skip to content

Commit

Permalink
Merge branch 'main' into fix-psycopg2-instrument-connection
Browse files Browse the repository at this point in the history
  • Loading branch information
tammy-baylis-swi authored Jan 14, 2025
2 parents 86dd604 + 52871b8 commit 9ef7b50
Show file tree
Hide file tree
Showing 21 changed files with 688 additions and 258 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/component-owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ jobs:
run_self:
runs-on: ubuntu-latest
name: Auto Assign Owners
permissions:
contents: read # to read changed files
issues: write # to read/write issue assignees
pull-requests: write # to read/write PR reviewers
# Don't fail tests if this workflow fails. Some pending issues:
# - https://github.com/dyladan/component-owners/issues/8
continue-on-error: true
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/misc_0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,21 @@ jobs:

- name: Run tests
run: tox -e ruff

typecheck:
name: typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout repo @ SHA - ${{ github.sha }}
uses: actions/checkout@v4

- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install tox
run: pip install tox

- name: Run tests
run: tox -e typecheck
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3100](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3100))
- Add support to database stability opt-in in `_semconv` utilities and add tests
([#3111](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3111))
- `opentelemetry-instrumentation-urllib` Add `py.typed` file to enable PEP 561
([#3131](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3131))
- `opentelemetry-opentelemetry-pymongo` Add `py.typed` file to enable PEP 561
([#3136](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3136))
- `opentelemetry-opentelemetry-requests` Add `py.typed` file to enable PEP 561
Expand All @@ -27,10 +29,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3133](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3133))
- `opentelemetry-instrumentation-falcon` add support version to v4
([#3086](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3086))
- `opentelemetry-instrumentation-falcon` Implement new HTTP semantic convention opt-in for Falcon
([#2790](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2790))
- `opentelemetry-instrumentation-wsgi` always record span status code to have it available in metrics
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
- add support to Python 3.13
([#3134](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3134))
- `opentelemetry-opentelemetry-wsgi` Add `py.typed` file to enable PEP 561
([#3129](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3129))
- `opentelemetry-util-http` Add `py.typed` file to enable PEP 561
([#3127](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3127))

Expand Down
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pylint==3.0.2
httpretty==1.1.4
mypy==0.931
pyright==v1.1.390
sphinx==7.1.2
sphinx-rtd-theme==2.0.0rc4
sphinx-autodoc-typehints==1.25.2
Expand Down
2 changes: 1 addition & 1 deletion instrumentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | experimental
| [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | experimental
| [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | experimental
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | experimental
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | migration
| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration
| [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio >= 1.42.0 | No | experimental
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ def response_hook(span, req, resp):

import opentelemetry.instrumentation.wsgi as otel_wsgi
from opentelemetry import context, trace
from opentelemetry.instrumentation._semconv import (
_get_schema_url,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilitySignalType,
_report_new,
_report_old,
_StabilityMode,
)
from opentelemetry.instrumentation.falcon.package import _instruments
from opentelemetry.instrumentation.falcon.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
Expand All @@ -203,18 +211,22 @@ def response_hook(span, req, resp):
from opentelemetry.instrumentation.utils import (
_start_internal_or_server_span,
extract_attributes_from_object,
http_status_to_status_code,
)
from opentelemetry.metrics import get_meter
from opentelemetry.semconv.attributes.http_attributes import (
HTTP_ROUTE,
)
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.semconv.metrics.http_metrics import (
HTTP_SERVER_REQUEST_DURATION,
)
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs

_logger = getLogger(__name__)

_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
_ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
_ENVIRON_EXC = "opentelemetry-falcon.exc"
Expand Down Expand Up @@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
def __init__(self, *args, **kwargs):
otel_opts = kwargs.pop("_otel_opts", {})

self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
_OpenTelemetryStabilitySignalType.HTTP,
)

# inject trace middleware
self._middlewares_list = kwargs.pop("middleware", [])
if self._middlewares_list is None:
Expand All @@ -257,19 +273,30 @@ def __init__(self, *args, **kwargs):
__name__,
__version__,
tracer_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
)
self._otel_meter = get_meter(
__name__,
__version__,
meter_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
)
self.duration_histogram = self._otel_meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="Measures the duration of inbound HTTP requests.",
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
)

self.duration_histogram_old = None
if _report_old(self._sem_conv_opt_in_mode):
self.duration_histogram_old = self._otel_meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="Measures the duration of inbound HTTP requests.",
)
self.duration_histogram_new = None
if _report_new(self._sem_conv_opt_in_mode):
self.duration_histogram_new = self._otel_meter.create_histogram(
name=HTTP_SERVER_REQUEST_DURATION,
description="Duration of HTTP server requests.",
unit="s",
)

self.active_requests_counter = self._otel_meter.create_up_down_counter(
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
unit="requests",
Expand All @@ -283,6 +310,7 @@ def __init__(self, *args, **kwargs):
),
otel_opts.pop("request_hook", None),
otel_opts.pop("response_hook", None),
self._sem_conv_opt_in_mode,
)
self._middlewares_list.insert(0, trace_middleware)
kwargs["middleware"] = self._middlewares_list
Expand Down Expand Up @@ -343,11 +371,14 @@ def __call__(self, env, start_response):
context_carrier=env,
context_getter=otel_wsgi.wsgi_getter,
)
attributes = otel_wsgi.collect_request_attributes(env)
attributes = otel_wsgi.collect_request_attributes(
env, self._sem_conv_opt_in_mode
)
active_requests_count_attrs = (
otel_wsgi._parse_active_request_count_attrs(attributes)
otel_wsgi._parse_active_request_count_attrs(
attributes, self._sem_conv_opt_in_mode
)
)
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
self.active_requests_counter.add(1, active_requests_count_attrs)

if span.is_recording():
Expand All @@ -364,6 +395,7 @@ def __call__(self, env, start_response):
activation.__enter__()
env[_ENVIRON_SPAN_KEY] = span
env[_ENVIRON_ACTIVATION_KEY] = activation
env[_ENVIRON_REQ_ATTRS] = attributes
exception = None

def _start_response(status, response_headers, *args, **kwargs):
Expand All @@ -379,12 +411,22 @@ def _start_response(status, response_headers, *args, **kwargs):
exception = exc
raise
finally:
if span.is_recording():
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
duration_s = default_timer() - start
if self.duration_histogram_old:
duration_attrs = otel_wsgi._parse_duration_attrs(
attributes, _StabilityMode.DEFAULT
)
self.duration_histogram_old.record(
max(round(duration_s * 1000), 0), duration_attrs
)
if self.duration_histogram_new:
duration_attrs = otel_wsgi._parse_duration_attrs(
attributes, _StabilityMode.HTTP
)
self.duration_histogram_new.record(
max(duration_s, 0), duration_attrs
)
duration = max(round((default_timer() - start) * 1000), 0)
self.duration_histogram.record(duration, duration_attrs)

self.active_requests_counter.add(-1, active_requests_count_attrs)
if exception is None:
activation.__exit__(None, None, None)
Expand All @@ -407,11 +449,13 @@ def __init__(
traced_request_attrs=None,
request_hook=None,
response_hook=None,
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
):
self.tracer = tracer
self._traced_request_attrs = traced_request_attrs
self._request_hook = request_hook
self._response_hook = response_hook
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode

def process_request(self, req, resp):
span = req.env.get(_ENVIRON_SPAN_KEY)
Expand All @@ -437,58 +481,60 @@ def process_resource(self, req, resp, resource, params):

def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201,R0912
span = req.env.get(_ENVIRON_SPAN_KEY)
req_attrs = req.env.get(_ENVIRON_REQ_ATTRS)

if not span or not span.is_recording():
if not span:
return

status = resp.status
reason = None
if resource is None:
status = "404"
reason = "NotFound"
status = falcon.HTTP_404
else:
exc_type, exc = None, None
if _ENVIRON_EXC in req.env:
exc = req.env[_ENVIRON_EXC]
exc_type = type(exc)
else:
exc_type, exc = None, None

if exc_type and not req_succeeded:
if "HTTPNotFound" in exc_type.__name__:
status = "404"
reason = "NotFound"
status = falcon.HTTP_404
elif isinstance(exc, (falcon.HTTPError, falcon.HTTPStatus)):
try:
if _falcon_version > 2:
status = falcon.code_to_http_status(exc.status)
else:
status = exc.status
except ValueError:
status = falcon.HTTP_500
else:
status = "500"
reason = f"{exc_type.__name__}: {exc}"
status = falcon.HTTP_500

# Falcon 1 does not support response headers. So
# send an empty dict.
response_headers = {}
if _falcon_version > 1:
response_headers = resp.headers

otel_wsgi.add_response_attributes(
span,
status,
response_headers,
req_attrs,
self._sem_conv_opt_in_mode,
)

status = status.split(" ")[0]
if (
_report_new(self._sem_conv_opt_in_mode)
and req.uri_template
and req_attrs is not None
):
req_attrs[HTTP_ROUTE] = req.uri_template
try:
status_code = int(status)
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
otel_status_code = http_status_to_status_code(
status_code, server_span=True
)

# set the description only when the status code is ERROR
if otel_status_code is not StatusCode.ERROR:
reason = None

span.set_status(
Status(
status_code=otel_status_code,
description=reason,
)
)

# Falcon 1 does not support response headers. So
# send an empty dict.
response_headers = {}
if _falcon_version > 1:
response_headers = resp.headers

if span.is_recording() and span.kind == trace.SpanKind.SERVER:
# Check if low-cardinality route is available as per semantic-conventions
if req.uri_template:
span.update_name(f"{req.method} {req.uri_template}")
span.set_attribute(HTTP_ROUTE, req.uri_template)
else:
span.update_name(f"{req.method}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
_instruments = ("falcon >= 1.4.1, < 5.0.0",)

_supports_metrics = True

_semconv_status = "migration"
Loading

0 comments on commit 9ef7b50

Please sign in to comment.