The Atatus iOS SDK can forward your application's logs to Atatus, where you search and group them alongside the RUM sessions and traces they came from. Logging ships as its own product, AtatusLogs, so adding it does not change what RUM collects.
Prerequisites
- RUM is set up in your application, or at minimum the SDK is initialized. See iOS Monitoring Setup.
- The
AtatusLogsproduct is added to your target.
Setup
1. Add the product
With Swift Package Manager, add the AtatusLogs product to your target. With CocoaPods, declare the pod:
pod 'AtatusLogs', '~> 1.0'
2. Enable logging
Enable the product once, right after Atatus.initialize():
import AtatusCore
import AtatusLogs
Atatus.initialize(
with: Atatus.Configuration(
licenseKey: "<LICENSE_KEY>",
env: "<ENV_NAME>",
service: "<APP_NAME>"
),
trackingConsent: .granted
)
Logs.enable()
Logs.enable() does nothing if the SDK is not initialized first, and Logger.create() then returns a logger that discards everything you send it. The SDK prints the reason to the console rather than throwing, so check the console if your logs never arrive.
3. Create a logger
A logger carries the configuration for the logs it sends. Create one per area of your application, or one for the whole application:
let logger = Logger.create(
with: Logger.Configuration(
name: "checkout",
networkInfoEnabled: true,
remoteLogThreshold: .info,
consoleLogFormat: .short
)
)
Hold on to the logger. Creating one per call site is wasteful, and loggers are safe to use from any thread.
4. Send logs
Six levels are available, from least to most severe: debug, info, notice, warn, error, and critical. Each takes a message, an optional Error, and optional attributes:
logger.info("Checkout opened", attributes: ["cart.items": 3])
logger.warn("Slow response", attributes: ["duration_ms": 1_430])
do {
try pay()
} catch {
logger.error("Payment failed", error: error, attributes: ["order.id": "A-1001"])
}
Passing an Error records its type, message, and stack trace as error.kind, error.message, and error.stack on the log.
Log uploads start once Atatus confirms that logging is enabled for your account, which the SDK checks shortly after launch. Logs written before that confirmation are held on disk, not lost, so expect a short delay on the first run. Logs stay disabled if log collection is turned off for the application in your Atatus account.
Logger configuration
Every option on Logger.Configuration:
| Option | Default | Purpose |
|---|---|---|
service |
Atatus.Configuration.service, then the bundle identifier |
Service name attached to each log |
name |
Bundle identifier | Logger name, reported as logger.name |
networkInfoEnabled |
false |
Adds reachability, connection type, and carrier attributes to each log |
bundleWithRumEnabled |
true |
Attaches the current RUM application, session, view, and action to each log |
bundleWithTraceEnabled |
true |
Attaches the active trace and span to each log |
remoteSampleRate |
100 |
Percentage of logs sent to Atatus. 0 sends none |
remoteLogThreshold |
.debug |
Minimum level sent to Atatus |
consoleLogFormat |
nil |
Prints logs to the Xcode console. nil prints nothing |
Two options are worth calling out:
remoteLogThresholdfilters what reaches Atatus, not what reaches the console. Console printing ignores the threshold.consoleLogFormataccepts.shortor.shortWith(prefix: "[MyApp] "). Console output carries the message only, so attributes and tags do not appear there.
A console-only logger is useful in debug builds, where you want the output without the upload:
let consoleOnly = Logger.create(
with: Logger.Configuration(remoteSampleRate: 0, consoleLogFormat: .short)
)
Add attributes and tags
Attributes are key and value pairs you filter and group by. Tags are flat labels.
Attach an attribute to every log from one logger:
logger.addAttribute(forKey: "screen", value: "Checkout")
logger.addAttribute(forKey: "user.tier", value: 2)
logger.removeAttribute(forKey: "screen")
Attach one to every log from every logger in your application:
Logs.addAttribute(forKey: "tenant", value: "acme")
Logs.removeAttribute(forKey: "tenant")
When the same key is set in more than one place, the most specific wins: attributes passed to the call beat logger attributes, which beat the global ones.
Tags are added by key and value, or as a raw string:
logger.addTag(withKey: "build_configuration", value: "release")
logger.add(tag: "beta_tester")
logger.removeTag(withKey: "build_configuration")
logger.remove(tag: "beta_tester")
removeTag(withKey:) removes every tag with that key, not just one value. Tag keys must start with a lowercase letter, and characters outside letters, digits, _, -, :, ., and / are replaced with an underscore. A log carries at most 100 tags and 256 attributes.
Some names are reserved by the intake and dropped from your attributes if you set them: host, message, status, service, source, application_id, session_id, view.id, user_action.id, and build_id. The reserved tag keys are host, device, source, service, and env.
Correlate logs with RUM and traces
Correlation is on by default, so there is nothing to configure in the common case:
- With
bundleWithRumEnabled, each log carries the RUM application, session, view, and action that was active when it was written. You can open a session in the dashboard and read its logs in order. - With
bundleWithTraceEnabled, a log written inside an active span carries that trace and span, so logs line up with the trace timeline. See Trace Collection.
RUM correlation needs the session to be sampled. A log written in a session that sampling dropped is still sent, just without the RUM context.
Logs at error and critical are also reported as RUM errors, with the source logger. This is not configurable, so expect one logger.error(...) call to appear twice in the dashboard: once in Logs and once in Error Tracking. Use warn for conditions you do not want surfaced as errors.
Group errors with a fingerprint
Errors are grouped by their stack trace and message. When that grouping is wrong, for example when a message embeds an order ID, set your own fingerprint:
logger.error(
"Payment failed for order A-1001",
error: error,
attributes: [Logs.Attributes.errorFingerprint: "payment-failed"]
)
Every log with the same fingerprint is grouped together regardless of its message. The attribute only applies to a log that carries an Error.
Redact or drop logs before they are sent
An event mapper runs on every log before it leaves the device. Use it to strip sensitive data, or return nil to drop the log entirely:
Logs.enable(
with: Logs.Configuration(
eventMapper: { event in
if event.message.contains("card_number") {
return nil
}
var event = event
event.message = redact(event.message)
return event
}
)
)
The mapper runs on a background thread. Keep it quick and side effect free, because it runs once per log.
Objective-C
The logging API is available to Objective-C through the same module, with AT-prefixed types:
@import AtatusCore;
@import AtatusLogs;
[ATLogs enableWith:[[ATLogsConfiguration alloc] init]];
ATLoggerConfiguration *loggerConfiguration = [[ATLoggerConfiguration alloc] init];
loggerConfiguration.name = @"checkout";
loggerConfiguration.networkInfoEnabled = YES;
loggerConfiguration.remoteLogThreshold = ATLogLevelInfo;
loggerConfiguration.printLogsToConsole = YES;
ATLogger *logger = [ATLogger createWith:loggerConfiguration];
[logger addAttributeForKey:@"screen" value:@"Checkout"];
[logger info:@"Checkout opened" attributes:@{@"cart.items": @3}];
[logger error:@"Payment failed"
error:[NSError errorWithDomain:@"PaymentDomain" code:402 userInfo:nil]
attributes:@{@"order.id": @"A-1001"}];
Two differences from Swift: console printing is the boolean printLogsToConsole, which prints in the short format, and the error: parameter takes an NSError rather than any error type.
Platform support
Logging behaves the same on iOS, iPadOS, and tvOS.
Next steps
- Report crashes and custom errors with Error Tracking.
- Connect logs to backend traces with Trace Collection.
- Review the RUM options in Configuration Options.
+1-415-800-4104