301 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			301 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--[[
 | 
						|
    https://github.com/overextended/ox_lib
 | 
						|
 | 
						|
    This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>
 | 
						|
 | 
						|
    Copyright © 2025 Linden <https://github.com/thelindat>
 | 
						|
]]
 | 
						|
 | 
						|
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 == 'fivemanage' then
 | 
						|
    local key = GetConvar('fivemanage:key', '')
 | 
						|
 | 
						|
    if key ~= '' then
 | 
						|
        local endpoint = 'https://api.fivemanage.com/api/logs/batch'
 | 
						|
 | 
						|
        local headers = {
 | 
						|
            ['Content-Type'] = 'application/json',
 | 
						|
            ['Authorization'] = key,
 | 
						|
            ['User-Agent'] = 'ox_lib'
 | 
						|
        }
 | 
						|
 | 
						|
        function lib.logger(source, event, message, ...)
 | 
						|
            if not buffer then
 | 
						|
                buffer = {}
 | 
						|
 | 
						|
                SetTimeout(500, function()
 | 
						|
                    PerformHttpRequest(endpoint, function(status, _, _, response)
 | 
						|
                        if status ~= 200 then 
 | 
						|
                            if type(response) == 'string' then
 | 
						|
                                response = json.decode(response) or response
 | 
						|
                                badResponse(endpoint, status, response)
 | 
						|
                            end
 | 
						|
                        end
 | 
						|
                    end, 'POST', json.encode(buffer), headers)
 | 
						|
 | 
						|
                    buffer = nil
 | 
						|
                    bufferSize = 0
 | 
						|
                end)
 | 
						|
            end
 | 
						|
 | 
						|
            bufferSize += 1
 | 
						|
            buffer[bufferSize] = {
 | 
						|
                level = "info",
 | 
						|
                message = message,
 | 
						|
                resource = cache.resource,
 | 
						|
                metadata = {
 | 
						|
                    hostname = hostname,
 | 
						|
                    service = event,
 | 
						|
                    source = source,
 | 
						|
                    tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
 | 
						|
                }
 | 
						|
            }
 | 
						|
        end
 | 
						|
    end
 | 
						|
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
 |