Skip to content

Commit

Permalink
doc: audit of docstrings for dev-friendliness & relevance (#85)
Browse files Browse the repository at this point in the history
<!--
Thank you for contributing to the project! 💜
Please see our [OSS process
document](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md#)
to get an idea of how we operate.
-->

## Which problem is this PR solving?

- Closes #76
- Touches #31 

## Short description of the changes
- adds module docstrings for Distro
- additional class and function docstrings
- more type annotations for return expectations
- additional tests on stdout for local vis exporter

## How to verify that this has the expected result
- reading the docstrings is helpful to you

---------

Co-authored-by: Purvi Kanal <purvikanal@honeycomb.io>
  • Loading branch information
emilyashley and pkanal committed Feb 27, 2023
1 parent 89141a0 commit 90fbf88
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 39 deletions.
43 changes: 37 additions & 6 deletions src/honeycomb/opentelemetry/distro.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
"""
Add module doc string
"""This honey-flavored Distro configures OpenTelemetry for use with Honeycomb.
Typical usage example:
using the opentelemetry-instrument command with
requisite env variables set:
$bash> opentelemetry-instrument python program.py
or configured by code within your service:
configure_opentelemetry(
HoneycombOptions(
debug=True,
apikey=os.getenv("HONEYCOMB_API_KEY"),
service_name="otel-python-example"
)
)
"""
from logging import getLogger
from opentelemetry.instrumentation.distro import BaseDistro
Expand All @@ -21,9 +36,12 @@ def configure_opentelemetry(
Args:
options (HoneycombOptions, optional): the HoneycombOptions used to
configure the the SDK
configure the the SDK. These options can be set either as parameters
to this function or through environment variables
Note: API key is a required option.
"""
_logger.debug("🐝 Configuring OpenTelemetry using Honeycomb distro 🐝")
_logger.info("🐝 Configuring OpenTelemetry using Honeycomb distro 🐝")
_logger.debug(vars(options))
resource = create_resource(options)
set_tracer_provider(
Expand All @@ -38,9 +56,22 @@ def configure_opentelemetry(
# pylint: disable=too-few-public-methods
class HoneycombDistro(BaseDistro):
"""
This honey-flavored Distro configures OpenTelemetry for use with Honeycomb.
An extension of the base python OpenTelemetry distro, which provides
a mechanism to automatically configure some of the more common options
for users. This class is auto-detected by the `opentelemetry-instrument`
command.
This class doesn't need to be touched directly when using the distro. If
you'd like to explicitly set configuration in code, use the
configure_opentelemetry() function above instead of the
`opentelemetry-instrument` command.
If you're wondering about the under-the-hood magic - we add the following
declaration to package metadata in our pyproject.toml, like so:
[tool.poetry.plugins."opentelemetry_distro"]
distro = "honeycomb.opentelemetry.distro:HoneycombDistro"
"""

def _configure(self, **kwargs):
print("🐝 auto instrumented 🐝")
configure_opentelemetry()
17 changes: 12 additions & 5 deletions src/honeycomb/opentelemetry/local_exporter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import typing
from logging import getLogger
import requests


from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
from honeycomb.opentelemetry.options import HoneycombOptions, is_classic

MISSING_API_OR_SERVICE_NAME_ERROR = "disabling local visualizations - " + \
"must have both service name and API key configured."

_logger = getLogger(__name__)


def configure_local_exporter(options: HoneycombOptions):
"""
Configures and returns an OpenTelemetry Span Exporter that prints
"""Configures and returns an OpenTelemetry Span Exporter that prints
direct web links for completed traces in Honeycomb on stdout.
Args:
Expand All @@ -26,6 +33,7 @@ def configure_local_exporter(options: HoneycombOptions):
class LocalTraceLinkSpanExporter(SpanExporter):
"""Implementation of :class:`SpanExporter` that prints direct trace links
to Honeycomb to the console.
This class can be used for diagnostic purposes. It prints the exported
spans to the console STDOUT.
"""
Expand All @@ -37,8 +45,7 @@ def __init__(
apikey: str
):
if not service_name or not apikey:
print("disabling local visualizations - must have both " +
"service name and API key configured.")
_logger.warning(MISSING_API_OR_SERVICE_NAME_ERROR)
return

self.trace_link_url = self._build_trace_link_url(
Expand Down Expand Up @@ -71,7 +78,7 @@ def _build_trace_link_url(self, apikey: str, service_name: str):
timeout=30000 # 30 seconds
)
if not resp.ok:
print("failed to get auth data from Honeycomb API")
_logger.warning("failed to get auth data from Honeycomb API")
return None

resp_data = resp.json()
Expand Down
50 changes: 33 additions & 17 deletions src/honeycomb/opentelemetry/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@
)
from grpc import ssl_channel_credentials

# Environment Variable Names
OTEL_SERVICE_VERSION = "OTEL_SERVICE_VERSION"
DEBUG = "DEBUG"
HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS = "HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS"
SAMPLE_RATE = "SAMPLE_RATE"

# HNY Credential Names
HONEYCOMB_API_KEY = "HONEYCOMB_API_KEY"
HONEYCOMB_TRACES_APIKEY = "HONEYCOMB_TRACES_APIKEY"
HONEYCOMB_DATASET = "HONEYCOMB_DATASET"
HONEYCOMB_METRICS_APIKEY = "HONEYCOMB_METRICS_APIKEY"
HONEYCOMB_METRICS_DATASET = "HONEYCOMB_METRICS_DATASET"

# Default values
DEFAULT_API_ENDPOINT = "https://api.honeycomb.io:443"
DEFAULT_EXPORTER_PROTOCOL = "grpc"
DEFAULT_SERVICE_NAME = "unknown_service:python"
DEFAULT_LOG_LEVEL = "ERROR"
DEFAULT_SAMPLE_RATE = 1
HONEYCOMB_API_KEY = "HONEYCOMB_API_KEY"
HONEYCOMB_DATASET = "HONEYCOMB_DATASET"
HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS = "HONEYCOMB_ENABLE_LOCAL_VISUALIZATIONS"
HONEYCOMB_METRICS_APIKEY = "HONEYCOMB_METRICS_APIKEY"
HONEYCOMB_METRICS_DATASET = "HONEYCOMB_METRICS_DATASET"
HONEYCOMB_TRACES_APIKEY = "HONEYCOMB_TRACES_APIKEY"

# Errors and Warnings
INVALID_DEBUG_ERROR = "Unable to parse DEBUG environment variable. " + \
"Defaulting to False."
INVALID_INSECURE_ERROR = "Unable to parse " + \
Expand Down Expand Up @@ -55,8 +64,6 @@
IGNORED_DATASET_ERROR = "Dataset is ignored in favor of service name."
# not currently supported in OTel SDK, open PR:
# https://github.com/open-telemetry/opentelemetry-specification/issues/1901
OTEL_SERVICE_VERSION = "OTEL_SERVICE_VERSION"
SAMPLE_RATE = "SAMPLE_RATE"

log_levels = {
"NOTSET": logging.NOTSET,
Expand All @@ -78,18 +85,21 @@
_logger = logging.getLogger(__name__)


def is_classic(apikey: str):
def is_classic(apikey: str) -> bool:
"""
Determines whether the passed in API key is a classic API key or not.
Modern API keys have 22 or 23 characters.
Classic API keys have 32 characters.
Returns:
bool: true if the api key is a classic key, false if not
"""
return apikey and len(apikey) == 32


def parse_bool(environment_variable: str,
default_value: bool,
error_message: str):
error_message: str) -> bool:
"""
Attempts to parse the provided environment variable into a bool. If it
does not exist or fails parse, the default value is returned instead.
Expand All @@ -114,7 +124,7 @@ def parse_bool(environment_variable: str,
def parse_int(environment_variable: str,
param: int,
default_value: int,
error_message: str):
error_message: str) -> int:
"""
Attempts to parse the provided environment variable into an int. If it
does not exist or fails parse, the default value is returned instead.
Expand All @@ -141,7 +151,7 @@ def parse_int(environment_variable: str,
return default_value


def _append_traces_path(protocol: str, endpoint: str):
def _append_traces_path(protocol: str, endpoint: str) -> str:
"""
Appends the OTLP traces HTTP path '/v1/traces' to the endpoint if the
protocol is http/protobuf.
Expand All @@ -154,7 +164,7 @@ def _append_traces_path(protocol: str, endpoint: str):
return endpoint


def _append_metrics_path(protocol: str, endpoint: str):
def _append_metrics_path(protocol: str, endpoint: str) -> str:
"""
Appends the OTLP metrics HTTP path '/v1/metrics' to the endpoint if the
protocol is http/protobuf.
Expand All @@ -172,8 +182,14 @@ class HoneycombOptions:
"""
Honeycomb Options used to configure the OpenTelemetry SDK.
Setting the debug flag enables verbose logging and sets the OTEL_LOG_LEVEL
to DEBUG.
Setting the debug flag TRUE enables verbose logging and sets the
OTEL_LOG_LEVEL to DEBUG.
An option set as an environment variable will override any existing
options declared as parameter variables, if neither are present it
will fall back to the default value.
Defaults are declared at the top of this file, i.e. DEFAULT_SAMPLE_RATE = 1
"""
traces_apikey = None
metrics_apikey = None
Expand Down Expand Up @@ -356,13 +372,13 @@ def __init__(
INVALID_LOCAL_VIS_ERROR
)

def get_traces_endpoint(self):
def get_traces_endpoint(self) -> str:
"""
Returns the OTLP traces endpoint to send spans to.
"""
return self.traces_endpoint

def get_metrics_endpoint(self):
def get_metrics_endpoint(self) -> str:
"""
Returns the OTLP metrics endpoint to send metrics to.
"""
Expand Down
27 changes: 24 additions & 3 deletions src/honeycomb/opentelemetry/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,34 @@
def configure_sampler(
options: HoneycombOptions = HoneycombOptions(),
):
"""Configures and returns an OpenTelemetry Sampler that is
configured based on the sample_rate determined in HoneycombOptions.
The configuration initializes a DeterministicSampler with
an inner sampler of either DefaultOn (1), Default Off (0),
or a TraceIdRatio as 1/N.
Each of these samplers is ParentBased, meaning it respects
its parent span's sampling decision.
Args:
options (HoneycombOptions): the HoneycombOptins containing
sample_rate used to configure the deterministic sampler.
Returns:
DeterministicSampler: the configured Sampler based on sample_rate
"""
return DeterministicSampler(options.sample_rate)


class DeterministicSampler(Sampler):
"""
Custom samplers can be created by subclassing Sampler and implementing
Sampler.should_sample as well as Sampler.get_description.
"""Implementation of :class:`Sampler` that uses an inner sampler
of either DefaultOn (1), Default Off (0), or a TraceIdRatio as 1/N
to determine a SamplingResult and SamplingDecision for a given span
in a trace. We append a SampleRate attribute to the span with the
given sample rate.
Note: Each of these samplers is ParentBased, meaning it respects
its parent span's sampling decision.
"""

def __init__(self, rate: int):
Expand Down
7 changes: 5 additions & 2 deletions src/honeycomb/opentelemetry/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
from honeycomb.opentelemetry.baggage import BaggageSpanProcessor


def create_tracer_provider(options: HoneycombOptions, resource: Resource):
def create_tracer_provider(
options: HoneycombOptions,
resource: Resource
) -> TracerProvider:
"""
Configures and returns a new TracerProvider to send traces telemetry.
Expand All @@ -26,7 +29,7 @@ def create_tracer_provider(options: HoneycombOptions, resource: Resource):
resource (Resource): the resource to use with the new tracer provider
Returns:
MeterProvider: the new tracer provider
TracerProvider: the new tracer provider
"""
if options.traces_exporter_protocol == "grpc":
exporter = GRPCSpanExporter(
Expand Down
7 changes: 4 additions & 3 deletions tests/test_distro.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import platform

from honeycomb.opentelemetry.distro import configure_opentelemetry
from honeycomb.opentelemetry.options import HoneycombOptions
from honeycomb.opentelemetry.version import __version__
from opentelemetry.metrics import get_meter_provider
from opentelemetry.trace import get_tracer_provider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
Expand All @@ -11,6 +8,10 @@
OTLPSpanExporter as GRPCSpanExporter
)

from honeycomb.opentelemetry.distro import configure_opentelemetry
from honeycomb.opentelemetry.options import HoneycombOptions
from honeycomb.opentelemetry.version import __version__


def test_distro_configure_defaults():
configure_opentelemetry()
Expand Down
10 changes: 8 additions & 2 deletions tests/test_local_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,30 @@ def _check_exporter_can_export_spans_successfully(exporter: SpanExporter):
assert result == SpanExportResult.SUCCESS


def test_exporter_formats_correct_url(requests_mock):
def test_exporter_formats_correct_url_and_in_stdout(requests_mock, capsys):
requests_mock.get("https://api.honeycomb.io/1/auth",
json={"environment": {"slug": "my-env"}, "team": {"slug": "my-team"}})
exporter = LocalTraceLinkSpanExporter(
service_name="my-service", apikey=APIKEY)
url = exporter._build_url(TRACE_ID)
assert url == "https://ui.honeycomb.io/my-team/environments/my-env/datasets/my-service/trace?trace_id=a59c68a6de76d5e642bdc9a7641ae5f0"
_check_exporter_can_export_spans_successfully(exporter)
# ensure the link is in stdout
captured = capsys.readouterr()
assert captured.out == f'Honeycomb link: {url}\n'


def test_exporter_formats_correct_url_classic(requests_mock):
def test_exporter_formats_correct_url_classic_and_in_stdout(requests_mock, capsys):
requests_mock.get("https://api.honeycomb.io/1/auth",
json={"team": {"slug": "my-team"}})
exporter = LocalTraceLinkSpanExporter(
service_name="my-service", apikey=CLASSIC_APIKEY)
url = exporter._build_url(TRACE_ID)
assert url == "https://ui.honeycomb.io/my-team/datasets/my-service/trace?trace_id=a59c68a6de76d5e642bdc9a7641ae5f0"
_check_exporter_can_export_spans_successfully(exporter)
# ensure the link is in stdout
captured = capsys.readouterr()
assert captured.out == f'Honeycomb link: {url}\n'


def test_exporter_without_apikey_does_not_build_url():
Expand Down
3 changes: 2 additions & 1 deletion tests/test_metrics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from opentelemetry.sdk.metrics import MeterProvider

from honeycomb.opentelemetry.options import HoneycombOptions
from honeycomb.opentelemetry.resource import create_resource
from honeycomb.opentelemetry.metrics import create_meter_provider
from opentelemetry.sdk.metrics import MeterProvider


def test_returns_meter_provider():
Expand Down

0 comments on commit 90fbf88

Please sign in to comment.