Log
local Operator = require(game:GetService("ServerStorage").Operator)
local Log = Operator.LogThe 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
Log.new(sink: Sink?, windowSeconds: number?): Loggerlocal 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
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.
logger:warn(`{player.Name} exceeded the rate limit`, tostring(player.UserId))Types
Level
type Level = "info" | "warn" | "error"Sink
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:
Operator.Start({
Logger = function(level, message) MyLogger[level](message) end,
})Logger also accepts a table with info, warn and error.