# Copyright 2025 Canonical Ltd.## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this# file except in compliance with the License. You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software distributed under# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF# ANY KIND, either express or implied. See the License for the specific language# governing permissions and limitations under the License."""The global implementation of the ops-tracing extension."""from__future__importannotationsimportpathlibfromtypingimportTYPE_CHECKINGfromopentelemetry.sdk.resourcesimportResourcefromopentelemetry.sdk.traceimportTracerProviderfromopentelemetry.sdk.trace.exportimportBatchSpanProcessorfromopentelemetry.traceimportget_tracer_provider,set_tracer_providerfromops._privateimportyamlifTYPE_CHECKING:fromops.jujucontextimport_JujuContextfrom._bufferimportDestinationfrom._exportimportBufferingSpanExporterBUFFER_FILENAME:str='.tracing-data.db'"""Name of the buffer file where the trace data is stored, next to .unit-state.db."""_exporter:BufferingSpanExporter|None=None"""A reference to the exporter that we passed to OpenTelemetry SDK at setup."""defsetup(juju_context:_JujuContext,charm_class_name:str)->None:"""Set up the tracing subsystem and configure OpenTelemetry. Args: juju_context: the context for this dispatch, for annotation charm_class_name: the name of the charm class, for annotation """app_name,unit_number=juju_context.unit_name.split('/',1)try:meta=yaml.safe_load((juju_context.charm_dir/'metadata.yaml').read_text())charmhub_charm_name=meta['name']exceptFileNotFoundError:charmhub_charm_name='[unknown]'resource=Resource.create(attributes={'service.namespace':juju_context.model_uuid,'service.namespace.name':juju_context.model_name,'service.name':app_name,'service.instance.id':unit_number,'charm':charmhub_charm_name,'charm_type':charm_class_name,'juju_model':juju_context.model_name,'juju_model_uuid':juju_context.model_uuid,'juju_application':app_name,'juju_unit':juju_context.unit_name,})set_tracer_provider(_create_provider(resource,juju_context.charm_dir))def_create_provider(resource:Resource,charm_dir:pathlib.Path)->TracerProvider:"""Create the OpenTelemetry tracer provider."""# Separate function so that it's easy to override in testsglobal_exporter_exporter=BufferingSpanExporter(charm_dir/BUFFER_FILENAME)span_processor=BatchSpanProcessor(_exporter)provider=TracerProvider(resource=resource)provider.add_span_processor(span_processor)returnprovider
[docs]defset_destination(url:str|None,ca:str|None)->None:"""Configure the destination service for trace data. Args: url: the URL of the telemetry service to send trace data to. An example could be ``http://localhost/v1/traces``. None or empty string disables sending out the data, which is still buffered. ca: the CA list (PEM bundle, a multi-line string), only used for HTTPS URLs. """ifurlandnoturl.startswith(('http://','https://')):raiseValueError('Only HTTP and HTTPS tracing destinations are supported.')config=Destination(url,ca)ifnot_exporter:# Perhaps our tracer provider was never set up.returnifconfig==_exporter.buffer.load_destination():return_exporter.buffer.save_destination(config)
defmark_observed()->None:"""Mark the trace data collected in this dispatch as higher priority."""ifnot_exporter:return_exporter.buffer.mark_observed()defshutdown()->None:"""Shutdown tracing, which is expected to flush the buffered data out."""provider=get_tracer_provider()ifisinstance(provider,TracerProvider):provider.shutdown()