Skip to main content

07 — Monitoring, Logging & Admin Dashboard

Ring: 1 (basic logging) + Ring 2 (dashboard UI + user dashboard) Dependency: R1-1 (Auth), R1-2 (Cost Control — usage_daily table) Handbook: Ch. 47 (observability), Ch. 206-210 (monitoring, security) Related: Hetzner migration plan — Uptime Kuma (infra monitoring)

Problem

  • We don’t know what users are doing — no activity log exists.
  • Admin panel only contains segment CRUD.
  • AI cost is invisible (addressed by 02-api-cost-control but no visualization).
  • Pipeline health is unmonitored (latency, error rate, accuracy).
  • No user dashboard — after login, goes directly to the companies page.
  • No click-level analytics (which button, which flow, where users drop off).

Decisions

D1: 3-Layer Monitoring

LAYER 1: Infrastructure (READY in Hetzner plan)
  → Uptime Kuma: site/API uptime, response time
  → Telegram alert: CPU/RAM threshold breach
  → Coolify: container health
  → NO ADDITIONAL WORK — defined in migration plan

LAYER 2: Application Monitoring (Ring 1-2)
  → Activity log: every significant action written to DB
  → AI cost tracking: usage_daily + ai_job_runs (with 02)
  → API metrics: response time, error rate per route
  → Pipeline metrics: latency per stage, accuracy

LAYER 3: Product Analytics — Click-Level (Ring 2-3)
  → PostHog self-hosted (Hetzner VPS, deployed via Coolify)
  → Heatmaps, session replay, funnel analysis
  → Data stays local, GDPR compliant
  → Cost: $0 (self-hosted)

D2: Activity Log — What Gets Logged

Ring 1 (launch):
Action CategoryExamples
Discoverydiscovery.started, discovery.completed, discovery.failed
Headhuntheadhunt.started, headhunt.completed
Leadlead.created, lead.updated, lead.deleted
Interactioninteraction.created
Tasktask.created, task.completed
Batchbatch.started, batch.completed, batch.failed
Authuser.login, user.logout, user.signup
Orgorg.created, member.invited, member.removed, settings.changed
Billingplan.upgraded, plan.downgraded, credit.purchased, credit.consumed
Viewcompany.viewed, lead.viewed, contact.viewed
Ring 2-3 (after PostHog is added):
ActionTool
Button clicksPostHog
Filter/sort changesPostHog
Scroll depthPostHog
Session durationPostHog
Funnel analysis (signup → first discovery → first lead)PostHog
HeatmapsPostHog
Session replayPostHog
DB activity_log = BUSINESS actions (queryable, integrated with billing). PostHog = UX actions (visual analysis, product decisions).

D3: Dashboard Pages

Admin Dashboard (super_admin — /admin/*):
PageContentRing
/admin/dashboardOverview: today’s cost, active users, error count, pipeline health score, recent actionsR2
/admin/usageCost detail: daily/weekly/monthly chart, by provider/route/orgR2
/admin/usersUser/org list: plan, last activity, usage rateR2
/admin/pipelinePipeline health: average latency, error rate, accuracy, per stageR2
/admin/activityActivity log: filterable table (user, action, date, org)R2
/admin/segmentsExisting: Segment CRUD✅ EXISTS
/admin/providersAI provider comparison: cost, latency, error rateR2
/admin/search-trendsPopular keywords, countries, industriesR2
User Dashboard (user — /dashboard) — Ring 2:
┌─────────────────────────────────────────────────────────────┐
│  Welcome, Alex                                   [Pro Plan] │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─── This Month ───────┐ ┌─── Quick Actions ───────────┐  │
│  │ 12 discoveries made  │ │ [+ New Discovery]            │  │
│  │ 47 companies found   │ │ [Recent Leads]               │  │
│  │ 5 leads created      │ │ [Pending Tasks (3)]          │  │
│  └──────────────────────┘ └──────────────────────────────┘  │
│                                                             │
│  ┌─── Usage ────────────────────────────────────────────┐   │
│  │ Discovery: ████████████░░░░ 38/50                    │   │
│  │ Contacts:  ██████░░░░░░░░░░ 28/100                   │   │
│  │ Credits:   23 remaining                               │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─── Recent Searches ───────────────────┐ ┌── Leads ───┐  │
│  │ "chemicals" Germany — 12 co. (yesterday) │ Saved: 5   │  │
│  │ "textiles" Italy — 8 co. (3 days ago)    │ Contact: 3 │  │
│  │ "machinery" Poland — 15 co. (1 week ago) │ Negot.: 1  │  │
│  └──────────────────────────────────────────┘ └──────────┘  │
│                                                             │
│  ┌─── Upcoming Tasks ──────────────────────────────────┐    │
│  │ 🔴 TextilChem GmbH — Send catalog (overdue!)       │    │
│  │ 🟡 ChemTrade AG — Follow up (tomorrow)              │    │
│  │ 🟢 Mueller KG — Send price quote (3 days)           │    │
│  └──────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─── Team Activity (Team+) ───────────────────────────┐   │
│  │ Ahmet: discovered 3 companies (today)                │   │
│  │ Mehmet: created 2 leads (yesterday)                  │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

D4: User Dashboard Timing

  • Ring 1: None. After login → /discovery page (same as current).
  • Ring 2: /dashboard page is created, becomes the landing page after login.

Data Model

New Table: Activity Log

CREATE TABLE export_ai_activity_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID NOT NULL REFERENCES export_ai_organizations(id),
  user_id UUID NOT NULL REFERENCES auth.users(id),
  action TEXT NOT NULL,               -- 'discovery.started', 'lead.created', etc.
  resource_type TEXT,                 -- 'search', 'company', 'lead', 'contact', 'batch_job'
  resource_id UUID,                   -- ID of the related record
  metadata JSONB DEFAULT '{}',        -- { keyword, country, cost, contacts_found, ... }
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- RLS
ALTER TABLE export_ai_activity_log ENABLE ROW LEVEL SECURITY;

-- Indexes (for fast queries)
CREATE INDEX idx_activity_org_date ON export_ai_activity_log(organization_id, created_at DESC);
CREATE INDEX idx_activity_user ON export_ai_activity_log(user_id, created_at DESC);
CREATE INDEX idx_activity_action ON export_ai_activity_log(action, created_at DESC);

-- Old record cleanup (after 90 days — via batch job)
-- Or: partition by month (Ring 4 for large data)

New Table: API Metrics (optional — Ring 2)

CREATE TABLE export_ai_api_metrics (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  endpoint TEXT NOT NULL,             -- '/api/discover', '/api/headhunt', ...
  method TEXT NOT NULL,               -- 'GET', 'POST'
  status_code INT NOT NULL,
  response_time_ms INT NOT NULL,
  organization_id UUID,
  user_id UUID,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_api_metrics_endpoint ON export_ai_api_metrics(endpoint, created_at DESC);
This table logs every API request. Implemented in Ring 2. In Ring 1, activity_log alone is sufficient.

Architecture

Activity Logger

// lib/logging/activity.ts

interface IActivityEvent {
  action: string;           // 'discovery.started'
  resourceType?: string;    // 'search'
  resourceId?: string;
  metadata?: Record<string, unknown>;
}

async function logActivity(
  session: ISessionContext,
  event: IActivityEvent,
  request?: Request
): Promise<void>

// Usage:
await logActivity(session, {
  action: 'discovery.started',
  resourceType: 'search',
  metadata: { keyword: 'chemicals', country: 'DE' }
}, request);
logActivity() is async + fire-and-forget (await is optional). Logging errors DO NOT BLOCK user operations.

Middleware-Based API Metrics (Ring 2)

// Added to middleware.ts
// Logs every API request's response time + status code
// Async — does not delay the response

PostHog Integration (Ring 2-3)

On Hetzner VPS (Galata or Kadikoy):
  → Deploy PostHog self-hosted via Coolify
  → posthog.cernio.com (dashboard behind VPN)
  → Client-side: posthog-js SDK
  → Server-side: posthog-node SDK (optional)

Current Code Impact

New Files (Ring 1)

FileContent
lib/logging/activity.tslogActivity() + IActivityEvent
lib/logging/types.tsAction type enum/const

New Files (Ring 2)

FileContent
app/admin/dashboard/page.tsxAdmin overview
app/admin/usage/page.tsxCost detail + charts
app/admin/users/page.tsxUser/org management
app/admin/pipeline/page.tsxPipeline health
app/admin/activity/page.tsxActivity log (filterable)
app/admin/providers/page.tsxAI provider comparison
app/admin/search-trends/page.tsxSearch trends
app/dashboard/page.tsxUser dashboard (landing)

Files to Change (Ring 1)

FileChange
All API routesAdd logActivity() call
middleware.tsAPI metrics logging (Ring 2)

Data Retention

TableRetention PeriodReason
activity_log90 days (then archive/delete)Generates a lot of data, old records unnecessary
api_metrics30 days (then aggregate + delete)Raw metrics only for short-term debugging
usage_dailyUnlimitedAggregated data, small, needed for billing
ai_job_runs180 daysCost analysis + debugging
PostHog90 days (self-hosted setting)Session replay takes a lot of space
Batch job (cron) to clean old data will be added in Ring 2.

Future Decisions

FD-1: PostHog Self-Hosted (Ring 2-3)

Deploy via Coolify on Hetzner. Click tracking, heatmaps, session replay, funnel analysis. Cost: $0. Data stays local.

FD-2: Anomaly Detection (Ring 3+)

Learn normal usage patterns, alert on deviations. “Today’s cost is 10x yesterday’s” → toast + email.

FD-3: Custom Admin Dashboards (Ring 3)

Admin can select and arrange their own widgets. Drag-and-drop layout.

FD-4: Data Warehouse / Export (Ring 4)

Activity log + metrics → BigQuery or ClickHouse export. For advanced analysis.

FD-5: Real-time Dashboard (Ring 4)

Live-updating admin dashboard via Supabase Realtime or WebSocket.

Atomic Tasks

#TaskRingSize
MON-1export_ai_activity_log table + RLS + indexR1Migration
MON-2lib/logging/activity.ts — logActivity() + typesR1Small
MON-3logActivity() integration in all API routesR1Medium
MON-4export_ai_api_metrics table + middleware loggingR2Medium
MON-5/admin/dashboard — overviewR2Large
MON-6/admin/usage — cost charts (Recharts or Chart.js)R2Large
MON-7/admin/users — user/org list + detailR2Medium
MON-8/admin/pipeline — latency, error rate, accuracyR2Medium
MON-9/admin/activity — filterable log tableR2Medium
MON-10/admin/providers — AI provider comparisonR2Small
MON-11/admin/search-trends — popular searchesR2Small
MON-12/dashboard — user dashboard (landing page)R2Large
MON-13PostHog self-hosted deploy (Hetzner)R2-3Medium
MON-14PostHog client-side SDK integrationR2-3Small
MON-15Data retention cron job (old record cleanup)R2Small