diff --git a/.circleci/config.yml b/.circleci/config.yml index 1bf5f781d..87874002b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -454,6 +454,7 @@ workflows: only: - master - production + - enable-otel - install-node-packages: requires: - checkout-to-workspace @@ -462,6 +463,7 @@ workflows: only: - master - production + - enable-otel # staging - build-front: @@ -473,6 +475,7 @@ workflows: branches: only: - master + - enable-otel - deploy: name: deploy-staging context: STAGING @@ -483,6 +486,7 @@ workflows: branches: only: - master + - enable-otel - deploy-api: name: deploy-api-staging context: STAGING diff --git a/back/boxtribute_server/app.py b/back/boxtribute_server/app.py index aa4a31ae0..da4090a5f 100644 --- a/back/boxtribute_server/app.py +++ b/back/boxtribute_server/app.py @@ -9,6 +9,7 @@ from sentry_sdk.integrations.flask import FlaskIntegration from .db import create_db_interface, db +from .utils import in_staging_environment def create_app(): @@ -76,6 +77,7 @@ def before_sentry_send(event, hint): # pragma: no cover ) app = create_app() + setup_opentelemetry(app) configure_app( app, *blueprints, @@ -91,3 +93,43 @@ def before_sentry_send(event, hint): # pragma: no cover replica_socket=os.getenv("MYSQL_REPLICA_SOCKET"), ) return app + + +def setup_opentelemetry(app): + if not in_staging_environment(): + return + + # https://cloud.google.com/stackdriver/docs/instrumentation/choose-approach#app_engine + # https://cloud.google.com/trace/docs/setup/python-ot + # https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/blob/1f1775886d7314b113acd322633afb278f875687/samples/instrumentation-quickstart/setup_opentelemetry.py + # No permission for trace.googleapis.com + from opentelemetry import trace + from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter + from opentelemetry.propagate import set_global_textmap + from opentelemetry.propagators.cloud_trace_propagator import ( + CloudTraceFormatPropagator, + ) + from opentelemetry.sdk.resources import SERVICE_INSTANCE_ID, Resource + + # from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + resource = Resource.create( + attributes={ + # Use the PID as the service.instance.id to avoid duplicate timeseries + # from different Gunicorn worker processes. + SERVICE_INSTANCE_ID: f"worker-{os.getpid()}", + } + ) + + provider = TracerProvider(resource=resource) + processor = BatchSpanProcessor(CloudTraceSpanExporter()) + provider.add_span_processor(processor) + + trace.set_tracer_provider(provider) + set_global_textmap(CloudTraceFormatPropagator()) + + from opentelemetry.instrumentation.flask import FlaskInstrumentor + + FlaskInstrumentor().instrument_app(app) diff --git a/back/requirements-deploy.txt b/back/requirements-deploy.txt index 9c97e00f7..19941da7a 100644 --- a/back/requirements-deploy.txt +++ b/back/requirements-deploy.txt @@ -1 +1,5 @@ google-cloud-logging==3.11.3 +opentelemetry-sdk==1.27.0 +opentelemetry-instrumentation-flask==0.48b0 +opentelemetry-exporter-gcp-trace==1.7.0 +opentelemetry-propagator-gcp==1.7.0