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
| Level | Use for |
|---|---|
trace | Very verbose — loop ticks, per-frame checks. Off by default in the overlay. |
debug | Dev detail you want when investigating a problem |
info | Important lifecycle events: started, stopped, locked, connected |
warn | Something unexpected happened but the script can keep running |
error | Something 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.