Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update charm tracing libs + add support for exporting traces via HTTPS #465

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
594 changes: 594 additions & 0 deletions lib/charms/observability_libs/v1/cert_handler.py

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions lib/charms/tempo_k8s/v1/charm_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,14 @@ def my_tracing_endpoint(self) -> Optional[str]:
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import Span, TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.trace import INVALID_SPAN, Tracer
from opentelemetry.trace import get_current_span as otlp_get_current_span
from opentelemetry.trace import (
INVALID_SPAN,
Tracer,
get_tracer,
get_tracer_provider,
set_span_in_context,
set_tracer_provider,
)
from opentelemetry.trace import get_current_span as otlp_get_current_span
from ops.charm import CharmBase
from ops.framework import Framework

Expand All @@ -147,7 +146,7 @@ def my_tracing_endpoint(self) -> Optional[str]:
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version

LIBPATCH = 8
LIBPATCH = 10

PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]

Expand Down Expand Up @@ -295,16 +294,17 @@ def wrap_init(self: CharmBase, framework: Framework, *args, **kwargs):
# self.handle = Handle(None, self.handle_kind, None)

original_event_context = framework._event_context
# default service name isn't just app name because it could conflict with the workload service name
_service_name = service_name or f"{self.app.name}-charm"

_service_name = service_name or self.app.name

unit_name = self.unit.name
resource = Resource.create(
attributes={
"service.name": _service_name,
"compose_service": _service_name,
"charm_type": type(self).__name__,
# juju topology
"juju_unit": self.unit.name,
"juju_unit": unit_name,
"juju_application": self.app.name,
"juju_model": self.model.name,
"juju_model_uuid": self.model.uuid,
Expand Down Expand Up @@ -342,16 +342,18 @@ def wrap_init(self: CharmBase, framework: Framework, *args, **kwargs):
_tracer = get_tracer(_service_name) # type: ignore
_tracer_token = tracer.set(_tracer)

dispatch_path = os.getenv("JUJU_DISPATCH_PATH", "")
dispatch_path = os.getenv("JUJU_DISPATCH_PATH", "") # something like hooks/install
event_name = dispatch_path.split("/")[1] if "/" in dispatch_path else dispatch_path
root_span_name = f"{unit_name}: {event_name} event"
span = _tracer.start_span(root_span_name, attributes={"juju.dispatch_path": dispatch_path})

# all these shenanigans are to work around the fact that the opentelemetry tracing API is built
# on the assumption that spans will be used as contextmanagers.
# Since we don't (as we need to close the span on framework.commit),
# we need to manually set the root span as current.
span = _tracer.start_span("charm exec", attributes={"juju.dispatch_path": dispatch_path})
ctx = set_span_in_context(span)

# log a trace id so we can look it up in tempo.
# log a trace id, so we can pick it up from the logs (and jhack) to look it up in tempo.
root_trace_id = hex(span.get_span_context().trace_id)[2:] # strip 0x prefix
logger.debug(f"Starting root trace with id={root_trace_id!r}.")

Expand Down
69 changes: 68 additions & 1 deletion lib/charms/tempo_k8s/v2/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(self, *args):
import enum
import json
import logging
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Expand All @@ -82,6 +83,7 @@ def __init__(self, *args):
Optional,
Sequence,
Tuple,
Union,
cast,
)

Expand All @@ -105,7 +107,7 @@ def __init__(self, *args):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 6
LIBPATCH = 7

PYDEPS = ["pydantic"]

Expand Down Expand Up @@ -921,3 +923,68 @@ def get_endpoint(

return None
return endpoint


def charm_tracing_config(
endpoint_requirer: TracingEndpointRequirer, cert_path: Optional[Union[Path, str]]
) -> Tuple[Optional[str], Optional[str]]:
"""Utility function to determine the charm_tracing config you will likely want.

If no endpoint is provided:
disable charm tracing.
If https endpoint is provided but cert_path is not found on disk:
disable charm tracing.
If https endpoint is provided and cert_path is None:
ERROR
Else:
proceed with charm tracing (with or without tls, as appropriate)

Usage:
If you are using charm_tracing >= v1.9:
>>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm
>>> from lib.charms.tempo_k8s.v2.tracing import charm_tracing_config
>>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path")
>>> class MyCharm(...):
>>> _cert_path = "/path/to/cert/on/charm/container.crt"
>>> def __init__(self, ...):
>>> self.tracing = TracingEndpointRequirer(...)
>>> self.my_endpoint, self.cert_path = charm_tracing_config(
... self.tracing, self._cert_path)

If you are using charm_tracing < v1.9:
>>> from lib.charms.tempo_k8s.v1.charm_tracing import trace_charm
>>> from lib.charms.tempo_k8s.v2.tracing import charm_tracing_config
>>> @trace_charm(tracing_endpoint="my_endpoint", cert_path="cert_path")
>>> class MyCharm(...):
>>> _cert_path = "/path/to/cert/on/charm/container.crt"
>>> def __init__(self, ...):
>>> self.tracing = TracingEndpointRequirer(...)
>>> self._my_endpoint, self._cert_path = charm_tracing_config(
... self.tracing, self._cert_path)
>>> @property
>>> def my_endpoint(self):
>>> return self._my_endpoint
>>> @property
>>> def cert_path(self):
>>> return self._cert_path

"""
if not endpoint_requirer.is_ready():
return None, None

endpoint = endpoint_requirer.get_endpoint("otlp_http")
if not endpoint:
return None, None

is_https = endpoint.startswith("https://")

if is_https:
if cert_path is None:
raise TracingError("Cannot send traces to an https endpoint without a certificate.")
elif not Path(cert_path).exists():
# if endpoint is https BUT we don't have a server_cert yet:
# disable charm tracing until we do to prevent tls errors
return None, None
return endpoint, str(cert_path)
else:
return endpoint, None
Loading
Loading