Skip to content

Commit

Permalink
loganalytics 1.2.0
Browse files Browse the repository at this point in the history
* Add logger class, useful for in the future having proper types for custom events
* Add .custom for logging custom events
* Change formatting to use = instead of - to seperate keys from values
  • Loading branch information
gaymeowing committed Nov 18, 2024
1 parent ff4615f commit 2b0a280
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 39 deletions.
7 changes: 2 additions & 5 deletions libs/loganalytics/LIBRARY.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
tags = [
"analytics",
"logging",
]
version = "1.1.0"
tags = [ "analytics", "logging" ]
version = "1.2.0"

[runtime]
main = "roblox"
151 changes: 117 additions & 34 deletions libs/loganalytics/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@

local AnalyticsService = game:GetService("AnalyticsService")

export type CustomFields = { [string]: string }

type ConvertedCustomFields = { [Enum.AnalyticsCustomFieldKeys]: string }?

type BaseAnalyticsInfo = {
custom_fields: { [string]: string }?
custom_fields: CustomFields?
}

export type AnalyticsCustomEventInfo = BaseAnalyticsInfo & {
value: number?,
name: string,
}

export type AnalyticsOnboardingFunnelStepInfo = BaseAnalyticsInfo & {
Expand Down Expand Up @@ -37,66 +46,140 @@ export type AnalyticsFunnelStepInfo = BaseAnalyticsInfo & {
step: number?,
}

type CustomLoggerMethodInfo<F = CustomFields> = {
value: number?,
fields: F,
}

type LoggerPrototype = {
event: (<S, N, F>(self: S & Logger, name: N & string, fields: F & {}) -> S & { [N]: (self: Logger, player: Player, info: CustomLoggerMethodInfo<F>) -> () }) &
(<S, N>(self: S & Logger, name: N & string) -> S & { [N]: (self: Logger, player: Player, value: number?) -> () }),
onboarding_funnel_step: (self: Logger, player: Player, info: AnalyticsOnboardingFunnelStepInfo) -> (),
progression: (self: Logger, player: Player, info: AnalyticsProgressionInfo) -> (),
funnel_step: (self: Logger, player: Player, info: AnalyticsFunnelStepInfo) -> (),
economy: (self: Logger, player: Player, info: AnalyticsEconomyInfo) -> (),
__index: LoggerPrototype,
}

export type Logger = typeof(setmetatable({} :: {}, {} :: LoggerPrototype))

local CUSTOM_FIELD_FORMAT = "%* = %*"
local FORMAT = string.format
local CUSTOM_FIELDS = {
Enum.AnalyticsCustomFieldKeys.CustomField01,
Enum.AnalyticsCustomFieldKeys.CustomField02,
Enum.AnalyticsCustomFieldKeys.CustomField03,
}
local MAX_CUSTOM_FIELDS = #CUSTOM_FIELDS
local TOO_MANY_ENTRIES_ERR = `[LOG_ANALYTICS] custom field dictionary cannot have more than {MAX_CUSTOM_FIELDS} entries`
local TOO_MANY_ENTRIES_ERR = `[LOG_ANALYTICS] cannot have more than {MAX_CUSTOM_FIELDS} custom fields`

@native
local function DICT_TO_CUSTOM_FIELDS(info: BaseAnalyticsInfo): { [Enum.AnalyticsCustomFieldKeys]: string }?
local function TABLE_TO_CUSTOM_FIELDS(fields: CustomFields): ConvertedCustomFields
local return_tbl = {}
local index = 1

for key, value in fields do
if index == MAX_CUSTOM_FIELDS then
error(TOO_MANY_ENTRIES_ERR)
else
return_tbl[CUSTOM_FIELDS[index]] = FORMAT(CUSTOM_FIELD_FORMAT, key, value)
index += 1
end
end
return return_tbl
end

local function INFO_TO_CUSTOM_FIELDS(info: BaseAnalyticsInfo): ConvertedCustomFields?
local custom_fields = info.custom_fields

if custom_fields then
local return_tbl = {}
local index = 1

for key, value in custom_fields do
if index == MAX_CUSTOM_FIELDS then
error(TOO_MANY_ENTRIES_ERR)
else
return_tbl[CUSTOM_FIELDS[index]] = `{key} - {value}`
index += 1
end
end
return return_tbl
return TABLE_TO_CUSTOM_FIELDS(custom_fields)
else
return nil
end
end

local function log_economy(player: Player, info: AnalyticsEconomyInfo)
AnalyticsService:LogEconomyEvent(
player, info.flow_type, info.currency_type, info.amount, info.ending_balance,
info.transaction_type :: any, info.item_sku, DICT_TO_CUSTOM_FIELDS(info)
local function onboarding_funnel_step(player: Player, info: AnalyticsOnboardingFunnelStepInfo)
AnalyticsService:LogOnboardingFunnelStepEvent(
player, info.step, info.step_name, INFO_TO_CUSTOM_FIELDS(info)
)
end

local function progression(player: Player, info: AnalyticsProgressionInfo)
AnalyticsService:LogProgressionEvent(
player, info.path_name, info.status, info.level,
info.level_name, INFO_TO_CUSTOM_FIELDS(info)
)
end

local function log_funnel_step(player: Player, info: AnalyticsFunnelStepInfo)
local function funnel_step(player: Player, info: AnalyticsFunnelStepInfo)
AnalyticsService:LogFunnelStepEvent(
player, info.funnel_name, info.sessionid, info.step,
info.step_name, DICT_TO_CUSTOM_FIELDS(info)
info.step_name, INFO_TO_CUSTOM_FIELDS(info)
)
end

local function log_onboarding_funnel_step(player: Player, info: AnalyticsOnboardingFunnelStepInfo)
AnalyticsService:LogOnboardingFunnelStepEvent(
player, info.step, info.step_name, DICT_TO_CUSTOM_FIELDS(info)
local function custom(player: Player, info: AnalyticsCustomEventInfo)
AnalyticsService:LogCustomEvent(
player, info.name, info.value, INFO_TO_CUSTOM_FIELDS(info)
)
end

local function log_progression(player: Player, info: AnalyticsProgressionInfo)
AnalyticsService:LogProgressionEvent(
player, info.path_name, info.status, info.level,
info.level_name, DICT_TO_CUSTOM_FIELDS(info)
local function economy(player: Player, info: AnalyticsEconomyInfo)
AnalyticsService:LogEconomyEvent(
player, info.flow_type, info.currency_type, info.amount, info.ending_balance,
info.transaction_type :: any, info.item_sku, INFO_TO_CUSTOM_FIELDS(info)
)
end

return table.freeze {
onboarding_funnel_step = log_onboarding_funnel_step,
progression = log_progression,
funnel_step = log_funnel_step,
economy = log_economy,
}
local logger = {} :: LoggerPrototype
logger.__index = logger

logger.event = function(logger: any, name: string, fields: CustomFields?)
if fields then
logger[name] = function(player: Player, info: CustomLoggerMethodInfo)
AnalyticsService:LogCustomEvent(player, name, info.value, TABLE_TO_CUSTOM_FIELDS(info.fields))
end
else
logger[name] = function(player: Player, value: number?)
AnalyticsService:LogCustomEvent(player, name, value)
end
end

return logger :: any
end :: any

-- having parens around all of these local function calls so they inline
-- fuck you multiret
function logger.onboarding_funnel_step(logger, player, info)
return ( onboarding_funnel_step(player, info) )
end

function logger.progression(logger, player, info)
return ( progression(player, info) )
end

function logger.funnel_step(logger, player, info)
return ( funnel_step(player, info) )
end

function logger.economy(logger, player, info)
return ( economy(player, info) )
end

local loganalytics_mt = {}

function loganalytics_mt.__call(): Logger
return setmetatable({}, logger)
end

table.freeze(loganalytics_mt)
table.freeze(logger)

return table.freeze(setmetatable({
onboarding_funnel_step = onboarding_funnel_step,
funnel_step = funnel_step,
progression = progression,
economy = economy,
custom = custom,
}, loganalytics_mt))

0 comments on commit 2b0a280

Please sign in to comment.