From 8b4f93dd4e9b1c1bd06fd365d74acf1210372443 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Fri, 31 Dec 2021 03:46:21 -0800 Subject: [PATCH 1/7] Integrate open telemetry This allows us to export to anyone that takes OTLP, which is most of the major players, I think! Nifty! Kinda like the S3 config but for tracing, you can slot in any provider that supports it via environment variables This uses the Django instrumentation, which gets us a bunch of nifty stuff right out of the box. --- bookwyrm/wsgi.py | 10 ++++++++++ requirements.txt | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/bookwyrm/wsgi.py b/bookwyrm/wsgi.py index e670f7f1..800b40d2 100644 --- a/bookwyrm/wsgi.py +++ b/bookwyrm/wsgi.py @@ -10,9 +10,19 @@ https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ import os from environs import Env from django.core.wsgi import get_wsgi_application +from opentelemetry.instrumentation.django import DjangoInstrumentor +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor Env.read_env() os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bookwyrm.settings") +DjangoInstrumentor().instrument() +trace.set_tracer_provider(TracerProvider()) +trace.get_tracer_provider().add_span_processor( + BatchSpanProcessor(OTLPSpanExporter()) +) application = get_wsgi_application() diff --git a/requirements.txt b/requirements.txt index 98ffcc45..f37a675d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,7 @@ django-rename-app==0.1.2 pytz>=2021.1 boto3==1.17.88 django-storages==1.11.1 +opentelemetry-api +opentelemetry-sdk +opentelemetry-exporter-otlp-proto-grpc +opentelemetry-instrumentation-django From 7cb7063da5f9cca6666f8b7c5094ad2fd9980c17 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Fri, 31 Dec 2021 03:49:25 -0800 Subject: [PATCH 2/7] Add more route names, and format I think these show up in telemetry, and nicer names are nice --- bookwyrm/urls.py | 24 ++++++++++++++++++------ bookwyrm/wsgi.py | 4 +--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 7220b545..ce741e98 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -34,9 +34,11 @@ urlpatterns = [ TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), ), # federation endpoints - re_path(r"^inbox/?$", views.Inbox.as_view()), - re_path(rf"{LOCAL_USER_PATH}/inbox/?$", views.Inbox.as_view()), - re_path(rf"{LOCAL_USER_PATH}/outbox/?$", views.Outbox.as_view()), + re_path(r"^inbox/?$", views.Inbox.as_view(), name="inbox"), + re_path(rf"{LOCAL_USER_PATH}/inbox/?$", views.Inbox.as_view(), name="user_inbox"), + re_path( + rf"{LOCAL_USER_PATH}/outbox/?$", views.Outbox.as_view(), name="user_outbox" + ), re_path(r"^\.well-known/webfinger/?$", views.webfinger), re_path(r"^\.well-known/nodeinfo/?$", views.nodeinfo_pointer), re_path(r"^\.well-known/host-meta/?$", views.host_meta), @@ -46,8 +48,16 @@ urlpatterns = [ re_path(r"^opensearch.xml$", views.opensearch, name="opensearch"), re_path(r"^ostatus_subscribe/?$", views.ostatus_follow_request), # polling updates - re_path("^api/updates/notifications/?$", views.get_notification_count), - re_path("^api/updates/stream/(?P[a-z]+)/?$", views.get_unread_status_count), + re_path( + "^api/updates/notifications/?$", + views.get_notification_count, + name="notification-updates", + ), + re_path( + "^api/updates/stream/(?P[a-z]+)/?$", + views.get_unread_status_count, + name="stream-updates", + ), # authentication re_path(r"^login/?$", views.Login.as_view(), name="login"), re_path(r"^login/(?Pconfirmed)?$", views.Login.as_view(), name="login"), @@ -147,7 +157,9 @@ urlpatterns = [ re_path( r"^invite-request/?$", views.InviteRequest.as_view(), name="invite-request" ), - re_path(r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view()), + re_path( + r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view(), name="invite" + ), re_path( r"^settings/email-blocklist/?$", views.EmailBlocklist.as_view(), diff --git a/bookwyrm/wsgi.py b/bookwyrm/wsgi.py index 800b40d2..013ff8f4 100644 --- a/bookwyrm/wsgi.py +++ b/bookwyrm/wsgi.py @@ -21,8 +21,6 @@ Env.read_env() os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bookwyrm.settings") DjangoInstrumentor().instrument() trace.set_tracer_provider(TracerProvider()) -trace.get_tracer_provider().add_span_processor( - BatchSpanProcessor(OTLPSpanExporter()) -) +trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) application = get_wsgi_application() From 3d7f73d73c63750d92c2904da2f1c9493919049b Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Sat, 1 Jan 2022 13:16:17 -0800 Subject: [PATCH 3/7] Document OTLP in env, only load if env vars exist Also move telemetry into its own file, all those imports seemed like unnecessary clutter --- .env.prod.example | 16 ++++++++++++++++ bookwyrm/telemetry.py | 10 ++++++++++ bookwyrm/wsgi.py | 12 ++++-------- 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 bookwyrm/telemetry.py diff --git a/.env.prod.example b/.env.prod.example index 2e0ced5e..521a56f8 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -73,3 +73,19 @@ PREVIEW_TEXT_COLOR=#363636 PREVIEW_IMG_WIDTH=1200 PREVIEW_IMG_HEIGHT=630 PREVIEW_DEFAULT_COVER_COLOR=#002549 + +# Below are example keys if you want to enable automatically +# sending telemetry to an OTLP-compatible service. Many of +# the main monitoring apps have OLTP collectors, including +# NewRelic, DataDog, and Honeycomb.io - consult their +# documentation for setup instructions, and what exactly to +# put below! +# +# Service name is an arbitrary tag that is attached to any +# data sent, used to distinguish different sources. Useful +# for sending prod and dev metrics to the same place and +# keeping them separate, for instance! +# +# OTEL_EXPORTER_OTLP_ENDPOINT=https://your.api.endpoint # API endpoint for your provider +# OTEL_EXPORTER_OTLP_HEADERS= # Any headers required, usually authentication info +# OTEL_SERVICE_NAME=your_service_name diff --git a/bookwyrm/telemetry.py b/bookwyrm/telemetry.py new file mode 100644 index 00000000..8e5bf2a2 --- /dev/null +++ b/bookwyrm/telemetry.py @@ -0,0 +1,10 @@ +def InstallOpenTelemetry(): + from opentelemetry.instrumentation.django import DjangoInstrumentor + from opentelemetry import trace + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + + DjangoInstrumentor().instrument() + trace.set_tracer_provider(TracerProvider()) + trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) diff --git a/bookwyrm/wsgi.py b/bookwyrm/wsgi.py index 013ff8f4..7b6f6f22 100644 --- a/bookwyrm/wsgi.py +++ b/bookwyrm/wsgi.py @@ -10,17 +10,13 @@ https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ import os from environs import Env from django.core.wsgi import get_wsgi_application -from opentelemetry.instrumentation.django import DjangoInstrumentor -from opentelemetry import trace -from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor +from bookwyrm.telemetry import InstallOpenTelemetry Env.read_env() os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bookwyrm.settings") -DjangoInstrumentor().instrument() -trace.set_tracer_provider(TracerProvider()) -trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) + +if 'OTEL_EXPORTER_OTLP_ENDPOINT' in os.environ: + InstallOpenTelemetry() application = get_wsgi_application() From 40bec838334b0a7c5f9a9b6d781b3a0d1ddf5ef5 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Sat, 1 Jan 2022 13:29:12 -0800 Subject: [PATCH 4/7] Add versions to requirements.txt --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index f37a675d..06d2f50e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ django-rename-app==0.1.2 pytz>=2021.1 boto3==1.17.88 django-storages==1.11.1 -opentelemetry-api -opentelemetry-sdk -opentelemetry-exporter-otlp-proto-grpc -opentelemetry-instrumentation-django +opentelemetry-api==1.8.0 +opentelemetry-sdk==1.8.0 +opentelemetry-exporter-otlp-proto-grpc==1.8.0 +opentelemetry-instrumentation-django==0.27b0 From 5b3ff0cf82d25fe6d5bca03ebe7196e312b4c071 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Sat, 1 Jan 2022 14:11:45 -0800 Subject: [PATCH 5/7] Instrument celery, move init into apps.py --- .env.prod.example | 8 ++++---- bookwyrm/apps.py | 10 ++++++++++ bookwyrm/settings.py | 4 ++++ bookwyrm/telemetry.py | 10 ---------- bookwyrm/telemetry/open_telemetry.py | 19 +++++++++++++++++++ bookwyrm/wsgi.py | 4 ---- celerywyrm/apps.py | 10 ++++++++++ requirements.txt | 1 + 8 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 bookwyrm/apps.py delete mode 100644 bookwyrm/telemetry.py create mode 100644 bookwyrm/telemetry/open_telemetry.py create mode 100644 celerywyrm/apps.py diff --git a/.env.prod.example b/.env.prod.example index 521a56f8..e85e2e8a 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -85,7 +85,7 @@ PREVIEW_DEFAULT_COVER_COLOR=#002549 # data sent, used to distinguish different sources. Useful # for sending prod and dev metrics to the same place and # keeping them separate, for instance! -# -# OTEL_EXPORTER_OTLP_ENDPOINT=https://your.api.endpoint # API endpoint for your provider -# OTEL_EXPORTER_OTLP_HEADERS= # Any headers required, usually authentication info -# OTEL_SERVICE_NAME=your_service_name + +OTEL_EXPORTER_OTLP_ENDPOINT= # API endpoint for your provider +OTEL_EXPORTER_OTLP_HEADERS= # Any headers required, usually authentication info +OTEL_SERVICE_NAME= # Service name to identify your app diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py new file mode 100644 index 00000000..1c0e0458 --- /dev/null +++ b/bookwyrm/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig +from bookwyrm import settings + +class BookwyrmConfig(AppConfig): + name = 'bookwyrm' + verbose_name = "BookWyrm" + def ready(self): + if settings.OTEL_EXPORTER_OTLP_ENDPOINT: + from bookwyrm.telemetry import open_telemetry + open_telemetry.instrumentDjango() diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index f8acca28..982e6c1f 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -233,3 +233,7 @@ else: MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}" STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}" MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images")) + +OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT") +OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS") +OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME") diff --git a/bookwyrm/telemetry.py b/bookwyrm/telemetry.py deleted file mode 100644 index 8e5bf2a2..00000000 --- a/bookwyrm/telemetry.py +++ /dev/null @@ -1,10 +0,0 @@ -def InstallOpenTelemetry(): - from opentelemetry.instrumentation.django import DjangoInstrumentor - from opentelemetry import trace - from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter - from opentelemetry.sdk.trace import TracerProvider - from opentelemetry.sdk.trace.export import BatchSpanProcessor - - DjangoInstrumentor().instrument() - trace.set_tracer_provider(TracerProvider()) - trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) diff --git a/bookwyrm/telemetry/open_telemetry.py b/bookwyrm/telemetry/open_telemetry.py new file mode 100644 index 00000000..1434a517 --- /dev/null +++ b/bookwyrm/telemetry/open_telemetry.py @@ -0,0 +1,19 @@ +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + +trace.set_tracer_provider(TracerProvider()) +trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) + +def instrumentDjango(): + from opentelemetry.instrumentation.django import DjangoInstrumentor + DjangoInstrumentor().instrument() + +def instrumentCelery(): + from opentelemetry.instrumentation.celery import CeleryInstrumentor + from celery.signals import worker_process_init + + @worker_process_init.connect(weak=False) + def init_celery_tracing(*args, **kwargs): + CeleryInstrumentor().instrument() diff --git a/bookwyrm/wsgi.py b/bookwyrm/wsgi.py index 7b6f6f22..e670f7f1 100644 --- a/bookwyrm/wsgi.py +++ b/bookwyrm/wsgi.py @@ -10,13 +10,9 @@ https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ import os from environs import Env from django.core.wsgi import get_wsgi_application -from bookwyrm.telemetry import InstallOpenTelemetry Env.read_env() os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bookwyrm.settings") -if 'OTEL_EXPORTER_OTLP_ENDPOINT' in os.environ: - InstallOpenTelemetry() - application = get_wsgi_application() diff --git a/celerywyrm/apps.py b/celerywyrm/apps.py new file mode 100644 index 00000000..c1b97cf3 --- /dev/null +++ b/celerywyrm/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig +from celerywyrm import settings + +class CelerywyrmConfig(AppConfig): + name = 'celerywyrm' + verbose_name = "BookWyrm Celery" + def ready(self): + if settings.OTEL_EXPORTER_OTLP_ENDPOINT: + from bookwyrm.telemetry import open_telemetry + open_telemetry.instrumentCelery() diff --git a/requirements.txt b/requirements.txt index 06d2f50e..e8d2a628 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,4 @@ opentelemetry-api==1.8.0 opentelemetry-sdk==1.8.0 opentelemetry-exporter-otlp-proto-grpc==1.8.0 opentelemetry-instrumentation-django==0.27b0 +opentelemetry-instrumentation-celery==0.27b0 From f5f861ce25bef9ae322d3e8aead4b70bcc0d8032 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Sat, 1 Jan 2022 14:49:28 -0800 Subject: [PATCH 6/7] =?UTF-8?q?Make=20it=20black=20=F0=9F=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bookwyrm/apps.py | 5 ++++- bookwyrm/telemetry/open_telemetry.py | 3 +++ celerywyrm/apps.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bookwyrm/apps.py b/bookwyrm/apps.py index 1c0e0458..8c9332cd 100644 --- a/bookwyrm/apps.py +++ b/bookwyrm/apps.py @@ -1,10 +1,13 @@ from django.apps import AppConfig from bookwyrm import settings + class BookwyrmConfig(AppConfig): - name = 'bookwyrm' + name = "bookwyrm" verbose_name = "BookWyrm" + def ready(self): if settings.OTEL_EXPORTER_OTLP_ENDPOINT: from bookwyrm.telemetry import open_telemetry + open_telemetry.instrumentDjango() diff --git a/bookwyrm/telemetry/open_telemetry.py b/bookwyrm/telemetry/open_telemetry.py index 1434a517..0b38a04b 100644 --- a/bookwyrm/telemetry/open_telemetry.py +++ b/bookwyrm/telemetry/open_telemetry.py @@ -6,10 +6,13 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor trace.set_tracer_provider(TracerProvider()) trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) + def instrumentDjango(): from opentelemetry.instrumentation.django import DjangoInstrumentor + DjangoInstrumentor().instrument() + def instrumentCelery(): from opentelemetry.instrumentation.celery import CeleryInstrumentor from celery.signals import worker_process_init diff --git a/celerywyrm/apps.py b/celerywyrm/apps.py index c1b97cf3..6aae849c 100644 --- a/celerywyrm/apps.py +++ b/celerywyrm/apps.py @@ -1,10 +1,13 @@ from django.apps import AppConfig from celerywyrm import settings + class CelerywyrmConfig(AppConfig): - name = 'celerywyrm' + name = "celerywyrm" verbose_name = "BookWyrm Celery" + def ready(self): if settings.OTEL_EXPORTER_OTLP_ENDPOINT: from bookwyrm.telemetry import open_telemetry + open_telemetry.instrumentCelery() From 320ac617cb925d2827fa4cd8f124d4c4eb3a7ef7 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Mon, 10 Jan 2022 23:17:22 -0800 Subject: [PATCH 7/7] Add default values for OTEL settings --- bookwyrm/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 982e6c1f..540e7339 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -234,6 +234,6 @@ else: STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}" MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images")) -OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT") -OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS") -OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME") +OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT", None) +OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS", None) +OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME", None)