Skip to content

Commit 3a98aca

Browse files
feat: Initial Prometheus support (#5254)
Co-authored-by: Matthew Elwell <matthew.elwell@flagsmith.com>
1 parent dcd6d07 commit 3a98aca

38 files changed

Lines changed: 145 additions & 167 deletions

File tree

api/Makefile

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ DOTENV_OVERRIDE_FILE ?= .env
99

1010
POETRY_VERSION ?= 2.1.1
1111

12-
GUNICORN_LOGGER_CLASS ?= util.logging.GunicornJsonCapableLogger
13-
1412
SAML_REVISION ?= v1.6.4
1513
RBAC_REVISION ?= v0.11.2
1614

@@ -98,10 +96,8 @@ django-collect-static:
9896

9997
.PHONY: serve
10098
serve:
101-
poetry run gunicorn --bind 0.0.0.0:8000 \
102-
--logger-class ${GUNICORN_LOGGER_CLASS} \
103-
--reload \
104-
app.wsgi
99+
flagsmith start --reload api
100+
105101

106102
.PHONY: generate-ld-client-types
107103
generate-ld-client-types:

api/app/settings/common.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,27 @@
1010
https://docs.djangoproject.com/en/1.9/ref/settings/
1111
"""
1212

13-
import importlib.util
13+
import importlib
1414
import json
1515
import os
1616
import warnings
1717
from datetime import datetime, time, timedelta
1818

1919
import dj_database_url # type: ignore[import-untyped]
20+
import django_stubs_ext
2021
import prometheus_client
2122
import pytz
2223
from corsheaders.defaults import default_headers # type: ignore[import-untyped]
2324
from django.core.exceptions import ImproperlyConfigured
2425
from django.core.management.utils import get_random_secret_key
2526
from environs import Env
26-
from task_processor.task_run_method import TaskRunMethod # type: ignore[import-untyped]
27+
from task_processor.task_run_method import TaskRunMethod
2728

2829
from app.routers import ReplicaReadStrategy
2930
from app.utils import get_numbered_env_vars_with_prefix
3031

32+
django_stubs_ext.monkeypatch()
33+
3134
env = Env()
3235

3336
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -69,6 +72,7 @@
6972
# Application definition
7073

7174
INSTALLED_APPS = [
75+
"common.core",
7276
"core.custom_admin.apps.CustomAdminConfig",
7377
"django.contrib.auth",
7478
"django.contrib.contenttypes",
@@ -279,6 +283,7 @@
279283
],
280284
}
281285
MIDDLEWARE = [
286+
"common.gunicorn.middleware.RouteLoggerMiddleware",
282287
"django.middleware.security.SecurityMiddleware",
283288
"whitenoise.middleware.WhiteNoiseMiddleware",
284289
"django.contrib.sessions.middleware.SessionMiddleware",
@@ -1351,5 +1356,10 @@
13511356
if LICENSING_INSTALLED: # pragma: no cover
13521357
INSTALLED_APPS.append("licensing")
13531358

1354-
PROMETHEUS_ENABLED = True
1355-
PROMETHEUS_HISTOGRAM_BUCKETS = prometheus_client.Histogram.DEFAULT_BUCKETS
1359+
PROMETHEUS_ENABLED = env.bool("PROMETHEUS_ENABLED", False)
1360+
PROMETHEUS_HISTOGRAM_BUCKETS = tuple(
1361+
env.list(
1362+
"PROMETHEUS_HISTOGRAM_BUCKETS",
1363+
default=prometheus_client.Histogram.DEFAULT_BUCKETS,
1364+
)
1365+
)

api/app_analytics/tasks.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.conf import settings
55
from django.db.models import Q, Sum
66
from django.utils import timezone
7-
from task_processor.decorators import ( # type: ignore[import-untyped]
7+
from task_processor.decorators import (
88
register_recurring_task,
99
register_task_handler,
1010
)
@@ -24,9 +24,9 @@
2424
)
2525
from environments.models import Environment
2626

27-
if settings.USE_POSTGRES_FOR_ANALYTICS:
27+
if settings.USE_POSTGRES_FOR_ANALYTICS: # pragma: no cover
2828

29-
@register_recurring_task( # type: ignore[misc]
29+
@register_recurring_task(
3030
run_every=timedelta(minutes=60),
3131
kwargs={
3232
"bucket_size": ANALYTICS_READ_BUCKET_SIZE,
@@ -68,7 +68,7 @@ def clean_up_old_analytics_data(): # type: ignore[no-untyped-def]
6868
).delete()
6969

7070

71-
@register_task_handler() # type: ignore[misc]
71+
@register_task_handler()
7272
def track_feature_evaluation_v2(
7373
environment_id: int, feature_evaluations: list[dict[str, int | str | bool]]
7474
) -> None:
@@ -86,7 +86,7 @@ def track_feature_evaluation_v2(
8686
FeatureEvaluationRaw.objects.bulk_create(feature_evaluation_objects)
8787

8888

89-
@register_task_handler() # type: ignore[misc]
89+
@register_task_handler()
9090
def track_feature_evaluation(
9191
environment_id: int,
9292
feature_evaluations: dict[str, int],
@@ -103,7 +103,7 @@ def track_feature_evaluation(
103103
FeatureEvaluationRaw.objects.bulk_create(feature_evaluation_objects)
104104

105105

106-
@register_task_handler() # type: ignore[misc]
106+
@register_task_handler()
107107
def track_request(resource: int, host: str, environment_key: str, count: int = 1): # type: ignore[no-untyped-def]
108108
environment = Environment.get_from_cache(environment_key) # type: ignore[no-untyped-call]
109109
if environment is None:

api/audit/tasks.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from django.contrib.auth import get_user_model
66
from django.utils import timezone
7-
from task_processor.decorators import ( # type: ignore[import-untyped]
7+
from task_processor.decorators import (
88
register_task_handler,
99
)
10-
from task_processor.models import TaskPriority # type: ignore[import-untyped]
10+
from task_processor.models import TaskPriority
1111

1212
from audit.constants import (
1313
FEATURE_STATE_UPDATED_BY_CHANGE_REQUEST_MESSAGE,
@@ -18,14 +18,14 @@
1818
logger = logging.getLogger(__name__)
1919

2020

21-
@register_task_handler(priority=TaskPriority.HIGHEST) # type: ignore[misc]
21+
@register_task_handler(priority=TaskPriority.HIGHEST)
2222
def create_feature_state_went_live_audit_log(feature_state_id: int): # type: ignore[no-untyped-def]
2323
_create_feature_state_audit_log_for_change_request(
2424
feature_state_id, FEATURE_STATE_WENT_LIVE_MESSAGE
2525
)
2626

2727

28-
@register_task_handler(priority=TaskPriority.HIGHEST) # type: ignore[misc]
28+
@register_task_handler(priority=TaskPriority.HIGHEST)
2929
def create_feature_state_updated_by_change_request_audit_log(feature_state_id: int): # type: ignore[no-untyped-def]
3030
_create_feature_state_audit_log_for_change_request(
3131
feature_state_id, FEATURE_STATE_UPDATED_BY_CHANGE_REQUEST_MESSAGE
@@ -63,7 +63,7 @@ def _create_feature_state_audit_log_for_change_request( # type: ignore[no-untyp
6363
)
6464

6565

66-
@register_task_handler(priority=TaskPriority.HIGHEST) # type: ignore[misc]
66+
@register_task_handler(priority=TaskPriority.HIGHEST)
6767
def create_audit_log_from_historical_record( # type: ignore[no-untyped-def]
6868
history_instance_id: int,
6969
history_user_id: typing.Optional[int],
@@ -123,7 +123,7 @@ def create_audit_log_from_historical_record( # type: ignore[no-untyped-def]
123123
)
124124

125125

126-
@register_task_handler() # type: ignore[misc]
126+
@register_task_handler()
127127
def create_segment_priorities_changed_audit_log( # type: ignore[no-untyped-def]
128128
previous_id_priority_pairs: typing.List[typing.Tuple[int, int]],
129129
feature_segment_ids: typing.List[int],

api/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from pytest_mock import MockerFixture
2525
from rest_framework.authtoken.models import Token
2626
from rest_framework.test import APIClient
27-
from task_processor.task_run_method import TaskRunMethod # type: ignore[import-untyped]
27+
from task_processor.task_run_method import TaskRunMethod
2828
from urllib3 import BaseHTTPResponse
2929
from urllib3.connectionpool import HTTPConnectionPool
3030
from xdist import get_xdist_worker_id # type: ignore[import-untyped]

api/core/signals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.core.exceptions import ObjectDoesNotExist
55
from django.utils import timezone
66
from simple_history.models import HistoricalRecords # type: ignore[import-untyped]
7-
from task_processor.task_run_method import TaskRunMethod # type: ignore[import-untyped]
7+
from task_processor.task_run_method import TaskRunMethod
88

99
from audit import tasks
1010
from core.models import AbstractBaseAuditableModel

api/custom_auth/tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from django.conf import settings
44
from django.utils import timezone
5-
from task_processor.decorators import ( # type: ignore[import-untyped]
5+
from task_processor.decorators import (
66
register_recurring_task,
77
)
88

api/edge_api/identities/tasks.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
import typing
33

44
from django.utils import timezone
5-
from task_processor.decorators import ( # type: ignore[import-untyped]
5+
from task_processor.decorators import (
66
register_task_handler,
77
)
8-
from task_processor.models import TaskPriority # type: ignore[import-untyped]
8+
from task_processor.models import TaskPriority
99

1010
from audit.models import AuditLog
1111
from audit.related_object_type import RelatedObjectType
@@ -29,7 +29,7 @@
2929
logger = logging.getLogger(__name__)
3030

3131

32-
@register_task_handler() # type: ignore[misc]
32+
@register_task_handler()
3333
def call_environment_webhook_for_feature_state_change( # type: ignore[no-untyped-def]
3434
feature_id: int,
3535
environment_api_key: str,
@@ -90,7 +90,7 @@ def call_environment_webhook_for_feature_state_change( # type: ignore[no-untype
9090
call_environment_webhooks(environment.id, data, event_type=event_type.value)
9191

9292

93-
@register_task_handler(priority=TaskPriority.HIGH) # type: ignore[misc]
93+
@register_task_handler(priority=TaskPriority.HIGH)
9494
def sync_identity_document_features(identity_uuid: str): # type: ignore[no-untyped-def]
9595
from .models import EdgeIdentity
9696

@@ -108,7 +108,7 @@ def sync_identity_document_features(identity_uuid: str): # type: ignore[no-unty
108108
identity.save()
109109

110110

111-
@register_task_handler() # type: ignore[misc]
111+
@register_task_handler()
112112
def generate_audit_log_records(
113113
environment_api_key: str,
114114
identifier: str,
@@ -148,7 +148,7 @@ def generate_audit_log_records(
148148
AuditLog.objects.bulk_create(audit_records)
149149

150150

151-
@register_task_handler() # type: ignore[misc]
151+
@register_task_handler()
152152
def update_flagsmith_environments_v2_identity_overrides(
153153
environment_api_key: str,
154154
identity_uuid: str,
@@ -172,7 +172,7 @@ def update_flagsmith_environments_v2_identity_overrides(
172172
dynamodb_wrapper_v2.update_identity_overrides(identity_override_changeset)
173173

174174

175-
@register_task_handler() # type: ignore[misc]
175+
@register_task_handler()
176176
def delete_environments_v2_identity_overrides_by_feature(feature_id: int) -> None:
177177
dynamodb_wrapper_v2 = DynamoEnvironmentV2Wrapper()
178178

api/environments/tasks.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from task_processor.decorators import ( # type: ignore[import-untyped]
1+
from task_processor.decorators import (
22
register_task_handler,
33
)
4-
from task_processor.models import TaskPriority # type: ignore[import-untyped]
4+
from task_processor.models import TaskPriority
55

66
from audit.models import AuditLog
77
from environments.dynamodb import DynamoIdentityWrapper
@@ -17,12 +17,12 @@
1717
)
1818

1919

20-
@register_task_handler(priority=TaskPriority.HIGH) # type: ignore[misc]
20+
@register_task_handler(priority=TaskPriority.HIGH)
2121
def rebuild_environment_document(environment_id: int) -> None:
2222
Environment.write_environments_to_dynamodb(environment_id=environment_id)
2323

2424

25-
@register_task_handler(priority=TaskPriority.HIGHEST) # type: ignore[misc]
25+
@register_task_handler(priority=TaskPriority.HIGHEST)
2626
def process_environment_update(audit_log_id: int): # type: ignore[no-untyped-def]
2727
audit_log = AuditLog.objects.get(id=audit_log_id)
2828

@@ -38,7 +38,7 @@ def process_environment_update(audit_log_id: int): # type: ignore[no-untyped-de
3838
send_environment_update_message_for_project(audit_log.project)
3939

4040

41-
@register_task_handler() # type: ignore[misc]
41+
@register_task_handler()
4242
def delete_environment_from_dynamo(api_key: str, environment_id: str): # type: ignore[no-untyped-def]
4343
# Delete environment
4444
environment_wrapper.delete_environment(api_key)
@@ -51,12 +51,12 @@ def delete_environment_from_dynamo(api_key: str, environment_id: str): # type:
5151
environment_v2_wrapper.delete_environment(environment_id) # type: ignore[arg-type]
5252

5353

54-
@register_task_handler() # type: ignore[misc]
54+
@register_task_handler()
5555
def delete_environment(environment_id: int) -> None:
5656
Environment.objects.get(id=environment_id).delete()
5757

5858

59-
@register_task_handler() # type: ignore[misc]
59+
@register_task_handler()
6060
def clone_environment_feature_states(
6161
source_environment_id: int, clone_environment_id: int
6262
) -> None:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
from task_processor.decorators import ( # type: ignore[import-untyped]
1+
from task_processor.decorators import (
22
register_task_handler,
33
)
44

55
from features.feature_health import services
66
from features.models import Feature
77

88

9-
@register_task_handler() # type: ignore[misc]
9+
@register_task_handler()
1010
def update_feature_unhealthy_tag(feature_id: int) -> None:
1111
if feature := Feature.objects.filter(id=feature_id).first():
1212
services.update_feature_unhealthy_tag(feature)

0 commit comments

Comments
 (0)