From 661f78ef7231ca5bf55fc54cd2bf6b5c3cdf98d3 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Wed, 17 Aug 2022 11:14:25 +0100 Subject: [PATCH] Switch to opentelemetry Opencensus has been replaced, there are some API changes. --- ai_web_common/flask_ai/app.py | 8 ++++- ai_web_common/flask_ai/test/__init__.py | 1 + .../flask_ai/test/test_simple_app.py | 29 ++++++++++++++++ ai_web_common/flask_ai/tracing.py | 33 +++++++++++-------- ai_web_common/rpc/core.py | 32 +++++++++--------- setup.py | 6 ++-- tox.ini | 1 + 7 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 ai_web_common/flask_ai/test/__init__.py create mode 100644 ai_web_common/flask_ai/test/test_simple_app.py diff --git a/ai_web_common/flask_ai/app.py b/ai_web_common/flask_ai/app.py index 0f17537..c92bac3 100644 --- a/ai_web_common/flask_ai/app.py +++ b/ai_web_common/flask_ai/app.py @@ -2,6 +2,7 @@ import hashlib import pkg_resources from flask import request, session, g from flask_talisman import DENY +from opentelemetry.instrumentation.flask import FlaskInstrumentor from .tracing import setup_tracing from whitenoise import WhiteNoise @@ -14,6 +15,9 @@ DEFAULT_LANGUAGES = [ ] +_telemetry_instrumentor = FlaskInstrumentor() + + def init_app(app, talisman): """Initialize the Flask application.""" @@ -44,7 +48,9 @@ def init_app(app, talisman): app.config['SUPPORTED_LANGUAGES_ISO'] = [ x[0] for x in app.config['SUPPORTED_LANGUAGES']] - setup_tracing(app, app.import_name) + _telemetry_instrumentor.instrument_app(app) + if setup_tracing(app.import_name): + app.logger.info('configured request tracing') # Autodetect language as best as we can, by retrieving it in order # from either: diff --git a/ai_web_common/flask_ai/test/__init__.py b/ai_web_common/flask_ai/test/__init__.py new file mode 100644 index 0000000..db462ff --- /dev/null +++ b/ai_web_common/flask_ai/test/__init__.py @@ -0,0 +1 @@ +from flask_testing import TestCase diff --git a/ai_web_common/flask_ai/test/test_simple_app.py b/ai_web_common/flask_ai/test/test_simple_app.py new file mode 100644 index 0000000..c984e76 --- /dev/null +++ b/ai_web_common/flask_ai/test/test_simple_app.py @@ -0,0 +1,29 @@ +from ai_web_common.flask_ai.app import init_app +from ai_web_common.flask_ai.test import TestCase +from flask import Flask, make_response +from flask_talisman import Talisman + + +app = Flask(__name__) +talisman = Talisman() + + +@app.route('/') +def index(): + return make_response('ok') + + +class TestSimpleApp(TestCase): + + def create_app(self): + app.config.update({ + 'TESTING': True, + 'DEBUG': False, + }) + init_app(app, talisman) + return app + + def test_request(self): + r = self.client.get('/') + self.assertEquals(200, r.status_code) + self.assertEquals('ok', r.data.decode('utf-8')) diff --git a/ai_web_common/flask_ai/tracing.py b/ai_web_common/flask_ai/tracing.py index 693b7a0..4af9a64 100644 --- a/ai_web_common/flask_ai/tracing.py +++ b/ai_web_common/flask_ai/tracing.py @@ -1,26 +1,33 @@ import json import os -from urllib.parse import urlsplit -from opencensus.trace import config_integration + +from opentelemetry import trace +from opentelemetry.exporter.zipkin.proto.http import ZipkinExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.resources import SERVICE_NAME, Resource + tracing_config_file = os.getenv('TRACING_CONFIG', '/etc/tracing/client.conf') -def setup_tracing(app, service_name): +def setup_tracing(service_name): try: with open(tracing_config_file) as fd: config = json.load(fd) except (OSError, IOError): - return + return False if 'report_url' not in config: - return + return False + + resource = Resource(attributes={ + SERVICE_NAME: service_name, + }) - # Patch the 'requests' module so that HTTP clients propagate the - # trace ID. Note that this is mostly useless, as the requests - # integration only patches the high-level API (get, post) and does - # not touch the low-level Session interface that we're using for - # our own RPC calls. - config_integration.trace_integrations(['requests']) + zipkin_exporter = ZipkinExporter(endpoint=config['report_url']) - app.logger.info('configured request tracing with service=%s', - service_name) + provider = TracerProvider(resource=resource) + processor = BatchSpanProcessor(zipkin_exporter) + provider.add_span_processor(processor) + trace.set_tracer_provider(provider) + return True diff --git a/ai_web_common/rpc/core.py b/ai_web_common/rpc/core.py index 9d8b455..ba1bb36 100644 --- a/ai_web_common/rpc/core.py +++ b/ai_web_common/rpc/core.py @@ -8,9 +8,11 @@ import threading import time import urllib3 import urllib3.util -from opencensus.trace import attributes_helper -from opencensus.trace import execution_context -from opencensus.trace import span as span_module + +from opentelemetry import trace +from opentelemetry.trace.status import Status +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.instrumentation.utils import http_status_to_status_code DNS_CACHE_TTL = 60 @@ -20,11 +22,6 @@ DEFAULT_MAX_TIMEOUT = 30 DEFAULT_MAX_BACKOFF_INTERVAL = 3 -HTTP_URL = attributes_helper.COMMON_ATTRIBUTES['HTTP_URL'] -HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES['HTTP_HOST'] -HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES['HTTP_STATUS_CODE'] - - class StatusError(Exception): def __init__(self, url, resp, extra_msg=None): @@ -229,11 +226,16 @@ class ClientStub(): target_addr, target_port = targets.next() - tracer = execution_context.get_opencensus_tracer() - with tracer.span(name=f'[client-rpc] {self.NAME}') as span: - span.span_kind = span_module.SpanKind.CLIENT - tracer.add_attribute_to_current_span(HTTP_URL, parsed_url.url) - tracer.add_attribute_to_current_span(HTTP_HOST, target_addr) + tracer = trace.get_tracer(self.NAME) + with tracer.start_as_current_span( + f'[client-rpc] {self.NAME}', + kind=trace.SpanKind.CLIENT, + attributes={ + SpanAttributes.HTTP_URL: parsed_url.url, + SpanAttributes.HTTP_HOST: target_addr, + SpanAttributes.HTTP_METHOD: 'POST', + }, + ) as span: pool_args = { 'cls': urllib3.HTTPConnectionPool, @@ -279,8 +281,8 @@ class ClientStub(): retries=False, ) try: - tracer.add_attribute_to_current_span( - HTTP_STATUS_CODE, str(resp.status)) + span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, resp.status) + span.set_status(Status(http_status_to_status_code(resp.status))) if resp.status == 429 or resp.status > 500: raise RetriableStatusError(parsed_url, resp) diff --git a/setup.py b/setup.py index 1f9ab2a..07e457a 100755 --- a/setup.py +++ b/setup.py @@ -14,9 +14,9 @@ setup( "backoff", "Flask", "flask-talisman", - "opencensus", - "opencensus-ext-zipkin", - "opencensus-ext-requests", + "opentelemetry-distro", + "opentelemetry-exporter-zipkin-proto-http", + "opentelemetry-instrumentation-flask", "sso", "urllib3", "whitenoise", diff --git a/tox.ini b/tox.ini index 2f7f292..d01873c 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py3 deps= git+https://git.autistici.org/ai/sso.git#egg=sso&subdirectory=src/python cryptography + Flask-Testing coverage nose mock -- GitLab