# 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.

```dart label="Dart"
// 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.

```dart label="Dart"
// 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.

```dart label="Dart"
// 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.

```dart label="Dart"
// `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.

```dart label="Dart"
// 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](https://github.com/GetStream/stream-feeds-flutter/issues), logs captured at `debug` with the `SF:Ws` and `SF:Http` tags are usually what makes it reproducible. Strip the `Authorization` header first.

---

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/flutter/logging/](https://getstream.io/activity-feeds/docs/flutter/logging/).