Skip to content

feat(gl-sdk): add LogListener for foreign-binding log capture#705

Merged
cdecker merged 2 commits intomainfrom
ave-logging
Apr 27, 2026
Merged

feat(gl-sdk): add LogListener for foreign-binding log capture#705
cdecker merged 2 commits intomainfrom
ave-logging

Conversation

@angelix
Copy link
Copy Markdown
Contributor

@angelix angelix commented Apr 24, 2026

[Two callback-based listener APIs for mobile integrators, both
following the Breez SDK shape.

Logging

  • LogListener: apps implement on_log(LogEntry) to receive log messages
    from both gl-sdk (via tracing's log bridge) and gl-client (111
    log::*! callsites flow through automatically).
  • LogEntry carries level, message, target module, and source file + line
    for easier triage of production issues.
  • LogLevel variants use Rust PascalCase (Error/Warn/Info/Debug/Trace)
    matching the ChannelState/OutputStatus/PaymentStatus convention;
    bindings still render as UPPER_SNAKE per uniffi convention.
  • set_logger(level, listener) returns Result<(), Error>. First call
    installs; subsequent calls are silent no-ops. Returns an error only
    when another crate has already installed a log logger, replacing the
    previous .expect("logger already set") panic.
  • Exposed to JS via a NAPI wrapper that bridges a ThreadsafeFunction to
    LogListener (napi4 feature enabled).

Node events

  • NodeEventListener: apps implement on_event(NodeEvent) to receive live
    events (InvoicePaid today; room for more).
  • Replaces the pull-based NodeEventStream + next() surface entirely.
    NodeEventStream is gone.
  • Listener is strictly a connect-time concern:
    register / recover / connect / register_or_recover each gain an
    event_listener: Option<Box> parameter. The
    SDK installs it atomically during node bring-up, so events that fire
    during the first round of RPCs are not missed.
  • set_event_listener is pub(crate) — not exposed via uniffi or NAPI.
    Callers who need post-construction flexibility wire a mutable delegate
    inside their listener implementation (Flow/LiveData/ObservableObject
    pattern). Matches Breez's EventListener shape.
  • Single listener per Node. Drop for Node aborts the dispatch task so
    teardown is automatic.

Tests

Returns a pretty-printed JSON envelope { timestamp, node, sdk } where
the node section serializes getinfo/listpeerchannels/listfunds and the
sdk section carries version + node_state. Failed sub-calls are embedded
as { "error": "..." } instead of failing the dump. Adds serde derive on
the response types so each section is real nested JSON, queryable with
jq. Payment and invoice history are intentionally excluded to avoid
leaking preimages, payment hashes, bolt11 strings, and labels into
support dumps.

Copy link
Copy Markdown
Collaborator

@cdecker cdecker left a comment

Choose a reason for hiding this comment

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

I'm not quite happy with encumbering what is supposed to be the simple interface (naked top-level functions) with things like logging and event subscriber handling. That's more advanced concerns and exactly a problem of naked functions: they have no context other than the global static context they could be configured, hence all possible variants must be expressible in the argument list, which adds complexity back in.

I'd say if you use the simple interface you do not get to play with these advanced features, if you want those you have to use the OOP interface, where we can use builders to incrementally expose details. That is also in tune with the semver logic: naked functions change their entire interface when you add optional arguments, whereas builders can add new setters without breaking existing code.

Can we split out the log handling, which I'd merge right away, and keep the event streaming discussion separate?

Comment thread libs/gl-sdk/src/logging.rs Outdated
@angelix angelix force-pushed the ave-logging branch 8 times, most recently from 1ff607a to ee2b047 Compare April 27, 2026 09:18
@angelix angelix changed the title feat(gl-sdk): add log + node-event listener callbacks feat(gl-sdk): add LogListener for foreign-binding log capture Apr 27, 2026
@angelix angelix force-pushed the ave-logging branch 3 times, most recently from 44c479b to 9f015d6 Compare April 27, 2026 09:23
angelix added 2 commits April 27, 2026 16:07
Returns a pretty-printed JSON envelope { timestamp, node, sdk } where
the node section serializes getinfo/listpeerchannels/listfunds and the
sdk section carries version + node_state. Failed sub-calls are embedded
as { "error": "..." } instead of failing the dump. Adds serde derive on
the response types so each section is real nested JSON, queryable with
jq. Payment and invoice history are intentionally excluded to avoid
leaking preimages, payment hashes, bolt11 strings, and labels into
support dumps.
Apps install a process-wide LogListener and receive every log message
emitted by gl-sdk and the underlying gl-client library. The bridge
sits on top of the `log` crate facade — gl-sdk's own `tracing` calls
are routed through `tracing`'s `log` feature, and gl-client's direct
`log::*!` calls flow through the same channel.

Public surface (free functions, not encumbering the Node entry
points):

  - `set_logger(level, listener)` — install once per process.
  - `set_log_level(level)` — adjust the filter at runtime.
  - `LogEntry { level, message, target, file, line }` — record
    forwarded to the listener.
  - `LogLevel { Error, Warn, Info, Debug, Trace }`.
  - `LogListener.on_log(entry)` — UniFFI callback interface.

Tests
- Python test_logging.py verifies LogLevel variants, LogEntry shape,
  LogListener callback type, and that set_log_level is callable.
- Kotlin LoggingTest.kt installs the listener in @BeforeClass
  (handling the "already installed" error gracefully) and asserts
  that register_or_recover actually drives log entries through the
  listener with non-empty targets.

Event-streaming changes (NodeEventListener trait, set_event_listener
on Node, the encumbering `event_listener` argument on register /
recover / connect / register_or_recover) are intentionally NOT in
this commit. The simple top-level functions stay 3-arg. Event
streaming can be reintroduced via the OOP / builder interface in a
separate PR.
@cdecker
Copy link
Copy Markdown
Collaborator

cdecker commented Apr 27, 2026

Rebased on top of main now that #704 is merged.

@cdecker
Copy link
Copy Markdown
Collaborator

cdecker commented Apr 27, 2026

Very cool to see the log-listener in action 👍
Merging as soon as CI is happy.

@cdecker cdecker dismissed their stale review April 27, 2026 14:38

Addressed

@cdecker cdecker merged commit d614086 into main Apr 27, 2026
16 checks passed
@cdecker cdecker deleted the ave-logging branch April 27, 2026 14:43
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