Basics #

Request baggage is a way to add tracing data to logs and metrics. As such it should be used with care as tracking a large amount of data per player can overwhelm the system. It can leverage session baggage which is a more general concept for attaching arbitrary data to a player or partner sessions.

To use request baggage you will have to enable it and have plugin implementations that determine what data are added to the logging or metric payloads. There are a number of plugins ready to use: DefaultRequestBaggagePlugin or RequestBaggagePluginWithSessionBaggage and a DefaultSessionPlugin for validating session baggage.

Enabling request baggage #

Request baggage processing is disabled by default. Enable it in your YAML config:

game: # or social
  core:
    logging:
      requestBaggageEnabled: true

DefaultRequestBaggagePlugin #

DefaultRequestBaggagePlugin adds a traceId to every inbound request. The traceId is a randomly generated UUID that is propagated through service-to-service calls, giving you a reliable identifier to correlate log entries across distributed call paths.

This plugin returns an empty map for getForMetrics.

This plugin is the default when no custom plugin is registered.

RequestBaggagePluginWithSessionBaggage #

RequestBaggagePluginWithSessionBaggage extends DefaultRequestBaggagePlugin. In addition to the traceId, it includes any key-value pairs that have been stored in the session’s baggage cache. This allows per-session context (such as a game build ID or region) to appear automatically in every log entry for that session.

This plugin returns an empty map for getForMetrics.

Adding data to session baggage #

To store values into the session baggage cache, use the updateSessionBaggageV1 (player) or updateSessionBaggagePartnerV1 (partner) RPCs. See Update Session Baggage for details.

Request Baggage Plugin #

If you want to add custom context beyond what the provided plugins offer, implement the RequestBaggagePlugin interface.

interface RequestBaggagePlugin {
    /**
     * Called for RPCs from player, partner, and operator sessions.
     * Return the baggage entries to attach to the request.
     */
    fun getForExternalRequest(request: ExternalRequest, session: RpcExecutionContext): MutableRequestBaggage

    /**
     * Called during service-to-service message flow.
     * The current baggage is provided and should be included in the result to avoid dropping propagated values.
     */
    fun <Request : pragma.rpc.Request> getForServiceRpc(
        current: RequestBaggage,
        request: Request,
        pragmaSession: PragmaSession,
    ): MutableRequestBaggage

    /**
     * Called when recording metrics. Return any baggage entries to attach as metric tags. Take care when implementing this method. Any tag value that is unbounded will cause high cardinality which is likely to crash otel, either in the engine or * the collector. Only include tags with a specific set of values, like how IdProvider can only be Steam, Epic, etc.
     */
    fun getForMetrics(metricName: String, requestBaggage: RequestBaggage): MutableRequestBaggage
}

Example custom plugin #

class MyRequestBaggagePlugin : RequestBaggagePluginWithSessionBaggage() {

    override fun <Request : pragma.rpc.Request> getForServiceRpc(
        current: RequestBaggage,
        request: Request,
        pragmaSession: PragmaSession,
    ): MutableRequestBaggage {
        val result = super.getForServiceRpc(current, request, pragmaSession)
        result["displayName"] = pragmaSession.playerSession.displayName
        return result
    }

    override fun getForMetrics(metricName: String, requestBaggage: RequestBaggage): MutableRequestBaggage {
        val result: MutableRequestBaggage = mutableMapOf()
        if (requestBaggage.containsKey("buildId")) {
            result["buildId"] = requestBaggage["buildId"]!!
        }
        return result
    }
}

Registering the plugin #

Assign your plugin to pragmaNode.requestBaggagePlugin inside an AlwaysStartedNodeService. You can create one just for updating the plugin or add the line to an existing service that makes sense.

@PragmaService(
    backendTypes = [BackendType.GAME, BackendType.SOCIAL],
)
class MyBaggageSetupService(pragmaNode: PragmaNode) : AlwaysStartedNodeService(pragmaNode) {

    override suspend fun run() {
        pragmaNode.requestBaggagePlugin = MyRequestBaggagePlugin()
    }
}

Request baggage limits #

After your plugin returns entries, the engine validates them against SessionCoreConfig before attaching the result to all logs that are triggered during an RPC’s execution. Configure these limits under game.core.sessionCoreConfig (and/or social.core.sessionCoreConfig).

Config keyDefaultDescription
baggageEntryLimit5Maximum number of key-value pairs the plugin may return. If the count exceeds this limit, all entries are rejected and a warning is logged.
baggageKeySizeLimit36Maximum character length of each key. Entries exceeding this limit are silently dropped and a warning is logged.
baggageValueSizeLimit36Maximum character length of each value. Entries exceeding this limit are silently dropped and a warning is logged.
game: # or social
  core:
    sessionCoreConfig:
      baggageEntryLimit: 5
      baggageKeySizeLimit: 36
      baggageValueSizeLimit: 36

Session Plugin #

The SessionPlugin interface controls how session baggage entries are validated before being stored in the session cache, and which entries are emitted to telemetry when a session ends.

interface SessionPlugin {
    /**
     * Validate and return entries to be saved in the session baggage cache.
     * Called when updateSessionBaggageV1 or updateSessionBaggagePartnerV1 is received.
     * 'current' contains the existing cache entries; 'requestData' contains the new entries to merge in.
     * Return the resulting map that should be stored.
     */
    fun validateBaggage(current: Map<String, String>, requestData: Map<String, String>): MutableMap<String, String>
}

DefaultSessionPlugin #

DefaultSessionPlugin is the provided implementation. It enforces configurable limits on the number and size of entries to constrain memory and network overhead, and returns all baggage fields for logout telemetry.

Config keyDefaultDescription
baggageEntryLimit5Maximum number of key-value pairs stored per session
baggageKeySizeLimit36Maximum character length of each key
baggageValueSizeLimit36Maximum character length of each value

If the total entry count would exceed baggageEntryLimit after merging, the update is rejected and a BaggageEntryLimitExceeded error is returned to the caller. Individual entries whose key or value exceeds the size limits are silently dropped and a warning is logged.

Example custom session plugin #

class MySessionPlugin(
    override val service: Service,
    override val contentDataNodeService: ContentDataNodeService,
) : SessionPlugin {

    /**
     * Only allow keys from a known allowlist; ignore everything else.
     */
    override fun validateBaggage(
        current: Map<String, String>,
        requestData: Map<String, String>,
    ): MutableMap<String, String> {
        val allowedKeys = setOf("buildId", "region", "matchType")
        val result = current.toMutableMap()
        requestData.forEach { (key, value) ->
            if (key in allowedKeys) {
                result[key] = value
            }
        }
        return result
    }
}

Registering the session plugin #

Configure your custom plugin in YAML under SessionService.sessionPlugin.

game: # or social
  pluginConfigs:
    SessionService.sessionPlugin:
      class: "com.your.package.MySessionPlugin"

To use DefaultSessionPlugin with custom limits, configure it directly:

game: # or social
  pluginConfigs:
    SessionService.sessionPlugin:
      class: "pragma.session.DefaultSessionPlugin"
      config:
        baggageEntryLimit: 5       # maximum number of key-value pairs stored per session (default: 5)
        baggageKeySizeLimit: 36    # maximum character length of each key (default: 36)
        baggageValueSizeLimit: 36  # maximum character length of each value (default: 36)