Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
297c17a
Dockerfile update: Uncommented the SKIP_NGINX and SKIP_CRON env
sadrultoaha Jun 3, 2026
5dd93f5
fix healthcheck command
chinmoyacharjee Jun 3, 2026
c9852d5
stopped overriding the ADCP_SALES_PORT to different port
chinmoyacharjee Jun 4, 2026
fc5d271
reduce cache size and pool size
chinmoyacharjee Jun 4, 2026
4db5916
hardcoded set SKIP_NGINX to True
chinmoyacharjee Jun 4, 2026
9328d36
Change ADCP_PORT to 8000 in Dockerfile
sadrultoaha Jun 4, 2026
156171f
reverted pool size and cache size
chinmoyacharjee Jun 4, 2026
c6d89f0
Removed Hardcoded SKIP_NGINX value
sadrultoaha Jun 4, 2026
11d67e4
skip_cron hardcoded value set to false
sadrultoaha Jun 4, 2026
cfd4d63
Merge branch 'main' into develop
sadrultoaha Jun 4, 2026
4d07b83
update healthcheck retry
sadrultoaha Jun 5, 2026
d093bcc
gam create service account access set to the api_mode = true
sadrultoaha Jun 5, 2026
db0d19f
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
chinmoyacharjee Jun 5, 2026
da26784
added gam sync button
chinmoyacharjee Jun 8, 2026
66c42e1
fix saveGAMConfig() issue now auto-create the row when it's missing.
sadrultoaha Jun 9, 2026
7e3c333
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jun 9, 2026
c1e0da3
gam creating service account error message improvment
sadrultoaha Jun 9, 2026
0bf650a
Merge branch 'main' into develop
sadrultoaha Jun 12, 2026
230b69f
Sync master brach from bokelley fork (#15)
sadrultoaha Jun 12, 2026
09b57a2
Feature/gam graceful order approval (#16)
sadrultoaha Jun 12, 2026
d6938ca
Merge branch 'main' into develop
sadrultoaha Jun 17, 2026
1470441
fix: media buy status update on GAM approval
sadrultoaha Jun 17, 2026
709dc91
Configure compacting mcp create media buy schema (#19)
chinmoyacharjee Jun 18, 2026
a052ef2
fix currency inconsistencies
chinmoyacharjee Jun 22, 2026
75c18eb
fix compact schema
chinmoyacharjee Jun 22, 2026
9c365a1
Fix: report fetch, Gam tenant oauth connection setup with service ac…
sadrultoaha Jun 22, 2026
9bf4958
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jun 22, 2026
d348dbd
fix validate_and_log_freshness validator removed from admin ui
sadrultoaha Jun 22, 2026
6be1ef8
Fix gam data/metric projection
sadrultoaha Jun 22, 2026
ec709c8
decouple health check call from db ping call
chinmoyacharjee Jun 23, 2026
a49922b
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
chinmoyacharjee Jun 23, 2026
061d56f
feat: add GAM orders sync button with loading state to media buys list
sadrultoaha Jun 23, 2026
c1c4f05
Fix currency_symbol made dynamic
sadrultoaha Jun 24, 2026
c9a98a9
fix imports and minimizing sync_all_tenants.py
chinmoyacharjee Jun 24, 2026
d26e28d
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
chinmoyacharjee Jun 24, 2026
3d02c67
Fix GAM currency set issue & product update
sadrultoaha Jun 24, 2026
d6e0617
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jun 24, 2026
b56d1be
compact schema for update media buy creation (#20)
chinmoyacharjee Jun 25, 2026
88bd96c
fix gam test overwrite adapter_config
sadrultoaha Jun 26, 2026
8c83566
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jun 26, 2026
0ecd6e2
Fix gam_service_account_email empty on GCP API failure
sadrultoaha Jun 26, 2026
2a0e794
added unit + integration tests for media buy creation (#23)
chinmoyacharjee Jun 29, 2026
225d0ec
adcp version aggregated (#22)
chinmoyacharjee Jun 29, 2026
cbd8444
fix get_tenant_access logic
sadrultoaha Jun 30, 2026
59f1c64
Merge branch 'main' into develop
sadrultoaha Jun 30, 2026
f11e7e3
Merge branch 'main' of github.com:ImproveDigital/salesagent into develop
sadrultoaha Jun 30, 2026
1a2f1ac
Revenue chart fix (#26)
sadrultoaha Jun 30, 2026
94941f0
Update media buy fixes (#27)
chinmoyacharjee Jun 30, 2026
cd86653
Bug/fix chart data (#29)
sadrultoaha Jul 2, 2026
1593f98
feat: European timezone defaults, all-time GAM reporting, and deliver…
sadrultoaha Jul 3, 2026
f4c57c4
Merge branch 'main' into develop
sadrultoaha Jul 3, 2026
b2284c6
Feature/update report timezone (#32)
sadrultoaha Jul 3, 2026
73337ed
Merge branch 'main' into develop
sadrultoaha Jul 8, 2026
168a82f
Feat: Creative approval and missing creative validation (#34)
sadrultoaha Jul 8, 2026
3fdf8e3
fix: stabilize admin sessions and unify login/signup tenant flows (#35)
chinmoyacharjee Jul 8, 2026
485e8dd
Feature/create media buy validation (#36)
sadrultoaha Jul 8, 2026
ccd95fb
fix: show existing tenants during OAuth signup and restore logout UI …
chinmoyacharjee Jul 8, 2026
bb16650
fix logout flow (#38)
chinmoyacharjee Jul 9, 2026
567409e
Fix logout flow (#39)
chinmoyacharjee Jul 9, 2026
c169c07
Fix redirect url inconsistencies (#40)
chinmoyacharjee Jul 9, 2026
e711244
fix: derive line-item goal units per pricing model and per package bu…
sadrultoaha Jul 9, 2026
05ca9ea
Fix auth flow (#41)
chinmoyacharjee Jul 9, 2026
a5821cc
fix: STANDARD line items no longer ship DAILY-divided units as LIFETI…
sadrultoaha Jul 9, 2026
f05b75d
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jul 9, 2026
b643a37
feat: label reporting Order/Line Item rows with external id and fix s…
sadrultoaha Jul 13, 2026
524191a
fix: dashboard revenue chart tooltip and add per-day points
sadrultoaha Jul 13, 2026
c6642a5
Merge branch 'main' into develop
sadrultoaha Jul 13, 2026
fafa52d
fix chart dots and report order name field
sadrultoaha Jul 14, 2026
c7c7a7c
chore: disable minimum package budget validation in media buy create/…
sadrultoaha Jul 14, 2026
65590a4
feat: add internal pending_ad_server_approval status for GAM order ap…
chinmoyacharjee Jul 14, 2026
bc505a8
fix: use bid_price fallback for goal units in approved media buy
sadrultoaha Jul 15, 2026
5758735
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jul 15, 2026
9864a96
feat: booking validation — goal/budget invariant and flight-window guard
sadrultoaha Jul 16, 2026
26fa253
Merge branch 'main' of github.com:ImproveDigital/salesagent into develop
sadrultoaha Jul 16, 2026
f8001e3
fix status mismatch in get media buy (#48)
chinmoyacharjee Jul 21, 2026
1f81127
Product UI fix (#50)
chinmoyacharjee Jul 21, 2026
690eeb5
feat: update media buy delivery metrics in DB on reporting page visit
sadrultoaha Jul 22, 2026
fafca22
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Jul 22, 2026
eace8b7
Merge branch 'main' of github.com:ImproveDigital/salesagent into develop
sadrultoaha Jul 23, 2026
21589aa
Improve digital marketplace scaffolding (#54)
chinmoyacharjee Jul 27, 2026
64a9700
feat: show tenant adapter type in admin nav header
sadrultoaha Jul 28, 2026
73d43e9
Feat: Improve digital adserver campaign book api integration
sadrultoaha Aug 6, 2026
196bbab
Onboard improve digital Ad server (#55)
chinmoyacharjee Aug 6, 2026
ec10e3b
Feature/improve delivery reports (#56)
sadrultoaha Aug 7, 2026
d8b354d
feat(improvedigital): business unit id added (#57)
chinmoyacharjee Aug 7, 2026
5664765
Fix product (#58)
chinmoyacharjee Aug 7, 2026
78ad1d3
Inventory improve digital (#59)
chinmoyacharjee Aug 10, 2026
c4e0637
feat(improvedigital): Adapter settings page update, Line item payload…
sadrultoaha Aug 10, 2026
35aebd6
fix: improvement of slim_schemas for better token utilization during…
sadrultoaha Aug 10, 2026
5590c6a
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Aug 10, 2026
44fdcb3
Inventory UI page (#60)
chinmoyacharjee Aug 12, 2026
bb4d264
postman update
sadrultoaha Aug 13, 2026
1f24de6
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Aug 13, 2026
ce99ccb
feat(improvedigital): attach campaign metadata to booked campaigns (#62)
sadrultoaha Aug 17, 2026
7859152
feat(improvedigital): persist inventory sync per page and run it async
sadrultoaha Aug 18, 2026
cadf054
feat(improvedigital): Reporting page and live delivery metrics
sadrultoaha Aug 18, 2026
5a36df1
fix: fexisting campaigns check prior sync report
sadrultoaha Aug 18, 2026
ebb2dda
Fix media buy details label
sadrultoaha Aug 18, 2026
718cbbc
Merge branch 'main' into develop
sadrultoaha Sep 4, 2026
348b561
Feature/tenant wise adserver (#63)
sadrultoaha Sep 7, 2026
7d6c6f4
Commented model_dump to fix completed delivery metrics
sadrultoaha Sep 9, 2026
0bd3ee0
product flow log
sadrultoaha Sep 10, 2026
cdac604
Run server logs
sadrultoaha Sep 10, 2026
0f73d11
enrich logs
chinmoyacharjee Sep 10, 2026
0dd6df8
fix boot
chinmoyacharjee Sep 10, 2026
d8bb099
enrich logs, added memwatch for memory utilization
chinmoyacharjee Sep 11, 2026
a872a4f
inventory fetch query optimization
chinmoyacharjee Sep 11, 2026
38f94f6
implemented paginated search for inventories
chinmoyacharjee Sep 11, 2026
0bed89b
added migration for pg_trgm index
chinmoyacharjee Sep 11, 2026
c18f2bd
Feature/inventory (#64)
sadrultoaha Sep 15, 2026
0a41202
fix: adapter default currency for wholesale bundle pricing
sadrultoaha Sep 15, 2026
e009924
Merge branch 'develop' of github.com:ImproveDigital/salesagent into d…
sadrultoaha Sep 15, 2026
f383a7d
fix(improvedigital): book bundle-backed packages against their picked…
sadrultoaha Sep 15, 2026
2dbc521
Fms 1929 fix webhooks (#65)
chinmoyacharjee Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"mcpServers": {
"local": {
"type": "http",
"url": "https://azerion-gaming.sales-agent.yieldpro.improvedigital.com/mcp",
"url": "http://0.0.0.0:8080/mcp",
"headers": {
"Authorization": "Bearer tok_JQOJr84VHKMZGD7R6xJe6YIQFToBK8fKVUIJojtxOHI"
"Authorization": "Bearer tok_TsDB5qiLETTMCZab_UAHmUES1cI3tInHt69sxf14TWE"
}
}
}
}
}
63 changes: 63 additions & 0 deletions alembic/versions/a9b8c7d6e5f4_add_adapter_config_locked_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""add adapter_config.config_locked_at

Explicit stored lock state for the adapter-configuration lock
(src/core/database/adapter_config_lock.py): stamped when a tenant's first
inventory sync completes; while non-NULL the ad server configuration is
frozen and only credentials remain editable. Cleared only by platform ops
under super_admin_override.

Backfill: tenants that already synced inventory (a terminal-success
sync_jobs row of sync_type='inventory', or legacy gam_inventory rows from
before sync history existed) are stamped locked at migration time, so the
lock applies retroactively.

Revision ID: a9b8c7d6e5f4
Revises: impd02b3c4d5
Create Date: 2026-09-04

"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "a9b8c7d6e5f4"
down_revision: str | Sequence[str] | None = "impd02b3c4d5"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.add_column(
"adapter_config",
sa.Column(
"config_locked_at",
sa.DateTime(timezone=True),
nullable=True,
comment=(
"Set when the tenant's first inventory sync completed; while non-NULL "
"the ad server configuration is frozen (only credentials editable)."
),
),
)

op.execute(
"""
UPDATE adapter_config
SET config_locked_at = NOW()
WHERE config_locked_at IS NULL
AND (
tenant_id IN (
SELECT DISTINCT tenant_id FROM sync_jobs
WHERE sync_type = 'inventory' AND status IN ('completed', 'success')
)
OR tenant_id IN (SELECT DISTINCT tenant_id FROM gam_inventory)
)
"""
)


def downgrade() -> None:
op.drop_column("adapter_config", "config_locked_at")
77 changes: 77 additions & 0 deletions alembic/versions/impd01_add_improvedigital_inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""add_improvedigital_inventory

Local cache of Improve Digital 360Yield buy-side inventory (publishers,
placements, reusable packages, creative sizes). Used by the Improve Digital
adapter's product configuration UI so operators can pick placements from
synced inventory without round-tripping to the 360Yield API on every page
render.

NOT exposed to AdCP buyers -- buyer-facing property discovery goes through
the AAO lookup path (adagents.json + brand.json). This is a private
adapter-side cache.

Refreshed on demand via the adapter settings "Sync Inventory" button or
the periodic AdapterSyncScheduler.

Revision ID: impd01a2b3c4
Revises: e8f1a2b3c4d5
Create Date: 2026-07-27 19:00:00.000000
"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "impd01a2b3c4"
down_revision: str | Sequence[str] | None = "e8f1a2b3c4d5"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Create improvedigital_inventory table."""
op.create_table(
"improvedigital_inventory",
sa.Column("tenant_id", sa.String(50), nullable=False),
sa.Column(
"entity_type",
sa.String(40),
nullable=False,
comment="360Yield entity kind: publisher, placement, package, size",
),
sa.Column("entity_id", sa.String(64), nullable=False),
sa.Column("name", sa.String(512), nullable=True),
sa.Column("parent_id", sa.String(64), nullable=True),
sa.Column(
"raw_json",
sa.JSON().with_variant(sa.dialects.postgresql.JSONB, "postgresql"),
nullable=False,
),
sa.Column(
"last_synced_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.tenant_id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("tenant_id", "entity_type", "entity_id"),
)
op.create_index(
"idx_improvedigital_inventory_tenant_type",
"improvedigital_inventory",
["tenant_id", "entity_type"],
)
op.create_index(
"idx_improvedigital_inventory_parent",
"improvedigital_inventory",
["tenant_id", "parent_id"],
)


def downgrade() -> None:
op.drop_index("idx_improvedigital_inventory_parent", table_name="improvedigital_inventory")
op.drop_index("idx_improvedigital_inventory_tenant_type", table_name="improvedigital_inventory")
op.drop_table("improvedigital_inventory")
83 changes: 83 additions & 0 deletions alembic/versions/impd02_add_improvedigital_line_item_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""add_improvedigital_line_item_stats

Per-line-item delivery stats cache for the Improve Digital adapter.
Populated by the periodic Report API sync (``POST /report/ext/preview``
with dimensions campaign_id/line_item_id and metrics impressions, clicks,
advertiser_payout, complete). Read by
``ImproveDigitalAdapter.get_media_buy_delivery`` so AdCP delivery surfaces
serve results without round-tripping to 360Yield on every request.

Spend is stored as currency-minor-unit micros (1 EUR = 1_000_000 micros)
to avoid floating-point precision loss when aggregating.

Revision ID: impd02b3c4d5
Revises: impd01a2b3c4
Create Date: 2026-08-04 10:00:00.000000
"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "impd02b3c4d5"
down_revision: str | Sequence[str] | None = "impd01a2b3c4"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.create_table(
"improvedigital_line_item_stats",
sa.Column("tenant_id", sa.String(length=50), nullable=False),
sa.Column(
"line_item_id",
sa.String(length=64),
nullable=False,
comment="360Yield Classic line-item ID (maps to the AdCP package)",
),
sa.Column(
"campaign_id",
sa.String(length=64),
nullable=True,
comment="360Yield Classic campaign ID (maps to the AdCP media buy)",
),
sa.Column("impressions", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("clicks", sa.BigInteger(), nullable=True),
sa.Column(
"completed_views",
sa.BigInteger(),
nullable=True,
comment="Video completions (Report API 'complete' metric)",
),
sa.Column(
"spend_micros",
sa.BigInteger(),
nullable=False,
server_default="0",
comment="Spend in currency-minor-unit micros (advertiser_payout * 1e6)",
),
sa.Column("currency", sa.String(length=3), nullable=True),
sa.Column("delivery_status", sa.String(length=40), nullable=True),
sa.Column("as_of", sa.DateTime(timezone=True), nullable=False),
sa.Column(
"last_synced_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
sa.ForeignKeyConstraint(["tenant_id"], ["tenants.tenant_id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("tenant_id", "line_item_id"),
)
op.create_index(
"idx_impd_li_stats_tenant_campaign",
"improvedigital_line_item_stats",
["tenant_id", "campaign_id"],
)


def downgrade() -> None:
op.drop_index("idx_impd_li_stats_tenant_campaign", table_name="improvedigital_line_item_stats")
op.drop_table("improvedigital_line_item_stats")
77 changes: 77 additions & 0 deletions alembic/versions/impd03_add_improvedigital_inventory_trgm_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""add trigram index for Improve Digital inventory picker search

The product-form pickers page through ``improvedigital_inventory`` with a
case-insensitive substring match on ``name`` OR ``entity_id`` (see
``ImproveDigitalInventoryRepository.list_picker_rows`` /
``count_picker_rows``). Without an index every keystroke is a sequential
scan of the tenant's placement rows — hundreds of thousands in dev. A GIN
trigram index lets PostgreSQL answer ``ILIKE '%term%'`` on both columns
with a bitmap scan.

``pg_trgm`` ships with PostgreSQL (and RDS) and is a *trusted* extension on
PostgreSQL 13+, so the application role can install it. The migration is
defensive anyway: if the extension is unavailable or cannot be created, it
logs a warning and leaves the table unindexed — the search still works,
just slower — rather than failing the boot-time migration and taking the
service down over a performance index.

Revision ID: impd03c4d5e6
Revises: a9b8c7d6e5f4
Create Date: 2026-09-11

"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "impd03c4d5e6"
down_revision: str | Sequence[str] | None = "a9b8c7d6e5f4"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None

INDEX_NAME = "ix_improvedigital_inventory_trgm"


def _ensure_pg_trgm(conn: sa.Connection) -> bool:
"""Install ``pg_trgm`` if needed. Returns False when it cannot be used."""
installed = conn.execute(sa.text("SELECT 1 FROM pg_extension WHERE extname = 'pg_trgm'")).scalar()
if installed:
return True
available = conn.execute(sa.text("SELECT 1 FROM pg_available_extensions WHERE name = 'pg_trgm'")).scalar()
if not available:
print("WARNING: pg_trgm is not available on this PostgreSQL server; skipping trigram index")
return False
# Savepoint so a permission failure does not abort the surrounding
# migration transaction.
try:
with conn.begin_nested():
conn.execute(sa.text("CREATE EXTENSION IF NOT EXISTS pg_trgm"))
except sa.exc.DBAPIError as exc:
print(f"WARNING: could not create pg_trgm extension ({exc.orig}); skipping trigram index")
return False
return True


def upgrade() -> None:
conn = op.get_bind()
if conn.dialect.name != "postgresql":
return
if not _ensure_pg_trgm(conn):
return
conn.execute(
sa.text(
f"CREATE INDEX IF NOT EXISTS {INDEX_NAME} "
"ON improvedigital_inventory USING gin (name gin_trgm_ops, entity_id gin_trgm_ops)"
)
)


def downgrade() -> None:
conn = op.get_bind()
if conn.dialect.name != "postgresql":
return
# The extension is left installed: other objects may come to rely on it.
conn.execute(sa.text(f"DROP INDEX IF EXISTS {INDEX_NAME}"))
45 changes: 42 additions & 3 deletions core/middleware/admin_mount.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@

from __future__ import annotations

import logging
import time
from typing import Any

from src.core.domain_config import get_sales_agent_domain, is_admin_domain

logger = logging.getLogger(__name__)

# Path prefixes the Flask admin claims. Anything under one of these
# segments dispatches to Flask; everything else falls through to A2A
# (which serves /.well-known/agent-card.json + the A2A RPC endpoint at
Expand Down Expand Up @@ -118,7 +122,7 @@ async def __call__(self, scope: dict, receive: Any, send: Any) -> None:
if self._is_admin_host(scope):
new_scope = dict(scope)
new_scope["root_path"] = scope.get("root_path", "") + "/admin"
await self.wsgi_app(new_scope, receive, send)
await self._dispatch_wsgi(new_scope, receive, send)
return

path = scope.get("path", "")
Expand Down Expand Up @@ -182,12 +186,47 @@ async def __call__(self, scope: dict, receive: Any, send: Any) -> None:
# str length for ASCII prefixes.
new_scope["raw_path"] = raw[len(prefix) :] or b"/"
new_scope["root_path"] = scope.get("root_path", "") + prefix
await self.wsgi_app(new_scope, receive, send)
await self._dispatch_wsgi(new_scope, receive, send)
return
await self.wsgi_app(scope, receive, send)
await self._dispatch_wsgi(scope, receive, send)
return
await self.app(scope, receive, send)

async def _dispatch_wsgi(self, scope: dict, receive: Any, send: Any) -> None:
"""Hand an HTTP request to the Flask WSGI app with boundary logging.

Logs one START line before the request enters the WSGI bridge and one
DONE/FAILED line when it comes back. A START with no matching DONE means
the request died inside the bridge or the worker thread — the case a
reverse proxy reports as 502 while Flask itself never logs anything.
Only unsafe methods are logged at INFO to keep GET noise down.
"""
method = scope.get("method", "?")
path = scope.get("path", "")
noisy = method in ("GET", "HEAD", "OPTIONS")
level = logging.DEBUG if noisy else logging.INFO
content_length = None
for raw_name, raw_value in scope.get("headers", ()):
if raw_name.lower() == b"content-length":
content_length = raw_value.decode("latin-1")
logger.log(
level,
"[admin_mount] START %s %s host=%s content_length=%s",
method,
path,
self._resolve_host(scope),
content_length,
)
started = time.monotonic()
try:
await self.wsgi_app(scope, receive, send)
except BaseException:
logger.exception(
"[admin_mount] FAILED %s %s after %.0f ms", method, path, (time.monotonic() - started) * 1000
)
raise
logger.log(level, "[admin_mount] DONE %s %s in %.0f ms", method, path, (time.monotonic() - started) * 1000)

@staticmethod
def _resolve_host(scope: dict) -> str | None:
"""Pick the externally-visible host from ASGI scope headers.
Expand Down
Loading
Loading