mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-16 22:46:03 +01:00
Some people accidentally, mistakenly, and without any malicious intent forget to attribute code from this resource to its source. This change will hopefully assist people, so that they do not make that mistake again.
302 lines
9.3 KiB
Lua
302 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 (c) 2025 Linden <https://github.com/thelindat/fivem>
|
|
]]
|
|
|
|
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
|