Skip to content

Log

local Log = require("@game/ReplicatedStorage/Twill").Log
local logger = Log.new("Shop")
logger:Info("ready") --> [Shop] ready

One logger per system, built once and kept for the lifetime of that system.

Debug ranks below Info, which ranks below Warn, which ranks below Error. One threshold silences everything beneath it.

LevelWritten withNames the caller
DebugprintNo
InfoprintNo
WarnwarnYes
Errorwarn, then the installed handlerYes

Error reports and never throws. Raise an error yourself where the caller should stop.

Warn and Error name the nearest line outside Twill, so a message about a bad call points at the call rather than at the framework.

[Twill.Data] (from MyGame.Services.Shop:42) Edit refused: unsupported value

A line is outside Twill when its source starts with neither ReplicatedStorage.Twill nor ServerScriptService.Twill, followed by a dot or nothing, so a script named TwillTests beside them still counts as yours. Native frames are stepped over. Twelve stack levels are examined, so a message raised through more layers than that reports no call-site rather than a wrong one.

[Server] | [Client]

Creates a logger that tags every message with the given scope.

function Log.new(scope: string): Logger

Parameters

NameTypeDescription
scopestringIdentifier shown alongside each message, such as a system name. Not empty.

Returns

Logger - A logger bound to that scope.

Throws when the scope is empty or is not a string.

[Server] | [Client]

export type Logger = {
Scope: string,
Debug: (self: Logger, ...any) -> (),
Info: (self: Logger, ...any) -> (),
Warn: (self: Logger, ...any) -> (),
Error: (self: Logger, ...any) -> (),
}

Every method is called with a colon and takes any number of values, written after the scope exactly as print writes them.

MethodUse it for
logger:Debug(...)Detail useful only while investigating. The first thing silenced.
logger:Info(...)Progress worth seeing in a healthy session, such as a system finishing setup.
logger:Warn(...)A recoverable problem that did not stop the operation.
logger:Error(...)A failure. Reaches the installed handler. Never throws.

Example

local logger = Log.new("Shop")
logger:Debug("loading catalogue")
logger:Info("ready")
logger:Warn("price table is empty")
logger:Error("could not reach the catalogue")

[Server] | [Client]

Silences every message ranked below the given level.

function Log.SetLevel(level: Level)
export type Level = "Debug" | "Info" | "Warn" | "Error"

Parameters

NameTypeDescription
levelLevelThe lowest severity that still reaches the output.

Throws when the level is not one of the four.

The threshold is session wide, not per logger. It applies to every logger on this side, Twill’s own included, whichever logger it was called from.

Example

local RunService = game:GetService("RunService")
Log.SetLevel(if RunService:IsStudio() then "Debug" else "Info")

[Server] | [Client]

Reports the level in force on this side.

function Log.GetLevel(): Level

Returns

Level - The lowest severity still reaching the output.

Each side keeps its own threshold, so the answer on a client says nothing about the server. The loglevel command reads and sets the server’s without republishing the place.

[Server] | [Client]

Routes every reported failure to a handler.

function Log.SetErrorHandler(handler: ((scope: string, ...any) -> ())?)

Parameters

NameTypeDescription
handler((scope: string, ...any) -> ())?Receives the reporting scope and the reported values. nil removes the handler.

One handler at a time. Installing a second replaces the first. The handler runs on its own thread in addition to the normal output, so it may yield.

Example

Log.SetErrorHandler(function(scope: string, ...: any)
AnalyticsService:LogCustomEvent(Players.LocalPlayer, scope, ...)
end)

For unhandled script errors rather than deliberate Error calls, see Twill.Error.

LimitValue
Level when nothing sets oneInfo
Stack levels examined for a call-site12