From 6c1676577112fe70dcc88e153e59c0feae51b15d Mon Sep 17 00:00:00 2001 From: Sjorza Date: Tue, 31 Mar 2026 14:08:42 +0200 Subject: [PATCH 1/9] feat(monitoring): filter robots.txt noise from sentry traces and events --- src/backend/app/monitoring.py | 70 +++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/backend/app/monitoring.py b/src/backend/app/monitoring.py index e2bffef329..b71a0c1736 100644 --- a/src/backend/app/monitoring.py +++ b/src/backend/app/monitoring.py @@ -28,7 +28,6 @@ log = logging.getLogger(__name__) - def add_endpoint_profiler(app: Litestar) -> None: """Add a simple per-request profiler middleware when DEBUG is enabled. @@ -98,25 +97,71 @@ async def send_wrapper(message: dict[str, Any]) -> None: # Register middleware with the Litestar app app.middleware.append(profiler_middleware) +from urllib.parse import urlparse -def set_sentry_otel_tracer(dsn: str): - """Add OpenTelemetry tracing only if environment variables configured.""" - from opentelemetry import trace - from opentelemetry.propagate import set_global_textmap - from opentelemetry.sdk.trace import TracerProvider - from sentry_sdk import init - from sentry_sdk.integrations.opentelemetry import ( - SentryPropagator, - SentrySpanProcessor, - ) +# Endpoints that should not create tracing or error noise +IGNORED_PATHS = { + "/robots.txt", +} + +def traces_sampler(sampling_context: dict) -> float: + """ + Determine if an incoming request should be traced. + Returns 0.0 to ignore, 1.0 to trace. + """ + scope = sampling_context.get("asgi_scope") + + if scope: + path = scope.get("path", "") + if path in IGNORED_PATHS: + return 0.0 + + return 1.0 + +def before_send(event: dict, hint: dict) -> dict | None: + """ + Filter out specific error events before they are sent to Sentry. + """ + request = event.get("request", {}) + url = request.get("url") + + if url: + path = urlparse(url).path + if path in IGNORED_PATHS: + return None + + return event + +def set_sentry_otel_tracer(dsn: str) -> None: + """ + Initialize Sentry with OpenTelemetry integration. + """ + if not dsn: + return + + try: + from opentelemetry import trace + from opentelemetry.propagate import set_global_textmap + from opentelemetry.sdk.trace import TracerProvider + from sentry_sdk import init + from sentry_sdk.integrations.opentelemetry import ( + SentryPropagator, + SentrySpanProcessor, + ) + except ImportError: + # Silently fail if packages are missing (e.g. in local dev environment) + return init( dsn=dsn, enable_tracing=True, - traces_sample_rate=1.0, instrumenter="otel", + traces_sampler=traces_sampler, + before_send=before_send, + propagate_traces=True, ) + # OpenTelemetry Tracing Setup provider = TracerProvider() provider.add_span_processor(SentrySpanProcessor()) trace.set_tracer_provider(provider) @@ -251,3 +296,4 @@ def get_otel_plugin(): from litestar.contrib.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin return OpenTelemetryPlugin(OpenTelemetryConfig()) + From 6cecd0d61ef0a122dea5e4c635ec627111a7296f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:25:17 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/backend/app/monitoring.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/backend/app/monitoring.py b/src/backend/app/monitoring.py index b71a0c1736..14f5e84a81 100644 --- a/src/backend/app/monitoring.py +++ b/src/backend/app/monitoring.py @@ -28,6 +28,7 @@ log = logging.getLogger(__name__) + def add_endpoint_profiler(app: Litestar) -> None: """Add a simple per-request profiler middleware when DEBUG is enabled. @@ -97,6 +98,7 @@ async def send_wrapper(message: dict[str, Any]) -> None: # Register middleware with the Litestar app app.middleware.append(profiler_middleware) + from urllib.parse import urlparse # Endpoints that should not create tracing or error noise @@ -104,13 +106,13 @@ async def send_wrapper(message: dict[str, Any]) -> None: "/robots.txt", } + def traces_sampler(sampling_context: dict) -> float: - """ - Determine if an incoming request should be traced. + """Determine if an incoming request should be traced. Returns 0.0 to ignore, 1.0 to trace. """ scope = sampling_context.get("asgi_scope") - + if scope: path = scope.get("path", "") if path in IGNORED_PATHS: @@ -118,10 +120,9 @@ def traces_sampler(sampling_context: dict) -> float: return 1.0 + def before_send(event: dict, hint: dict) -> dict | None: - """ - Filter out specific error events before they are sent to Sentry. - """ + """Filter out specific error events before they are sent to Sentry.""" request = event.get("request", {}) url = request.get("url") @@ -132,10 +133,9 @@ def before_send(event: dict, hint: dict) -> dict | None: return event + def set_sentry_otel_tracer(dsn: str) -> None: - """ - Initialize Sentry with OpenTelemetry integration. - """ + """Initialize Sentry with OpenTelemetry integration.""" if not dsn: return @@ -296,4 +296,3 @@ def get_otel_plugin(): from litestar.contrib.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin return OpenTelemetryPlugin(OpenTelemetryConfig()) - From 1c8cffb897ad576c4a905a54c2a8ca05375abbd0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:50:02 +0000 Subject: [PATCH 3/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- cla.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cla.json b/cla.json index 901409c295..3bf516dfbc 100644 --- a/cla.json +++ b/cla.json @@ -25,4 +25,4 @@ "pullRequestNo": 3046 } ] -} \ No newline at end of file +} From 44198952b1d9bf533e1a844be91e8af8b631f916 Mon Sep 17 00:00:00 2001 From: Sjorza Date: Tue, 31 Mar 2026 19:21:56 +0200 Subject: [PATCH 4/9] refactor: use OTEL_PYTHON_EXCLUDED_URLS for robots.txt as suggested --- src/backend/app/config.py | 2 +- src/backend/app/monitoring.py | 67 ++++++----------------------------- 2 files changed, 12 insertions(+), 57 deletions(-) diff --git a/src/backend/app/config.py b/src/backend/app/config.py index 162dc58826..451b0dff5c 100644 --- a/src/backend/app/config.py +++ b/src/backend/app/config.py @@ -102,7 +102,7 @@ def otel_service_name(self) -> Optional[HttpUrlStr]: @property def otel_python_excluded_urls(self) -> Optional[str]: """Set excluded URLs for Python instrumentation.""" - endpoints = "__lbheartbeat__,docs,openapi.json,^/static/.*" + endpoints = "__lbheartbeat__,docs,openapi.json,robots.txt,^/static/.*" os.environ["OTEL_PYTHON_EXCLUDED_URLS"] = endpoints # Add extra endpoints ignored by for requests # NOTE we add ODK Central session auth endpoint here diff --git a/src/backend/app/monitoring.py b/src/backend/app/monitoring.py index 14f5e84a81..e2bffef329 100644 --- a/src/backend/app/monitoring.py +++ b/src/backend/app/monitoring.py @@ -99,69 +99,24 @@ async def send_wrapper(message: dict[str, Any]) -> None: app.middleware.append(profiler_middleware) -from urllib.parse import urlparse - -# Endpoints that should not create tracing or error noise -IGNORED_PATHS = { - "/robots.txt", -} - - -def traces_sampler(sampling_context: dict) -> float: - """Determine if an incoming request should be traced. - Returns 0.0 to ignore, 1.0 to trace. - """ - scope = sampling_context.get("asgi_scope") - - if scope: - path = scope.get("path", "") - if path in IGNORED_PATHS: - return 0.0 - - return 1.0 - - -def before_send(event: dict, hint: dict) -> dict | None: - """Filter out specific error events before they are sent to Sentry.""" - request = event.get("request", {}) - url = request.get("url") - - if url: - path = urlparse(url).path - if path in IGNORED_PATHS: - return None - - return event - - -def set_sentry_otel_tracer(dsn: str) -> None: - """Initialize Sentry with OpenTelemetry integration.""" - if not dsn: - return - - try: - from opentelemetry import trace - from opentelemetry.propagate import set_global_textmap - from opentelemetry.sdk.trace import TracerProvider - from sentry_sdk import init - from sentry_sdk.integrations.opentelemetry import ( - SentryPropagator, - SentrySpanProcessor, - ) - except ImportError: - # Silently fail if packages are missing (e.g. in local dev environment) - return +def set_sentry_otel_tracer(dsn: str): + """Add OpenTelemetry tracing only if environment variables configured.""" + from opentelemetry import trace + from opentelemetry.propagate import set_global_textmap + from opentelemetry.sdk.trace import TracerProvider + from sentry_sdk import init + from sentry_sdk.integrations.opentelemetry import ( + SentryPropagator, + SentrySpanProcessor, + ) init( dsn=dsn, enable_tracing=True, + traces_sample_rate=1.0, instrumenter="otel", - traces_sampler=traces_sampler, - before_send=before_send, - propagate_traces=True, ) - # OpenTelemetry Tracing Setup provider = TracerProvider() provider.add_span_processor(SentrySpanProcessor()) trace.set_tracer_provider(provider) From 81fd07b20e0ec27a2baa7ebc2bd7f1942dd1cc1d Mon Sep 17 00:00:00 2001 From: Sjorza Date: Wed, 1 Apr 2026 08:32:34 +0200 Subject: [PATCH 5/9] fix: restore original monitoring.py logic --- src/backend/app/monitoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/app/monitoring.py b/src/backend/app/monitoring.py index e2bffef329..dd381176d4 100644 --- a/src/backend/app/monitoring.py +++ b/src/backend/app/monitoring.py @@ -250,4 +250,4 @@ def get_otel_plugin(): """Return an OpenTelemetry LiteStar plugin instance.""" from litestar.contrib.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin - return OpenTelemetryPlugin(OpenTelemetryConfig()) + return OpenTelemetryPlugin(OpenTelemetryConfig()) \ No newline at end of file From 7a680f09b24b3eb8214efdc0309a214b299658d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:35:22 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/backend/app/monitoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/app/monitoring.py b/src/backend/app/monitoring.py index dd381176d4..e2bffef329 100644 --- a/src/backend/app/monitoring.py +++ b/src/backend/app/monitoring.py @@ -250,4 +250,4 @@ def get_otel_plugin(): """Return an OpenTelemetry LiteStar plugin instance.""" from litestar.contrib.opentelemetry import OpenTelemetryConfig, OpenTelemetryPlugin - return OpenTelemetryPlugin(OpenTelemetryConfig()) \ No newline at end of file + return OpenTelemetryPlugin(OpenTelemetryConfig()) From 966e9da647aca3513f07672b626c0e3f596fc930 Mon Sep 17 00:00:00 2001 From: Sjorza Date: Thu, 2 Apr 2026 20:39:56 +0200 Subject: [PATCH 7/9] docs: fix overview link and hide broken assets --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 139b48b865..b41c2b53c2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ | :--- | :--- | :--- | | **Tech Stack** | | [![Litestar][badge-litestar]][14] [![HTMX][badge-htmx]][15] [![Postgres][badge-postgres]][16] [![Kubernetes][badge-kubernetes]][17] [![Docker][badge-docker]][18] | | **Code Style** | | [![Backend Style][badge-ruff]][19] [![Oxfmt][badge-oxfmt]][20] [![pre-commit][badge-pre-commit]][21] [![uv][badge-uv]][32] | -| **Quality** | | [![Coverage][badge-coverage]][22] [![Translation][badge-translation]][23] [![OpenSSF Best Practices][badge-openssf]][24] | +| **Quality** | | [![Translation][badge-translation]][23] [![OpenSSF Best Practices][badge-openssf]][24] | | **Community** | | [![Slack][badge-slack]][25] [![All Contributors][badge-all-contributors]][26] | | **Other Info** | | [![docs][badge-docs]][27] [![dev-roadmap][badge-roadmap]][28] [![timeline][badge-timeline]][29] [![license-code][badge-license-code]][30] [![license-translations][badge-license-translations]][31] | @@ -43,12 +43,14 @@ the Field-TM aims to solve the problem of **coordinating** field mapping campaig > [!NOTE] > More details can be found here: -> [overview](https://www.hotosm.org/updates/field-mapping-tasking-manager-field-tm), +> [overview](https://field.hotosm.org/), > [timeline](https://docs.field.hotosm.org/timeline), > [docs](https://docs.field.hotosm.org) page, and the > [FAQ](https://docs.field.hotosm.org/about/faq). -![field-tm-splash][6] + + + ## How Field-TM Works From e3c26d0793d5ecad9d8e3660d7b7734fc4886ab2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 18:50:47 +0000 Subject: [PATCH 8/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index b41c2b53c2..c8450f3529 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,6 @@ the Field-TM aims to solve the problem of **coordinating** field mapping campaig - - ## How Field-TM Works 1. Project is created in an area with three things: @@ -263,7 +261,6 @@ Thanks goes to these wonderful people: [2]: https://github.com/hotosm/field-tm/releases/tag/2024.5.0 "Mapper Frontend" [3]: https://github.com/hotosm/field-tm/releases/tag/2025.1.0 "New Geoms" [4]: https://github.com/hotosm/field-tm/releases/tag/2025.2.0 "Web Forms" -[6]: https://raw.githubusercontent.com/hotosm/field-tm/main/src/mapper/static/screenshot-mapper.jpeg "Mapper Page Screenshot" [7]: https://github.com/hotosm/field-tm/releases/tag/2025.3.0 "Offline Mode" [8]: https://github.com/hotosm/field-tm/discussions/2878 "Removed offline support" [9]: https://github.com/hotosm/field-tm/releases/tag/2026.1.0 "Renewed" @@ -279,7 +276,6 @@ Thanks goes to these wonderful people: [19]: https://github.com/astral-sh/ruff "Ruff Format" [20]: https://oxc.rs/docs/guide/usage/formatter.html "Oxfmt" [21]: https://results.pre-commit.ci/latest/github/hotosm/field-tm/dev "pre-commit" -[22]: https://docs.field.hotosm.org/coverage.html "Coverage Report" [23]: https://hosted.weblate.org/engage/hotosm "Weblate" [24]: https://www.bestpractices.dev/projects/9218 "OpenSSF Best Practices" [25]: https://slack.hotosm.org "HOTOSM Slack" @@ -303,7 +299,6 @@ Thanks goes to these wonderful people: [badge-oxfmt]: https://img.shields.io/badge/code%20style-oxfmt-F7B93E [badge-pre-commit]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white [badge-uv]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json -[badge-coverage]: https://docs.field.hotosm.org/coverage.svg [badge-translation]: https://hosted.weblate.org/widget/hotosm/field-tm/svg-badge.svg [badge-openssf]: https://www.bestpractices.dev/projects/9218/badge [badge-slack]: https://img.shields.io/badge/Slack-Join%20the%20community!-d63f3f?style=for-the-badge&logo=slack&logoColor=d63f3f From cb2da113589051edc51d2371644eed600bf44b2e Mon Sep 17 00:00:00 2001 From: Sjorza Date: Fri, 3 Apr 2026 21:26:48 +0200 Subject: [PATCH 9/9] fix(docs): restore coverage generation in publish task --- tasks/docs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks/docs b/tasks/docs index 51c374dc1b..5c7114844a 100644 --- a/tasks/docs +++ b/tasks/docs @@ -75,6 +75,8 @@ publish output_dir="site": #!/usr/bin/env sh set -eu + just test backend-with-coverage-upload + just docs openapi just docs build "{{ output_dir }}"