Skip to content

OTLP gRPC internal metrics report a server.port the exporter does not use, and none at all for a scheme-less endpoint #5651

Description

@chrikrah

Describe your environment

OS: Ubuntu 24.04
Python version: 3.12.3
SDK version: 1.45.0.dev, from main at 5aa2f8f
API version: same
grpcio 1.83.1

What happened?

With OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED=true, the internal exporter metrics carry a server.port
the exporter never connects to, and for a scheme-less endpoint they carry no server.address or
server.port at all.

_exporter_metrics.py:77-82 fills in an HTTP default when the endpoint omits the port:

port = endpoint.port
if port is None:
    if endpoint.scheme == "https":
        port = 443
    elif endpoint.scheme == "http":
        port = 80

gRPC does not use 80. A target with no port resolves to 443 whether the channel is secure or not, so for
http://otlp.example.com the attribute reads 80 while the channel dials 443. The OTLP/gRPC default in the
specification is 4317, which is neither.

Separately, otlp.example.com:4317 with no scheme is an endpoint form this exporter accepts and passes to
gRPC verbatim: exporter.py:299 leaves self._endpoint alone when urlparse yields an empty netloc.
It builds a secure channel, since _insecure is False without an http scheme, so reaching a plaintext
collector this way needs insecure=True or OTEL_EXPORTER_OTLP_INSECURE=true. The specification blesses
that combination for OTLP/gRPC. Either way the endpoint connects, and yet urlparse gives
scheme='otlp.example.com', netloc='', path='4317', so endpoint.hostname and endpoint.port are
both None and the two attributes are dropped from every internal metric.

Steps to Reproduce

import os
os.environ["OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED"] = "true"
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

for endpoint in ["http://otlp.example.com", "https://otlp.example.com",
                 "otlp.example.com:4317", "http://localhost:4317"]:
    exporter = OTLPSpanExporter(endpoint=endpoint, insecure=True)
    print(endpoint, {k: v for k, v in exporter._metrics._standard_attrs.items()
                     if k.startswith("server")})

For the port gRPC actually dials, with a listener bound to 127.0.0.1:443:

import grpc
grpc.channel_ready_future(grpc.insecure_channel("localhost")).result(timeout=5)

Expected Result

server.port matches the port the channel connects to. server.address and server.port are present for
every endpoint form the exporter accepts, including a scheme-less host:port.

Actual Result

http://otlp.example.com   {'server.address': 'otlp.example.com', 'server.port': 80}
https://otlp.example.com  {'server.address': 'otlp.example.com', 'server.port': 443}
otlp.example.com:4317     {}
http://localhost:4317     {'server.address': 'localhost', 'server.port': 4317}

The listener on 127.0.0.1:443 accepts the connection from insecure_channel("localhost"), so an insecure
channel with no port dials 443 and not 80:

listening 443
GOT CONNECTION from ('127.0.0.1', 33690)

Additional context

This came out of reproducing #3619, a different defect in the same constructor: the endpoint path dropped
without a warning. This one lives in opentelemetry-exporter-otlp-proto-common, touches only the internal
SDK metrics, and needs no change to connection behaviour.

I did not check the HTTP exporter, which shares _exporter_metrics.py and where the 80 and 443 defaults
are correct.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions