Logging

The client can report what it is doing: the requests it makes, the WebSocket's comings and goings, the tokens it signs requests with. It says nothing until you ask.

Turning logging on

FeedsConfig.logConfig is what asks. A priority on its own writes to the console.

// Nothing is logged until you ask. A priority on its own writes to the console.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    logConfig: StreamLogConfig(priority: StreamLogPriority.debug),
  ),
);
await client.connect();

// Terminal, and what a real app calls when it is done with the client for good. Use `disconnect`
// to close the connection and keep the client.
await client.dispose();

Left out entirely, the client touches no logger at all: the one every Stream SDK in the process shares stays as whatever configured it, or silent if nothing did.

Priorities run verbose, debug, info, warning, error, none. A record is emitted when its priority is at least the one you set, so debug gets you everything except verbose.

Warning:

Records include the Authorization header, which carries the user's token. Weigh what reads them before sending records off the device.

Sending records somewhere else

A handler of your own replaces the console: a crash reporter, a log file, your own analytics.

// A handler of your own replaces the console.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    logConfig: StreamLogConfig(
      priority: StreamLogPriority.debug,
      handler: StreamLogHandler.from(reportToYourCrashReporter),
    ),
  ),
);
await client.connect();
await client.dispose();

StreamLogRecord carries time, priority, tag, message, and the error and stackTrace when there is one.

Keeping the console as well

StreamLogHandler.composite fans each record out to several handlers. Naming the default one only under kDebugMode keeps a console for whoever is developing without leaving one in the build your users run, while the crash reporter goes on receiving records everywhere.

// Or compose with the one the client would have used. Naming it only under `kDebugMode` keeps a
// console for whoever is developing without leaving one in the build your users run, while the
// crash reporter goes on receiving records everywhere.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    logConfig: StreamLogConfig(
      priority: StreamLogPriority.debug,
      handler: StreamLogHandler.composite([
        if (kDebugMode) StreamLogConfig.defaultHandler,
        StreamLogHandler.from(reportToYourCrashReporter),
      ]),
    ),
  ),
);
await client.connect();
await client.dispose();

Only while developing

StreamLogHandler.silent drops everything, which is the other way to keep logs out of a release build.

// `kDebugMode` is what leaves a console out of the build your users run.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    logConfig: StreamLogConfig(
      priority: StreamLogPriority.debug,
      handler: kDebugMode ? StreamLogConfig.defaultHandler : StreamLogHandler.silent,
    ),
  ),
);
await client.connect();
await client.dispose();

Filtering by subsystem

Records are tagged by the part of the client that emitted them:

Tag What it covers
SF:Ws The WebSocket connection, its reconnections and the events it receives
SF:WsRecovery Automatic reconnection: retry timing, network and lifecycle signals
SF:Http The API requests the client makes
SF:HttpAuth Token retrieval and the signing of requests

StreamLogFilter.prefix turns one of them up and the rest down. It is also how you tell this SDK's records apart from another Stream SDK sharing the same handler.

// Records are tagged `SF:Ws` for the connection, `SF:Http` for the requests it makes and
// `SF:HttpAuth` for the tokens it signs them with. A filter picks out one of them, or tells this
// SDK's records apart from another Stream SDK sharing the same handler.
final client = StreamFeedsClient(
  apiKey: '<your_api_key>',
  user: const User(id: 'alice'),
  tokenProvider: TokenProvider.static(UserToken('<your_jwt_token>')),
  config: const FeedsConfig(
    logConfig: StreamLogConfig(
      filter: StreamLogFilter.prefix(
        {'SF:Ws': StreamLogPriority.verbose},
        otherwise: StreamLogPriority.warning,
      ),
    ),
  ),
);
await client.connect();
await client.dispose();

Reporting a problem

When you open an issue on stream-feeds-flutter, logs captured at debug with the SF:Ws and SF:Http tags are usually what makes it reproducible. Strip the Authorization header first.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Feeds Flutter v3: <Your Question>