otlp¶
OTLP Provider and Requirer Library.
OTLP is a general-purpose telemetry data delivery protocol defined by the design goals and requirements of the project.
This library provides a way for charms to share OTLP endpoint information, and associated Loki and Prometheus rules. This library requires that the charm’s workload already supports sending/receiving OTLP data and focuses on communicating those endpoints.
Getting Started¶
Provider Side (Charms offering OTLP endpoints)¶
To provide OTLP endpoints, use the OtlpProvider class. Configure and send endpoints with the
add_endpoint and publish() methods:
from charmlibs.interfaces.otlp import OtlpProvider
class MyOtlpServer(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.framework.observe(self.on.ingress_ready, self._publish_endpoints)
def _publish_endpoints(self, event):
OtlpProvider(self).add_endpoint(
protocol="grpc",
endpoint="https://my-app.ingress:4317",
telemetries=["logs", "metrics"],
).add_endpoint(
protocol="http",
endpoint="https://my-app.ingress:4318",
telemetries=["traces"],
).publish()
Providers add endpoints explicitly; nothing is auto-published by default. Make sure to add endpoints and publish them after the charm’s endpoint details have been updated e.g., ingress or TLS changes.
The OtlpProvider also consumes rules from related OtlpRequirer charms, which can be retrieved with
the rules property:
from charmlibs.interfaces.otlp import OtlpProvider
class MyOtlpServer(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.framework.observe(self.on.update_status, self._access_rules)
def _access_rules(self, event):
for relation_id, rule_store in OtlpProvider(self).rules.items():
pass # do something with rule_store.logql and/or rule_store.promql
Requirer Side (Charms requiring OTLP endpoints)¶
To consume OTLP endpoints, use the OtlpRequirer class. The OTLP sender may only support a
subset of protocols and telemetries, which can be configured at instantiation:
from charmlibs.interfaces.otlp import OtlpRequirer
class MyOtlpSender(CharmBase):
def __init__(self, framework: ops.Framework):
super().__init__(framework)
self.framework.observe(self.on.update_status, self._access_endpoints)
def _access_endpoints(self, _: ops.EventBase):
OtlpRequirer(
self,
protocols=["grpc", "http"],
telemetries=["logs", "metrics", "traces"],
).endpoints
Given the defined, supported protocols and telemetries, the OtlpRequirer will filter out unsupported endpoints and prune unsupported telemetries. After filtering, requirer selection condenses the list to a single endpoint per relation. Endpoints with modern protocols are favoured over legacy ones. That means an endpoint supporting the gRPC protocol will be selected over one supporting HTTP. Unknown protocols will receive the lowest priority.
The OtlpRequirer also publishes user-defined and generic (applied to all charms) rules to related
OtlpProvider charms with the publish() method:
from charmlibs.interfaces.otlp import OtlpRequirer, RulesStore
class MyOtlpSender(CharmBase):
def __init__(self, framework: ops.Framework):
super().__init__(framework)
self.framework.observe(self.on.update_status, self._publish_rules)
def _publish_rules(self, _: ops.EventBase):
rules = (
RuleStore(JujuTopology.from_charm(self))
.add_logql(SINGLE_LOGQL_ALERT, group_name='test_logql_alert')
.add_promql(SINGLE_PROMQL_RECORD, group_name='test_promql_record')
.add_logql(OFFICIAL_LOGQL_RULES)
)
OtlpRequirer(self, rules=rules).publish()
Generic rules are sourced from cosl.rules.generic_alert_groups.
If the charm is an aggregator e.g., opentelemetry-collector, the type of generic rules to be
injected into the charm’s RuleStore should reflect that. This is configurable by setting the
aggregator_peer_relation_name with the name of the charm’s peer relation:
OtlpRequirer(..., aggregator_peer_relation_name="my-peers").publish()
Relation Data Format¶
The OtlpProvider offers a list of OTLP endpoints in the relation databag under the endpoints
key. Each provider may offer any number of OTLP endpoints:
"endpoints": [
{
"protocol": "grpc",
"endpoint": "https://my-app.ingress:4317",
"telemetries": ["logs", "metrics"],
},
{
"protocol": "http",
"endpoint": "https://my-app.ingress:4318",
"telemetries": ["traces"],
},
]
The OtlpRequirer offers compressed rules in the relation databag under the rules key, which
have this structure when decompressed:
"rules": {
"promql": {...},
"logql": {...},
}
The charm’s metadata is included under the metadata key for the provider to know the source of
the rules:
"metadata": {
"model": "my-model",
"model_uuid": "f4d59020-c8e7-4053-8044-a2c1e5591c7f",
"application": "my-app",
"charm_name": "my-charm",
"unit": "my-charm/0",
}
- class OtlpEndpoint( )¶
Bases:
BaseModelA pydantic model for a single OTLP endpoint.
- class OtlpProvider(charm: CharmBase, relation_name: str = 'receive-otlp')¶
Bases:
objectA class for publishing all supported OTLP endpoints.
- Parameters:
charm – The charm instance.
relation_name – The name of the relation to use.
- add_endpoint(
- protocol: Literal['http', 'grpc'],
- endpoint: str,
- telemetries: Sequence[Literal['logs', 'metrics', 'traces']],
- insecure: bool = False,
Add an OtlpEndpoint to the list of endpoints to publish.
- property rules: dict[int, RuleStore]¶
Fetch rules for all relations of the desired query and rule types.
This method returns all rules of varying query and rule types, provided by related OTLP requirer charms. This method ensures rules:
have labels from the charm’s Juju topology.
have expression labels from the charm’s Juju topology.
are validated using CosTool.
- Returns:
a mapping of relation ID to a RuleStore object.
- class OtlpRequirer(
- charm: CharmBase,
- relation_name: str = 'send-otlp',
- protocols: Sequence[Literal['http', 'grpc']] | None = None,
- telemetries: Sequence[Literal['logs', 'metrics', 'traces']] | None = None,
- *,
- aggregator_peer_relation_name: str | None = None,
- rules: RuleStore | None = None,
Bases:
objectA class for consuming OTLP endpoints.
- Parameters:
charm – The charm instance.
relation_name – The name of the relation to use.
protocols – The protocols to filter for in the provider’s OTLP endpoints.
telemetries – The telemetries to filter for in the provider’s OTLP endpoints.
aggregator_peer_relation_name – Name of the peers relation of this charm. This should only be set IFF the charm is an aggregator AND it has a peer relation with this name. When provided, generic aggregator rules are used instead of application-level rules.
rules – Rules of different types e.g., logql or promql, that the requirer will publish for the provider.
- publish()¶
Triggers programmatically the update of the relation data.
- These rule sources are included when publishing:
Any rules provided at the instantiation of this class.
Generic (not specific to any charm) PromQL rules.
- property endpoints: dict[int, OtlpEndpoint]¶
Return a mapping of relation ID to OTLP endpoint.
For each remote’s list of OtlpEndpoints, the requirer filters out unsupported endpoints and telemetries. If multiple compatible endpoints remain, the requirer prefers newer protocols (grpc over http). Unknown protocols are treated as the lowest priority. This allows providers to expose multiple endpoints with different protocol and telemetry combinations while the requirer selects the best match.
- class RuleStore(topology: JujuTopology)¶
Bases:
objectAn API for users to provide rules of different types to the OtlpRequirer.
- topology: JujuTopology¶
- logql: Rules¶
- promql: Rules¶
- __post_init__()¶
- add_logql(
- rule_dict: OfficialRuleFileFormat | AlertingRuleFormat | RecordingRuleFormat,
- *,
- group_name: str | None = None,
- group_name_prefix: str | None = None,
Add rules from dict to the existing LogQL ruleset.
- Parameters:
rule_dict – a single-rule or official-rule YAML dict
group_name – a custom group name, used only if the new rule is of single-rule format
group_name_prefix – a custom group name prefix, used only if the new rule is of single-rule format
- add_logql_path( ) RuleStore¶
Add LogQL rules from a dir path.
All rules from files are aggregated into a data structure representing a single rule file. All group names are augmented with juju topology.
- Parameters:
dir_path – either a rules file or a dir of rules files.
recursive – whether to read files recursively or not (no impact if path is a file).
- add_promql(
- rule_dict: OfficialRuleFileFormat | AlertingRuleFormat | RecordingRuleFormat,
- *,
- group_name: str | None = None,
- group_name_prefix: str | None = None,
Add rules from dict to the existing PromQL ruleset.
- Parameters:
rule_dict – a single-rule or official-rule YAML dict
group_name – a custom group name, used only if the new rule is of single-rule format
group_name_prefix – a custom group name prefix, used only if the new rule is of single-rule format
- add_promql_path( ) RuleStore¶
Add PromQL rules from a dir path.
All rules from files are aggregated into a data structure representing a single rule file. All group names are augmented with juju topology.
- Parameters:
dir_path – either a rules file or a dir of rules files.
recursive – whether to read files recursively or not (no impact if path is a file).