Skip to content

Commit 049a5fc

Browse files
committed
settle on reousrce attributes, start on the docs
1 parent 2a55fbb commit 049a5fc

File tree

6 files changed

+73
-54
lines changed

6 files changed

+73
-54
lines changed

docs/howto/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Get started with charm testing <get-started-with-charm-testing>
2424
Write unit tests for a charm <write-scenario-tests-for-a-charm>
2525
Write integration tests for a charm <write-integration-tests-for-a-charm>
2626
Write legacy unit tests for a charm <write-unit-tests-for-a-charm>
27+
Trace the charm code <trace-the-charm-code>
2728
Turn a hooks-based charm into an ops charm <turn-a-hooks-based-charm-into-an-ops-charm>
2829
2930
```

docs/howto/trace-the-charm-code.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
(trace-the-charm-code)=
2+
# Trace the charm code
3+
4+
## Tracing from scratch
5+
6+
FIXME: write this up
7+
8+
- depend on `ops[tracing]`
9+
- remove charm\_tracing charm lib, if it's installed
10+
- observe the `SetupTracingEvent`
11+
12+
```py
13+
class YourCharm(ops.CharmBase):
14+
def __init__(self, framework: ops.Framework):
15+
super().__init__(framework)
16+
self.framework.observe(self.on.setup_tracing, self._on_setup_tracing)
17+
...
18+
19+
def _on_setup_tracing(self, event: ops.SetupTracingEvent) -> None:
20+
# FIXME must get this from some relation
21+
event.set_destination(url='http://localhost:4318/v1/traces')
22+
```
23+
24+
## Migrating from charm\_tracing
25+
26+
- depend on `ops[tracing]`
27+
- remove charm\_tracing charm lib, if it's installed
28+
- remove `@trace_charm` decorator
29+
- observe the `SetupTracingEvent`
30+
- instrument key functions in the charm
31+
32+
NOTE: charm\_tracing auto-instruments all public function on the class. `ops[tracing]` doesn't do that.
33+
34+
```py
35+
# FIXME example
36+
```

docs/reference/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ ops
99
pebble
1010
ops-testing
1111
ops-testing-harness
12+
ops-tracing
1213
```
1314

docs/reference/ops-tracing.rst

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.. _ops_tracing:
2+
3+
`ops[tracing]`, telemetry
4+
=========================
5+
6+
FIXME: write this up.
7+
8+
Open Telemetry resource attributes.
9+
10+
- ``service.namespace`` the UUID of the Juju model.
11+
- ``service.namespace.name`` the name of the Juju model.
12+
- ``service.name`` the application name, like ``user_db``.
13+
- ``service.instance.id`` the unit number, like ``0``.
14+
- ``service.charm`` the charm class name, like ``DbCharm``.

ops/_tracing/buffer.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import functools
1818
import logging
1919
import sqlite3
20+
from pathlib import Path
2021
from typing import Callable
2122

2223
from typing_extensions import ParamSpec, TypeVar
@@ -83,8 +84,8 @@ class Buffer:
8384
"""Marks that data from this dispatch invocation has been marked observed."""
8485
stored: int | None = None
8586

86-
def __init__(self, path: str):
87-
self.path = path
87+
def __init__(self, path: Path | str):
88+
self.path = str(path)
8889
self.ids = set()
8990
self._set_db_schema()
9091

ops/_tracing/export.py

+18-52
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import time
2222
import urllib.error
2323
import urllib.request
24+
from pathlib import Path
2425
from typing import Sequence
2526

2627
# FIXME: single-file Python package can't be marked as py.typed
@@ -58,15 +59,15 @@
5859

5960

6061
# NOTE: OTEL SDK suppresses errors while exporting data
61-
# TODO: decide if we need to remove this before going to prod
62+
# FIXME: decide if we need to remove this before going to prod
6263
logger.addHandler(logging.StreamHandler())
6364

6465

6566
class ProxySpanExporter(SpanExporter):
6667
settings: tuple[str | None, str | None] = (None, None)
6768
cache: dict[str | None, ssl.SSLContext]
6869

69-
def __init__(self, buffer_path: str):
70+
def __init__(self, buffer_path: Path | str):
7071
self.buffer = ops._tracing.buffer.Buffer(buffer_path)
7172
self.lock = threading.Lock()
7273
self.cache = {}
@@ -119,6 +120,11 @@ def ssl_context(self, ca: str | None) -> ssl.SSLContext:
119120
def _ssl_context(self, ca: str | None) -> ssl.SSLContext:
120121
# FIXME: What should our protocol range be?
121122
# this means TLS {v1, v1.1, v1.2}
123+
# Do we need to set v1_2 or v1_3 explicitly?
124+
# Py 3.10 update the secure cipher list
125+
# Do we need to set ciphers explicitly for Py 3.8?
126+
# We'd match https://wiki.mozilla.org/Security/Server_Side_TLS "Modern".
127+
# FIXME SSLContext(args) or create_default_context(args)?
122128
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
123129
# FIXME: we should probably allow ca=None
124130
# and then we'd pick up system or certifi certs?
@@ -165,7 +171,8 @@ def do_export(self, buffered_id: int, data: bytes, mime: str) -> None:
165171
# if it's the latter, the response test/JSON is helpful
166172
resp = e.fp.read()
167173
print('FIXME', e.code, str(resp)[:1000])
168-
except (urllib.error.URLError, TimeoutError, ssl.SSLError):
174+
except OSError:
175+
# URLError, TimeoutError, SSLError, socket.error
169176
pass
170177
except Exception:
171178
logger.exception('Failed to send telemetry out')
@@ -200,62 +207,21 @@ def setup_tracing(charm_class_name: str) -> None:
200207
global _exporter
201208
# FIXME would it be better to pass Juju context explicitly?
202209
juju_context = ops.jujucontext._JujuContext.from_dict(os.environ)
203-
app_name = '' if juju_context.unit_name is None else juju_context.unit_name.split('/')[0]
204-
service_name = f'{app_name}-charm' # only one COS charm sets custom value
210+
# FIXME is it ever possible for unit_name to be unset (empty)?
211+
app_name, unit_number = juju_context.unit_name.split('/', 1)
205212

206213
resource = Resource.create(
207214
attributes={
208-
# https://opentelemetry.io/docs/languages/sdk-configuration/general/
209-
# https://github.com/open-telemetry/semantic-conventions/tree/main/docs/resource#semantic-attributes-with-dedicated-environment-variable
210-
#
211-
# TODO:
212-
# service.namespace: model uuid, because that's globally unique
213-
# (find some field): model name, human-readable
214-
# service.name: unit name (?)
215-
# service.instance.id: /etc/machine-id (set for vms, empty for k8s)
216-
# (these follow Juju topology by Simme)
217-
#
218-
# alternatively, one could argue that:
219-
# service.name: app name
220-
# (some field): unit name
221-
# - could abuse instance id, though that's not what it's for
222-
#
223-
# alternatively still:
224-
# service.namespace: model uuid
225-
# service.name: charm name (perhaps not app name?)
226-
# service.instance.id: unit name?
227-
#
228-
# OTEL defines some standard-ish attributes:
229-
# service.name required
230-
# service.instance.id recommended
231-
# service.namespace recommended -- maybe model name?
232-
# service.version recommended
233-
#
234-
# FIXME: this is quite important, I think
235-
# because we're setting the stage for future Juju telemetry
236-
# ideally we'd agree what these fields mean for the end users
237-
# to have a consistent view over their deployment.
238-
'service.name': charm_class_name,
215+
# TODO: document these
239216
'service.namespace': juju_context.model_uuid,
240-
'service.instance.id': juju_context.unit_name,
241-
# charm lib tags
242-
'compose_service': service_name, # FIXME why is this copy needed?
243-
'charm_type': charm_class_name,
244-
# these may not be needed if included in service.xxx
245-
# Following same attribute names as charm_tracing lib
246-
# FIXME: decide if it makes sense
247-
'juju_unit': juju_context.unit_name,
248-
'juju_application': app_name,
249-
'juju_model': juju_context.model_name,
250-
'juju_model_uuid': juju_context.model_uuid,
217+
'service.namespace.name': juju_context.model_name,
218+
'service.name': app_name,
219+
'service.instance.id': unit_number,
220+
'service.charm': charm_class_name,
251221
}
252222
)
253223
provider = TracerProvider(resource=resource)
254-
255-
# How
256-
257-
buffer_path = str(juju_context.charm_dir / BUFFER_FILE)
258-
_exporter = ProxySpanExporter(buffer_path)
224+
_exporter = ProxySpanExporter(juju_context.charm_dir / BUFFER_FILE)
259225
span_processor = BatchSpanProcessor(_exporter)
260226
provider.add_span_processor(span_processor)
261227
set_tracer_provider(provider)

0 commit comments

Comments
 (0)