Skip to main content

Pulse.Log

The problem with print and warn is that they're noise. In a busy script dozens of prints fire every second and you can't filter them, search them, or know which component they came from.

Pulse.Log solves this with structured, tagged, level-filtered logging that goes to an in-memory buffer — readable in the dev overlay's log tab without touching the Roblox output window.


Basic usage

Pulse.Log.trace("Aimbot", "RenderStepped tick") -- very verbose
Pulse.Log.debug("Aimbot", "candidates found", { count = 5 }) -- dev detail
Pulse.Log.info ("Aimbot", "locked onto target", { name = t.Name }) -- key events
Pulse.Log.warn ("ESP", "hitboxes missing", { model = m.Name }) -- unexpected state
Pulse.Log.error("NapeSize", "pcall failed", { err = tostring(e) }) -- errors

The first argument is always a tag — usually your component name. This is what you use to filter in the overlay.

The third argument is an optional data table — it gets JSON-encoded and shown next to your message.


Log levels

LevelUse for
traceVery verbose — loop ticks, per-frame checks. Off by default in the overlay.
debugDev detail you want when investigating a problem
infoImportant lifecycle events: started, stopped, locked, connected
warnSomething unexpected happened but the script can keep running
errorSomething definitely broke

In production builds (rb build without --dev), all log calls compile to a single boolean check — effectively free.


Throttled logging

Some things happen constantly — don't spam the log:

-- At most one log per 5 seconds for this tag
Pulse.Log.throttle("ESP", 5, "debug", "no enemies found", { players = n })

Signature: Pulse.Log.throttle(tag, intervalSeconds, level, message, data?)

Use this for anything in a loop that might run constantly even when nothing is happening.


Assertions

Pulse.Log.assert(folder ~= nil, "ESP", "workspace.Enemies folder not found")

If the condition is false, logs at error level. Does not throw — the script keeps running.


Saving logs to a file

Pulse.Log.save() -- writes to "pulse_dev.log"
Pulse.Log.save("my_log.txt") -- custom filename

Requires your executor to support writefile. The file gets all buffered entries formatted as plain text.


Configuration

Pulse.Log.configure({
level = "debug", -- minimum level ("trace"|"debug"|"info"|"warn"|"error")
tags = {"Aimbot"}, -- whitelist specific tags (nil = all tags allowed)
})

Practical debugging pattern

When something isn't working, add logs at key decision points:

local targets = getTargets()
Pulse.Log.debug("Targeter", "target scan", { count = #targets })

local nearest = Pulse.Aim.findNearest(targets, { fovRadius = radius() })
if nearest then
Pulse.Log.info("Targeter", "found nearest", { name = nearest.Name })
else
Pulse.Log.debug("Targeter", "no target in FOV", { radius = radius() })
end

Open the dev overlay, switch to LOG, filter by tag "Targeter". You'll see exactly what's happening and why.


Pulse.Monitor

Monitor is a separate, simpler system. It's a key-value store that shows up in the status bar of the dev overlay — updated live, visible in all tabs.

Pulse.Monitor.set("titans", 3)
Pulse.Monitor.set("shifters", 1)
Pulse.Monitor.set("mode", "Anarchy")

Use it for quick runtime stats you want to glance at while the script is running. Values appear as titans:3 shifters:1 mode:Anarchy in the overlay header.

-- Read all values (used internally by the overlay)
local all = Pulse.Monitor.getAll()

Any key you set appears automatically — no registration needed. Remove a key by setting it to nil.


Limitations

  • Buffer holds up to 600 entries. Older entries are dropped automatically.
  • Log calls in production builds have zero overhead but also produce no output.
  • Pulse.Log.save() requires executor writefile support — not all executors have it.
  • Throttle timers are per-tag, not per-message. If you throttle "ESP" at 5s, all messages with tag "ESP" share that timer.