|
21 | 21 | import time
|
22 | 22 | import urllib.error
|
23 | 23 | import urllib.request
|
| 24 | +from pathlib import Path |
24 | 25 | from typing import Sequence
|
25 | 26 |
|
26 | 27 | # FIXME: single-file Python package can't be marked as py.typed
|
|
58 | 59 |
|
59 | 60 |
|
60 | 61 | # 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 |
62 | 63 | logger.addHandler(logging.StreamHandler())
|
63 | 64 |
|
64 | 65 |
|
65 | 66 | class ProxySpanExporter(SpanExporter):
|
66 | 67 | settings: tuple[str | None, str | None] = (None, None)
|
67 | 68 | cache: dict[str | None, ssl.SSLContext]
|
68 | 69 |
|
69 |
| - def __init__(self, buffer_path: str): |
| 70 | + def __init__(self, buffer_path: Path | str): |
70 | 71 | self.buffer = ops._tracing.buffer.Buffer(buffer_path)
|
71 | 72 | self.lock = threading.Lock()
|
72 | 73 | self.cache = {}
|
@@ -119,6 +120,11 @@ def ssl_context(self, ca: str | None) -> ssl.SSLContext:
|
119 | 120 | def _ssl_context(self, ca: str | None) -> ssl.SSLContext:
|
120 | 121 | # FIXME: What should our protocol range be?
|
121 | 122 | # 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)? |
122 | 128 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
123 | 129 | # FIXME: we should probably allow ca=None
|
124 | 130 | # 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:
|
165 | 171 | # if it's the latter, the response test/JSON is helpful
|
166 | 172 | resp = e.fp.read()
|
167 | 173 | print('FIXME', e.code, str(resp)[:1000])
|
168 |
| - except (urllib.error.URLError, TimeoutError, ssl.SSLError): |
| 174 | + except OSError: |
| 175 | + # URLError, TimeoutError, SSLError, socket.error |
169 | 176 | pass
|
170 | 177 | except Exception:
|
171 | 178 | logger.exception('Failed to send telemetry out')
|
@@ -200,62 +207,21 @@ def setup_tracing(charm_class_name: str) -> None:
|
200 | 207 | global _exporter
|
201 | 208 | # FIXME would it be better to pass Juju context explicitly?
|
202 | 209 | 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) |
205 | 212 |
|
206 | 213 | resource = Resource.create(
|
207 | 214 | 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 |
239 | 216 | '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, |
251 | 221 | }
|
252 | 222 | )
|
253 | 223 | 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) |
259 | 225 | span_processor = BatchSpanProcessor(_exporter)
|
260 | 226 | provider.add_span_processor(span_processor)
|
261 | 227 | set_tracer_provider(provider)
|
|
0 commit comments