Skip to content

feat(logger): [SDK-4835] adopt shared KMP logger module via OneSignal-KMP-SDK submodule#2687

Merged
abdulraqeeb33 merged 15 commits into
mainfrom
feat/SDK-4835-kmp-logger-module
Jul 24, 2026
Merged

feat(logger): [SDK-4835] adopt shared KMP logger module via OneSignal-KMP-SDK submodule#2687
abdulraqeeb33 merged 15 commits into
mainfrom
feat/SDK-4835-kmp-logger-module

Conversation

@abdulraqeeb33

@abdulraqeeb33 abdulraqeeb33 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces the multiplatform :logger module — the OpenTelemetry-free replacement for the otel module — and wires the Android SDK to consume it from the standalone OneSignal-KMP-SDK repo as a git submodule, so the exact same logging/telemetry pipeline can be shared with the iOS SDK and both release in parity.

This branch is up to date with main (latest merge brings in SDK-4822, the app-state-aware ANR watchdog + fatal/non-fatal crash indicator), and that work is now ported into the new logger path so it stays at parity with otel.

What's included

  • Shared module consumption: OneSignal-KMP-SDK added as a git submodule; settings.gradle remaps :OneSignal:logger to the submodule path (project path + dependency substitution unchanged). In-repo logger sources removed.
  • Wire format: the KMP logger sends OTLP/protobuf (custom dependency-free encoder) and matches the old otel batch sizes, LogLimits (128 attrs / 32000 chars), service.name=OneSignalDeviceSDK / scope loggerBuilder, and background-thread I/O.
  • Android platform adapters (host implements the injected interfaces): AndroidLogger, FileLogStore, OneSignalLogHttpSender, AndroidLogCrashHandler, AndroidLogAnrDetector, LoggerPlatformProviderAdapter, composed via LoggerPlatformFactory / LoggerLifecycleManager.
  • Crash paths at parity with otel:
    • general log (level-driven, carries exception.*),
    • fatal saveCrash (Severity.FATAL) for real crashes and foreground ANRs,
    • non-fatal saveNonFatal (Severity.WARN) for backgrounded main-thread blocks,
    • an explicit, typed ossdk.crash.fatal OTLP bool attribute so the backend segments crash/ANR metrics on a stable flag rather than inferring intent from severity/exception type.
  • App-state-aware ANR (SDK-4822 parity): AndroidLogAnrDetector reuses the shared, JVM-tested AnrCheckEvaluator — the same decision core OtelAnrDetector uses, so the two can't drift. Foreground block > threshold → fatal ANR via saveCrash; backgrounded block > (higher) threshold → non-fatal warning via saveNonFatal under a distinct BackgroundMainThreadBlockException + stack fingerprint; watchdog-oversleep (Doze/frozen process) is suppressed. isAppInForeground is sourced from the platform provider's appState.
  • Feature flag: LoggerModuleSwitch gates otel vs the new logger module.
  • CI: every SDK-compiling checkout now fetches submodules recursively (project-setup, e2e, codeql, publish-release).
  • Release automation: reusable bump-kmp-submodule.yml (workflow_call) that the KMP repo's Release workflow calls to re-point the submodule to a released tag and open a bump PR here — authenticated with the shared org GH_PUSH_TOKEN (mirrors the OneSignal/sdk-shared pattern; no GitHub App).
  • Docs: CONTRIBUTING.md + README.md document cloning with --recurse-submodules.

Related / dependencies

Notes for reviewers

  • Fresh clones / CI must run git submodule update --init --recursive (CI updated; documented in CONTRIBUTING).
  • The submodule is currently pinned to 9cf1a33, a commit on the unmerged KMP branch feat/anr-app-state-and-fatal-flag (stacked on unmerged onPause NPE #3). Release ordering before this can ship:
    1. merge KMP onPause NPE #3main,
    2. merge feat/anr-app-state-and-fatal-flagmain,
    3. tag the KMP repo, then re-point this submodule from the branch commit to that tag (the release automation does step 3's bump PR).
  • New Android classes (FileLogStore, OneSignalLogHttpSender, AndroidLog*, LoggerLifecycleManager) are intentional: some replace functionality the OTel libraries provided (HTTP export, disk buffering), the rest are parallel ILog*-based analogues of the IOtel* classes so both stacks can coexist behind the feature flag during migration. They collapse into a single stack once otel is removed.

Test plan

  • KMP: ./gradlew :logger:testDebugUnitTest :logger:iosSimulatorArm64Test spotlessCheck (green).
  • Android: ./gradlew :OneSignal:core:compileDebugKotlin + crash-package unit tests (com.onesignal.debug.internal.crash.*) green against the submodule.
  • Build/run the demo app with LoggerModuleSwitch enabled; confirm logs + a forced crash upload reach the backend (verified previously on production).
  • Exercise a foreground vs backgrounded main-thread block; confirm foreground → FATAL crash record, background → WARN non-fatal record with ossdk.crash.fatal=false.
  • CI green (submodule checkout resolves).

Made with Cursor

AR Abdul Azeez and others added 8 commits July 2, 2026 20:26
…otel replacement

Introduces a new Kotlin Multiplatform `logger` module that mirrors the decoupled
architecture of the `otel` module but has zero OpenTelemetry dependency, so the
same observability pipeline can be shared with iOS.

- commonMain: LogRecord/LogSeverity model, platform/logger/telemetry/crash
  interfaces, hand-rolled OTLP/JSON encoder, coroutine batch processor, remote +
  crash-local telemetry, crash reporter/uploader, and LoggerFactory composition root.
- HTTP, file IO, clock and uuid are injected (no Ktor/okio); iOS actuals stubbed.
- commonTest: unit tests for encoder, fields, batching, telemetry, crash flows.
- core: Android adapters (logger, platform provider, HTTP sender, file store,
  crash handler, ANR detector) + LoggerLifecycleManager, wired behind a single
  LoggerModuleSwitch.USE_LOGGER_MODULE toggle (default false keeps the otel path
  and all existing tests unchanged). Logging gains a parallel logger sink.

otel module left intact; removal tracked as follow-up.

Co-authored-by: Cursor <cursoragent@cursor.com>
…its, and background I/O

Switch the KMP logger module's wire format from OTLP/JSON to OTLP/protobuf
(application/x-protobuf) via a dependency-free ProtoWriter, so the ingestion
endpoint receives byte-compatible payloads with the old otel exporter.

- Encode via hand-rolled ProtoWriter; drop kotlinx.serialization + OtlpDtos.
- Port otel LogLimits: cap log-record attributes at 128 and truncate values
  to 32,000 chars (bounds long exception.stacktrace).
- Make ILogFileStore.listReadable/delete suspend; run file I/O off-thread.
- Route OneSignalLogHttpSender diagnostics through ILogger, gated on the
  exporter-logging toggle instead of unconditional logcat.
- Add independent protobuf decode tests (ProtoTestReader) and limit tests.
- Fix stale OTLP/JSON doc comments.

Co-authored-by: Cursor <cursoragent@cursor.com>
…KMP-SDK submodule

Extract the multiplatform :logger module into the standalone OneSignal-KMP-SDK
repo (single source of truth for Android + iOS parity) and consume it here as a
git submodule pinned to a commit. Remap the :OneSignal:logger project directory to
the submodule so the existing project path and dependency substitution are
unchanged. Fetch submodules recursively in every CI checkout that compiles the SDK,
and document the clone/init workflow in CONTRIBUTING and README.

Co-authored-by: Cursor <cursoragent@cursor.com>
…main

Re-point the logger submodule from the feature-branch commit to the squash-merged
main commit (503d610, PR #1). Logger sources are identical to the previously
verified commit; :OneSignal:logger:testDebugUnitTest stays green.

Co-authored-by: Cursor <cursoragent@cursor.com>
Companion to OneSignal-KMP-SDK#3 — FileLogStore reports persistence
success, and crash/ANR handlers call saveCrash synchronously.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reusable (workflow_call) job the OneSignal-KMP-SDK Release workflow invokes to
re-point the submodule gitlink to a released tag and open a bump PR here. Mirrors
the OneSignal/sdk-shared fan-out pattern and authenticates with the shared org
GH_PUSH_TOKEN, so no dedicated GitHub App is needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…logger path

Brings the KMP logger path to parity with the otel path after merging main (SDK-4822):
- AndroidLogAnrDetector is now app-state aware, reusing the shared AnrCheckEvaluator
  (same tested decision core as OtelAnrDetector): foreground blocks report a fatal ANR
  via saveCrash; backgrounded blocks report a non-fatal warning via saveNonFatal under a
  distinct exception type + stack fingerprint, so they stay out of the ANR metric.
- Wire isAppInForeground from the platform provider's appState in LoggerLifecycleManager.
- Crash/ANR handlers call the synchronous saveCrash/saveNonFatal (no runBlocking), matching
  the fatal-handler-safe crash API.
- Bump OneSignal-KMP-SDK submodule to 9cf1a33 (adds saveNonFatal + ossdk.crash.fatal).

Co-authored-by: Cursor <cursoragent@cursor.com>
AR Abdul Azeez and others added 4 commits July 22, 2026 15:43
Companion to KMP revert of the sync crash API — durability remains the
blocking ILogFileStore.save; hosts block the fatal handler until complete.

Co-authored-by: Cursor <cursoragent@cursor.com>
Companion to KMP restoring synchronous ILogCrashReporter entry points.

Co-authored-by: Cursor <cursoragent@cursor.com>
The bump PR now embeds the published OneSignal-KMP-SDK release notes for the target
tag (fetched best-effort via gh; falls back to linking the release page) so a reviewer
can see what the new logger version contains before merging. Re-runs refresh the PR
body via `gh pr edit`.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a "Working on the shared :logger module" section explaining the nested two-git
model and two Mermaid flowcharts: Flow A (local edit → commit → push → merge to KMP
main, no tag) and Flow B (manual Release workflow → tag + Android submodule-bump PR).

Co-authored-by: Cursor <cursoragent@cursor.com>
@fadi-george

Copy link
Copy Markdown
Contributor

@claude review once

@fadi-george fadi-george left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:core now publishes a runtime dependency on com.onesignal:logger, but the logger module has no Maven publishing tasks. This would make the next SDK release unresolvable for Maven consumers. Please configure/publish the logger artifact or embed it in core, then verify using a Maven-local consumer build.

Needed so the logger module can stamp and verify vX.Y.* provenance under
actions/checkout's shallow submodule clone.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abdulraqeeb33

Copy link
Copy Markdown
Contributor Author

yup, i was planning to do that much later, but here you go. there are related PRs that address the publishing.
OneSignal/OneSignal-KMP-SDK#7
#2687

Align the wrapped exceptionMessage argument to satisfy spotlessCheck.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📊 Diff Coverage Report

Diff Coverage Report (Changed Lines Only)

Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff).

Changed Files Coverage

  • OneSignalCrashUploaderWrapper.kt: 0/10 touched executable lines (0.0%) (21 touched lines in diff)
    • 10 uncovered touched lines in this file
  • Logging.kt: 3/23 touched executable lines (13.0%) (57 touched lines in diff)
    • 20 uncovered touched lines in this file
  • AndroidLogAnrDetector.kt: 0/111 touched executable lines (0.0%) (209 touched lines in diff)
    • 111 uncovered touched lines in this file
  • AndroidLogCrashHandler.kt: 0/55 touched executable lines (0.0%) (113 touched lines in diff)
    • 55 uncovered touched lines in this file
  • AndroidLogger.kt: 0/9 touched executable lines (0.0%) (26 touched lines in diff)
    • 9 uncovered touched lines in this file
  • FileLogStore.kt: 0/34 touched executable lines (0.0%) (79 touched lines in diff)
    • 34 uncovered touched lines in this file
  • LoggerPlatformFactory.kt: 0/3 touched executable lines (0.0%) (19 touched lines in diff)
    • 3 uncovered touched lines in this file
  • LoggerPlatformProviderAdapter.kt: 0/28 touched executable lines (0.0%) (48 touched lines in diff)
    • 28 uncovered touched lines in this file
  • OneSignalLogHttpSender.kt: 0/35 touched executable lines (0.0%) (78 touched lines in diff)
    • 35 uncovered touched lines in this file
  • LoggerLifecycleManager.kt: 0/118 touched executable lines (0.0%) (210 touched lines in diff)
    • 118 uncovered touched lines in this file
  • OneSignalImp.kt: 0/6 touched executable lines (0.0%) (9 touched lines in diff)
    • 6 uncovered touched lines in this file

Overall (aggregate gate)

3/432 touched executable lines covered (0.7% — requires ≥ 80%)

Per-file detail (informational; gate is aggregate above):

  • OneSignalCrashUploaderWrapper.kt: 0.0% (10 uncovered touched lines)

  • Logging.kt: 13.0% (20 uncovered touched lines)

  • AndroidLogAnrDetector.kt: 0.0% (111 uncovered touched lines)

  • AndroidLogCrashHandler.kt: 0.0% (55 uncovered touched lines)

  • AndroidLogger.kt: 0.0% (9 uncovered touched lines)

  • FileLogStore.kt: 0.0% (34 uncovered touched lines)

  • LoggerPlatformFactory.kt: 0.0% (3 uncovered touched lines)

  • LoggerPlatformProviderAdapter.kt: 0.0% (28 uncovered touched lines)

  • OneSignalLogHttpSender.kt: 0.0% (35 uncovered touched lines)

  • LoggerLifecycleManager.kt: 0.0% (118 uncovered touched lines)

  • OneSignalImp.kt: 0.0% (6 uncovered touched lines)

❌ Coverage Check Failed

Aggregate coverage on touched lines is 0.7% (minimum 80%).

📥 View workflow run

@fadi-george

fadi-george commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The KMP publishing fix exists at aae38acc, but this PR still pins 9cf1a33, whose logger module has no publishing configuration. The publication blocker remains until the submodule is bumped and Android adopts the renamed :kmp module.

Bump the OneSignal-KMP-SDK submodule to aae38ac, which renames the shared
Gradle module :logger -> :kmp and adds opt-in Maven publishing (published as
com.onesignal:kmp). Previously :core's POM referenced com.onesignal:logger,
which had no publishing configuration, so an SDK release would be unresolvable
for Maven consumers.

Android-side adoption:
- settings.gradle: include :OneSignal:kmp at ../OneSignal-KMP-SDK/kmp and
  substitute com.onesignal:kmp.
- core/build.gradle: depend on project(':OneSignal:kmp').
- gradle.properties: set onesignalPublishSharedKmp=true so the shared module
  wires up Maven publishing during the Android SDK release.
- CONTRIBUTING.md: update module name/tasks to :kmp.

The Kotlin package stays com.onesignal.logger, so no source imports change.
Verified with a Maven-local consumer build: com.onesignal:kmp:5.9.7 publishes
and :core's POM now depends on it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abdulraqeeb33

Copy link
Copy Markdown
Contributor Author

Thanks @fadi-george — addressed. Pushed 684e6ea9 which bumps the OneSignal-KMP-SDK submodule 9cf1a33aae38ac, so the shared module now ships the publishing config and the renamed :kmp module.

Android-side adoption:

  • settings.gradle: :OneSignal:kmp at ../OneSignal-KMP-SDK/kmp, substitute com.onesignal:kmp.
  • core/build.gradle: depends on project(':OneSignal:kmp').
  • gradle.properties: onesignalPublishSharedKmp = true so the shared module wires up Maven publishing during the Android release.
  • Kotlin package stays com.onesignal.logger, so no source imports changed.

Provenance / pin: tagged the KMP repo v0.1.0 at aae38ac and the submodule is pinned to that tagged commit, so the publish-time provenance gate passes without the ALLOW_UNTAGGED_LOGGER_KMP escape hatch.

Verified with a Maven-local consumer build:

  • com.onesignal:kmp:5.9.7 publishes (publishToMavenLocal, provenance gate green).
  • :core's generated POM now declares a runtime dependency on com.onesignal:kmp:5.9.7 (previously com.onesignal:logger).
  • :core compiles cleanly against the renamed module.

Note: I dispatched the KMP release.yml workflow first but it 422'd — its bump-android job references OneSignal-Android-SDK/.github/workflows/bump-kmp-submodule.yml@main, which only lands once this PR merges to main. So I cut v0.1.0 manually after running the same verify gate (Spotless + JVM/Android unit tests) locally; future releases can use the workflow once it's on main.

@abdulraqeeb33
abdulraqeeb33 merged commit 69e568b into main Jul 24, 2026
4 of 5 checks passed
@abdulraqeeb33
abdulraqeeb33 deleted the feat/SDK-4835-kmp-logger-module branch July 24, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants