Architecture

Data Model

Domain entities and important relationships in the Logga Prisma schema.

The Prisma schema is the best source of truth for how Logga stores information. This page translates the current models into a product-oriented mental model.

Core Hierarchy

text
User
  -> WorkspaceMembership
    -> Workspace
      -> Environment
        -> Project
          -> Channel
            -> Event
            -> Session
          -> TrackedUser

Additional workspace-scoped entities:

  • ApiKey
  • AlertRule
  • Notification
  • DeviceToken
  • ReportConfig

Model Cheatsheet

Model Purpose Important fields
User Human account email, name, passwordHash, avatarUrl
Workspace Top-level tenant name
Environment Deployment slice inside a workspace slug, name, defaultProjectId
Project Product or app within an environment name, workspaceId, environmentId
Channel Named stream inside a project slug, name, notificationsEnabled, notificationRule
Event Immutable telemetry record eventType, level, tags, metadata, actor, triggeredBy, trackedUserId, createdAt
TrackedUser Stable identity for a product user in a project externalId, name, email, avatarUrl, role, label, properties, firstSeenAt, lastSeenAt
Session Mutable process with lifecycle status, progress, currentStep, summary, ttlSeconds
AlertRule Notification rule config condition, isActive, optional channelId
Notification Persisted alert result title, body, ruleId, optional eventId, optional sessionId
ReportConfig Scheduled aggregation config schedule, metric, aggregateField, filters, groupBy, channelIds

Workspace And Environment Scoping

Most operational models carry both workspaceId and environmentId.

This matters because:

  • the same workspace can separate production and sandbox activity
  • rules, reports, sessions, notifications, and device tokens are environment-aware
  • clients should avoid assuming a workspace has only one active environment

Events

Events are the canonical telemetry primitive.

Important characteristics:

  • immutable once created
  • attached to a project and a channel
  • optionally linked to a session
  • can carry both userId and richer actor snapshot data
  • can carry triggeredBy metadata derived from the authenticated caller
  • now drives the dashboard's per-project tracked-users view

Interesting fields:

Field Use
eventType Semantic event name, such as invoice_sent
level Severity or tone: debug, info, success, warning, error
tags Lightweight filtering labels
metadata Arbitrary JSON payload
idempotencyKey Deduplication handle

Tracked Users

TrackedUser is a project-scoped identity row representing one of your product's end users. It is separate from the User model (which represents humans using the Logga dashboard).

Shape:

  • projectId + externalId (unique together) — the stable id your SDK sends as actor.id or userId
  • reserved identity columns: name, email, avatarUrl, role, label
  • properties: free-form JSONB for custom key-value metadata (strings, numbers, booleans, null; ≤50 keys, key ≤64 chars, string value ≤1024 chars)
  • firstSeenAt, lastSeenAt: aggregate timestamps maintained by ingestion

How rows get populated:

  • every event with an actor.id or userId upserts a matching TrackedUser row
  • identity fields from the event's actor object overwrite the stored values
  • properties from the event are a patch: sent keys are written, missing keys are kept, a key sent as null is removed. The merge runs in one UPDATE, so concurrent events for the same user keep each other's keys, and it is refused when the result would exceed 50 keys (the event is still stored, with a warnings entry in the response)
  • fields the event does not carry are left untouched
  • the dashboard PATCH /v1/dashboard/projects/:projectId/users endpoint can also edit identity fields and properties directly, replacing the whole properties object. Subsequent events with actor.* overwrite identity fields and patch the properties they carry (there is no "lock" mechanism today)

Event.trackedUserId is the FK linking each event back to its tracked user. Event.userId (the external id) and Event.actor (the point-in-time snapshot) are still populated for historical audit.

Unattributed events are events with neither actor.id nor userId and no corresponding trackedUserId.

Sessions

Sessions represent multi-step flows that evolve over time.

Common examples:

  • background jobs
  • deploy flows
  • AI agent runs
  • build pipelines

Fields worth knowing:

  • status: starts as running-state and can become completed, failed, or closed
  • progress: integer 0 through 100
  • currentStep: short text description
  • summary: final operator-facing outcome
  • ttlSeconds: optional expected time budget

Rules And Notifications

Rules are configuration. Notifications are outcomes.

Relationship:

text
AlertRule -> Notification
Notification -> Event? / Session?

A rule can be:

  • global to the environment
  • scoped to one channel

Rules store conditions as JSON, which gives flexibility while keeping the API explicit.

Reports

Reports are scheduled, persisted query definitions rather than ad hoc saved UI state.

Important fields:

  • schedule: currently daily or weekly
  • metric: count, sum, or average
  • aggregateField: required for sum and average
  • filters: serialized JSON object
  • groupBy: currently supports actor grouping
  • channelIds: one or more targeted channels

Indexing And Performance Notes

The schema already includes targeted indexes on common access patterns such as:

  • event creation time per project and per channel
  • session status and recency
  • workspace and environment recency for rules and notifications
  • user plus project access patterns

Recent additions include a partial event index optimized for project-level user drilldown:

  • Event(projectId, userId, createdAt DESC) WHERE userId IS NOT NULL
  • Event(trackedUserId, createdAt) for the tracked-user detail aggregations
  • TrackedUser(projectId, lastSeenAt DESC) for the users list
  • TrackedUser(projectId, email) for email-based search

If query shapes change substantially, revisit the Prisma schema rather than assuming current indexes are enough.