Events
Understand how Owlmetry captures, stores, and queries events with log levels, sessions, and timestamps.
Events are the core data unit in Owlmetry. Every interaction your app records -- a screen view, a button tap, a warning, an error -- is captured as an event and sent to the server for storage and analysis.
Instrument this project with Owlmetry event logging.
Load the `/owlmetry-swift` or `/owlmetry-node` skill first
(install the owlmetry-skills Claude Code plugin if missing).
- Add Owl.info/warn/error calls at key points: caught exceptions,
user actions, state transitions, and failure paths.
- Add .owlScreen() to every distinct screen for automatic screen
view tracking.
- Don't log inside loops or high-frequency callbacks — each call
sends a network event.Event Structure
Each event contains the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | What happened -- a human-readable description of the event |
level | string | Yes | Log level: info, debug, warn, or error |
session_id | string (UUID) | Yes | Groups events from a single app session |
client_event_id | string (UUID) | No | Client-generated ID used for deduplication |
user_id | string | No | User identifier (anonymous or real) |
source_module | string | No | Module or component that generated the event |
screen_name | string | No | Current screen or page name |
custom_attributes | object | No | Arbitrary string key-value pairs (200 char value limit) |
environment | string | No | Runtime environment (ios, ipados, macos, android, web, backend) |
os_version | string | No | Operating system version |
app_version | string | No | App version string |
sdk_name | string | No | Identifier of the Owlmetry SDK that produced the event (e.g. owlmetry-swift, owlmetry-node). Auto-set by official SDKs. |
sdk_version | string | No | Version of the Owlmetry SDK that produced the event. Auto-set by official SDKs. |
build_number | string | No | App build number |
device_model | string | No | Device hardware model |
locale | string | No | User locale (e.g., en_US) |
country_code | string | No | 2-letter ISO country code, derived server-side from the request IP via Cloudflare. Never sent by SDKs. Always null for backend apps. |
is_dev | boolean | No | Whether the event came from a development build |
experiments | object | No | Active A/B experiment variant assignments |
timestamp | string (ISO 8601) | No | When the event occurred (server uses receipt time if omitted) |
Events are sent to the server in batches via the ingest endpoint. The SDKs handle batching automatically.
Log Levels
Every event has a log level that indicates its severity:
| Level | Purpose |
|---|---|
info | Normal activity -- screen views, user actions, lifecycle events |
debug | Diagnostic information useful during development |
warn | Something unexpected happened but the app continued normally |
error | Something failed -- a network request, a parse operation, etc. |
The dashboard and CLI support filtering events by level to help you focus on what matters.
Sessions
A session_id is a UUID generated fresh every time an SDK calls configure(). This typically happens once per app launch, so a session corresponds to a single run of your app from open to close.
All events emitted during that session share the same session_id, which allows you to:
- Reconstruct a user's journey through a single app session
- Filter the event timeline to a specific session
- Count unique sessions over a time period
The SDKs generate the session ID automatically. You do not need to manage it yourself.
Timestamp Validation
The server validates event timestamps on ingestion:
- Future limit: Events with timestamps more than 5 minutes in the future are rejected.
- Past limit: Events with timestamps more than 30 days in the past are rejected.
- Invalid format: Events with unparseable timestamps are rejected.
If a timestamp is omitted, the server uses the time the event was received.
Deduplication
If an event includes a client_event_id, the server checks for duplicates within a 48-hour window. If an event with the same client_event_id and app_id was already ingested, the duplicate is silently dropped.
This makes it safe for SDKs to retry failed ingestion requests without creating duplicate data. The 48-hour window is bounded so Postgres can skip older partitions during the lookup.
Custom Attributes
Events can carry arbitrary metadata as custom_attributes -- a flat object of string keys and string values. Each value is limited to 200 characters.
{
"custom_attributes": {
"plan": "pro",
"feature_flag": "new-checkout",
"item_count": "3"
}
}The SDKs also auto-capture certain fields as custom attributes. For example, the Swift SDK records _connection (wifi, cellular, ethernet, offline) via its network monitor.
Auto-Captured Fields
The SDKs automatically populate device and app metadata on every event without any additional configuration:
device_model-- hardware model (e.g., "iPhone15,2")os_version-- operating system version (e.g., "17.4.1")app_version-- the app's version string from its bundle or packagebuild_number-- the app's build numberlocale-- the device locale (e.g., "en_US")environment-- the runtime platform (ios, ipados, macos, android, web, backend)sdk_name-- identifier of the Owlmetry SDK producing the event (e.g.owlmetry-swift,owlmetry-node)sdk_version-- version of the Owlmetry SDK producing the eventis_dev-- whether this is a development build (see Data Mode)
For SDK-specific details, see the Swift SDK events guide or the Node SDK events guide.
Country (server-derived)
country_code on events and last_country_code on users is a 2-letter ISO
country code (e.g. US, DE). It's derived server-side from Cloudflare's
CF-IPCountry header — SDKs do not send it, and IP addresses are never
stored. If the request didn't pass through Cloudflare (self-hosted without a
CDN, direct-to-origin dev requests), the field is null. Historical events
ingested before the feature was introduced are also null.
Backend apps: events from apps with platform: "backend" always have
country_code: null. The header on those requests reflects the customer's
hosting datacenter, not the end user, so capturing it would just pollute
geographic reporting.
Partitioned Storage
Events are stored in a PostgreSQL table partitioned by month on the timestamp column. Partitions for the current month and the next two months are created automatically on server startup and during migrations.
Monthly partitioning allows Postgres to scan only the relevant partitions when querying a date range, keeping event queries fast as your data grows. The events table has no primary key or foreign keys due to PostgreSQL partition constraints -- data integrity is enforced at the application level.
Querying Events
Events can be queried through the dashboard, the CLI, or the API. Common filters include:
- Team, project, or app -- scope events to a specific context
- Log level -- filter by severity
- User ID or session ID -- trace a specific user or session
- Environment -- filter by platform (ios, android, web, etc.)
- Screen name -- find events from a specific screen
- Date range -- query a time window
- Data mode -- filter by production, development, or all (see Data Mode)
Results are paginated with cursor-based pagination.
Concepts
Core building blocks of Owlmetry — events, metrics, funnels, experiments, issues, attachments, integrations, user properties, jobs, projects, authentication, data mode, and latest version detection.
Structured Metrics
Track performance with lifecycle operations (start, complete, fail, cancel) and single-shot measurements.
