Game Engine Analytics
Overview
SparkLogs has specialized support for ingesting, processing, aggregating, and analyzing metrics from games. The scale and flexibility of SparkLogs makes it a great choice for analytics, allowing you to focus on building your game and not on building and maintaining your own analytics infrastructure.
Capturing analytics events allows you to understand how players are behaving so you can optimize for various outcomes (daily activity, playtime, progression, in-app purchases and ads) and for balance (resource grants and sinks), etc.
Competing analytics solutions are often very expensive (10x) and have field, cardinality, and event count limits. SparkLogs delivers a cost-effective solution that supports unlimited fields, unlimited values, and can effortlessly scale to petabytes, supporting games with 100s of millions of daily active users (DAU) and beyond. We also provide automatic user geolocation (country), currency conversion for purchase events, and auto extracts structured fields from your unstructured logs.
Open-source plugins are available for popular game engines to make it easy to capture and send analytics events and backend logs from your games.
Our rich analytics data model lets you capture key events, including including session start and end, design, progression, real money purchases, ads, resource grants and sinks, and error or other textual events.
Once configured, daily aggregate analytics snapshots are automatically generated and retained for 10 years (paid plan) or 3 months (free plan), so you can analyze daily active users (DAU), average session and ad statistics, progression funnels, and other key game metrics.
Direct access to your aggregate and granular analytics data is available, to make it easy to visualize trends on your own dashboards and analyze with your own ML models.
Free SparkLogs accounts are available to ingest up to 25 GB/month. That's enough free quota for over 1 million analytics sessions of data every month (assuming each session records 16 events and each event is about 1.5 KiB).
Setup and Configuration
First activate game engine analytics within your SparkLogs workspace. Login to the app, go to Workspace Settings, and click the Enable Game Engine Analytics
button.
This will activate automatic daily aggregate analytics snapshots.
Next, determine how you will ingest game engine analytics events into SparkLogs.
Data Ingestion and Plugins
You can generate and ingest analytics events into SparkLogs using one of our open-source plugins for supported game engines:
- Unreal Engine plugin: This plugin supports Unreal Engine 4 and 5, and captures analytics events and/or backend logs.
If you're using another game engine that does not yet have a supported plugin, contact us to request support for your game engine.
Also, even without a plugin you can record analytics events from your player's game clients and get the benefits of the custom analytics analysis that SparkLogs performs (e.g., daily snapshot summarization of analytics data, etc.), provided that you ingest data into SparkLogs with the same schema for analytics events.
There are a number of options to ingest analytics events:
- Work with us to adapt a plugin's source code to your game engine. We'd like to develop plugins for other popular game engines, so chat with us on discord to discuss.
- Submit analytics JSON events using any of our APIs (such as the HTTP+JSON API). The schema of submitted events must match known analytics event types. See also the example JSON data to understand how these events are structured in practice. Make sure to batch your events (details below).
- Generate JSON data for analytics events as described above, but instead of submitting directly via API, send the data to a log forwarding agent of your choice (e.g., append JSON data to a local NDJSON file and configure fluentbit to read this file, batch, and submit the data to SparkLogs).
If you submit analytics events data directly via one of the APIs, you must batch your events rather than sending them one at a time. We recommend batching analytics events at the client-side and sending all events at the end of a game session. Alternatively, you could use the batching scheme of one of our plugins, which is to send queued events only after 15 minutes, when 128 KiB data is queued, when the analytics session ends, or when the game engine exits (whichever comes first).
If you are using a forwarding agent such as fluentbit to send analytics events (e.g., from a local NDJSON file that you append to), make sure to configure it to batch events so that events are not sent more often than every 15 minutes. For example, for fluentbit, set the service flush setting to 900 (seconds).
In addition to sending game engine analytics data from your player's game clients, if you want to ship logs from your backend game servers to SparkLogs, some of our plugins have built-in support for log shipping. For other platforms, you can log your game engine output to a (rotating) file and then use any log forwarding agent to ship your logs.
Whether using a plugin or an API, you will need authentication information for the agent(s) you want to use to ingest the data. You can setup the organizations and agents you want to use for your ingested data in the SparkLogs app. You can optionally configure different authentication credentials for the different build configurations (client, editor, server) of your game or use the same auth for all. You may want to use a different SparkLogs organization for each game you develop, and also have suborganizations for development (editor) and production environments.
Confirming Data Flow
After configuring your plugin and adding logic to record analytics events, you can confirm that the data is flowing correctly. Play a test game session so that some analytics events will be generated and transmitted. Note that analytics events are typically transmitted to SparkLogs at the end of a session or when the game engine exits.
You can then use Explore UI with the following LQL query to show analytics events:
g_analytics.type!
Conceptual Model
All analytics events happen within the context of an analytics session, which marks the beginning and end of when a player is actively playing the game. Sessions are globally identified by a unique random session ID. Each analytics event is associated with a session and is of a particular type (session start/end, purchase, ad, resource, progression, design, log).
Individual analytics events are ingested and stored individually to preserve detail (see data model), where in SparkLogs you can explore and export your data using LQL in the Explore UI. If you're using a private-cloud service plan you'll have direct access to your data in your own Google Cloud BigQuery dataset, and you can perform your own analysis directly using BigQuery. You should make sure to use a BigQuery Enterprise reservation that auto-scales from zero slots to optimize your BigQuery query costs.
Aggregate analytics snapshots are also automatically computed once a day shortly after midnight UTC, allowing you to visualize and analyze trends such as daily active users, playtime statistics, player revenue, and other key metrics.
User Segmentation
You can optionally store one or more "user tags" with each analytics event to help segment your users. For example, you could include an A/B testing experiment ID as a user tag, or tags identifying whether or not a user is free-to-play or has made purchases. There is no limit to the number of unique tag values you can use.
Refer to the documentation for your specific game engine plugin how to set user tags.
User tags are stored in the user_tags
and user_tags_array
fields of the g_analytics
object in each analytics event.
Types of Analytics Events
The following types of analytics events are supported:
- Session Start: A player is actively playing the game.
- Session End: A player has stopped playing the game.
- Purchase: Represents real money purchases to obtain a certain status, item, or resource in the game. If the purchase granted certain amounts of virtual currencies, you may want to also record one or more resource source events.
- Ad: Represents one or more ad impressions and a corresponding action taken by the player.
- Resource: Represents the granting (resource source) or using up (resource sink) of a virtual currency (gems, lives, etc.).
- Progression: Represents starting, failing, or completing a certain part of a game.
- Design: A design event is any in-game event you wish to record that does not fit into the other categories.
- Log: A log event is a textual event with a severity that you want to record and associate with a session, such as an error causing a game crash, or any other information that is not captured by the other event types.
Each analytics event includes certain common fields (see analytics data model). You can also optionally provide a textual reason why a given event occurred, and you can also provide unlimited custom JSON fields (including complex values such as objects or arrays) with additional information. See also the example JSON data for each event type.
Session Start Event
Marks the beginning of a session. This event does not have an event_id field, and there are no additional event fields beyond common fields.
Session End Event
Marks the end of a session. This event does not have an event_id field.
Additional event fields beyond common fields:
Field Name | Type | Description |
---|---|---|
session_ended | Timestamp | The timestamp when the session ended. |
session_duration_secs | Numeric | The duration of the session in seconds, from the time the session started to the time it ended. |
Purchase Event
Marks the purchase of a certain item or resource in the game using real-world currency. This event has the following additional fields:
This event takes as input an item category and item ID to identify what was purchased, as well as the amount of real-world currency spent on the purchase.
If the purchase granted certain amounts of virtual currencies, you should also record one or more resource source events.
The generated event ID is derived as follows: <item_category>:<item_id>
Additional event fields beyond common fields:
Field Name | Type | Description |
---|---|---|
item_category | String | Optional. The category of the item that was purchased (e.g., "starter_packs", "individual_resource"). |
item_id | String | The unique identifier for the item that was purchased. (e.g. "pack_1", "pink_gems", "lives") |
currency | String | The currency used for the purchase (e.g., "USD", "EUR"). This is the ISO 4217 currency code. Over 200 are supported. |
amount | Numeric | The amount of real-world currency spent on the purchase (for example one cent would be 0.01 ). |
amount_converted | Numeric | The amount converted into your preferred target currency (e.g., USD) using the average exchange rate at the time of event ingestion. Will only be present if the source currency is supported and the conversion is successful. |
amount_converted_currency | String | The ISO 4217 currency code of the target currency used for conversion. Will only be present if the source currency is supported and the conversion is successful. |
transaction_num | Numeric | The number of the transaction since the game was first installed, starting with 1. |
first_purchased | Timestamp | The timestamp when the first purchase was made since the game was installed. |
You can customize plugin settings to change the target currency. See the currency conversion documentation for details.
Ad Event
Marks one or more ad impressions and a corresponding action taken by the player. You can choose to either record one event for each ad impression, or you can aggregate multiple ad impressions into a single event.
This event takes as input the ad provider, ad placement ID, and ad type, as well as the action taken by the player. Optionally you can record revenue from the ad, if known.
The generated event ID is derived as follows: <ad_provider>:<ad_placement>:<ad_type>
Additional event fields beyond common fields:
Field Name | Type | Description |
---|---|---|
ad_provider | String | A string identifying the ad provider. For example, unity , admob , etc. or use unknown |
ad_placement | String | An ID representing where the ad was placed within you app. For example, end_of_level , treasure_chest , etc. |
ad_type | String | Optional. The type of ad that was shown. One of interstitial , video , rewarded_video , banner , native , playable , app_open , offerwall , cross_promo , other , or any custom string. |
ad_action | String | Optional. The action taken by the player in response to the ad. One of completed , skipped , clicked , rewarded , failed_to_show , other , or any custom string. |
ad_fail_reason | String | Optional. The reason the ad failed to show, if applicable. One of no_fill , offline , network_error , invalid_request , other , or any custom string. |
revenue | Numeric | Optional. The total revenue generated from these ad impression(s), if known. Use null or 0.0 if unknown. |
currency | String | Optional. The ISO 4217 code of the currency used for the ad revenue, if known (e.g., USD ). |
duration_secs | Numeric | Optional. The duration of the ad in seconds, if applicable. If this event represents multiple impressions, use the average duration. |
count | Numeric | Optional. The number of ad impressions recorded in this event. Defaults to 1 if not specified. |
Based on the currency conversion logic, it will also calculate the revenue_converted
and revenue_converted_currency
fields if applicable.
Resource Event
Marks the granting (resource source) or using up (resource sink) of a virtual currency (gems, lives, etc.). This event takes as input the flow type, an optional item category and item ID to identify what was purchased, and the type and amount of virtual currency granted or used.
Be thoughtful about the balance between recording every resource source/sink event as it happens (which could be very often) vs aggregating the information and recording it only at key points (e.g. when losing a life or ending a level). The system can handle sending frequent events, but this could generate a lot more data and could thus cost more at large scale. Be especially thoughtful when you have 100s of thousands of users or more.
If a resource was granted as the result of a purchase using real money, then make sure to also record a corresponding purchase event as well with the same item category and item ID.
The generated event ID is derived as follows: <flow_type>:<virtual_currency>:<item_category>:<item_id>
Additional event fields beyond common fields:
Field Name | Type | Description |
---|---|---|
flow_type | String | Either "Source" or "Sink". |
virtual_currency | String | The name of the virtual currency (e.g., "pink_gems", "lives"). |
item_category | String | Optional. The category of the item associated with the resource being granted or used. e.g., "currency_purchase" or "pickup" |
item_id | String | Optional. The ID of the item associated with the resource being granted or used. e.g., "100_pink_gems_pack" or "ad_item" |
amount | Numeric | The amount of virtual currency granted or used. This will be enforced to be positive for sources and negative for sinks. |
Progression Event
Marks the starting, failing, or completing of a certain part of a game. Progression events form a hierarchy and can have up to N arbitrary tiers (world, region, level, segment, etc.). Note that you do not have to record a start event for a given event ID, you can just record failure or success events as you wish.
There is no fixed number of tiers for progression events. There are convenience functions to generate progression events with 1-5 tiers.
While there is no limit on depth or cardinality of the combination of tier values, be smart about how many total possible values of unique progression event IDs you create based on your planned analysis.
It will automatically compute and record the number of attempts it took to complete a given event successfully.
Progression events may optionally be associated with a numeric value as well (e.g., score).
The generated event ID is derived as follows: <status>:<tier1>:...:<tierN>
Additional event fields beyond common fields:
Field Name | Type | Description |
---|---|---|
status | String | One of Started, Failed, or Completed. |
tiers | String | The flattened value of all tiers, each separated by : (e.g., addonpack:trial_master_sword ) |
tiers_array | Array of String | The JSON array of all tier values. (e.g., ["addonpack", "trial_master_sword"] ) |
tier1 .. tierN | String | The value of the Nth tier. (e.g., could have a tier2 field with value trial_master_sword ) |
value | Numeric | The optional numeric value associated with the event (e.g., score achieved for the attempt) |
attempt | Numeric | The number of attempts for this given event ID before successfully completing it. Starts at 1. |
Design Event
Marks any custom event in your game, identified by a given event ID. Event ID is a colon delimited string that forms an event hierarchy of arbitrary depth
(e.g., addonpack:quest:trial_master_sword:unlocked
or achievements:single_player:beat_game
).
While there is no limit on depth or cardinality of event ID, be smart about how many categories you create based on your planned analysis.
Design events may optionally be associated with a numeric value as well.
Additional event fields beyond common fields (including event ID):
Field Name | Type | Description |
---|---|---|
value | Numeric | Optional numeric value associated with the event. |
Log Event
Records a severity and textual event that is associated with an analytics session. For example, if the game encounters a fatal error that stops gameplay, you could record the internal technical details of the failure.
Log events are a way you can explicitly record especially critical log messages on game clients, without having to capture all logs from a client. You would typically record error events, but you can record a log event of any severity.
Log events have the standard common analytics fields that records information about the current session. It also stores information in the following standard fields in the root of the JSON object so that these events have a similar schema as regular log events:
Field Name | Type | Description |
---|---|---|
severity | Severity | One of Trace, Debug, Info, Notice, Warn, Error, Critical, Fatal, Alert, Panic, Emergency. |
message | String | The textual message (could be one or more lines of text). This will be processed by AutoExtract. |
Analytics Data Model
Each analytics event is stored as a JSON object that contains a root-level JSON object field g_analytics
that contains all
data for the analytics event. In this g_analytics
object there are fields common to all event types, as well as fields
specific to each type of event.
Each event will also contain the standard fields (for
example, source
with the hostname of the sending machine, and client_ip_location
with the two-letter ISO country code
associated with the client's IP address), and any other standard fields that might be added by your game engine specific plugin
(e.g., the Unreal Engine plugin generates a game_instance_id
).
Analytics Common Fields
All analytics events will have the following common fields inside of the root-level g_analytics
JSON object:
Field Name | Type | Description |
---|---|---|
type | String | One of session_start , session_end , purchase , ad , resource , progression , design , log . |
reason | String | A textual reason for the event (only present if provided for a given event). |
session_id | String | A randomly generated unique ID for the current session. 50 character alphanumeric. |
session_num | Numeric | The chronological number of the session since the game was installed, beginning with 1. |
session_started | Timestamp | The timestamp when the current session started. |
session_type | String | Specifies the build configuration of the game engine for this session. One of client , server , editor . |
game_id | String | The unique identifier for the game as configured in the plugin settings. |
user_tags | String | A colon-separated list of user tags associated with this user, in alphabetical order (e.g., free_to_play:from_cross_promo ). |
user_tags_array | Array of String | A JSON string array of user tags associated with this user, in alphabetical order (e.g., ["free_to_play", "from_cross_promo"] ). |
user_id | String | 32-char ID for the user. Based on plugin settings is either generated once on first-run or is idvf (iOS) or Android ID. |
player_id | String | 32-char hash combining game_id and user_id. Should be unique for this game for this install. |
first_installed | Timestamp | The timestamp when the game was first run. |
meta.platform | String | The platform on which the game is running (e.g., ios , android , windows , linux ). |
meta.os_version | String | The major and minor operating system version (e.g., 10.0 ). |
meta.sdk_version | String | The version of the sparklogs plugin that generated the event (e.g., unreal-plugin-1.0.1 ). |
meta.engine_version | String | The version of the game engine used by the game (e.g., unreal-4.27.2 ). |
meta.build | String | The build identifier for the game as returned by the game engine or can be customized. |
meta.device_make | String | The make of the device running the game. |
meta.device_model | String | The model of the device running the game. |
meta.connection_type | String | The type of network connection as returned by the game engine. One of None, AirplaneMode, Cell, WiFi, Ethernet. |
Additionally, all purchase, ad, resource, progression, and design events will have the following common fields:
Field Name | Type | Description |
---|---|---|
event_ids | Array of String | A JSON string array with the event hierarchy from higher-level to lower-level. For example, ["level", "highrise", "begin"] |
event_id | String | The flattened form of event_ids, with each event ID separated by : -- for example, level:highrise:begin . |
Note that event_id
does not contain the event type. If you want a truly unique event ID, combine the type
and event_id
fields.
There are no cardinality limits on the possible values for event_id (or things used to calculate event_id such as item category and item ID), but in general be thoughtful about what values you use and how you will design your analysis.
Example LQL Queries
Because all analytics events contain these fields, the following LQL query will match all analytics events:
g_analytics.type!
You can use LQL to filter on specific parts of the event ID. For example, to find all "level" events, you could use the query:
g_analytics.event_ids[0]=level
Or to find all events that mention the "highrise" level name in any part of the event hierarchy, you could use:
g_analytics.event_ids=highrise
This matches any event where any member of the event_ids array matches exactly the string "highrise". Looser matching on the flattened string field also works just fine:
g_analytics.event_id: highrise
Note that LQL queries are case-insensitive and use the search index by default, and so will search for whole terms (see details).
You can search for partial terms (substring in a word) by surrounding a search term with *
(e.g., *highrise*
).
Example JSON Data
This section provides JSON for examples of each type of analytics event. This is just for reference, as your game engine plugin will automatically generate the appropriate JSON for you when you record events using the plugin.
- Session Start
- Session End
- Purchase
- Ad
- Resource
- Progression
- Design
- Log
{
"timestamp": "2025-07-15T20:32:53.450Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "session_start",
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:38:09.857Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "session_end",
"reason": "automatically ended at app exit",
"session_ended": "2025-07-15T20:38:09.857Z",
"session_duration_secs": 316.407,
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:33:15Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "purchase",
"reason": "new_user_5min_special_offer",
"event_id": "starter_packs:100_pink_gems_pack",
"event_ids": ["starter_packs", "100_pink_gems_pack"],
"item_category": "starter_packs",
"item_id": "100_pink_gems_pack",
"currency": "EUR",
"amount": 5.00,
"amount_usd": 5.8122375,
"amount_converted": 5.8122375,
"amount_converted_currency": "USD",
"transaction_num": 1,
"first_purchased": "2025-07-15T20:33:15Z",
"custom": {
"offer_id": "offer_12345",
"discount": 0.25,
"view_secs": 17,
"ios_receipt": "abcd1234"
},
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:33:16Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "ad",
"event_id": "admob:end_of_level_chest:rewarded_video",
"event_ids": ["admob", "end_of_level_chest", "rewarded_video"],
"ad_provider": "admob",
"ad_placement": "end_of_level_chest",
"ad_type": "rewarded_video",
"ad_fail_reason": "offline",
"ad_action": "failed_to_show",
"duration_secs": 0.2,
"count": 1,
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:33:15Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "resource",
"reason": "new_user_5min_special_offer",
"event_id": "Source:pink_gems:currency_purchase",
"event_ids": ["Source", "pink_gems", "currency_purchase"],
"flow_type": "Source",
"virtual_currency": "pink_gems",
"item_category": "currency_purchase",
"item_id": "100_pink_gems_pack",
"amount": 100,
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:34:45Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "progression",
"reason": "failed on floor 23",
"event_id": "Failed:addonpack:trial_master_sword:middle_floors",
"event_ids": ["Failed", "addonpack", "trial_master_sword", "middle_floors"],
"status": "Failed",
"tiers": "addonpack:trial_master_sword:middle_floors",
"tiers_array": ["addonpack", "trial_master_sword", "middle_floors"],
"tier1": "addonpack",
"tier2": "trial_master_sword",
"tier3": "middle_floors",
"value": 23,
"attempt": 15,
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:37:37Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"g_analytics": {
"type": "design",
"event_id": "achievements:single_player:beat_game",
"event_ids": ["achievements", "single_player", "beat_game"],
"value": 1688350,
"custom": {
"difficulty": "hard",
"time_taken_secs": 7801,
"score": 1688350,
},
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
{
"timestamp": "2025-07-15T20:38:00Z",
"app": "MyGame",
"hostname": "my-computer",
"pid": 12345,
"game_instance_id": "amqm3opzx4wa8i6ma9j7tzd2",
"severity": "error",
"message": "LogStreaming: Error: Failed to load package '/Game/Maps/LevelEndCredits'",
"g_analytics": {
"type": "log",
"game_id": "ExampleGame",
"user_tags": "free_to_play:from_cross_promo",
"user_tags_array": ["free_to_play", "from_cross_promo"],
"user_id": "A3A3E023442EF421D6C3DCBDBCDC0D3D",
"player_id": "057D1F32934EA2240598BCC9590DAC4C",
"session_id": "bofba3k7w4og4byc73u2lvxyn9f75wy0bfp2ndwmny3m7sn0os",
"session_num": 5,
"first_installed": "2025-07-04T15:45:05.581Z",
"session_started": "2025-07-15T20:32:53.450Z",
"session_type": "client",
"meta": {
"build": "++UE4+Release-4.27-CL-18319896",
"platform": "windows",
"os_version": 10,
"device_make": "AuthenticAMD",
"device_model": "AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics",
"sdk_version": "unreal-plugin-1.0.1",
"engine_version": "unreal-4.27.2"
}
}
}
Analytics Daily Snapshots
In addition to ingesting and querying real-time individual analytics events, SparkLogs will automatically generate daily aggregate snapshots of analytics data.
(coming soon)
Analytics Materialized View
To make it convenient and efficient to query analytics data, SparkLogs generates a materialized view named (workspace_id)_analytics
that
contains only analytics events and flattens the g_analytics
JSON object into top-level fields. The table is
partitioned over the t
field and clustered over (t
, event_type
, event_id
).
The most common fields for each supported analytics event type are included in this view.
On the private cloud service plans you can directly access this materialized view in your BigQuery project. The table schema is as follows:
Field Name | Type | Description |
---|---|---|
org_id | String | The ID of the organization that owns the data. |
agent_id | String | The ID of the agent that ingested the data. |
source | String | The hostname of the machine sending the data. |
ingested_t | Timestamp | The timestamp when the data was ingested. |
event_index | Integer | The index of the ingested event within the batch of events submitted in one ingestion request. This allows to reconstruct the exact order of log events even if their timestamps are exactly the same. |
t | Timestamp | The timestamp of the event, as indicated by the client. This is typically the time when the event was generated in the game. |
severity | Severity | (log events) The severity level of the log message. 1-24. Has the same meaning as SeverityNumber in the OpenTelemetry spec. (1=trace, 5=debug, 9=info, 13=warn, 17=error, 21=fatal) |
app | String | The name of the application that generated the event. |
game_id | String | The unique identifier for the game as configured in the plugin settings. |
user_tags | String | A colon-separated list of user tags associated with this user, in alphabetical order. |
user_id | String | 32-char ID for the user. Based on plugin settings is either generated once on first-run or is idvf (iOS) or Android ID. |
player_id | String | 32-char hash combining game_id and user_id. Should be unique for this game for this install. |
game_instance_id | String | The unique identifier for the game instance, as generated by the plugin. This is typically a randomly generated ID that is unique for the lifetime of the game engine process. |
session_id | String | A randomly generated unique ID for the current session. 50 character alphanumeric. |
session_type | String | Specifies the build configuration of the game engine for this session. One of client , server , editor . |
session_started | Timestamp | The timestamp when the current session started. |
session_ended | Timestamp | (session end events) The timestamp when the session ended. |
session_duration_secs | Numeric | (session end events) The duration of the session in seconds, from the time the session started to the time it ended. |
session_num | Numeric | The chronological number of the session since the game was installed, beginning with 1. |
event_type | String | One of session_start , session_end , purchase , ad , resource , progression , design , log . |
event_id | String | (purchase, ad, resource, progression, and design events) The flattened form of event_ids, with each event ID separated by : -- for example, level:highrise:begin |
event_id_(n) | String | Represents the n th value in the event_ids array, if any, up to event_id_7 . For example, event_id_1 =level or event_id_3 =begin |
status | String | (progression events) One of Started, Failed, or Completed. |
value | Numeric | (progression events) The optional numeric value associated with the event (e.g., score achieved for the attempt) |
attempt | Numeric | (progression events) The number of attempts for this given event ID before successfully completing it. Starts at 1. |
flow_type | String | (resource events) Either "Source" or "Sink". |
virtual_currency | String | (resource events) The name of the virtual currency (e.g., "pink_gems", "lives"). |
amount | Numeric | (resource and purchase events) For resource events, the amount of virtual currency granted or used. This will be enforced to be positive for sources and negative for sinks. For purchase events, the amount of real-world currency spent on the purchase (for example one cent would be 0.01 ). |
amount_converted | Numeric | (purchase events) The amount converted into your preferred target currency (e.g., USD) using the average exchange rate at the time of event ingestion. Will only be present if the source currency is supported and the conversion is successful. |
amount_converted_currency | String | (purchase events) The ISO 4217 currency code of the target currency used for conversion. Will only be present if the source currency is supported and the conversion is successful. |
currency | String | (purchase and ad events) The currency used for the purchase amount or ad revenue (e.g., "USD", "EUR"). This is the ISO 4217 currency code. Over 200 are supported. |
transaction_num | Numeric | (purchase events) The number of the transaction since the game was first installed, starting with 1. |
first_purchased | Timestamp | (purchase events) The timestamp when the first purchase was made since the game was installed. |
ad_provider | String | (ad events) A string identifying the ad provider. For example, unity , admob , etc. or use unknown |
ad_placement | String | (ad events) An ID representing where the ad was placed within you app. For example, end_of_level , treasure_chest , etc. |
ad_type | String | (ad events) Optional. The type of ad that was shown. One of interstitial , video , rewarded_video , banner , native , playable , app_open , offerwall , cross_promo , other , or any custom string. |
ad_action | String | (ad events) Optional. The action taken by the player in response to the ad. One of completed , skipped , clicked , rewarded , failed_to_show , other , or any custom string. |
ad_fail_reason | String | (ad events) Optional. The reason the ad failed to show, if applicable. One of no_fill , offline , network_error , invalid_request , other , or any custom string. |
revenue | Numeric | (ad events) Optional. The total revenue generated from these ad impression(s), if known. |
revenue_converted | Numeric | (ad events) Optional. The ad revenue converted into your preferred target currency (e.g., USD) using the average exchange rate at the time of event ingestion. Will only be present if the source currency is supported and the conversion is successful. |
revenue_converted_currency | String | (ad events) Optional. The ISO 4217 currency code of the target currency used for conversion of ad revenue. Will only be present if the source currency is supported and the conversion is successful. |
duration_secs | Numeric | (ad events) Optional. The duration of the ad in seconds, if applicable. If this event represents multiple impressions, use the average duration. |
count | Numeric | (ad events) Optional. The number of ad impressions recorded in this event. Defaults to 1 if not specified. |
log_message | String | (log events) The textual message of the analytics log event. |
reason | String | A textual reason for the event (only present if provided for a given event). |
first_installed | Timestamp | The timestamp when the game was first run. |
engine_version | String | The version of the game engine used by the game (e.g., unreal-4.27.2 ). |
sdk_version | String | The version of the sparklogs plugin that generated the event (e.g., unreal-plugin-1.0.1 ). |
build | String | The build identifier for the game as returned by the game engine or can be customized. |
platform | String | The platform on which the game is running (e.g., ios , android , windows , linux ). |
os_version | String | The major and minor operating system version (e.g., 10.0 ). |
device_make | String | The make of the device running the game. |
device_model | String | The model of the device running the game. |
connection_type | String | The type of network connection as returned by the game engine. One of None, AirplaneMode, Cell, WiFi, Ethernet. |
client_ip_location | String | The two-letter ISO country code associated with the client's IP address. |
data_json | JSON | The full JSON data from the original event, including all custom data (will be stored in $.g_analytics.custom ). |
Any fields that are not relevant for a given event type will be NULL. Note that the original array forms of certain data (e.g., event_ids
) are
preserved in the JSON data in data_json
, so you can query the array forms if needed.
Dashboards and Visualizations
With the private cloud service plans, you can connect your aggregate and granular analytics data to Looker Studio in your own Google Cloud project.
Simply connect Looker Studio to the BigQuery project that holds your SparkLogs data and add dashboard widgets with appropriate queries.
We recommend having your dashboards query the analytics snapshots tables for performance and query cost-optimization.
For large-scale environments, we recommend that you setup a BigQuery Enterprise reservation that auto-scales from zero slots. This will optimize your BigQuery query costs and performance. Note that this does not require a long-term BigQuery commitment.
Querying Custom Data
With the private cloud service plans, you have direct access to your data in your own BigQuery project, including aggregate data and individual events. Individual events store any custom JSON data you sent with each analytics event.
To query and filter on custom data in individual events, use the BigQuery JSON functions
(e.g., JSON_QUERY
and LAX_STRING
) on the data_json
field in the analytics materialized view that was automatically created for you.
For example, to filter on a custom field named offer_id
, you can use:
LAX_STRING(JSON_QUERY(data_json, '$.g_analytics.custom.offer_id'))
Use LAX_STRING
, LAX_INT64
, LAX_FLOAT64
, or LAX_BOOL
to convert the JSON value to a native SQL type
(or returns NULL if the JSON value is incompatible with the desired type).
Note that any timestamps in your custom data will be stored in the JSON as the number of microseconds since the Unix epoch.
You can convert these to a native SQL timestamp type using the TIMESTAMP_MICROS
SQL function.
For example:
TIMESTAMP_MICROS(LAX_INT64(JSON_QUERY(event_json, '$.g_analytics.custom.first_subscribed')))
Use the TIMESTAMP_TRUNC
SQL function
to calculate cohorts from a timestamp. For example, to calculate a monthly cohort value from from the standard first_installed field:
TIMESTAMP_TRUNC(session_started, MONTH)
You can also do this with custom fields. For example, to calculate a weekly cohort value from the custom timestamp
field first_subscribed
do:
TIMESTAMP_TRUNC(TIMESTAMP_MICROS(LAX_INT64(JSON_QUERY(event_json, '$.g_analytics.custom.first_subscribed'))), WEEK)
Additional Resources
Please share your questions, ideas, and feedback with us in our discord community. SparkLogs is under rapid development and we value your input. The game engine plugins are open source and we welcome bug reports and pull requests.