Events
Log events at different levels (info, debug, warn, error) with custom attributes and screen context.
Events are the core unit of data in Owlmetry. The Swift SDK provides four log levels, each designed for a different class of information. For a broader overview of the event model, see Events.
Log Levels
Info
Normal operations worth recording: user actions, feature usage, successful completions. This is the default level for most instrumentation.
Owl.info("User tapped checkout", screenName: "Cart")Debug
Verbose detail useful only during development: cache hits, state transitions, intermediate values. Debug events are filtered out when viewing production data.
Owl.debug("Cache hit for user preferences", screenName: "Home", attributes: ["key": "user_prefs"])Warn
Something unexpected that the app recovered from: slow responses, fallback paths, retries needed.
Owl.warn("Slow network response", attributes: ["latency_ms": "1200", "endpoint": "/api/feed"])Error
Something failed: network errors, parse failures, missing data, caught exceptions.
Owl.error("Failed to load profile image", screenName: "Profile", attributes: ["error": error.localizedDescription])Error from a caught Error value
If you have an Error instance from a do/catch (or any other source), pass it directly. The SDK extracts the runtime type, NSError domain/code, the underlying-error cause chain, and the call stack into reserved _error_* attributes — and the server's issue tracker uses _error_type as a fingerprint discriminator so different error classes with the same wording stay on separate issues.
do {
try await PhotoConverter.convert(inputURL: url)
} catch {
Owl.error(error, "while converting photo", screenName: "PhotoConverterView")
}The second argument is an optional caller-supplied message that overrides the auto-derived one. Pass nil (or omit) to use error.localizedDescription / String(describing: error).
The SDK auto-attaches these reserved attributes (all underscore-prefixed):
| Attribute | Description |
|---|---|
_error_type | Fully-qualified Swift type name (e.g. Foundation.URLError). Drives issue grouping. |
_error_stack | Thread call stack at the call site (truncated at 16000 chars). |
_error_domain | Bridged NSError domain. |
_error_code | Bridged NSError code (stringified). |
_error_cause_<n>_type, _error_cause_<n>_message | Underlying-error chain via userInfo[NSUnderlyingErrorKey], up to 5 levels deep. |
Caller-provided keys in attributes survive untouched; the SDK never overwrites a non-_error_* key you pass.
Method Signature
All four methods share the same signature:
Owl.info(
_ message: String,
screenName: String? = nil,
attributes: [String: String?] = [:],
attachments: [OwlAttachment]? = nil,
file: String = #file,
function: String = #function,
line: Int = #line
)| Parameter | Description |
|---|---|
message | A descriptive string for the event. Choose messages that are specific and searchable. Truncated to 2000 characters — put long content in attributes instead. |
screenName | The screen where the event occurred. Ties the event to a location in the UI. |
attributes | Key-value pairs of additional structured data. Values are String? so optional values pass through directly — nil-valued keys are silently dropped before the event ships. |
attachments | Optional file attachments (logs, screenshots, etc.). Most useful on Owl.error(). See Attachments. |
file, function, line | Automatically captured from the call site. Do not pass these manually. |
Optional attribute values
Because attributes accepts optional values, a String? from your domain code can flow into the literal without unwrapping. Anything that resolves to nil is omitted from the event:
let contractId: String? = session.draftId // may be nil
let kind: String? = draft?.type // may be nil
Owl.info("Draft created", attributes: [
"context": "createDraft",
"contractId": contractId,
"kind": kind,
])If contractId is nil, only context and (if present) kind reach the dashboard.
Auto-Captured Source Info
The SDK automatically captures the source file, function name, and line number of every log call. This metadata is included on the event and visible in the dashboard, making it straightforward to trace an event back to the code that emitted it.
Country Is Server-Derived
Each event's country_code is resolved server-side from the Cloudflare CF-IPCountry header on the ingest request. The SDK never sends it, and raw IP addresses are not stored. If the server is self-hosted without Cloudflare (or the request didn't pass through it), country_code is null. See Events › Country.
File Attachments
You can attach files to any event via the attachments parameter, but in practice this is almost always used with Owl.error() to capture the exact input bytes that triggered a failure (a media file that failed to decode, a document that failed to parse, etc.).
OwlAttachment supports two sources:
// From a file on disk
OwlAttachment(fileURL: url, name: "input.heic", contentType: "image/heic")
// From in-memory bytes
OwlAttachment(data: debugJSON, name: "debug.json", contentType: "application/json")Both initializers take an optional name (defaults to fileURL.lastPathComponent) and an optional contentType (MIME type).
do {
try await PhotoConverter.convert(inputURL: url)
} catch {
Owl.error(
"image conversion failed",
screenName: "PhotoConverterView",
attributes: ["stage": "decode", "error": "\(error)"],
attachments: [
OwlAttachment(fileURL: url),
]
)
}Uploads run on a separate serial queue so a large file never blocks event batching. Upload behaviour is strictly non-fatal: if the device is offline, the per-user bucket (default 250 MB per user) or project quota (default 5 GB) is exhausted, or the server otherwise rejects the file, the event itself still posts and the attachment is dropped silently (with a warning logged via OSLog). There is no offline queue for attachments in v1: if the device is offline when the error fires, the attachment is discarded but the event queues normally.
The SDK automatically tags each attachment with the currently identified user (Owl.setUser()) so per-user quotas apply correctly. See Attachments for a deeper overview.
Automatic Lifecycle Events
The SDK emits several events automatically without any manual calls:
| Event | Level | When |
|---|---|---|
sdk:session_started | info | On Owl.configure(). Includes _launch_ms (time from process start to configure). |
sdk:app_foregrounded | info | When the app enters the foreground. |
sdk:app_backgrounded | info | When the app enters the background. |
sdk:screen_appeared | debug | When a view with .owlScreen() appears. See Screen Tracking. |
sdk:screen_disappeared | debug | When a view with .owlScreen() disappears. Includes _duration_ms. |
sdk:network_request | debug/warn/error | On URLSession HTTP request completion. See below. |
Network Request Tracking
The SDK automatically instruments URLSession completion-handler-based requests. This is enabled by default and requires no code beyond Owl.configure().
Each tracked request emits a sdk:network_request event with these attributes:
| Attribute | Description |
|---|---|
_http_method | GET, POST, PUT, etc. |
_http_url | Sanitized URL (scheme + host + path only; query parameters stripped for privacy). |
_http_status | HTTP status code. |
_http_duration_ms | Request duration in milliseconds. |
_http_response_size | Response body size in bytes. |
_http_error | Error description (network failures only). |
Log levels are assigned based on the response: debug for 2xx/3xx, warn for 4xx/5xx, and error for network failures with no response. Debug-level network events stay out of production views by default, while 4xx/5xx and network errors surface clearly.
Error-level network events are grouped into issues per HTTP method + host + path, so a failure to api.revenuecat.com/v1/subscribers produces a different issue from a failure to your own backend. Numeric path segments and UUIDs are templated (e.g. /users/123 and /users/456 collapse onto the same issue).
The SDK's own requests to the Owlmetry ingest endpoint are automatically excluded. To disable network request tracking entirely:
try Owl.configure(
endpoint: "https://ingest.owlmetry.com",
apiKey: "owl_client_...",
networkTrackingEnabled: false
)Linking Client and Backend Events
Every event the Swift SDK emits is stamped with a session ID that's generated on Owl.configure() and accessible via Owl.sessionId. When the app calls your backend, forward this value as a request header so that events logged by the backend can be tagged with the same session ID:
var request = URLRequest(url: apiURL)
if let sessionId = Owl.sessionId {
request.setValue(sessionId, forHTTPHeaderField: "X-Owl-Session-Id")
}On a Node.js backend using @owlmetry/node, read the header and scope the handler with Owl.withSession(). Events from both sides of the request then share the same session_id and line up in the dashboard's session view.
Best Practices
- Be specific with messages. Prefer
"Failed to load profile image"over"error". Specific messages are easier to search and filter. - Use
screenNamefor UI events. It ties events to where they happened, making dashboard filtering more useful. - Use
attributesfor structured data. Anything you might want to filter, group, or search on later should be an attribute rather than embedded in the message string. - Avoid logging PII. Do not include emails, phone numbers, passwords, or authentication tokens in events.
- Avoid high-frequency events. Do not log on every frame, scroll position change, or timer tick. Focus on meaningful actions and outcomes.
