# Fields

For extra context, every log is accompanied with an optional `fields` dictionary.

It's encouraged to put changing parts of your log messages inside of `fields` argument as it makes it simpler to match on such attributes
while creating workflows. For example, instead of logging message "Timezone changed from X to Y" log "Timezone changed" message with
fields two fields: `from` and `to`.

=== "Android (Kotlin)"

    ```kotlin
    Logger.log(LogLevel.INFO, mapOf("from" to "UTC", "to" to "PST")) { "Timezone changed" }
    ```

=== "Android (Java)"

    ```java
    HashMap fields = new HashMap();
    fields.put("from", "UTC");
    fields.put("to", "PST");

    Logger.log(LogLevel.INFO, fields, () -> "Timezone changed");
    ```

=== "iOS (Swift)"

    ```swift
    Logger.log(level: .info, "Timezone changed", fields: ["from": "UTC", "to": "PST"])
    ```

=== "iOS (Objective-C)"

    ```objective-c
    [CAPLogger logWithLevel:LogLevel.info message:@"Timezone changed" fields: @{@"from": @"UTC", @"to": @"PST"}];
    ```

=== "React Native"

    ```javascript
    import { info } from '@bitdrift/react-native';

    info('Timezone changed', { from: 'UTC', to: "PST" });
    ```

There are three different ways to attach fields to logs. Below is the list, in order of priority, indicating which methods have precedence over others (fields provided by methods higher in the list override those provided by methods lower in the list):

- Using the `fields` argument in a specific `log(...)` method: This gives the greatest level of control on a per-log basis but has the highest performance impact. Refer to [Custom Fields](#custom-fields) for more details.
- Using the `addField(...)` method: This attaches fields to all logs emitted by the SDK after the method call. It is the most performant way to add fields to logs. Refer to[Field Addition and Removal Methods](#field-addition-and-removal-methods) for more details.
- Implementing `FieldsProvider` protocol/interface: This approach is more flexible but less direct. Refer to [Field Providers](#field-providers) for more details.

## Default Fields

Every log emitted by the SDK is tagged with extra out-of-the-box attributes related to the device and app state. Below is the their list:

=== "Android"

    | Field Name | Field Key | Notes |
    |---|---|---|
    | App Identifier | `app_id` ||
    | App Version | `app_version` | The value of the [`versionName`](https://developer.android.com/reference/android/content/pm/PackageInfo#versionName) attribute of the `PackageInfo` |
    | App Version Code | `_app_version_code` | The value obtained using [`getLongVersionCode`](https://developer.android.com/reference/android/content/pm/PackageInfo#getLongVersionCode) or [`versionCode`](https://developer.android.com/reference/android/content/pm/PackageInfo#versionCode) accessors. |
    | Architecture | `_architecture` ||
    | Carrier | `carrier` ||
    | Device ID | `device_id` ||
    | Foreground | `foreground` ||
    | Locale | `_locale` | The string value of the [`getLocales`](https://developer.android.com/reference/androidx/core/os/ConfigurationCompat#getLocales(android.content.res.Configuration)) method call. |
    | Log Level | `log_level` ||
    | Log Type | `log_type` ||
    | Message | `message` ||
    | Network Type | `network_type` ||
    | Network Quality | `_network_quality` | Expose if the app is `Offline` based on its ability to reach the bitdrift ingest API. The field is not shown if the app is online. Note that any successful network request will identify the app as online |
    | OS | `os` ||
    | OS API Level | `_os_api_level` ||
    | OS Version | `os_version` | The value of [`RELEASE`](https://developer.android.com/reference/android/os/Build.VERSION#RELEASE) property. |
    | Radio Type | `radio_type` ||
    | Session ID | `session_id` ||

=== "iOS"

    | Field Name | Field Key | Notes |
    |---|---|---|
    | App Identifier | `app_id` ||
    | App Version | `app_version` | The value stored under the [`CFBundleShortVersionString`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) key of the info dictionary of the main Bundle. |
    | Build Number | `_build_number` | The valued stored under the [`kCFBundleVersionKey`](https://developer.apple.com/documentation/corefoundation/kcfbundleversionkey?language=objc) key of the [`infoDictionary`](https://developer.apple.com/documentation/foundation/nsbundle/1413477-infodictionary?language=objc) property of the main Bundle. |
    | Device ID | `device_id` ||
    | File | `_file` ||
    | Foreground | `foreground` ||
    | Function | `_function` ||
    | Line | `_line` ||
    | Locale | `_locale` |  The value of the `Locale.current.identifier` property. |
    | Log Level | `log_level` ||
    | Log Type | `log_type` ||
    | Message | `message` ||
    | Network Type | `network_type` ||
    | Network Quality | `_network_quality` | Expose if the app is `Offline` based on its ability to reach the bitdrift ingest API. The field is not shown if the app is online. Note that any successful network request will identify the app as online |
    | OS | `os` ||
    | OS Version | `os_version` | The value of [`systemVersion`](https://developer.apple.com/documentation/uikit/uidevice/systemversion) property. |
    | Radio Type | `radio_type` ||
    | Session ID | `session_id` ||

=== "React Native"

React Native supports all the out of the box fields provided by the platform the app is running on, see the iOS and Android sections for more details.

## Custom Fields

The SDK allows all but the following field names to be used by the customers of the SDK:

- Fields with keys that conflict with keys of the [default fields](#default-fields) emitted by the SDK.
  This is enforced for all fields and offending fields are dropped (a warning message is printed in the console when that happens).
- Fields whose names start with "_" (underscore character). This is enforced for [global fields](#global-fields)
  and offending fields are dropped (a warning message is printed in the console when that happens). The rule is going to be
  enforced for `fields` arguments of [custom logs](custom-logs.md) in the future.


### Providing Fields with Logging Methods

You can attach arbitrary fields to logs emitted by and with the use of the SDK.

=== "Android (Kotlin)"

    ```kotlin
    Logger.log(LogLevel.INFO, mapOf("key" to "value")) { "Info log" }

    Logger.logTrace(mapOf("key" to "value")) { "Trace log" }
    Logger.logDebug(mapOf("key" to "value")) { "Debug log" }
    Logger.logInfo(mapOf("key" to "value")) { "Info log" }
    Logger.logWarning(mapOf("key" to "value")) { "Warning log" }
    Logger.logError(mapOf("key" to "value")) { "Error log" }
    ```

=== "Android (Java)"

    ```java
    Logger.log(LogLevel.INFO, Collections.singletonMap("key", "value"), () -> "Info log");

    Logger.logTrace(Collections.singletonMap("key", "value"), () -> "Trace log");
    Logger.logDebug(Collections.singletonMap("key", "value"), () -> "Debug log");
    Logger.logInfo(Collections.singletonMap("key", "value"), () -> "Info log");
    Logger.logWarning(Collections.singletonMap("key", "value"), () -> "Warning log");
    Logger.logError(Collections.singletonMap("key", "value"), () -> "Error log");
    ```

=== "iOS (Swift)"

    ```swift
    Logger.log(level: .info, "Info log", fields: ["key": "value"])

    Logger.logTrace("Trace log", fields: ["key": "value"])
    Logger.logDebug("Debug log", fields: ["key": "value"])
    Logger.logInfo("Info log", fields: ["key": "value"])
    Logger.logWarning("Warning log", fields: ["key": "value"])
    Logger.logError("Error log", fields: ["key": "value"])
    ```

=== "iOS (Objective-C)"

    ```objective-c
    [CAPLogger logWithLevel:LogLevel.info message:@"Info log" fields: @{@"key": @"value"}];

    [CAPLogger logTrace:@"Trace log" fields:@{@"key": @"value"}];
    [CAPLogger logDebug:@"Debug log" fields:@{@"key": @"value"}];
    [CAPLogger logInfo:@"Info log" fields:@{@"key": @"value"}];
    [CAPLogger logWarning:@"Warning log" fields:@{@"key": @"value"}];
    [CAPLogger logError:@"Error log" fields:@{@"key": @"value"}];
    ```

=== "React Native"

    ```javascript
    import { info } from '@bitdrift/react-native';

    // Log line with LogLevel of Trace
    trace('Hello world!', { key: 'value' });

    debug('Hello world!', { key: 'value' });
    info('Hello world!', { key: 'value' });
    warning('Hello world!', { key: 'value' });
    error('Hello world!', { key: 'value' });
    ```

### Global Fields

The SDK supports two ways of adding log fields to attach to every emitted log. Each of these methods manages
a separate pool of logs, ensuring that the method of managing fields does not impact those managed by the other method.

!!! note
  Global fields are not persisted to disk and thus do not survive process restarts, so it is possible for these values to change within a session depending on your [session management strategy](session-management.md).

#### Field Addition and Removal Methods

The logger exposes `addField(...)` and `removeField(...)` methods, which can be used to control the list of
global fields added to emitted logs.

=== "Android (Kotlin)"

    ```kotlin
    Logger.addField("key", "value")
    Logger.removeField("key")
    ```

=== "Android (Java)"

    ```java
    Logger.addField("key", "value");
    Logger.removeField("key");
    ```

=== "iOS (Swift)"

    ```swift
    Logger.addField(withKey: "key", value: "value")
    Logger.removeField(withKey: "key")
    ```

=== "React Native"

    ```javascript
    import { addField, removeField } from '@bitdrift/react-native';

    addField('key', 'value');
    removeField('key');
    ```

#### Field Providers

The `start` method takes an optional list of `fieldProviders` where you can implement your own logic for retrieving the data you want to pass.

In cases of key conflicts (where two or more fields share the same key), the earlier a field provider appears in the list
of registered providers, the higher priority the field it returns is given.

=== "Android (Kotlin)"

    ```kotlin
    // Attach your own custom user_id field to each log
    class CustomUserIdProvider : FieldProvider {
      override fun invoke(): Fields {
        return mapOf("user_id" to "<your_custom_user_id>")
      }
    }

    Logger.start(
      // ...
      fieldProviders = listOf(CustomUserIdProvider()),
    )
    ```

=== "Android (Java)"

    ```java
    // Attach your own custom user_id field to each log
    class CustomUserIdProvider implements FieldProvider {
      @Override
      public Map<String, ? extends String> invoke() {
        return Collections.singletonMap("user_id", "<your_custom_user_id>");
      }
    }

    Logger.start(
      // ...
      Collections.singletonList(new CustomUserIdProvider()) //fieldProviders
    );
    ```


=== "iOS (Swift)"

    ```swift
    // Attach your own custom user_id field to each log
    final class CustomUserIdProvider: FieldProvider {
      func getFields() -> Fields {
        ["user_id": "<your_custom_user_id>"]
      }
    }

    Logger.start(
      // ...
      fieldProviders: [CustomUserIdProvider()],
    )
    ```

!!! tip
    `user_id` is a one-of-a-kind special field. Providing it as shown above will cause it to appear in the [Timeline header](../../product/timeline/session-view.md#session-details).
