Skip to content

Commit

Permalink
feat: Configure traces and metrics pipelines (#27)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?
As we develop more functionality into the Honeycomb distro, setting
environment variables doesn't offer enough flexability for our needs.
This PR updates the distro to instead configure both the tracer and
meter providers directly by configuring their exporters.

This will enable additional resources (eg span processors or samplers)
to be added later.

- Closes #14 
- Closes #11 

## Short description of the changes
- [x] Adds openetelemetry-otlp-exporter as dependency
- [x] Add create_tracer_provider func to tracing.py
- [x] Add create_meter_provider func to metrics.py
- [x] Wire tracing and meter provider pipelines using options & resource
in distro/configure_opentelemetry
- [x] Update HoneycombOptions with member functions to get trace & meter
exporter credentials & headers
- [x] Add simple test to verify new tracer / meter provider creation
funcs return expected types
- [x] Remove env var based tests from test_distro.py
- [x] Update distro/configure_opentelemetry to take HoneycombOptions arg
instead of discrete optional fields
- [x] Re-add custom instrumentation to example app to show how to create
additional spans

Co-authored-by: Robb Kidd <robbkidd@honeycomb.io>
  • Loading branch information
MikeGoldsmith and robbkidd committed Dec 12, 2022
1 parent ef751a0 commit 460da85
Show file tree
Hide file tree
Showing 14 changed files with 695 additions and 86 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ bin
.vscode
.coverage
htmlcov
.env
test-results/
6 changes: 5 additions & 1 deletion examples/hello-world-flask/app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from flask import Flask
from opentelemetry import trace

app = Flask(__name__)

tracer = trace.get_tracer(__name__)

@app.route("/")
def hello_world():
with tracer.start_as_current_span("foo"):
with tracer.start_as_current_span("bar"):
print("baz")
return "Hello World"
277 changes: 277 additions & 0 deletions examples/hello-world-flask/poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions honeycomb/opentelemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from honeycomb.opentelemetry.distro import configure_opentelemetry
from honeycomb.opentelemetry.options import HoneycombOptions
45 changes: 13 additions & 32 deletions honeycomb/opentelemetry/distro.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
"""
Add module doc string
"""
import os
from honeycomb.opentelemetry.metrics import create_meter_provider
from honeycomb.opentelemetry.options import HoneycombOptions
from honeycomb.opentelemetry.resource import create_resource
from honeycomb.opentelemetry.trace import create_tracer_provider
from opentelemetry.instrumentation.distro import BaseDistro
from opentelemetry.environment_variables import (
OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER
)
from opentelemetry.sdk.environment_variables import (
OTEL_SERVICE_NAME,
OTEL_EXPORTER_OTLP_PROTOCOL,
OTEL_EXPORTER_OTLP_HEADERS,
OTEL_EXPORTER_OTLP_ENDPOINT
)

HONEYCOMB_API_KEY = "HONEYCOMB_API_KEY"
HONEYCOMB_API_ENDPOINT = "HONEYCOMB_API_ENDPOINT"

DEFAULT_API_ENDPOINT = "api.honeycomb.io:443"
DEFAULT_SERVICE_NAME = "unknown_service:python"
from opentelemetry.metrics import set_meter_provider
from opentelemetry.trace import set_tracer_provider


def configure_opentelemetry(
apikey: str = None,
service_name: str = None,
endpoint: str = None
options: HoneycombOptions = HoneycombOptions(),
):

options = HoneycombOptions(apikey, service_name, endpoint)

# TODO - remove once pipelines are configured directly
os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")
os.environ.setdefault(OTEL_TRACES_EXPORTER, "otlp")
# disable metrics for now
os.environ.setdefault(OTEL_METRICS_EXPORTER, "none")
os.environ.setdefault(OTEL_SERVICE_NAME, options.service_name)
os.environ.setdefault(OTEL_EXPORTER_OTLP_ENDPOINT, options.endpoint)
if options.apikey:
os.environ.setdefault(OTEL_EXPORTER_OTLP_HEADERS,
f"x-honeycomb-team={options.apikey}")
resource = create_resource(options)
set_tracer_provider(
create_tracer_provider(options, resource)
)
set_meter_provider(
create_meter_provider(options, resource)
)


class HoneycombDistro(BaseDistro):
Expand Down
23 changes: 23 additions & 0 deletions honeycomb/opentelemetry/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from honeycomb.opentelemetry.options import HoneycombOptions
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter
)


def create_meter_provider(options: HoneycombOptions, resource: Resource):
exporter = OTLPMetricExporter(
endpoint=options.endpoint,
credentials=options.get_metrics_endpoint_credentials(),
headers=options.get_metrics_headers()
)
reader = PeriodicExportingMetricReader(
exporter,
export_timeout_millis=10000 # TODO set via OTEL env var
)
return MeterProvider(
metric_readers=[reader],
resource=resource
)
35 changes: 34 additions & 1 deletion honeycomb/opentelemetry/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
OTEL_SERVICE_NAME,
OTEL_EXPORTER_OTLP_ENDPOINT
)
from grpc import ssl_channel_credentials


HONEYCOMB_API_KEY = "HONEYCOMB_API_KEY"
Expand All @@ -15,11 +16,14 @@ class HoneycombOptions:
apikey = None
service_name = DEFAULT_SERVICE_NAME
endpoint = DEFAULT_API_ENDPOINT
insecure = False
enable_metrics = False

def __init__(
self, apikey: str = None,
service_name: str = None,
endpoint: str = None
endpoint: str = None,
insecure: bool = False
):
self.apikey = os.environ.get(HONEYCOMB_API_KEY, apikey)

Expand All @@ -32,3 +36,32 @@ def __init__(
self.endpoint = os.environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, endpoint)
if not self.endpoint:
self.endpoint = DEFAULT_API_ENDPOINT

self.insecure = insecure

def get_trace_endpoint_credentials(self):
# TODO: use trace endpoint
if self.insecure:
return None
return ssl_channel_credentials()

def get_metrics_endpoint_credentials(self):
# TODO: use metrics endpoint
if self.insecure:
return None
return ssl_channel_credentials()

def get_trace_headers(self):
# TODO: use trace api key
headers = {
"x-honeycomb-team": self.apikey,
}
return headers

def get_metrics_headers(self):
# TODO: use metrics api key & metrics dataset
headers = {
"x-honeycomb-team": self.apikey,
"x-honeycomb-dataset": self.service_name + "_metrics"
}
return headers
21 changes: 21 additions & 0 deletions honeycomb/opentelemetry/trace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from honeycomb.opentelemetry.options import HoneycombOptions
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter
)


def create_tracer_provider(options: HoneycombOptions, resource: Resource):
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint=options.endpoint,
credentials=options.get_trace_endpoint_credentials(),
headers=options.get_trace_headers()
)
)
)
return trace_provider
Loading

0 comments on commit 460da85

Please sign in to comment.