Skip to content

Debugging


Log Database

The SDK maintains an extremely verbose internal log database on the device. Every significant event — motion transitions, location records, HTTP requests and responses, geofence activity, errors — is written to SQLite with a timestamp. By default the database retains the last 3 days of logs (logger.logMaxDays, configurable up to any number of days).

When something unexpected happens, the log database is the first place to look. It contains far more detail than the console and captures events that occur while your app is backgrounded or terminated, including iOS silent relaunches and Android headless service activity.

Always run LogLevel.Verbose during development

The default log level records only key events. Set logger.logLevel to LogLevel.Verbose while developing — it captures the full internal trace of every decision the SDK makes, which is essential for diagnosing subtle issues like missed motion transitions, unexpected stop detection, or silent HTTP failures. Without verbose logging, the log database may not contain enough information to diagnose a problem after the fact.

Retrieve and email the full log directly from the device:

// Email the log (opens the system mail composer)
await BackgroundGeolocation.logger.emailLog('you@example.com');

// Or retrieve as a string for custom handling
const log = await BackgroundGeolocation.logger.getLog();
await bg.Logger.emailLog('you@example.com');

String log = await bg.Logger.getLog();
try await BGGeo.shared.logger.emailLog(to: "you@example.com")

let log = try await BGGeo.shared.logger.getLog()
// emailLog requires a coroutine scope and an Activity reference
BGGeo.instance.logger.emailLog("you@example.com", activity)

val log = BGGeo.instance.logger.getLog()

See the API reference for Logger.emailLog and Logger.getLog for full options, including filtering by log level and date range.


Debug Sounds

The fastest way to understand what the SDK is doing in the field is to enable debug mode:

await BackgroundGeolocation.ready({
  logger: {
    debug: true,
    logLevel: BackgroundGeolocation.LogLevel.Verbose,
  },
});
await bg.BackgroundGeolocation.ready(bg.Config(
  logger: bg.LoggerConfig(
    debug: true,
    logLevel: LogLevel.verbose,
  ),
));
try await BGGeo.shared.ready { config in
    config.logger.debug = true
    config.logger.logLevel = .verbose
}
BGGeo.instance.ready {
    logger.debug = true
    logger.logLevel = LogLevel.VERBOSE
}

With debug: true the SDK plays a distinct sound for every significant event. Click to listen:

Event iOS Android
LOCATION_RECORDED
LOCATION_SAMPLE
LOCATION_ERROR
LOCATION_SERVICES_ON n/a
LOCATION_SERVICES_OFF n/a
STATIONARY_GEOFENCE_EXIT
MOTIONCHANGE_FALSE
MOTIONCHANGE_TRUE
MOTION_TRIGGER_DELAY_START n/a
MOTION_TRIGGER_DELAY_CANCEL n/a
STOP_DETECTION_DELAY_INITIATED n/a
STOP_TIMER_ON
STOP_TIMER_OFF
HEARTBEAT
GEOFENCE_ENTER
GEOFENCE_EXIT
GEOFENCE_DWELL_START n/a
GEOFENCE_DWELL_CANCEL n/a
GEOFENCE_DWELL GEOFENCE_ENTER after GEOFENCE_DWELL_START
ERROR
WARNING n/a
BACKGROUND_FETCH n/a

iOS — hearing sounds in the background

To hear debug sounds while the app is backgrounded, enable the Audio and AirPlay background mode in Xcode under Signing & Capabilities → Background Modes.

Warning

Never enable debug in a production build.


Log Verbosity & Retention

Control how much the SDK logs and how long it keeps it:

await BackgroundGeolocation.ready({
  logger: {
    debug: false,
    logLevel: BackgroundGeolocation.LogLevel.Verbose,
    logMaxDays: 3,
  },
});
await bg.BackgroundGeolocation.ready(bg.Config(
  logger: bg.LoggerConfig(
    debug: false,
    logLevel: LogLevel.verbose,
    logMaxDays: 3,
  ),
));
try await BGGeo.shared.ready { config in
    config.logger.debug = false
    config.logger.logLevel = .verbose
    config.logger.logMaxDays = 3
}
BGGeo.instance.ready {
    logger.debug = false
    logger.logLevel = LogLevel.VERBOSE
    logger.logMaxDays = 3
}

HTTP Inspection

Subscribe to onHttp to inspect every server response from the client side:

BackgroundGeolocation.onHttp(response => {
  console.log('[onHttp]', response.status, response.responseText);
});
bg.BackgroundGeolocation.onHttp((bg.HttpEvent response) {
  print('[onHttp] ${response.status} ${response.responseText}');
});
BGGeo.shared.onHttp { response in
    print("[onHttp] \(response.statusCode) \(response.responseText ?? "")")
}
BGGeo.instance.onHttp { response ->
    Log.d(TAG, "[onHttp] ${response.statusCode} ${response.responseText}")
}

A 400 Bad Request originates at your server, not the plugin. Check your server logs and use http.rootProperty or http.locationTemplate to adjust the payload structure if needed.


Viewing Live Logs

iOS

Launch your app through Xcode and watch the debug console. The SDK writes timestamped log entries in real time as events fire — motion transitions, location records, HTTP responses, geofence activity. With logger: { debug: true } each log line is also accompanied by the corresponding sound, making it easy to correlate what you hear with what you read.

For background testing, keep the device connected and the Xcode console open. When iOS relaunches your app after a stationary geofence exit, logs resume automatically in the same console session.

Android

Filter adb logcat to show only the SDK's native log output:

adb logcat '*:S' TSLocationManager:V

The single quotes around *:S suppress glob expansion in both zsh and bash. *:S silences all other log tags; TSLocationManager:V sets the SDK tag to Verbose — the most detailed level.

Common level suffixes:

Suffix Level
V Verbose — full SDK trace
D Debug
I Info
W Warning
E Error
S Silent (suppress)

To capture your app's JS bridge logs alongside the SDK:

adb logcat '*:S' TSLocationManager:V ReactNative:V ReactNativeJS:V

To write the log to a file for sharing:

adb logcat '*:S' TSLocationManager:V | tee tslocationmanager.log

Example log output

The SDK uses box-drawing characters and emoji to make the log scannable at a glance. Here is a representative excerpt showing a motionchange event — the device transitions from stationary to moving, a location is persisted to SQLite, and a successful HTTP upload follows:

TSLocationManager: [SingleLocationRequest trySatisfyLastLocation] 📍
TSLocationManager: ╟─ age: 3041ms
TSLocationManager: ╟─ maximumAge: 30000
TSLocationManager: ╟─ desiredAccuracy: 20.0
TSLocationManager: ╟─ meetsAccuracy: true
TSLocationManager: ╟─ meetsStaleness: true
TSLocationManager: [TSLocationManager logSingleLocationResult]
TSLocationManager: ╔═════════════════════════════════════════════
TSLocationManager: ║ motionchange LocationResult: 7 (3043ms old)
TSLocationManager: ╠═════════════════════════════════════════════
TSLocationManager: ╟─ 📍  Location[fused 45.518863,-73.600554 hAcc=19.8 alt=46.9 vel=0.04 sAcc=1.5]
TSLocationManager: [TSLocationManager onSingleLocationResult]
TSLocationManager:   🔵  MOTIONCHANGE isMoving=true df=50.0 — resetting short-term filter state
TSLocationManager: [TSLocationManager onSingleLocationResult]
TSLocationManager:   🔵  Acquired motionchange position, isMoving: true
TSLocationManager: [TSLocationManager requestLocationUpdates]
TSLocationManager:   🎾  Location-services: ON
TSLocationManager: [SQLiteLocationDAO persist]
TSLocationManager:   💾 ✅ 12d596fa-d089-42eb-a084-a50ac50cc11e
TSLocationManager: [HttpService flush]
TSLocationManager: ╔═════════════════════════════════════════════
TSLocationManager: ║ HTTP Service (count: 1)
TSLocationManager: ╠═════════════════════════════════════════════
TSLocationManager: [AbstractService start]
TSLocationManager:   🎾  motionchange [TrackingService startId: 1, eventCount: 1]
TSLocationManager: [TrackingService handleMotionChangeResult]
TSLocationManager: ╔═════════════════════════════════════════════
TSLocationManager: ║ TrackingService motionchange: true
TSLocationManager: ╠═════════════════════════════════════════════
TSLocationManager: [AbstractService finish]
TSLocationManager:   ⚙️  FINISH [TrackingService startId: 1, eventCount: 0, sticky: true]
TSLocationManager: [ActivityRecognitionService start]
TSLocationManager:   🎾  Start motion-activity updates
TSLocationManager: [BackgroundTaskManager$Task start] ⏳ startBackgroundTask: 3
TSLocationManager: [SQLiteLocationDAO first]
TSLocationManager:   ✅  Locked 1 records
TSLocationManager: [HttpService createRequest]
TSLocationManager:   🔵  HTTP POST: 12d596fa-d089-42eb-a084-a50ac50cc11e
TSLocationManager: [ActivityRecognitionService handleActivityRecognitionResult]
TSLocationManager:   🚘  DetectedActivity [type=STILL, confidence=100]
TSLocationManager:   🔵  Response: 200
TSLocationManager: [EventManager fire]   🛜 ⚡️ http
TSLocationManager: [SQLiteLocationDAO destroy]
TSLocationManager:   ✅  DESTROY: 12d596fa-d089-42eb-a084-a50ac50cc11e
TSLocationManager: [BackgroundTaskManager$Task stop] ⏳ stopBackgroundTask: 3
🟢-[TSTrackingService changePace:] isMoving: 1
🟢-[TSLocationRequestService requestLocation:] [motionchange] maximumAge: 5000
✅-[TSBackgroundTaskManager stopBackgroundTask:]_block_invoke 2 OF {(
    2
)}

1:📍<+37.33233141,-122.03121860> +/- 5.00m (speed 0.00 mps / course -1.00) @ 2026-04-05, 10:59:11 PM EDT | age: 551 ms

╔═══════════════════════════════════════════════════════════
║ -[TSSingleLocationRequest trySatisfyWithLocation:now:] 📍🔎 [motionchange] desiredAccuracy: 20.0 m, maximumAge: 5000 ms
╚═══════════════════════════════════════════════════════════
✅-[TSSingleLocationRequest trySatisfyWithLocation:now:] 📍🔎 meets accuracy & staleness: <+37.33233141,-122.03121860> +/- 5.00m
[Swift][location] sample: true, isMoving: true, odometer: 96396.96 ± 164.32
🟢-[TSTrackingService startMonitoringSignificantLocationChanges]
ℹ️-[TSOdometer onMotionChange:] Filter state: odometer=96396.96m ±164.32m
✅-[TSDataStore persist:] 💾 INSERT: 9A409B53-92CE-4812-BD58-3823A5AA06AF (type: 0, persistMode: 2, maxRecords: -1)
🟢-[TSTrackingService startUpdatingLocation] Location-services: ON
[Swift][location] sample: false, isMoving: true, odometer: 96396.96 ± 164.32
[Swift][motionchange] 1

╔═══════════════════════════════════════════════════════════
║ -[TSHttpService beginFlushWithCallback:overrideSyncThreshold:error:]
╚═══════════════════════════════════════════════════════════
✅-[TSBackgroundTaskManager createBackgroundTask] Created background task: 3
✅-[TSHttpService schedulePost] LOCKED: 9A409B53-92CE-4812-BD58-3823A5AA06AF

╔═══════════════════════════════════════════════════════════
║ -[TSTrackingService locationManager:didUpdateLocations:] Enabled: 1 | isMoving: 1 | df: 20.0m
╚═══════════════════════════════════════════════════════════
📍<+37.33233141,-122.03121860> +/- 5.00m (speed 0.00 mps / course -1.00) @ 2026-04-05, 10:59:11 PM EDT | age: 567 ms
ℹ️-[TSMotionActivityClassifier updateSpeed:] speed=0.00
📍-[TSLocationFilter evaluateWithMetrics:] decision=Accepted reason=OK raw=0.0m effective=0.0m smoothed=0.0m cap=0.0m acc=5.0m speed=0.00 sigma=0.0m df=20.0m
[Swift][location] sample: false, isMoving: true, odometer: 96396.96 ± 164.32
🔵-[TSHttpResponse handleResponse] Response: 200

Demo App

Each SDK ships with a fully functional demo application. Run it to verify your installation and license key before integrating into your own app. The demo app uses debug: true and displays a live event log on screen, making it the most complete debugging environment available.


Simulating Location

iOS Simulator

The iOS Simulator has a built-in location simulation feature. With your app running in the simulator:

  1. In the Xcode menu bar choose Debug → Simulate Location → Freeway Drive

The simulator will generate a continuous stream of GPS coordinates following a highway route at realistic speeds, transitioning the SDK into moving state and producing location records just as a real device would. Other presets include City Run, City Bicycle Ride, and Pedestrian. You can also load a custom GPX file via Add GPX File to Project... for a specific route.

Android

Android does not have a built-in location simulation tool. Use a third-party Mock Location app from the Play Store. A popular option is Lockito, which lets you define a route and replay it at a configurable speed.

To enable mock locations on Android:

  1. Enable Developer Options on the device (tap Build Number seven times in Settings → About Phone)
  2. In Developer Options, set Select mock location app to your chosen app
  3. Start the mock route before launching your app

Note

Some devices require the mock location app to be running in the foreground, or the mock coordinates will stop when the screen locks. Check the app's documentation for background operation requirements.


Common Issues

Tracking stops on Samsung / Huawei / Xiaomi

OEM battery optimisation layers on these devices can kill the foreground service. Add your app to the device's battery whitelist. See dontkillmyapp.com for per-manufacturer steps.

No locations after iOS app termination

Ensure ready() is called unconditionally at app launch — not inside a component mount or behind a conditional. When iOS relaunches your app silently after a stationary geofence event, the launch path must reach ready() for tracking to resume.

HTTP requests not firing

Check that http.url is set and http.autoSync is true. Inspect responses with onHttp. Verify the device has network access and your server is reachable from the device's network (not just localhost).