Skip to content

Log

luau
local Operator = require(game:GetService("ServerStorage").Operator)
local Log = Operator.Log

The logging adapter. It is silent unless you opt in, so the package never prints into someone else's output. A logger is an object you pass in, not global state, so two dispatchers can log to different places.

Functions

Log.new

luau
Log.new(sink: Sink?, windowSeconds: number?): Logger
luau
local logger = Log.new(function(level, message)
	MyLogger[level](message)
end)

With no sink, nothing is emitted. windowSeconds defaults to 60 and controls the throttle window below.

Logger

luau
type Logger = {
	info: (self: Logger, message: string, key: string?) -> (),
	warn: (self: Logger, message: string, key: string?) -> (),
	error: (self: Logger, message: string, key: string?) -> (),
}

key groups messages for throttling. Pass a per-player key where the message is about a player, a per-command key where it is about a command. A call site that omits it is still bounded by the message text.

luau
logger:warn(`{player.Name} exceeded the rate limit`, tostring(player.UserId))

Types

Level

luau
type Level = "info" | "warn" | "error"

Sink

luau
type Sink = (level: Level, message: string) -> ()

Throttling

Anything that logs on a rejection can be driven by whoever causes the rejections. Left uncapped, an exploiter spamming the remote turns your logging adapter into an amplifier, and if that adapter feeds a webhook or a log service, into your infrastructure.

Log therefore throttles at the adapter, not at each call site: at most 3 lines per key per 60-second window, then a single summary line carrying the suppressed count.

One player flooding cannot suppress logging about another, because their keys differ.

The default

Operator.Start with no Logger configured routes warnings and errors to Studio output prefixed with [Operator], and drops info. This is deliberately not silent: the "no roles configured, place owner only" notice is something you need to see on the first run.

Pass a Logger and everything routes to yours instead:

luau
Operator.Start({
	Logger = function(level, message) MyLogger[level](message) end,
})

Logger also accepts a table with info, warn and error.

Released under the MIT Licence.