Logging (SDK Layer)#
HaapiLogger is shared across all layers — Driver, SDK, and UIKit/UIWidget — so a single configuration covers the entire stack. Configure it once at app startup, and include the SDK follow-up tags alongside the Driver tags. For the full concept (severity ordering, sensitive-value masking, custom sinks), see Logging and Observability .
What the SDK Layer Adds#
If you have already configured HaapiLogger at the Driver Layer, the only SDK-specific delta is the SDK follow-up tag set — the tag prefixes (HAAPI_SDK_FLOW, HAAPI_SDK_HTTP, HAAPI_SDK_MAPPING, HAAPI_SDK_OAUTH, and on Android HAAPI_SDK_STORAGE) that identify records emitted by the SDK Layer’s flow stepping, HTTP plumbing, payload mapping, and OAuth operations.
Configure SDK-Layer logging when you need to:
- Diagnose flow-stepping bugs —
HAAPI_SDK_FLOWrecords every transition through the HAAPI state machine. - Trace OAuth token operations —
HAAPI_SDK_OAUTHcovers fetch, refresh, and revoke, including thedpop-noncelifecycle that the SDK manages on your behalf. - Investigate payload-mapping failures —
HAAPI_SDK_MAPPINGreports when the SDK can’t deserialize a server response into its typed model.
If your app stays at the Driver Layer (no HaapiManager or OAuthTokenManager), skip the SDK-tag wiring; the SDK records do not exist on that path.
Configuration#
iOS and Android use follow-up tags differently. On iOS, tags are an emission gate — HaapiLogger.followUpTags defaults to an empty array, so nothing logs until you set it; the SDK Layer requires explicitly appending SdkFollowUpTag.allCases. On Android, tags have no behavioural effect on emission — enabled and setLevel(...) are the only gates, and tags travel through to your sink as labels. Adding the SDK Layer has no logger-side step beyond initialising the shared logger; if you want SDK-only output, filter by tag prefix inside your LogSink.log(...) (or at the LogCat command line).
Combine Driver and SDK follow-up tags when configuring in AppDelegate.application(_:didFinishLaunchingWithOptions:):
HaapiLogger.followUpTags = DriverFollowUpTag.allCases + SdkFollowUpTag.allCases
HaapiLogger.setLogType(LogType.info)SDK follow-up tags include HAAPI_SDK_FLOW, HAAPI_SDK_HTTP, HAAPI_SDK_MAPPING, and HAAPI_SDK_OAUTH. Adding only DriverFollowUpTag.allCases silently suppresses every SDK-emitted record — a common cause of “the SDK isn’t logging anything”.
Configure in Application.onCreate. There is no SDK-specific tag configuration: once the logger is enabled, SDK records flow through alongside Driver records, and you filter them at the sink or by LogCat tag prefix. HaapiLogger.LogLevel and enabled are the only emission gates — tags are labels attached to each record.
class ClientApplication : Application() {
override fun onCreate() {
super.onCreate()
HaapiLogger.enabled = true
HaapiLogger.setLevel(HaapiLogger.LogLevel.INFO)
}
}SDK tag prefixes include HAAPI_SDK_FLOW, HAAPI_SDK_HTTP, HAAPI_SDK_MAPPING, HAAPI_SDK_OAUTH, and HAAPI_SDK_STORAGE. Filter LogCat with logcat -s 'HAAPI_SDK_*:V' (or any prefix subset) to focus on the SDK Layer.
getRNLogger is a singleton factory — call it once with the desired configuration; subsequent calls return the same instance. The configuration is forwarded to the native HaapiLogger atomically on first call. The bridge configures both native loggers so iOS tag emission is enabled in addition to the Android level/enabled gates; you do not configure followUpTags from JS.
import { getRNLogger, RNLogLevel } from 'identityserver.haapi.reactnative.sdk'
const logger = getRNLogger({
logLevel: RNLogLevel.Info, // Info | Debug | Trace | Warn | Error
isSensitiveValueMasked: true, // keep true outside local development
})
// Pass into the accessor factories so SDK records flow through this logger.
const accessor = await initializeForHaapi(haapiConfig, logger)Records from both layers (iOS and Android) reach the JS side through a single event channel. Custom sinks (see below) receive every record regardless of which native layer emitted it; tag prefixes (HAAPI_SDK_*, HAAPI_DRIVER_*) survive the bridge so you can filter by layer in the sink.
Sensitive-Value Masking#
HaapiLogger.isSensitiveValueMasked defaults to true and should remain true in any non-development build. The SDK Layer is where access tokens, refresh tokens, DPoP proofs, and dpop-nonce headers flow — disabling masking here surfaces them all in log output. When disabled, the logger emits an explicit warning every time it prints an unmasked sensitive value so the misconfiguration is hard to miss.
Setting isSensitiveValueMasked = false in a release build leaks the OAuth tokens and DPoP material handled by the SDK Layer into application logs. If those logs ship to a third-party crash reporter or observability backend, the leak follows them. Keep masking on outside of local debugging, and never combine isSensitiveValueMasked = false with a remote log sink.
Custom Sinks#
To route logs into a host application’s observability platform or crash reporter, register a LogSink implementation. The sink receives every log record with its level, follow-up tag, message, file, and line; it can filter, transform, or forward as needed.
final class MyLogSink: LogSink {
func log(
level: LogType,
tag: FollowUpTag,
message: String,
file: String,
line: Int
) {
// Forward to the host app's crash reporter or analytics pipeline.
// Keep this fast — sinks fire on the calling thread.
}
}
HaapiLogger.add(sink: MyLogSink()) class MyLogSink : LogSink {
override fun log(
level: HaapiLogger.LogLevel,
tag: String,
message: String,
file: String,
line: Int
) {
// Forward to the host app's crash reporter or analytics pipeline.
// Keep this fast — sinks fire on the calling thread.
}
}
HaapiLogger.addSink(MyLogSink()) Register an RNLogSinkHandler on the singleton logger. Each native log record is delivered as an RNLogEvent through the bridge — the sink receives the event with its level, tag, message, file, and line:
import { getRNLogger, RNLogLevel } from 'identityserver.haapi.reactnative.sdk'
import type { RNLogEvent } from 'identityserver.haapi.reactnative.sdk'
const logger = getRNLogger({ logLevel: RNLogLevel.Info, isSensitiveValueMasked: true })
const sinkHandle = logger.addSink((event: RNLogEvent) => {
// Forward to the host app's crash reporter or analytics pipeline.
// event.level (RNLogLevel), event.tag, event.message, event.file, event.line.
// Keep this fast — sinks fire on the JS thread synchronously.
})
// Later: sinkHandle.remove() to detach the sink.Unlike iOS / Android where sinks live on the native side, RN sinks run on the JS thread — keep them non-blocking. For heavy work, post to a worker or queue, then return.
See Logging and Observability for the full sink contract and ordering guarantees.
How to implement this: Logging (Driver Layer) · Logging and Observability (concept) · How to Customize Logging