Skip to main content

structured_log_riverpod 0.1.0-dev.2 copy "structured_log_riverpod: ^0.1.0-dev.2" to clipboard
structured_log_riverpod: ^0.1.0-dev.2 copied to clipboard

StructuredLogRiverpodObserver — a ProviderObserver that turns the lifecycle of every Riverpod provider (created, updated, failed, removed) into structured_log entries. Works with flutter_riverpod as is.

structured_log_riverpod #

CI

Читать на русском.

See what every Riverpod provider in your app did — created, updated, failed, removed — as structured log entries, with one line of setup.

Documentation: structured-log.openidealab.com.

Why #

"Why is this screen empty?" is a question a Riverpod app keeps asking, and the answer is usually a provider that failed, or one that never got the value you expected. Without a record you reproduce the bug under a debugger or add a ProviderObserver that prints — and on a user's device you can do neither.

StructuredLogRiverpodObserver keeps the record for you. Passed to the container once, it writes the creation, updates, failures and removal of every provider as structured_log entries — with the provider's name, its family argument and the values involved as separate fields, so you can filter the story of one provider out of everything else.

Features #

Coverage #

  • One line to install — ProviderScope(observers: [StructuredLogRiverpodObserver()]).
  • The whole life of a provider — creation, updates, failures with stack traces, removal.
  • Providers told apart — by name, or by type when there is none; a family's argument is a field of its own.
  • Each failure once — Riverpod reports a failing future twice, as an update to AsyncError and as a failure; the observer logs the failure and skips the update.
  • Works with flutter_riverpod and hooks_riverpod as is — depends on package:riverpod only, which both are built on, so it also runs in pure Dart.

Control #

  • Its own category — every entry carries category: 'riverpod', so a LogSink can route provider traffic separately and the in-app log viewer offers it as a filter.
  • A level per hook — or null to turn a hook off, usually the updates.
  • A filter — leave noisy providers out of the log entirely.
  • Values under your control — values and family arguments go through a describer you can replace, to keep secrets out of the log or hand redactKeys a map.

Safety #

  • Never breaks a provider — a toString() or describer that throws costs the entry its values, and a filter that throws costs the entry, but neither reaches the code that changed the state.
  • Costs nothing when off — a hook that is off, a provider that is filtered out or a level no sink takes never has its value described.

Where it fits #

The observer needs nothing but structured_log — no server, no Flutter. Its entries flow to whatever sinks you have configured: the console, a file, an in-app log viewer (structured_log_material, structured_log_fluent or structured_log_cupertino), where the riverpod category becomes a filter, or a self-hosted structured_log_server through structured_log_remote_sync. It is one of nine adapters that log what your app's libraries already do; the whole project is at structured-log.openidealab.com.

Installation #

Riverpod 3 only. The observer hooks of Riverpod 2.x have other signatures, and one class cannot override both; for 2.x this package does not work.

Published on pub.dev as a pre-release:

dependencies:
  flutter_riverpod: ^3.0.0 # or riverpod: ^3.0.0
  structured_log: ^0.3.0
  structured_log_riverpod: ^0.1.0-dev.1

Quick Start #

import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:structured_log/structured_log.dart';
import 'package:structured_log_riverpod/structured_log_riverpod.dart';

void main() {
  StructlogConfiguration.configure(
    sinks: [LogSink(name: 'console', output: coloredConsoleOutput)],
  );

  runApp(
    ProviderScope(
      observers: [StructuredLogRiverpodObserver()],
      child: const MyApp(),
    ),
  );
}

In pure Dart pass the same observer to the container: ProviderContainer(observers: [StructuredLogRiverpodObserver()]).

A named counter provider that is read and then set to 1 logs:

[2026-10-13T22:27:13.862226Z] DEBUG: provider_added {"logger":"riverpod","category":"riverpod","provider":"counter","value":"0","value_type":"int"}
[2026-10-13T22:27:13.875054Z] DEBUG: provider_updated {"logger":"riverpod","category":"riverpod","provider":"counter","previous_value":"0","value":"1","value_type":"int"}

A failing FutureProvider named profile logs once, as an error:

[2026-10-13T22:27:13.884681Z] ERROR: provider_failed {"logger":"riverpod","category":"riverpod","provider":"profile","error":"Bad state: offline","error_type":"StateError","stack_trace":"#0 ..."}

See example/main.dart for a runnable version (dart run example/main.dart).

What gets logged #

Every entry carries category (riverpod by default), logger (riverpod by default) and provider: the provider's name, or its type when it has no name. For a family, provider_argument holds the argument.

Hook event Default level Fields
didAddProvider provider_added debug value, value_type
didUpdateProvider provider_updated debug previous_value, value, value_type
providerDidFail provider_failed error error, error_type, stack_trace
didUnmountProvider provider_disposed debug —

A provider that throws while building is logged as provider_added with no value and value_type: Null, then as provider_failed.

provider_disposed is written when a provider is removed from memory, for example an autoDispose provider that lost its last listener. A provider that merely rebuilds — a dependency changed, refresh, invalidate — is not disposed, and writes no entry of that name. The hook behind it, didUnmountProvider, exists from riverpod 3.3.2: on 3.0.0–3.2.1 this entry is not written at all, and nothing tells you so. The other three entries are the same on every 3.x. If you need provider_disposed, depend on riverpod: ^3.3.2.

Mutations (mutationStart and friends, new in Riverpod 3) are not logged: their API is marked experimental.

Keeping secrets out of the log #

Values and family arguments are logged through toString() by default (cut to 1000 characters). If they can carry passwords, tokens or personal data — and especially if the log leaves the device through structured_log_remote_sync — pass a describe that withholds them. Returning null drops the value and keeps its type:

StructuredLogRiverpodObserver(
  describe: (value) => switch (value) {
    Session() || Credentials() => null,
    _ => describeRiverpodValue(value),
  },
)

The same describe sees the family argument, which is often a user id or a token, so one function covers both. To log types only, everywhere: describe: (_) => null.

A describe can also return a map instead of a string. A map stays a structure in the entry, so a redactKeys processor reaches its fields, as it reaches any other field. A string from toString() is opaque to it:

StructlogConfiguration.configure(processors: [redactKeys(), dropNullValues]);

StructuredLogRiverpodObserver(
  describe: (value) => switch (value) {
    Session(:final user, :final token) => {'user': user, 'token': token},
    _ => describeRiverpodValue(value),
  },
)
// value: {"user": "u", "token": "***"}

The error of provider_failed is not run through describe: it is written by structured_log itself, as error, error_type and stack_trace.

Configuration #

StructuredLogRiverpodObserver(
  // Which hooks are logged, and at what level; null turns one off.
  levels: const RiverpodLogLevels(
    added: LogLevel.trace,
    updated: null, // a provider bound to a text field updates all the time
  ),
  // Leave noisy providers out.
  filter: (provider) => provider.name != 'ticker',
  category: 'state',
)

API Reference #

Symbol Description
StructuredLogRiverpodObserver({logger, loggerName, category, levels, describe, filter}) The observer, a final class extending ProviderObserver. Without logger, it calls getLogger(loggerName) on every hook, so a later StructlogConfiguration.configure reaches it too. category: null binds no category.
RiverpodLogLevels({added, updated, failed, disposed}) A LogLevel? per hook; null turns the hook off.
RiverpodValueDescriber Object? Function(Object? value) — turns a value or a family argument into an entry value; null omits the field.
describeRiverpodValue(value) The default describer: toString(), cut to defaultRiverpodValueMaxLength (1000) characters; a throwing toString() becomes a placeholder.

The rest of the structured_log family:

Core

  • structured_log — structured JSON logging with context binding, processors and multi-sink routing

In-app log viewer

Shipping logs to a server

Integrations

License #

MIT — see LICENSE.

0
likes
150
points
84
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

StructuredLogRiverpodObserver — a ProviderObserver that turns the lifecycle of every Riverpod provider (created, updated, failed, removed) into structured_log entries. Works with flutter_riverpod as is.

Homepage
Repository (GitHub)
View/report issues

Topics

#logging #riverpod #state-management #structured-logging #debugging

License

MIT (license)

Dependencies

riverpod, structured_log

More

Packages that depend on structured_log_riverpod