How To Set Up The Module
Step 1: Put the module in ReplicatedStorage
Step 2: Require the module through a client or server script
local LogService = require(game.ReplicatedStorage.LogService)
Step 3: Use it by returning a function (exampled below)
-- script.Name is not needed but can be added for extra log details
game.Players.PlayerAdded:Connect(function(Player)
LogService("Print", Player.Name .. " has joined the game!", script.Name)
end)
Use Types
There are 3 different use types, Print, Warn, & Error.
Each of these will print your log in a different way, which should be decided based off your log need.
Print: Usually used for basic logging, such as information being printed, or certain values being understood inside your game.
Error: this should be self explanatory, but this type will return an error with your message data you provide.
Like whatâs the point???
You can recreate these functions with one line of code? You donât need emojis and things like [WARNING] because you can identify it by the text colour(unless you are colourblind).
warn(" A player has joined the game, warned from script: "..script.Name)
same for print:
print("A player has joined the game, printed from script: "..script.Name)
and for error:
error("A player has joined the game, errored from script: ".. script.Name, 2)
You havenât even explained why itâs better than functions I provided above. So I have no idea why would anyone use this?
People can probably write the same module in 10-20 minutes, and probably make it better than what you made now(no offence).
Was asked by someone in my community to open source it. I did not state anywhere in the post that its supposed to be better, its simply just for aesthetic and if people find it helpful they can use it.
Hereâs something quick I made that supports vargs, and outputs reference to the script and automatically gets the script reference if not provided
type LogTypes = "Error" | "Warn" | "Print"
local Types = {
Error = {
Format = "Ă°ÂÂÂ [ERROR]",
Function = function(... : any?)
local result = "" do
local Arguments = {...}
local Size = #Arguments
for i = 1, Size-1 do
result ..= tostring(Arguments[i]).." "
end
result ..= tostring(Arguments[Size])
end
error(result)
end,
},
Warn = {
Format = "â ï¸ [WARNING]",
Function = warn,
},
Print = {
Format = "Ă°ÂÂÂ [LOG]",
Function = print
}
}
return function(ScriptName : string?, Type : LogTypes, ...)
local LogType = Types[Type] do
if not LogType then
error(`{Types.Error.Format} ([ {script:GetFullName()} ] | {Type} is not a valid LogType`)
end
end
local Orgin = ScriptName do
if not Orgin then
local TempScript = rawget(getfenv(2), "script")
Orgin = typeof(TempScript) == "Instance" and TempScript:IsA("LuaSourceContainer") and TempScript or "Unknown"
end
end
LogType.Function(LogType.Format.."[", Orgin, "] |", ...)
end
What youâre likely thinking of is likely the source identifier (debug.info(function() end, "s")) which returns the source that the function was defined in (it returns [C] when a C closure is passed)
What I am doing is attempting to retrieve the functionâs environment to get the script instance. I have the type/IsA check in off chance the calling script overwrote the script in the functionâs environment.
Currently there is no other way to retrieve the functions environment other than using getfenv
You can literally just use debug.info(fn, "s"), and it will achieve the same result. Get the last component of the datamodel path that is returned, and you have the script name of the caller. Using getfenv on the caller will de-optimize its environment, no logging module is worth that trade-off. debug.info should cover almost all use cases.
Incorrect.
By me retrieving the original script Instance and printing it out I am able to then go to the output and click on the script which will select the Instance
debug.info âsâ returns a string which does NOT allow me to click on it to open the calling scripts document.
I fail to see what youâre trying to prove. Additionally if you care that much about the deoptimization of the environment localize everything
If you really want that functionality (is it really that important?) then you can get the script instance using the path returned by debug.info. Using setfenv turns off tons of Luau optimizations, and your logging library should absolutely not be doing that.
Iâm not trying to prove anything. I just want to make sure people on the forum donât unknowingly fall into that trap.
The click to select is a quality of life (just like luau optimizations)
My main reason for using the selection feature is it saves a lot of time especially in debugging (which I imagine a lot of people can agree).
This is true assuming none of the ancestors of the calling script have changed (name/parent) after the calling functionâs definition. The issue with this though is that it is static and does not update. Additionally this undermines the core reason of this implementation; having the click select feature. If youâre proposing to manually search for the Instance using the path it still would still be unreliable and inefficient (same reason I said it was technically true).
A solution though (which I personally wouldnât use) would be to require the user to pass ScriptName with either a string or the calling scripts Instance which would essentially defeat the entire purpose of this implementation which is automatic retrieval of the calling script when not provided it.
(also a âtonâ is an overstatement but understandable)
yes
TLDR: debug.info is not applicable to my implementation but your concerns are valid
local LogTypes = {
Error = "đ [ERROR]",
Warn = "â ď¸ [WARNING]",
Print = "đ [LOG]"
}
local function logMessage(logType, message, scriptName, line)
--// Validate the log type
if not LogTypes[logType] then
error(string.format("%s No Log Type Found For '%s' | Errored From Script: %s", LogTypes.Error, logType, script.Name))
end
--// Format the script name & line (default to "nil" if not provided)
local formattedScriptName = scriptName or "nil"
local formattedLine = line or "nil"
--// Construct the full log message
local fullMessage = string.format(
"%s %s | Logged From: %s (Line %d)",
LogTypes[logType],
message,
formattedScriptName,
formattedLine
)
--// Dispatch the appropriate logging function
if logType == "Print" then
print(fullMessage)
elseif logType == "Error" then
error(fullMessage)
elseif logType == "Warn" then
warn(fullMessage)
end
end
return logMessage
local logService = require(game.ReplicatedStorage.Modules.LogService)
--// Check if the module loaded successfully
if logService == nil then
warn("Failed to load LogService module!")
else
print("LogService module loaded successfully!")
end
--// Log a warning message
logService("Warn", "This is a warn message.", script.Name, debug.info(1, "l"))
Explanation of Each Argument:
Warn (logType):
This specifies the type of log message being generated. In this case, itâs a warning (Warn), which corresponds to the "â ď¸ [WARNING]" entry in the LogTypes table.
"This is a warn message." (message):
This is the actual content of the log message. It describes what is being logged, such as an event, error, or informational message.
script.Name (scriptName):
This provides the name of the script that is calling the logService function. The script.Name property retrieves the name of the current script from the Roblox Explorer.
For example, if the script is named MainScript, this will pass "MainScript" as the scriptName.
debug.info(1, "l") (line):
This retrieves the current line number in the script where the logService function is called.
debug.info(1, "l") works as follows:
- The 1 indicates the level of the call stack to inspect. A value of 1 refers to the current function (the one making the call to debug.info).
- The "l" specifies that you want to retrieve the line number.
For example, if the logService call is on line 15 of your script, this will pass 15 as the line argument.