Skip to content

Commit

Permalink
fix: Handle missing API_BASE_URL (#60)
Browse files Browse the repository at this point in the history
* Fix detecting if Judoscale should report metrics. Fixes #59

* Update sample apps
  • Loading branch information
karls authored Apr 28, 2023
1 parent e101123 commit 79f2753
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 85 deletions.
21 changes: 14 additions & 7 deletions judoscale/asgi/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from judoscale.core.adapter import Adapter, AdapterInfo
from judoscale.core.config import config as judoconfig
from judoscale.core.logger import logger
from judoscale.core.metric import Metric
from judoscale.core.metrics_collectors import WebMetricsCollector
from judoscale.core.reporter import reporter
Expand All @@ -14,6 +15,11 @@ class RequestQueueTimeMiddleware:
def __init__(self, app, extra_config: Mapping = {}, **kwargs):
self.app = app
judoconfig.update(extra_config)

if not judoconfig.is_enabled:
logger.info("Not activated - no API URL provivded")
return

self.collector = WebMetricsCollector(judoconfig)
adapter = Adapter(
identifier=f"judoscale-{self.platform}",
Expand All @@ -28,14 +34,15 @@ async def __call__(self, scope, receive, send):
await self.app(scope, receive, send)
return

for header, value in scope["headers"]:
if header.lower() == b"x-request-start":
request_start = value.decode()
if metric := Metric.for_web(request_start):
self.collector.add(metric)
break
if judoconfig.is_enabled:
for header, value in scope["headers"]:
if header.lower() == b"x-request-start":
request_start = value.decode()
if metric := Metric.for_web(request_start):
self.collector.add(metric)
break

reporter.ensure_running()
reporter.ensure_running()

await self.app(scope, receive, send)

Expand Down
6 changes: 6 additions & 0 deletions judoscale/celery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from judoscale.celery.collector import CeleryMetricsCollector
from judoscale.core.adapter import Adapter, AdapterInfo
from judoscale.core.config import config as judoconfig
from judoscale.core.logger import logger
from judoscale.core.reporter import reporter


Expand All @@ -20,6 +21,11 @@ def judoscale_celery(celery: Celery, extra_config: Mapping = {}) -> None:
celery.conf.task_send_sent_event = True

judoconfig.update(extra_config)

if not judoconfig.is_enabled:
logger.info("Not activated - no API URL provivded")
return

collector = CeleryMetricsCollector(config=judoconfig, broker=celery)
adapter = Adapter(
identifier="judoscale-celery",
Expand Down
4 changes: 4 additions & 0 deletions judoscale/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def for_render(cls, env: Mapping):
api_base_url = f"https://adapter.judoscale.com/api/{service_id}"
return cls(runtime_container, api_base_url, env)

@property
def is_enabled(self) -> bool:
return bool(self["API_BASE_URL"])

def update(self, new_config: Mapping):
for k, v in new_config.items():
k = k.upper()
Expand Down
2 changes: 1 addition & 1 deletion judoscale/django/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class JudoscaleDjangoConfig(AppConfig):
def ready(self):
judoconfig.update(getattr(settings, "JUDOSCALE", {}))

if judoconfig["API_BASE_URL"] is None:
if not judoconfig.is_enabled:
logger.info("Not activated - No API URL provided")
return

Expand Down
6 changes: 6 additions & 0 deletions judoscale/flask/judoscale.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from judoscale.core.adapter import Adapter, AdapterInfo
from judoscale.core.config import config as judoconfig
from judoscale.core.logger import logger
from judoscale.core.metric import Metric
from judoscale.core.metrics_collectors import WebMetricsCollector
from judoscale.core.reporter import reporter
Expand All @@ -28,6 +29,11 @@ def __init__(self, app: Optional[Flask] = None):

def init_app(self, app: Flask):
judoconfig.update(app.config.get("JUDOSCALE", {}))

if not judoconfig.is_enabled:
logger.info("Not activated - no API URL provivded")
return

collector = WebMetricsCollector(judoconfig)
adapter = Adapter(
identifier="judoscale-flask",
Expand Down
6 changes: 6 additions & 0 deletions judoscale/rq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@

from judoscale.core.adapter import Adapter, AdapterInfo
from judoscale.core.config import config as judoconfig
from judoscale.core.logger import logger
from judoscale.core.reporter import reporter
from judoscale.rq.collector import RQMetricsCollector


def judoscale_rq(redis: Redis, extra_config: Mapping = {}) -> None:
judoconfig.update(extra_config)

if not judoconfig.is_enabled:
logger.info("Not activated - no API URL provivded")
return

collector = RQMetricsCollector(config=judoconfig, redis=redis)
adapter = Adapter(
identifier="judoscale-rq",
Expand Down
2 changes: 1 addition & 1 deletion judoscale/rq/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class JudoscaleRQConfig(AppConfig):
def ready(self):
judoconfig.update(getattr(settings, "JUDOSCALE", {}))

if judoconfig["API_BASE_URL"] is None:
if not judoconfig.is_enabled:
logger.info("Not activated - No API URL provided")
return

Expand Down
25 changes: 14 additions & 11 deletions sample-apps/django_celery_sample/blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ def many_tasks(request):
def index(request):
# Log message in level warning as this is Django's default logging level
logger.warning("Hello, world")
catcher_url = settings.JUDOSCALE["API_BASE_URL"].replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
if url := settings.JUDOSCALE.get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
else:
return HttpResponse("Judoscale Django Celery Sample App. No API URL provided.")
25 changes: 14 additions & 11 deletions sample-apps/django_rq_sample/blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@ def many_tasks(request):
def index(request):
# Log message in level warning as this is Django's default logging level
logger.warning("Hello, world")
catcher_url = settings.JUDOSCALE["API_BASE_URL"].replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django RQ Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
if url := settings.JUDOSCALE.get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django RQ Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
else:
return HttpResponse("Judoscale Django RQ Sample App. No API URL provided.")
13 changes: 8 additions & 5 deletions sample-apps/django_sample/blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
def index(request):
# Log message in level warning as this is Django's default logging level
logger.warning("Hello, world")
catcher_url = settings.JUDOSCALE["API_BASE_URL"].replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
if url := settings.JUDOSCALE.get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return HttpResponse(
"Judoscale Django Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
else:
return HttpResponse("Judoscale Django Sample App. No API URL provided.")
27 changes: 16 additions & 11 deletions sample-apps/fastapi_celery_sample/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,22 @@ def create_app():
@app.get("/")
async def index():
logger.warning("Hello, world")
catcher_url = settings.JUDOSCALE["API_BASE_URL"].replace("/inspect/", "/p/")
return HTMLResponse(
"Judoscale FastAPI Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
if url := settings.JUDOSCALE.get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return HTMLResponse(
"Judoscale FastAPI Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
else:
return HTMLResponse(
"Judoscale FastAPI Celery Sample App. No API URL provided."
)

@app.post("/task")
async def task():
Expand Down
13 changes: 8 additions & 5 deletions sample-apps/fastapi_sample/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ def create_app() -> FastAPI:

@app.get("/")
async def index():
catcher_url = judoconfig["API_BASE_URL"].replace("/inspect/", "/p/")
return HTMLResponse(
"Judoscale FastAPI Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
if url := judoconfig.get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return HTMLResponse(
"Judoscale FastAPI Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
else:
return HTMLResponse("Judoscale FastAPI Sample App. No API URL provided.")

return app
27 changes: 14 additions & 13 deletions sample-apps/flask_celery_sample/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@ def create_app():
@app.get("/")
def index():
current_app.logger.warning("Hello, world")
catcher_url = current_app.config["JUDOSCALE"]["API_BASE_URL"].replace(
"/inspect/", "/p/"
)
return (
"Judoscale Flask Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
if url := current_app.config["JUDOSCALE"].get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return (
"Judoscale Flask Celery Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
else:
return "Judoscale Flask Celery Sample App. No API URL provided."

@app.post("/task")
def task():
Expand Down
27 changes: 14 additions & 13 deletions sample-apps/flask_rq_sample/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,20 @@ def create_app():
@app.get("/")
def index():
current_app.logger.warning("Hello, world")
catcher_url = current_app.config["JUDOSCALE"]["API_BASE_URL"].replace(
"/inspect/", "/p/"
)
return (
"Judoscale Flask RQ Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
if url := current_app.config["JUDOSCALE"].get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return (
"Judoscale Flask RQ Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
"<form action='/task' method='POST'>"
"<input type='submit' value='Add task'>"
"</form>"
"<form action='/batch_task' method='POST'>"
"<input type='submit' value='Add 10 tasks'>"
"</form>"
)
else:
return "Judoscale Flask RQ Sample App. No API URL provided."

@app.post("/task")
def task():
Expand Down
15 changes: 8 additions & 7 deletions sample-apps/flask_sample/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ def create_app():
@app.route("/", methods=["GET"])
def index():
current_app.logger.warning("Hello, world")
catcher_url = current_app.config["JUDOSCALE"]["API_BASE_URL"].replace(
"/inspect/", "/p/"
)
return (
"Judoscale Flask Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
if url := current_app.config["JUDOSCALE"].get("API_BASE_URL"):
catcher_url = url.replace("/inspect/", "/p/")
return (
"Judoscale Flask Sample App. "
f"<a target='_blank' href={catcher_url}>Metrics</a>"
)
else:
return "Judoscale Flask Sample App. No API URL provided."

return app

Expand Down
10 changes: 10 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ def test_on_render(self):
assert config["LOG_LEVEL"] == "WARN"
assert config["API_BASE_URL"] == "https://adapter.judoscale.com/api/srv-123"

def test_is_enabled(self):
config = Config(None, "", {})
assert not config.is_enabled

config = Config(None, None, {})
assert not config.is_enabled

config = Config(None, "https://some-url.com", {})
assert config.is_enabled

def test_for_report(self):
fake_env = {
"DYNO": "web.1",
Expand Down

0 comments on commit 79f2753

Please sign in to comment.