Scripts/resources/[standalone]/ox_lib/imports/logger/server.lua
2024-12-29 21:02:43 +01:00

247 lines
7.6 KiB
Lua

local service = GetConvar('ox:logger', 'datadog')
local buffer
local bufferSize = 0
local function removeColorCodes(str)
-- replace ^[0-9] with nothing
str = string.gsub(str, "%^%d", "")
-- replace ^#[0-9A-F]{3,6} with nothing
str = string.gsub(str, "%^#[%dA-Fa-f]+", "")
-- replace ~[a-z]~ with nothing
str = string.gsub(str, "~[%a]~", "")
return str
end
local hostname = removeColorCodes(GetConvar('ox:logger:hostname', GetConvar('sv_projectName', 'fxserver')))
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
local function base64encode(data)
return ((data:gsub(".", function(x)
local r, b = "", x:byte()
for i = 8, 1, -1 do
r = r .. (b % 2 ^ i - b % 2 ^ (i - 1) > 0 and "1" or "0")
end
return r;
end) .. "0000"):gsub("%d%d%d?%d?%d?%d?", function(x)
if (#x < 6) then
return ""
end
local c = 0
for i = 1, 6 do
c = c + (x:sub(i, i) == "1" and 2 ^ (6 - i) or 0)
end
return b:sub(c + 1, c + 1)
end) .. ({"", "==", "="})[#data % 3 + 1])
end
local function getAuthorizationHeader(user, password)
return "Basic " .. base64encode(user .. ":" .. password)
end
local function badResponse(endpoint, status, response)
warn(('unable to submit logs to %s (status: %s)\n%s'):format(endpoint, status, json.encode(response, { indent = true })))
end
local playerData = {}
AddEventHandler('playerDropped', function()
playerData[source] = nil
end)
local function formatTags(source, tags)
if type(source) == 'number' and source > 0 then
local data = playerData[source]
if not data then
local _data = {
('username:%s'):format(GetPlayerName(source))
}
local num = 1
---@cast source string
for i = 0, GetNumPlayerIdentifiers(source) - 1 do
local identifier = GetPlayerIdentifier(source, i)
if not identifier:find('ip') then
num += 1
_data[num] = identifier
end
end
data = table.concat(_data, ',')
playerData[source] = data
end
tags = tags and ('%s,%s'):format(tags, data) or data
end
return tags
end
if service == 'datadog' then
local key = GetConvar('datadog:key', ''):gsub("[\'\"]", '')
if key ~= '' then
local endpoint = ('https://http-intake.logs.%s/api/v2/logs'):format(GetConvar('datadog:site', 'datadoghq.com'))
local headers = {
['Content-Type'] = 'application/json',
['DD-API-KEY'] = key,
}
function lib.logger(source, event, message, ...)
if not buffer then
buffer = {}
SetTimeout(500, function()
PerformHttpRequest(endpoint, function(status, _, _, response)
if status ~= 202 then
if type(response) == 'string' then
response = json.decode(response:sub(10)) or response
badResponse(endpoint, status, type(response) == 'table' and response.errors[1] or response)
end
end
end, 'POST', json.encode(buffer), headers)
buffer = nil
bufferSize = 0
end)
end
bufferSize += 1
buffer[bufferSize] = {
hostname = hostname,
service = event,
message = message,
resource = cache.resource,
ddsource = tostring(source),
ddtags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
}
end
end
end
if service == 'loki' then
local lokiUser = GetConvar('loki:user', '')
local lokiPassword = GetConvar('loki:password', GetConvar('loki:key', ''))
local lokiEndpoint = GetConvar('loki:endpoint', '')
local lokiTenant = GetConvar('loki:tenant', '')
local startingPattern = '^http[s]?://'
local headers = {
['Content-Type'] = 'application/json'
}
if lokiUser ~= '' then
headers['Authorization'] = getAuthorizationHeader(lokiUser, lokiPassword)
end
if lokiTenant ~= '' then
headers['X-Scope-OrgID'] = lokiTenant
end
if not lokiEndpoint:find(startingPattern) then
lokiEndpoint = 'https://' .. lokiEndpoint
end
local endpoint = ('%s/loki/api/v1/push'):format(lokiEndpoint)
-- Converts a string of comma seperated kvp string to a table of kvps
-- example `discord:blahblah,fivem:blahblah,license:blahblah` -> `{discord="blahblah",fivem="blahblah",license="blahblah"}`
local function convertDDTagsToKVP(tags)
if not tags or type(tags) ~= 'string' then
return {}
end
local tempTable = { string.strsplit(',', tags) } -- outputs a number index table wth k:v strings as values
local bTable = table.create(0, #tempTable) -- buffer table
-- Loop through table and grab only values
for _, v in pairs(tempTable) do
local key, value = string.strsplit(':', v) -- splits string on ':' character
bTable[key] = value
end
return bTable -- Return the new table of kvps
end
function lib.logger(source, event, message, ...)
if not buffer then
buffer = {}
SetTimeout(500, function()
-- Strip string keys from buffer
local tempBuffer = {}
for _,v in pairs(buffer) do
tempBuffer[#tempBuffer+1] = v
end
local postBody = json.encode({streams = tempBuffer})
PerformHttpRequest(endpoint, function(status, _, _, _)
if status ~= 204 then
badResponse(endpoint, status, ("%s"):format(status, postBody))
end
end, 'POST', postBody, headers)
buffer = nil
end)
end
-- Generates a nanosecond unix timestamp
---@diagnostic disable-next-line: param-type-mismatch
local timestamp = ('%s000000000'):format(os.time(os.date('*t')))
-- Initializes values table with the message
local values = {message = message}
-- Format the args into strings
local tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil)
local tagsTable = convertDDTagsToKVP(tags)
-- Concatenates tags kvp table to the values table
for k,v in pairs(tagsTable) do
values[k] = v -- Store the tags in the values table ready for logging
end
-- initialise stream payload
local payload = {
stream = {
server = hostname,
resource = cache.resource,
event = event
},
values = {
{
timestamp,
json.encode(values)
}
}
}
-- Safety check incase it throws index issue
if not buffer then
buffer = {}
end
-- Checks if the event exists in the buffer and adds to the values if found
-- else initialises the stream
if not buffer[event] then
buffer[event] = payload
else
local lastIndex = #buffer[event].values
lastIndex += 1
buffer[event].values[lastIndex] = {
timestamp,
json.encode(values)
}
end
end
end
return lib.logger