betaNew accounts get 7 days of the Server plan, free. No card. what is in it

[ 0.00]

[script:fivetrace] docs · live logs

Logging from your server

One key in server.cfg, then any resource on the server can log. There is no dashboard step before the first line arrives.

>1 · set your log key
[ 0.00]

Each server you add in the dashboard gets its own key, on that server’s page. One per server, not one per script — a convar, so you set it once rather than pasting it into every resource you want to log from.

# server.cfg
set fivetrace_log_key "fl_your_key_here"

This is not a DSN. Error tracking is the other product, bought by whoever wrote the script; a DSN ships inside a resource sold to strangers, so logging through one would send your server’s activity to its author. The two keys never mix, and you need no DSN to log.

The key is also what says where a line came from. Nothing takes the server’s word for that, which matters the day you are settling an argument about which of your servers something happened on. Run two and they are two keys, two sets of channels, and two sets of staff — a moderator you add to one cannot read the other.

>2 · load the SDK into the resource
[ 0.11]

The same two lines the error tracking uses. Into the resource, not beside it: FiveM gives every resource its own isolated Lua state, so a shared include is the only way FiveTrace exists inside yours.

-- my-resource/fxmanifest.lua
shared_script '@fivetrace-sdk/init.lua'
dependency  'fivetrace-sdk'

Ensure fivetrace-sdk before anything that uses it, or the include will not resolve.

# server.cfg
ensure fivetrace-sdk
ensure my-resource
Download fivetrace-sdk 0.2.0zip · fivetrace.com/sdk/download always serves the current one
>3 · log something
[ 0.14]

The channel is named in the call and created the first time it is used. Nothing to register, nothing to configure.

FiveTrace.log('money', { target = 'Ana', amount = 500 })

Send fields, not a sentence. The moment Marijo paid Ana $500 is a string the amount is gone, and every transaction over $10,000 stops being a question anyone can ask. Fields stay searchable; the channel’s template turns them into that sentence at display time instead.

FiveTrace.log does not need FiveTrace.init. It returns false and does nothing at all when no log key is set, so a resource that logs never breaks a server that has not set logging up.

>4 · prove it works
[ 0.15]

A server that has never logged and a server with a mistyped key look exactly the same from the dashboard: empty. This is how you tell them apart.

FiveTrace.test()

Add it temporarily, restart the resource, and #fivetrace appears in your dashboard within a few seconds. It goes down the real path rather than a shortcut, so seeing it means the key, the network and the endpoint all work. Delete the channel and the line once you have.

>who did it

A log nobody can filter by player is a wall of text. Who did it and how much it matters are metadata rather than payload, so they go in a third table:

FiveTrace.log('money', { amount = 500, reason = 'vehicle sale' }, {
  actor = { id = GetPlayerIdentifier(src, 0), name = GetPlayerName(src) },
  level = 'warn',
})

A licence on its own works too — { actor = 'license:1100...' } — since that is usually what a server has to hand. Levels are debug, info, warn, error, and anything else is recorded as info rather than dropped: a typo in a level should not cost you the record of what happened.

>channels

A channel key is lowercase letters, digits and hyphens, two to thirty-two characters. A name that cannot be turned into one is dropped — that line only, never the batch around it.

There is a limit on how many channels a team holds — 10 on the free plan. It exists because channels appear on first use: without it, one script with a bug in its channel name would invent a channel per line. Once you are at the limit, lines naming a new channel are dropped and existing channels keep working.

>templates

Each channel owns one template, written in the dashboard. It is display only: it never changes what is stored, because the moment it does, search stops working and this is Discord again.

{actor} paid {data.target} ${data.amount}
→  Kaya paid Ana $500

Available: {actor}, {actorId}, {server} (the name you gave it, not what the server called itself), {resource}, {level} and any field you sent, as {data.name}. A field the line does not carry renders as a dash rather than disappearing, so a template that is wrong looks wrong. With no template at all the line reads as its fields.

You do not have to guess what to put in it. The settings panel lists the fields your server is actually sending and clicking one adds it, and opening any line in the feed shows everything that line carries — every field, whether the sentence uses it, and the token that would put it there. A sentence is one view of a line, not the line; the open panel also copies the whole line as JSON, for pasting into a ticket.

The dimmed text at the right of each row is a second template, with the same tokens. It starts as {resource} · {server}; a channel only one script writes to says that same word on every line, so change it to something worth the space or empty it for a clean row.

>the scripts you already run

Everything above assumes you can open the script and add a line. Most of what runs on a FiveM server is not like that. A paid resource ships escrowed and the file you would edit is not readable, which would leave the scripts your players spend all their money in as the ones you cannot log.

You do not need to open them. The frameworks announce every money move to any resource that cares to listen, whichever script made it, so one handler covers every shop, job and banking script on the server at once, including the ones you bought.

The handlers below go in a server file of a resource that already loads the SDK — step 2 above, in whichever resource of your own is the natural home for them. A server file, because FiveTrace.log does nothing on the client by design: a log line a player can write is not evidence of anything.

[ 01.02]

[esx] every money move on the server

-- In a server file of a resource that loads the SDK.
local function onMoney(operation)
  return function(source, account, amount, reason)
    FiveTrace.log('money', {
      operation = operation,
      account   = account,
      amount    = amount,
      reason    = reason,
    }, {
      actor = { id = GetPlayerIdentifier(source, 0), name = GetPlayerName(source) },
    })
  end
end

AddEventHandler('esx:addAccountMoney',    onMoney('add'))
AddEventHandler('esx:removeAccountMoney', onMoney('remove'))
AddEventHandler('esx:setAccountMoney',    onMoney('set'))
[ 01.03]

[qbcore · qbox] the same, and job changes

AddEventHandler('QBCore:Server:OnMoneyChange', function(source, account, amount, operation, reason)
  FiveTrace.log('money', {
    operation = operation,  -- add, remove or set
    account   = account,    -- cash, bank or crypto
    amount    = amount,
    reason    = reason,
  }, {
    actor = { id = GetPlayerIdentifier(source, 0), name = GetPlayerName(source) },
  })
end)

AddEventHandler('QBCore:Server:OnJobUpdate', function(source, job)
  FiveTrace.log('jobs', { job = job.name }, {
    actor = { id = GetPlayerIdentifier(source, 0), name = GetPlayerName(source) },
  })
end)
[ 01.04]

[ox_inventory] items, which no framework event sees

exports.ox_inventory:registerHook('swapItems', function(payload)
  FiveTrace.log('items', {
    action = payload.action,
    count  = payload.count,
    from   = payload.fromInventory,
    to     = payload.toInventory,
  }, {
    actor = { id = GetPlayerIdentifier(payload.source, 0), name = GetPlayerName(payload.source) },
  })

  -- Return nothing. A hook that returns false cancels the move it was told
  -- about, which turns a logger into a script that eats your players' items.
end)

Print the payload once before you write the fields you want. Inventory hooks carry more than the four above, and what a slot holds is worth reading rather than guessing at.

[ 01.05]

[txadmin] who was banned, and by which admin

AddEventHandler('txAdmin:events:playerKicked', function(data)
  FiveTrace.log('admin', {
    action = 'kick',
    admin  = data.author,
    reason = data.reason,
    target = data.target,
  }, { level = 'warn' })
end)

AddEventHandler('txAdmin:events:playerBanned', function(data)
  FiveTrace.log('admin', {
    action = 'ban',
    admin  = data.author,
    reason = data.reason,
    target = data.targetName,
    length = data.durationTranslated,
  }, { level = 'warn' })
end)

This is the one people are surprised by. Moderation is the part of a server most often disputed later, and it is normally recorded only in a Discord channel the accused admin can also see.

One cost worth knowing. A line records the resource it was written from, which is now the resource holding the handler rather than the script that did the thing, so the {resource} field stops answering “which script” for these lines. The frameworks pass a reason string through and it is usually the calling script saying what it was for, so put that in a field and search on it instead.

>ten you probably have
es_extended

ESX Legacy fires esx:addAccountMoney, esx:removeAccountMoney and esx:setAccountMoney on the server for every money move on the box, whichever script made it. The handler above is the entire integration.

qb-core

QBCore:Server:OnMoneyChange carries the source, the account, the amount, whether it was an add, a remove or a set, and the reason the calling script gave. QBCore:Server:OnJobUpdate carries a job change.

qbx_core

Qbox keeps both of those event names, so one handler covers a QBCore server and a Qbox one. It adds SetDuty, which is the difference between a player being a paramedic and a player being on shift.

ox_inventory

The only one here with a hook API of its own: registerHook takes swapItems, openInventory, openShop, createItem, buyItem, craftItem and usingItem. Item movement is invisible to the framework hooks, so this is the one that earns its own handler.

txAdmin

Bans, kicks, warns and revoked actions, each carrying the admin who did it. Note the shapes differ between events: a kick names its target as target, a ban names it as targetName, and pasting one into the other silently logs nothing.

qb-banking

Moves money by calling the framework’s own AddMoney and RemoveMoney, so the money handler above already has every deposit, withdrawal and transfer it makes. There is nothing to add for it.

qs-inventory

Escrowed, and its hooks are not something we can read. Use the test below rather than a guess: move an item, watch the channel, and you have the answer in ten seconds.

oxmysql

Everything on the server goes through it, which makes it the worst available place to log. You would get queries rather than events, at a volume no allowance survives.

ox_lib

A library other scripts are built on. It announces nothing of its own worth keeping.

pma-voice, ox_target

Voice and targeting. Both are on nearly every server and neither has anything a log wants, which is worth saying so nobody goes looking.

For anything not on that list the test takes ten seconds: do the thing once and watch the channel. A script that moves money through the framework appears immediately. One that writes its own database table does not, and there is nothing to listen for, which is an answer worth having before you spend an evening looking for a hook.

These are other people’s event names and they can change. Every one here was read from that project’s own documentation or source rather than remembered. If a handler goes quiet after you update something, print what arrives before assuming the key is wrong.

>everything at once

Channels are how you ask a question later. They are a poor way to watch something now: a payment, the admin command that caused it and the vehicle that changed hands are three channels and one incident, and reading them separately means rebuilding the order in your head. Every server has a feed of everything you can read, in one order.

Each line still carries its own channel’s sentence, because one feed cannot have one template — the same fields mean different things in #money and #admin. Add channel:money to narrow it back to one.

It reads exactly the channels your role allows, one at a time or all at once. A combined view that widened that would be the quiet way to undo the whole point of per-channel permissions.

>searching

A few prefixes and free text. Anything unrecognised falls through to the text, so a typo narrows nothing rather than silently matching nothing — the failure where you conclude it never happened.

actor:license:110000112345678      everything one player did
level:warn                        warnings and nothing else
channel:money                     one channel, on a server's feed
last:24h                          the last day; also last:90m, last:7d
on:2026-09-03                     that day, start to end
since:2026-09-01 until:2026-09-05 a range, both days included
amount:>10000                     a number in a field, not text
amount:>1000 amount:<5000         a range on one field
actor:license:1100... refund      any of them, and the word refund

The comparison is why the fields are worth sending. Once Marijo paid Ana $500 is a string, the amount is a run of characters and 10000 is not a substring of 9,412 — so “every transaction over $10,000” is a question no amount of scrolling answers. It works on any numbered field your scripts send, and a value that is not a number simply does not match: a plate is not a quantity. The comparator is what makes it a comparison, so plate:ABC123 is still the text search it has always been.

A day means a day where you are. Your browser sends its offset with the search, so on:2026-09-03 is your third of September rather than one in London — and because the offset travels in the URL, a search you paste to somebody in another country still means the day you meant.

Free text reads the whole payload, not one named field, and it is text rather than a pattern — searching for 100% finds 100% and not 1000. Filters narrow at the database, so a search reaches past the page into the whole retention window.

Any search downloads as a CSV, which is what a dispute usually ends in — the person you are answering to has no account here. The file carries the sentence your channel renders and the raw fields beside it, and it exports what you can read: a moderator downloading gets the channels a moderator sees.

Channels page 200 lines at a time, oldest-ward, and each page is a URL you can bookmark or paste to a teammate. It is a cursor rather than a page number, so lines arriving while you read never make a page repeat itself or skip one.

>being told

A log is a record you read later, and most of the time that is the point. Some lines are not: a payment that size, an admin command at 4am. Each channel can carry one rule and its own Discord webhook, so #money and #admin land in different places.

Two conditions: a level floor, and a numeric field over or under a value. Set both and they are and, not or — a warn line about a large amount, rather than either. A rule needs at least one of them; one with neither would match every line, which is not an alert.

A rule that is wrong fails as silence, and so does a rule that is right most of the time. So the panel holds yours up against the lines already on the page and says how many it would have fired for — a field name nothing sends, or a threshold above anything that happens, says so before you save it. There is a send a test button for the webhook itself, which no amount of matching can check.

There is a cooldown and it is not optional. Logs are constant, and one busy minute at one message per line is how a webhook gets deleted. You get one ping per window, and it says how many lines matched, so a flood reads as a flood instead of as one event.

>who can read it

Invite your staff to the account, then assign them to a server — no server files, no shared password. They get an email saying who added them, and there is nothing to accept: signing in with that address is the whole of it. Two steps rather than one, because an invitation grants access to an account and a server admin does not get to hand that out. Within a server, each channel is visible to everyone assigned to it or to its admins only, so a moderator can work the report channel without reading payments.

An invitation also says what it is for. A member invited for the live logs reads the logs and nothing else — if you also sell scripts, your error tracking and the DSNs inside it stay out of their account. You can hand that over later from the team page, one member at a time. Admins are the exception, because an admin runs the account rather than helping with a corner of it.

Log lines carry whoever your server says did the thing, because a log without that is useless. Every identifier gets a page of its own: what they did, which channels they turn up in, how far back it goes — and it reaches only the channels you are allowed to read, so a moderator’s view of a player stops where their access does.

You decide what to send and you own it: an admin can erase every line for one player from that same page, which is what a deletion request looks like in practice. Seeing what is held before deleting it is the half that makes the answer a real one.

>if the key gets out

A log key lives in server.cfg, so every admin who touches the box can read it and it survives in backups and screenshots. What a leaked one does is write — which means a record nobody can trust afterwards, and that is worse than a leak that only reads.

Rotating issues a new key from the server’s page. Keep the old one alive for seven days while you edit configs and restart, or cut it off at once, which is the answer when it is being abused and silence is the point. The page says whether the old key is still being used and how long it has left, so you can tell a rotation that is finished from one that is about to break something.

>limits
batching

Lines queue and flush on their own thread every five seconds, up to 100 per request. If we are unreachable the batch stays at the head of the queue and is retried with a widening gap, so an outage costs you the lines you sent during it only if it outlasts the queue.

payload

Scalars, up to 32 fields a line. A nested table is sent as its JSON text: it cannot be searched as a field or read by a template, but a coords table is worth more in the panel than nothing at all.

order

Timestamps carry milliseconds, so two lines in the same second stay in the order your server wrote them. They are your server’s clock, not ours — a box with the wrong time reports the wrong time.

retention

How long lines are kept is the plan — 3 days free. Going over the monthly allowance shortens that window proportionally instead of rejecting new lines: losing the incident you are about to investigate is the wrong answer.

which resource

Every line records the resource that wrote it. Your key is used by every script on the server, including ones you bought, so this is how you tell which one is flooding a channel.

Selling a script rather than running a server? Error tracking is the other half, and it installs differently.