I recently started using GameAnalytics. It works great, except for the fact that events fail to send frequently.
This warning pops up frequently. When I shutdown all servers and joined a server, I checked logs and more than half of the session start events failed to send. This means that my analytics aren’t as accurate as they should be.
Edit: I should’ve been more clear in my post. I’m using the GameAnalytics Roblox SDK. All I did was install it and set it up with all the default values. No custom events, dimensions, etc.
I took a look at the code where the warning was being sent and here is what I found:
if statusCode == http_api.EGAHTTPApiResponse.BadRequest and responseBody then
logger:w("Event queue: " .. tostring(#queue) .. " events sent. " .. tostring(#responseBody) .. " events failed GA server validation.")
else
logger:w("Event queue: Failed to send events.")
end
Whole function
local function processEvents()
local queue = dequeueMaxEvents()
if #queue == 0 then
logger:i("Event queue: No events to send")
return
end
-- Log
logger:i("Event queue: Sending " .. tostring(#queue) .. " events.")
local eventsResult = http_api:sendEventsInArray(events.GameKey, events.SecretKey, queue)
local statusCode = eventsResult.statusCode
local responseBody = eventsResult.body
if statusCode == http_api.EGAHTTPApiResponse.Ok and responseBody then
logger:i("Event queue: " .. tostring(#queue) .. " events sent.")
else
if statusCode == http_api.EGAHTTPApiResponse.NoResponse then
logger:w("Event queue: Failed to send events to collector - Retrying next time")
for _,e in pairs(queue) do
if #store.EventsQueue < MAX_AGGREGATED_EVENTS then
store.EventsQueue[#store.EventsQueue + 1] = e
else
break
end
end
else
if statusCode == http_api.EGAHTTPApiResponse.BadRequest and responseBody then
logger:w("Event queue: " .. tostring(#queue) .. " events sent. " .. tostring(#responseBody) .. " events failed GA server validation.")
else
logger:w("Event queue: Failed to send events.")
end
end
end
end
Edit 2: I looked into the code and printed statusCode and responseBody when an event fails to send. Here’s what I got:
Its hard for me to tell you an issue except for what the error tells us:
From the error it tells us that the event did not pass validation and was rejected - that is all we can gain as to insight in why the error occurred. This sadly doesn’t provide much information and I can’t suggest a reason for failure as the possibilities are huge.
I would assume its to do with the event failing to send from Roblox to where you expect it to end up, Roblox could possibly just send an error over and as such it can likely be assumed that this is an issue on your coding side and not something to do with the site itself. Roblox likely sends some poorly made thing and hence that is why your provider on the other end receives an sdk_error as it couldn’t understand the mess.
I honestly wish you luck and I’m not sure how many people will be able to give you a response - I hope you get a response which helps though. I’d provide snippets of your code for others (because its an even bigger shot in the dark as to why your code doesn’t work if we can’t actually see the code / your method).
I should’ve been more clear in my post. I’m using the GameAnalytics Roblox SDK. All I did was install it and set it up with all the default scripts and values. No custom events, dimensions, etc.
I took a look at the code where the warning was being sent and here is what I found:
if statusCode == http_api.EGAHTTPApiResponse.BadRequest and responseBody then
logger:w("Event queue: " .. tostring(#queue) .. " events sent. " .. tostring(#responseBody) .. " events failed GA server validation.")
else
logger:w("Event queue: Failed to send events.")
end
Whole function
local function processEvents()
local queue = dequeueMaxEvents()
if #queue == 0 then
logger:i("Event queue: No events to send")
return
end
-- Log
logger:i("Event queue: Sending " .. tostring(#queue) .. " events.")
local eventsResult = http_api:sendEventsInArray(events.GameKey, events.SecretKey, queue)
local statusCode = eventsResult.statusCode
local responseBody = eventsResult.body
if statusCode == http_api.EGAHTTPApiResponse.Ok and responseBody then
logger:i("Event queue: " .. tostring(#queue) .. " events sent.")
else
if statusCode == http_api.EGAHTTPApiResponse.NoResponse then
logger:w("Event queue: Failed to send events to collector - Retrying next time")
for _,e in pairs(queue) do
if #store.EventsQueue < MAX_AGGREGATED_EVENTS then
store.EventsQueue[#store.EventsQueue + 1] = e
else
break
end
end
else
if statusCode == http_api.EGAHTTPApiResponse.BadRequest and responseBody then
logger:w("Event queue: " .. tostring(#queue) .. " events sent. " .. tostring(#responseBody) .. " events failed GA server validation.")
else
logger:w("Event queue: Failed to send events.")
end
end
end
end
I’d recommend printing statusCode and printing responseBody:
That way we can start narrowing down to our issue - research the status code online first.
If you have no responseBody - issue found. The statusCode however should tell you the issue.
(Pretty sure http_api.EGAHTTPApiResponse.BadRequest is just a number and the fancy length is so its easier for people to see all the errors.)
Here’s the code where it prints Sending 'events' URL: and subsequently body: :
Code
logger:d("Sending 'events' URL: " .. url)
-- make JSON string from data
local payload = HTTP:JSONEncode(eventArray)
local authorization = encode(payload, secretKey)
local res
local success, err = pcall(function()
res = HTTP:RequestAsync({
Url = url,
Method = "POST",
Headers = {
["Authorization"] = authorization
},
Body = payload
})
end)
if not success then
logger:d("Failed Events Call. error: " .. err)
return {
statusCode = http_api.EGAHTTPApiResponse.UnknownResponseCode,
body = nil
}
end
logger:d("body: " .. res.Body)
Whole function
function http_api:sendEventsInArray(gameKey, secretKey, eventArray)
if not eventArray or #eventArray == 0 then
logger:d("sendEventsInArray called with missing eventArray")
return
end
-- Generate URL
local url = baseUrl .. "/" .. gameKey .. "/" .. self.eventsUrlPath
if RunService:IsStudio() then
url = baseUrl .. "/5c6bcb5402204249437fb5a7a80a4959/" .. self.eventsUrlPath
end
logger:d("Sending 'events' URL: " .. url)
-- make JSON string from data
local payload = HTTP:JSONEncode(eventArray)
local authorization = encode(payload, secretKey)
local res
local success, err = pcall(function()
res = HTTP:RequestAsync({
Url = url,
Method = "POST",
Headers = {
["Authorization"] = authorization
},
Body = payload
})
end)
if not success then
logger:d("Failed Events Call. error: " .. err)
return {
statusCode = http_api.EGAHTTPApiResponse.UnknownResponseCode,
body = nil
}
end
logger:d("body: " .. res.Body)
local requestResponseEnum = processRequestResponse(res, "Events")
-- if not 200 result
if requestResponseEnum ~= http_api.EGAHTTPApiResponse.Ok and requestResponseEnum ~= http_api.EGAHTTPApiResponse.BadRequest then
logger:d("Failed Events Call. URL: " .. url .. ", JSONString: " .. payload .. ", Authorization: " .. authorization)
return {
statusCode = requestResponseEnum,
body = nil
}
end
local responseBody
ypcall(function()
responseBody = HTTP:JSONDecode(res.Body)
end)
if not responseBody then
logger:d("Failed Events Call. Json decoding failed")
return {
statusCode = http_api.EGAHTTPApiResponse.JsonDecodeFailed,
body = nil
}
end
-- print reason if bad request
if requestResponseEnum == http_api.EGAHTTPApiResponse.BadRequest then
logger:d("Failed Events Call. Bad request. Response: " .. res.Body)
return {
statusCode = requestResponseEnum,
body = nil
}
end
-- all ok
return {
statusCode = http_api.EGAHTTPApiResponse.Ok,
body = responseBody
}
end
The difference I see is that when the event is sent successfully, it prints body: {}, but when it fails, body: is some table that is incomplete.
The difference I see is that when the event is sent successfully, it prints body: {} , but when it fails, body: is some table that is incomplete.
However, I looked into the code and tried for an hour to figure out why, and I only recently realised that the script does not change body:, but the response does from HttpService:RequestAsync. I think this is an issue on GameAnalytic’s end. Not sure how I’d report this to them tho other than filling out a support ticket which I’ve already done.
Sorry to hear you have troubles with the events. From the screenshot I can’t see the whole contents of the event trying to be sent (the one that starts with “Event added to queue”). The processed error events suggests it is something to do with validation of the events, which is why I would like to see the rest of the event trying to be sent to see what could cause the problem.
The only difference I see is the device. So what I did was change the code where the player’s device is set so that when the device is unknown, it sets the device to “uwp_desktop”:
PlayerData.Platform = (PlayerPlatform == "Console" and "uwp_console") or (PlayerPlatform == "Mobile" and "uwp_mobile") or (PlayerPlatform == "Desktop" and "uwp_desktop") or ("uwp_desktop")
Previously, it was like this:
PlayerData.Platform = (PlayerPlatform == "Console" and "uwp_console") or (PlayerPlatform == "Mobile" and "uwp_mobile") or (PlayerPlatform == "Desktop" and "uwp_desktop") or ("unknown")
(Location of code is in GameAnalytics module, line 425)
Ever since I made this change, I haven’t seen any events fail to send in either the GameAnalytics Live feed page where it shows the processed events, or the game console.
This probably made my data unreliable for filtering data between different devices, but I’m willing to sacrifice that in return for events being sent successfully which makes the data much more accurate.
I’m unable to check for sure but I think PlayerPlatform should be nil
Code where PlayerPlatform is defined:
local PlayerPlatform = "unknown"
local isSuccessful, platform = Postie.InvokeClient("getPlatform", Player, 15)
if isSuccessful then
PlayerPlatform = platform
end
--Fill Data
for key, value in pairs(store.BasePlayerData) do
PlayerData[key] = PlayerData[key] or value
end
store.PlayerCache[Player.UserId] = PlayerData
PlayerData.Platform = (PlayerPlatform == "Console" and "uwp_console") or (PlayerPlatform == "Mobile" and "uwp_mobile") or (PlayerPlatform == "Desktop" and "uwp_desktop") or ("uwp_desktop")
Client code:
--Services
local GS = game:GetService("GuiService")
local UIS = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Postie = require(ReplicatedStorage.Postie)
--Functions
local function getPlatform()
if (GS:IsTenFootInterface()) then
return "Console"
elseif (UIS.TouchEnabled and not UIS.MouseEnabled) then
return "Mobile"
else
return "Desktop"
end
end
--Filtering
Postie.SetCallback("getPlatform", getPlatform);
There can only be 3 values- “Console”, “Mobile” and “Desktop”. So the only thing PlayerPlatform could be that isn’t any of those 3 is nil