Owlmetry
SDKsSwift SDK

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):

AttributeDescription
_error_typeFully-qualified Swift type name (e.g. Foundation.URLError). Drives issue grouping.
_error_stackThread call stack at the call site (truncated at 16000 chars).
_error_domainBridged NSError domain.
_error_codeBridged NSError code (stringified).
_error_cause_<n>_type, _error_cause_<n>_messageUnderlying-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
)
ParameterDescription
messageA descriptive string for the event. Choose messages that are specific and searchable. Truncated to 2000 characters — put long content in attributes instead.
screenNameThe screen where the event occurred. Ties the event to a location in the UI.
attributesKey-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.
attachmentsOptional file attachments (logs, screenshots, etc.). Most useful on Owl.error(). See Attachments.
file, function, lineAutomatically 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:

EventLevelWhen
sdk:session_startedinfoOn Owl.configure(). Includes _launch_ms (time from process start to configure).
sdk:app_foregroundedinfoWhen the app enters the foreground.
sdk:app_backgroundedinfoWhen the app enters the background.
sdk:screen_appeareddebugWhen a view with .owlScreen() appears. See Screen Tracking.
sdk:screen_disappeareddebugWhen a view with .owlScreen() disappears. Includes _duration_ms.
sdk:network_requestdebug/warn/errorOn 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:

AttributeDescription
_http_methodGET, POST, PUT, etc.
_http_urlSanitized URL (scheme + host + path only; query parameters stripped for privacy).
_http_statusHTTP status code.
_http_duration_msRequest duration in milliseconds.
_http_response_sizeResponse body size in bytes.
_http_errorError 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 screenName for UI events. It ties events to where they happened, making dashboard filtering more useful.
  • Use attributes for 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.

Ready to get started?

Connect your agent via MCP or CLI and start tracking.