fix(swift): wire setLogLevel and configureLogging into commons logger…

ON
3d ago
+130-2

fix(swift): wire setLogLevel and configureLogging into commons logger (#996)

Description

Background & Problem

Calling RunAnywhere.setLogLevel(_:) or RunAnywhere.configureLogging(_:) on the Swift SDK previously only mutated Swift-side state in SDKLogger (Logging.shared.setMinLogLevel / Logging.shared.configure). It never forwarded the configured minimum log level across the C ABI boundary to the C++ commons logger (rac_logger_set_min_level).

This caused two critical issues:

  1. Native C++ commons statements (RAC_LOG_*) continued filtering against commons' default level (RAC_LOG_INFO), ignoring whatever level the host application configured.
  2. The Apple MLX runtime log filter added in #989 (MLX.swift:66) dynamically queries commons' log level via rac_logger_get_min_level(). Because commons never received updates from Swift:
    • setLogLevel(.debug) failed to lower the threshold, keeping MLX debug logs silenced.
    • setLogLevel(.error) failed to raise the threshold, allowing MLX info/warning output to continue writing to stderr.

Changes Made

  1. Type-Safe Enum Mapping (SDKLogger.swift):
    • Added bidirectional mappings between proto RALogLevel and C ABI rac_log_level_t (RALogLevel.racLogLevel and rac_log_level_t.raLogLevel), avoiding fragile integer clamping or unsafe pointer casts.
  2. Central Logging Service Synchronization (SDKLogger.swift):
    • Logging.configure(_:) now forwards rac_logger_set_min_level(config.minLogLevel.racLogLevel).
    • Logging.setMinLogLevel(_:) now forwards rac_logger_set_min_level(level.racLogLevel).
  3. Public API Entrypoints (RunAnywhere+Logging.swift):
    • Imported CRACommons.
    • Explicitly forwarded both RunAnywhere.setLogLevel(_:) and RunAnywhere.configureLogging(_:) to rac_logger_set_min_level.
    • RunAnywhere.setDebugMode(_:) automatically synchronizes commons since it routes through setLogLevel.
  4. Unit Test Coverage (LoggingParityTests.swift):
    • Added comprehensive tests verifying full bidirectional mapping between RALogLevel and rac_log_level_t.
    • Verified that RunAnywhere.setLogLevel, setDebugMode, and configureLogging update rac_logger_get_min_level().
    • Added clean teardown resetting the level to .info to prevent test-order side effects.

Cross-SDK Audit (Issue #996 Checklist Item 4)

As requested in #996, audited the sibling SDKs:

  • Kotlin: RunAnywhere.setLogLevel currently mutates host-side _configuration only (SDKLogger.kt:143). JNI does not yet expose rac_logger_set_min_level in RunAnywhereBridge.kt.
  • Flutter: RunAnywhereLogging.setLogLevel delegates to SDKLoggerConfig (runanywhere_logging.dart:70), mutating Dart-side state only.
  • React Native & Web: Setters update JS-side LoggingManager / SDKLogger without forwarding to native / WASM commons.
  • Recommended follow-up: open companion issues/PRs for Kotlin JNI, Flutter FFI, and RN/Web to expose rac_logger_set_min_level in their respective native bridges.

Closes #996.


Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring

Testing

  • Lint passes locally
  • Added/updated tests for changes

Platform-Specific Testing (check all that apply)

Swift SDK / iOS Sample:

  • Tested on Mac (macOS target) , CI test matrix
  • Pre-commit validation gates passed (check_agents_claude_sync.sh, check_no_hardcoded_defaults.sh, test_release_version_coherence.py, check_engine_prebuilt_pins.sh --offline).
  • New unit tests added: LoggingParityTests.swift.

Kotlin SDK / Android Sample:

  • Tested on Android Phone (Emulator or Device)
  • Tested on Android Tablet

Flutter SDK / Flutter Sample:

  • Tested on iOS
  • Tested on Android

React Native SDK / React Native Sample:

  • Tested on iOS
  • Tested on Android

Web SDK / Web Sample:

  • Tested in Chrome (Desktop)
  • Tested in Firefox
  • Tested in Safari
  • WASM backends load (LlamaCpp + ONNX)
  • OPFS storage persistence verified (survives page refresh)
  • Settings persistence verified (localStorage)

Labels

Please add the appropriate label(s):

SDKs:

  • Swift SDK - Changes to Swift SDK (bindings/swift)
  • Kotlin SDK - Changes to Kotlin SDK (bindings/kotlin)
  • Flutter SDK - Changes to Flutter SDK (bindings/flutter)
  • React Native SDK - Changes to React Native SDK (bindings/react-native)
  • Web SDK - Changes to Web SDK (bindings/web)
  • Commons - Changes to shared native code (core)

Sample Apps:

  • Flutter Sample - Changes to Flutter example app (bindings/flutter/example)
  • React Native Sample - Changes to React Native example app (bindings/react-native/example)
  • Minimal Examples - Changes to an in-repo SDK harness (bindings/{swift,kotlin,web}/example)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)

Screenshots

N/A (SDK logging configuration and C++ commons synchronization; no UI changes).


Summary by cubic

Makes Swift setLogLevel, setDebugMode, and configureLogging forward the configured minimum log level to the C++ commons logger via rac_logger_set_min_level, so native RAC_LOG_* statements and the MLX runtime filter respect host-configured levels instead of defaulting to info.

  • Adds type-safe bidirectional mappings between RALogLevel and rac_log_level_t.
  • Synchronizes commons at both the Logging service layer and the RunAnywhere public entrypoints.
  • Performs the commons sync inside the existing unfair lock so concurrent readers never observe a stale level.
  • Adds LoggingParityTests covering the mappings and that all three entrypoints update rac_logger_get_min_level(), with teardown restoring the prior configuration and commons level to avoid test-order side effects.

Summary by CodeRabbit

  • Bug Fixes
    • Swift logging settings now stay synchronized with the C++ logger when configuring logging, changing the minimum log level, or enabling debug mode.
    • Log levels now map consistently between Swift and C++, with unrecognized values falling back to the informational level.