Para ver en el panel los logs de ox_inventory (y otros que usan lib.logger) hay que adaptar el logger de ox_lib. No hace falta set ox:logger: el parche detecta si Adictos_WebLogs está iniciado.
Elige tu versión
- 3.39+ — existe la carpeta
imports/logger/providers/→ sección de abajo. - Anterior a 3.39 — solo un
server.lua→ sección siguiente.
ox_lib 3.39 o superior
Sustituye solo /ox_lib/imports/logger/server.lua por este archivo (no toques providers/). Luego reinicia ox_lib y Adictos_WebLogs.
En 3.39+ no basta pegar el bloque al final del oficial: ese archivo hace
return anticipado y el parche no cargaría.--[[
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 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 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
---@class LogContext
---@field hostname string
---@field formatTags fun(source: any, tags: string?): string?
---@class LogProvider
---@field endpoint string
---@field headers table<string, string>
---@field okStatus number
---@field append fun(buffer: table, source: any, event: string, message: string, ...): nil
---@field encode fun(buffer: table): string
---@field parseError fun(status: number, response: any, body: string): any|nil
local KNOWN = { datadog = true, fivemanage = true, loki = true }
---@type fun(ctx: LogContext): LogProvider?
local providerFactory = KNOWN[service] and lib.require(('imports.logger.providers.%s'):format(service))
local provider = providerFactory and providerFactory({ hostname = hostname, formatTags = formatTags })
-- Sin early-return: el oficial sale con `return lib.logger` si no hay proveedor
-- (Datadog/FiveManage/Loki). Eso impediría el parche de Adictos_WebLogs.
if provider then
local buffer
function lib.logger(source, event, message, ...)
if not buffer then
buffer = {}
SetTimeout(500, function()
local body = provider.encode(buffer)
buffer = nil
PerformHttpRequest(provider.endpoint, function(status, _, _, response)
if status == provider.okStatus then return end
local err = provider.parseError(status, response, body)
if err == nil then return end
badResponse(provider.endpoint, status, err)
end, 'POST', body, provider.headers)
end)
end
provider.append(buffer, source, event, message, ...)
end
end
-- Adictos_WebLogs — integración con el panel web (ver documentación WebLogs)
do
local upstreamLogger = lib.logger
local webLogsResource = 'Adictos_WebLogs'
local webLogsActive = GetResourceState(webLogsResource) == 'started'
AddEventHandler('onResourceStart', function(name)
if name == webLogsResource then webLogsActive = true end
end)
AddEventHandler('onResourceStop', function(name)
if name == webLogsResource then webLogsActive = false end
end)
function lib.logger(source, event, message, ...)
if webLogsActive then
exports.Adictos_WebLogs:OxLibLogRaw({
source = source,
identifier = type(source) == 'string' and source or nil,
event = event,
message = message,
tags = { ... },
})
return
end
if upstreamLogger then
return upstreamLogger(source, event, message, ...)
end
end
end
return lib.logger
ox_lib anterior a 3.39
Pega este bloque justo antes de return lib.logger al final de /ox_lib/imports/logger/server.lua. Reinicia ox_lib y Adictos_WebLogs.
-- Adictos_WebLogs — integración con el panel web (ver documentación WebLogs)
do
local upstreamLogger = lib.logger
local webLogsResource = 'Adictos_WebLogs'
local webLogsActive = GetResourceState(webLogsResource) == 'started'
AddEventHandler('onResourceStart', function(name)
if name == webLogsResource then webLogsActive = true end
end)
AddEventHandler('onResourceStop', function(name)
if name == webLogsResource then webLogsActive = false end
end)
function lib.logger(source, event, message, ...)
if webLogsActive then
exports.Adictos_WebLogs:OxLibLogRaw({
source = source,
identifier = type(source) == 'string' and source or nil,
event = event,
message = message,
tags = { ... },
})
return
end
if upstreamLogger then
return upstreamLogger(source, event, message, ...)
end
end
endAlternativa: sustituir el archivo completo:
--[[
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
-- Adictos_WebLogs — integración con el panel web (ver documentación WebLogs)
do
local upstreamLogger = lib.logger
local webLogsResource = 'Adictos_WebLogs'
local webLogsActive = GetResourceState(webLogsResource) == 'started'
AddEventHandler('onResourceStart', function(name)
if name == webLogsResource then webLogsActive = true end
end)
AddEventHandler('onResourceStop', function(name)
if name == webLogsResource then webLogsActive = false end
end)
function lib.logger(source, event, message, ...)
if webLogsActive then
exports.Adictos_WebLogs:OxLibLogRaw({
source = source,
identifier = type(source) == 'string' and source or nil,
event = event,
message = message,
tags = { ... },
})
return
end
if upstreamLogger then
return upstreamLogger(source, event, message, ...)
end
end
end
return lib.logger
server.cfg y panel
ensure oxmysql
ensure ox_lib
ensure ox_inventory
ensure Adictos_WebLogsox_lib antes de Adictos_WebLogs. En el panel: Mis servidores → ox_lib → módulo activo.