add gs
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
--[[
|
||||
Package link auto-generated by Rotriever
|
||||
]]
|
||||
local PackageIndex = script.Parent.Parent
|
||||
|
||||
local package = PackageIndex["roblox_cryo"]["cryo"]
|
||||
|
||||
if package.ClassName == "ModuleScript" then
|
||||
return require(package)
|
||||
end
|
||||
|
||||
return package
|
||||
@@ -0,0 +1,6 @@
|
||||
# Generated by Rotriever. Format subject to change in future releases.
|
||||
name = "roblox/lumberyak"
|
||||
version = "0.1.0"
|
||||
commit = "47552268e68c899226295e82936d3b68dfd53565"
|
||||
source = "git+https://github.rbx.com/roblox/lumberyak#master"
|
||||
dependencies = ["Cryo roblox/cryo 1.0.0 url+https://github.com/roblox/cryo"]
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
local Root = script.Parent.Parent
|
||||
local Cryo = require(Root.Cryo)
|
||||
|
||||
local Logger = {}
|
||||
Logger.__index = Logger
|
||||
|
||||
Logger.Levels = {
|
||||
Error = "Error",
|
||||
Warning = "Warning",
|
||||
Info = "Info",
|
||||
Debug = "Debug",
|
||||
Trace = "Trace",
|
||||
}
|
||||
|
||||
local levelOrder = {
|
||||
Logger.Levels.Error,
|
||||
Logger.Levels.Warning,
|
||||
Logger.Levels.Info,
|
||||
Logger.Levels.Debug,
|
||||
Logger.Levels.Trace,
|
||||
}
|
||||
|
||||
local levelRank = {}
|
||||
for k, v in pairs(levelOrder) do
|
||||
levelRank[v] = k
|
||||
end
|
||||
|
||||
function Logger.Levels.fromString(str)
|
||||
if type(str) ~= "string" then
|
||||
return nil
|
||||
end
|
||||
for _, k in pairs(levelOrder) do
|
||||
if string.lower(k) == string.lower(str) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Logger.new(parent, name)
|
||||
local logger = {
|
||||
name = name,
|
||||
sinks = {},
|
||||
children = {},
|
||||
parent = parent,
|
||||
context = {},
|
||||
dirty = true,
|
||||
active = {},
|
||||
cache = {
|
||||
sinks = {},
|
||||
context = {},
|
||||
}
|
||||
}
|
||||
|
||||
for k, _ in pairs(Logger.Levels) do
|
||||
if parent then
|
||||
logger.active[k] = parent.active[k]
|
||||
else
|
||||
logger.active[k] = false
|
||||
end
|
||||
end
|
||||
|
||||
if parent then
|
||||
parent.children[logger] = true
|
||||
end
|
||||
|
||||
setmetatable(logger, Logger)
|
||||
return logger
|
||||
end
|
||||
|
||||
-- Activate `level` and above logging levels.
|
||||
local function setActive(level, node)
|
||||
local maxLevel = levelRank[level]
|
||||
if maxLevel then
|
||||
for n = 1,maxLevel do
|
||||
node.active[levelOrder[n]] = true
|
||||
end
|
||||
for k, _ in pairs(node.children) do
|
||||
setActive(level, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Set the dirty flag for `node` and all its children.
|
||||
local function setDirty(node)
|
||||
node.dirty = true
|
||||
for k, _ in pairs(node.children) do
|
||||
setDirty(k)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update the context and sinks cache for `node` and its ancestors.
|
||||
local function updateCache(node)
|
||||
if not node.dirty then
|
||||
return
|
||||
end
|
||||
|
||||
if not node.parent then
|
||||
node.cache.context = node.context
|
||||
node.cache.sinks = node.sinks
|
||||
node.dirty = false
|
||||
return
|
||||
end
|
||||
|
||||
updateCache(node.parent)
|
||||
|
||||
-- Dictionary join the context. List join the sinks. Concatenate the prefixes.
|
||||
node.cache.context = Cryo.Dictionary.join(node.parent.cache.context, node.context)
|
||||
if node.parent.cache.context.prefix and node.context.prefix then
|
||||
node.cache.context.prefix = node.parent.cache.context.prefix .. node.context.prefix
|
||||
end
|
||||
node.cache.sinks = Cryo.List.join(node.parent.cache.sinks, node.sinks)
|
||||
node.dirty = false
|
||||
end
|
||||
|
||||
-- Set the parent of this Logger and update its cache, active bits and dirty bit.
|
||||
function Logger:setParent(parent)
|
||||
if self.parent then
|
||||
self.parent.children[self] = nil
|
||||
end
|
||||
|
||||
updateCache(parent)
|
||||
self.parent = parent
|
||||
self.parent.children[self] = true
|
||||
|
||||
local maxLevel = -1
|
||||
for _, sink in pairs(parent.cache.sinks) do
|
||||
local sinkLevel = levelRank[sink.maxLevel]
|
||||
if sinkLevel then
|
||||
maxLevel = math.max(maxLevel, levelRank[sink.maxLevel])
|
||||
end
|
||||
end
|
||||
|
||||
if maxLevel > -1 then
|
||||
setActive(levelOrder[maxLevel], self)
|
||||
end
|
||||
|
||||
setDirty(self)
|
||||
end
|
||||
|
||||
function Logger:addSink(sink)
|
||||
setActive(sink.maxLevel, self)
|
||||
table.insert(self.sinks, sink)
|
||||
setDirty(self)
|
||||
end
|
||||
|
||||
function Logger:setContext(context)
|
||||
self.context = context
|
||||
setDirty(self)
|
||||
end
|
||||
|
||||
local function log(level, node, args)
|
||||
if node.dirty then
|
||||
updateCache(node)
|
||||
end
|
||||
|
||||
-- Collect per-log context.
|
||||
local fullContext = {
|
||||
level = level,
|
||||
rawMessage = args,
|
||||
loggerName = node.name,
|
||||
}
|
||||
|
||||
-- Call any functions in the context.
|
||||
for k, v in pairs(node.cache.context) do
|
||||
if type(v) == "function" then
|
||||
fullContext[k] = v()
|
||||
else
|
||||
fullContext[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Interpolate the log message.
|
||||
local interpMsg
|
||||
if args.n == 0 then
|
||||
interpMsg = "LUMBERYAK INTERNAL: No log message given"
|
||||
else
|
||||
interpMsg = args[1]
|
||||
end
|
||||
if fullContext.prefix then
|
||||
interpMsg = fullContext.prefix .. interpMsg
|
||||
end
|
||||
|
||||
if interpMsg:find("{") then
|
||||
local i = 1
|
||||
interpMsg = (interpMsg:gsub("{(.-)}", function(w)
|
||||
-- Treat {} as a positional arg.
|
||||
if w == "" then
|
||||
i = i + 1
|
||||
return args[i]
|
||||
end
|
||||
return fullContext[w] or w
|
||||
end))
|
||||
if i < args.n then
|
||||
interpMsg = interpMsg .. "\nLUMBERYAK INTERNAL: Too many arguments given for format string"
|
||||
elseif i > args.n then
|
||||
interpMsg = interpMsg .. "\nLUMBERYAK INTERNAL: Too few arguments given for format string"
|
||||
end
|
||||
elseif args.n > 1 then
|
||||
interpMsg = interpMsg .. "\nLUMBERYAK INTERNAL: Too many arguments given for format string"
|
||||
end
|
||||
|
||||
-- Send the message to any sinks that are listening to the right level.
|
||||
local rank = levelRank[level]
|
||||
for _, k in pairs(node.cache.sinks) do
|
||||
if levelRank[k.maxLevel] and levelRank[k.maxLevel] >= rank then
|
||||
k:log(interpMsg, fullContext)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Logger:error(...)
|
||||
if not self.active[Logger.Levels.Error] then
|
||||
return
|
||||
end
|
||||
log(Logger.Levels.Error, self, table.pack(...))
|
||||
end
|
||||
|
||||
function Logger:warning(...)
|
||||
if not self.active[Logger.Levels.Warning] then
|
||||
return
|
||||
end
|
||||
log(Logger.Levels.Warning, self, table.pack(...))
|
||||
end
|
||||
|
||||
function Logger:info(...)
|
||||
if not self.active[Logger.Levels.Info] then
|
||||
return
|
||||
end
|
||||
log(Logger.Levels.Info, self, table.pack(...))
|
||||
end
|
||||
|
||||
function Logger:debug(...)
|
||||
if not self.active[Logger.Levels.Debug] then
|
||||
return
|
||||
end
|
||||
log(Logger.Levels.Debug, self, table.pack(...))
|
||||
end
|
||||
|
||||
function Logger:trace(...)
|
||||
if not self.active[Logger.Levels.Trace] then
|
||||
return
|
||||
end
|
||||
log(Logger.Levels.Trace, self, table.pack(...))
|
||||
end
|
||||
|
||||
return Logger
|
||||
+659
@@ -0,0 +1,659 @@
|
||||
return function()
|
||||
local Logger = require(script.Parent.Logger)
|
||||
|
||||
local function newSink(level)
|
||||
return {
|
||||
maxLevel = level,
|
||||
seen = {},
|
||||
log = function(self, message, context)
|
||||
table.insert(self.seen, {message=message, context=context})
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
describe("A new Logger", function()
|
||||
it("should be creatable without a parent", function()
|
||||
expect(function()
|
||||
local _ = Logger.new()
|
||||
end).to.never.throw()
|
||||
end)
|
||||
|
||||
it("should be creatable with a parent", function()
|
||||
expect(function()
|
||||
local log1 = Logger.new()
|
||||
local _ = Logger.new(log1)
|
||||
end).to.never.throw()
|
||||
end)
|
||||
|
||||
it("should be creatable with a parent, alternate syntax", function()
|
||||
expect(function()
|
||||
local log1 = Logger.new()
|
||||
local _ = log1:new()
|
||||
end).to.never.throw()
|
||||
end)
|
||||
|
||||
it("should add sinks", function()
|
||||
expect(function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
end).to.never.throw()
|
||||
end)
|
||||
|
||||
it("should add context", function()
|
||||
expect(function()
|
||||
local log = Logger.new()
|
||||
log:setContext({foo = "bar"})
|
||||
end).to.never.throw()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Basic logging", function()
|
||||
it("to the root logger", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
log:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("to a child logger", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
|
||||
log2:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("to a sibling logger", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local log3 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log2:addSink(sink)
|
||||
|
||||
log3:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("to a parent logger", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log2:addSink(sink)
|
||||
|
||||
log1:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("to a child logger, sink added first", function()
|
||||
local log1 = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
local log2 = log1:new()
|
||||
|
||||
log2:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("to a sibling logger, sink added first", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log2:addSink(sink)
|
||||
local log3 = log1:new()
|
||||
|
||||
log3:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("When logging different levels", function()
|
||||
local cases = {
|
||||
[Logger.Levels.Error] = 1,
|
||||
[Logger.Levels.Warning] = 2,
|
||||
[Logger.Levels.Info] = 3,
|
||||
[Logger.Levels.Debug] = 4,
|
||||
[Logger.Levels.Trace] = 5,
|
||||
}
|
||||
|
||||
for level, count in pairs(cases) do
|
||||
it("logging to the root should respect " .. level, function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(level)
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(count)
|
||||
end)
|
||||
|
||||
it("logging to the child should respect " .. level, function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(level)
|
||||
log1:addSink(sink)
|
||||
|
||||
log2:error("error")
|
||||
log2:warning("warning")
|
||||
log2:info("info")
|
||||
log2:debug("debug")
|
||||
log2:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(count)
|
||||
end)
|
||||
end
|
||||
|
||||
describe("should treat invalid log levels as disabled", function()
|
||||
it("should log to no levels when given a bad maxLevel", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink("not-a-level")
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should log to no levels when given nil", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(nil)
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should handle multiple sinks when some are disabled", function()
|
||||
local log = Logger.new()
|
||||
local sink1 = newSink(nil)
|
||||
local sink2 = newSink(Logger.Levels.Trace)
|
||||
log:addSink(sink1)
|
||||
log:addSink(sink2)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink1.seen).to.equal(0)
|
||||
expect(#sink2.seen).to.equal(5)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("When logging different levels using fromString", function()
|
||||
local cases = {
|
||||
["error"] = 1,
|
||||
["Warning"] = 2,
|
||||
["INFO"] = 3,
|
||||
["dEBUG"] = 4,
|
||||
["TrAcE"] = 5,
|
||||
["invalid"] = 0,
|
||||
}
|
||||
|
||||
for level, count in pairs(cases) do
|
||||
it("fromString should handle " .. level, function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.fromString(level))
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(count)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("sinks", function()
|
||||
it("should be disabled without error when maxLevel isn't set", function()
|
||||
local log = Logger.new()
|
||||
local seen = 0
|
||||
log:addSink({
|
||||
log = function()
|
||||
seen = seen + 1
|
||||
end,
|
||||
})
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should be disabled without error when maxLevel is set incorrectly", function()
|
||||
local log = Logger.new()
|
||||
local seen = 0
|
||||
log:addSink({
|
||||
maxLevel = "foo",
|
||||
log = function()
|
||||
seen = seen + 1
|
||||
end,
|
||||
})
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should not cause problems with parenting when not set", function()
|
||||
local log1 = Logger.new()
|
||||
local seen = 0
|
||||
log1:addSink({
|
||||
log = function()
|
||||
seen = seen + 1
|
||||
end,
|
||||
})
|
||||
|
||||
local log2 = log1:new()
|
||||
|
||||
log2:error("error")
|
||||
log2:warning("warning")
|
||||
log2:info("info")
|
||||
log2:debug("debug")
|
||||
log2:trace("trace")
|
||||
|
||||
expect(seen).to.equal(0)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("should treat invalid log levels as disabled", function()
|
||||
it("should log to no levels when given a bad maxLevel", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink("not-a-level")
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should log to no levels when given nil", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(nil)
|
||||
log:addSink(sink)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink.seen).to.equal(0)
|
||||
end)
|
||||
|
||||
it("should handle multiple sinks when some are disabled", function()
|
||||
local log = Logger.new()
|
||||
local sink1 = newSink(nil)
|
||||
local sink2 = newSink(Logger.Levels.Trace)
|
||||
log:addSink(sink1)
|
||||
log:addSink(sink2)
|
||||
|
||||
log:error("error")
|
||||
log:warning("warning")
|
||||
log:info("info")
|
||||
log:debug("debug")
|
||||
log:trace("trace")
|
||||
|
||||
expect(#sink1.seen).to.equal(0)
|
||||
expect(#sink2.seen).to.equal(5)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("with positional arguments", function()
|
||||
it("should work with one arg", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
log:info("foo {}", "bar")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo bar")
|
||||
end)
|
||||
|
||||
it("should work with two args", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
log:info("foo {} {}", "bar", "baz")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo bar baz")
|
||||
end)
|
||||
|
||||
it("should output a warning with too many arguments", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
log:info("foo {}", "bar", "baz")
|
||||
|
||||
assert(string.find(sink.seen[1].message, "LUMBERYAK INTERNAL"),
|
||||
"Expected an internal warning, got [[\n" .. sink.seen[1].message .. "\n]]")
|
||||
end)
|
||||
|
||||
it("should output a warning with too few arguments", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
log:info("foo {} {}", "bar")
|
||||
|
||||
assert(string.find(sink.seen[1].message, "LUMBERYAK INTERNAL"),
|
||||
"Expected an internal warning, got [[\n" .. sink.seen[1].message .. "\n]]")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("When passing in context", function()
|
||||
describe("with context from root", function()
|
||||
it("should pass along static context", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({bar = 1})
|
||||
|
||||
log:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].context.bar).to.equal(1)
|
||||
end)
|
||||
|
||||
it("should call dynamic context", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({bar = function()
|
||||
return 1
|
||||
end})
|
||||
|
||||
log:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].context.bar).to.equal(1)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("when combining context", function()
|
||||
it("should merge non-overlapping context", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local log3 = log2:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
log1:setContext({bar = 1})
|
||||
log2:setContext({baz = 2})
|
||||
log3:setContext({quz = 3})
|
||||
|
||||
log3:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].context.bar).to.equal(1)
|
||||
expect(sink.seen[1].context.baz).to.equal(2)
|
||||
expect(sink.seen[1].context.quz).to.equal(3)
|
||||
end)
|
||||
|
||||
it("should overwrite overlapping context", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local log3 = log2:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
log1:setContext({bar = 1, baz = 1, quz = 1})
|
||||
log2:setContext({bar = 2, baz = 2})
|
||||
log3:setContext({baz = 3})
|
||||
|
||||
log3:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].context.quz).to.equal(1)
|
||||
expect(sink.seen[1].context.bar).to.equal(2)
|
||||
expect(sink.seen[1].context.baz).to.equal(3)
|
||||
end)
|
||||
|
||||
it("should call dynamic context", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
log1:setContext({bar = 1})
|
||||
log2:setContext({bar = function()
|
||||
return 2
|
||||
end})
|
||||
|
||||
log2:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].context.bar).to.equal(2)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Message interpolation", function()
|
||||
it("should leave plain messages alone", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({bar = "baz"})
|
||||
|
||||
log:info("foo")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo")
|
||||
end)
|
||||
|
||||
it("should substitute info from static context", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({bar = "baz"})
|
||||
|
||||
log:info("foo {bar}")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo baz")
|
||||
end)
|
||||
|
||||
it("should substitute info from dynamic context", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({bar = function()
|
||||
return "baz"
|
||||
end})
|
||||
|
||||
log:info("foo {bar}")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo baz")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Message prefix", function()
|
||||
it("should prepend the prefix", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({prefix = "foo: "})
|
||||
|
||||
log:info("bar")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo: bar")
|
||||
end)
|
||||
|
||||
it("should interpolate the prefix", function()
|
||||
local log = Logger.new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log:addSink(sink)
|
||||
log:setContext({prefix = "{foo}: ", foo = "baz"})
|
||||
|
||||
log:info("bar")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("baz: bar")
|
||||
end)
|
||||
|
||||
it("should stack prefixes", function()
|
||||
local log1 = Logger.new()
|
||||
local log2 = log1:new()
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
log1:setContext({prefix = "foo: "})
|
||||
log2:setContext({prefix = "bar: "})
|
||||
|
||||
log2:info("baz")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("foo: bar: baz")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("Should get the name of logger used", function()
|
||||
local log1 = Logger.new(nil, "log1")
|
||||
local log2 = log1:new("log2")
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
log1:addSink(sink)
|
||||
|
||||
log1:info("{loggerName}")
|
||||
log2:info("{loggerName}")
|
||||
|
||||
expect(#sink.seen).to.equal(2)
|
||||
expect(sink.seen[1].message).to.equal("log1")
|
||||
expect(sink.seen[2].message).to.equal("log2")
|
||||
end)
|
||||
|
||||
describe("setParent should work", function()
|
||||
it("when calling A->B then B->C", function()
|
||||
local a = Logger.new()
|
||||
local b = Logger.new()
|
||||
local c = Logger.new()
|
||||
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
a:addSink(sink)
|
||||
|
||||
a:setContext({a = "A"})
|
||||
b:setContext({b = "B"})
|
||||
c:setContext({c = "C"})
|
||||
|
||||
b:setParent(a)
|
||||
c:setParent(b)
|
||||
|
||||
c:info("{a} {b} {c}")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("A B C")
|
||||
end)
|
||||
|
||||
it("when calling B->C then A->B", function()
|
||||
local a = Logger.new()
|
||||
local b = Logger.new()
|
||||
local c = Logger.new()
|
||||
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
a:addSink(sink)
|
||||
|
||||
a:setContext({a = "A"})
|
||||
b:setContext({b = "B"})
|
||||
c:setContext({c = "C"})
|
||||
|
||||
c:setParent(b)
|
||||
b:setParent(a)
|
||||
|
||||
c:info("{a} {b} {c}")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("A B C")
|
||||
end)
|
||||
|
||||
it("when moving D from B to C", function()
|
||||
local a = Logger.new()
|
||||
local b = Logger.new()
|
||||
local c = Logger.new()
|
||||
local d = Logger.new()
|
||||
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
a:addSink(sink)
|
||||
|
||||
a:setContext({x = "A"})
|
||||
b:setContext({y = "B"})
|
||||
c:setContext({y = "C"})
|
||||
d:setContext({z = "D"})
|
||||
|
||||
c:setParent(a)
|
||||
b:setParent(a)
|
||||
|
||||
d:setParent(b)
|
||||
d:info("{x} {y} {z}")
|
||||
|
||||
d:setParent(c)
|
||||
d:info("{x} {y} {z}")
|
||||
|
||||
expect(#sink.seen).to.equal(2)
|
||||
expect(sink.seen[1].message).to.equal("A B D")
|
||||
expect(sink.seen[2].message).to.equal("A C D")
|
||||
end)
|
||||
|
||||
it("when mixing setParent and static parents", function()
|
||||
local a = Logger.new()
|
||||
local b = Logger.new()
|
||||
local c = b:new()
|
||||
|
||||
local sink = newSink(Logger.Levels.Info)
|
||||
a:addSink(sink)
|
||||
|
||||
a:setContext({a = "A"})
|
||||
b:setContext({b = "B"})
|
||||
c:setContext({c = "C"})
|
||||
|
||||
b:setParent(a)
|
||||
|
||||
c:info("{a} {b} {c}")
|
||||
|
||||
expect(#sink.seen).to.equal(1)
|
||||
expect(sink.seen[1].message).to.equal("A B C")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
-- This mock has the same API as the Logger, but none of the calls do anything.
|
||||
-- Note that MockLogger and Logger can't be parented to each other.
|
||||
|
||||
local MockLogger = {}
|
||||
|
||||
MockLogger.Levels = {
|
||||
Error = "MockError",
|
||||
Warning = "MockWarning",
|
||||
Info = "MockInfo",
|
||||
Debug = "MockDebug",
|
||||
Trace = "MockTrace",
|
||||
fromString = function()
|
||||
return "MockInfo"
|
||||
end,
|
||||
}
|
||||
|
||||
MockLogger.__index = MockLogger
|
||||
|
||||
function MockLogger.new()
|
||||
return setmetatable({}, MockLogger)
|
||||
end
|
||||
|
||||
function MockLogger.setParent()
|
||||
end
|
||||
|
||||
function MockLogger.setContext()
|
||||
end
|
||||
|
||||
function MockLogger.addSink()
|
||||
end
|
||||
|
||||
function MockLogger.error()
|
||||
end
|
||||
|
||||
function MockLogger.warning()
|
||||
end
|
||||
|
||||
function MockLogger.info()
|
||||
end
|
||||
|
||||
function MockLogger.debug()
|
||||
end
|
||||
|
||||
function MockLogger.trace()
|
||||
end
|
||||
|
||||
return MockLogger
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
return function()
|
||||
local MockLogger = require(script.Parent.MockLogger)
|
||||
local Logger = require(script.Parent.Logger)
|
||||
|
||||
it("MockLogger should have the same API as Logger", function()
|
||||
for k, v in pairs(Logger) do
|
||||
local mock = MockLogger[k]
|
||||
assert(mock, "Expected a mock of " .. k)
|
||||
assert(type(mock) == type(v),
|
||||
"Expected the type of " .. k .. " to be " .. type(v) .. ", got " .. type(mock))
|
||||
end
|
||||
end)
|
||||
|
||||
it("MockLogger.Levels should have the same API as Logger.Levels", function()
|
||||
for k, v in pairs(Logger.Levels) do
|
||||
local mock = MockLogger.Levels[k]
|
||||
assert(mock, "Expected a mock of " .. k)
|
||||
assert(type(mock) == type(v),
|
||||
"Expected the type of " .. k .. " to be " .. type(v) .. ", got " .. type(mock))
|
||||
end
|
||||
end)
|
||||
end
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
local Logger = require(Workspace.LoadedCode.Packages.Lumberyak.Logger)
|
||||
|
||||
local runs = 2000000
|
||||
|
||||
local function timeit(makeLogger, callLogger)
|
||||
local log = makeLogger()
|
||||
|
||||
local t = tick()
|
||||
for _ = 1, runs do
|
||||
callLogger(log)
|
||||
end
|
||||
return (tick() - t) / runs
|
||||
end
|
||||
|
||||
local function callSimple(log)
|
||||
log:info("foo")
|
||||
end
|
||||
|
||||
local function callInterp(log)
|
||||
log:info("foo {}", 2)
|
||||
end
|
||||
|
||||
local function simple()
|
||||
local count = 0
|
||||
local countSink = {
|
||||
maxLevel = Logger.Levels.Info,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
|
||||
local log = Logger.new()
|
||||
log:setContext({number = 1})
|
||||
log:addSink(countSink)
|
||||
return log
|
||||
end
|
||||
|
||||
local function empty()
|
||||
return {
|
||||
info = function() end
|
||||
}
|
||||
end
|
||||
|
||||
local function off()
|
||||
local count = 0
|
||||
local countSink = {
|
||||
maxLevel = Logger.Levels.Error,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
|
||||
local log = Logger.new()
|
||||
log:setContext({number = 1})
|
||||
log:addSink(countSink)
|
||||
return log
|
||||
end
|
||||
|
||||
local function short()
|
||||
local count = 0
|
||||
local countSink = {
|
||||
maxLevel = Logger.Levels.Info,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
|
||||
local log = Logger.new()
|
||||
local child = log:new()
|
||||
log:setContext({number = 1})
|
||||
log:addSink(countSink)
|
||||
return child
|
||||
end
|
||||
|
||||
local function long()
|
||||
local count = 0
|
||||
local countSink = {
|
||||
maxLevel = Logger.Levels.Info,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
|
||||
local log = Logger.new()
|
||||
log:setContext({number = 1})
|
||||
log:addSink(countSink)
|
||||
local a = log:new()
|
||||
local b = a:new()
|
||||
local c = b:new()
|
||||
local d = c:new()
|
||||
return d
|
||||
end
|
||||
|
||||
local function many()
|
||||
local count = 0
|
||||
local sink1 = {
|
||||
maxLevel = Logger.Levels.Error,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
local sink2 = {
|
||||
maxLevel = Logger.Levels.Warning,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
local sink3 = {
|
||||
maxLevel = Logger.Levels.Info,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
local sink4 = {
|
||||
maxLevel = Logger.Levels.Debug,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
local sink5 = {
|
||||
maxLevel = Logger.Levels.Trace,
|
||||
log = function(_, _, context)
|
||||
count = count + context.number
|
||||
end,
|
||||
}
|
||||
|
||||
local log = Logger.new()
|
||||
log:setContext({number = 1})
|
||||
log:addSink(sink1)
|
||||
log:addSink(sink2)
|
||||
log:addSink(sink3)
|
||||
log:addSink(sink4)
|
||||
log:addSink(sink5)
|
||||
return log
|
||||
end
|
||||
|
||||
local base = timeit(empty, callSimple)
|
||||
local interp = timeit(empty, callInterp)
|
||||
|
||||
local tests = {
|
||||
["Log level off"] = off,
|
||||
["Simple logger"] = simple,
|
||||
["Short chain"] = short,
|
||||
["Long chain"] = long,
|
||||
["Many sinks"] = many,
|
||||
}
|
||||
|
||||
local function fmt(name, case, t)
|
||||
print(string.format("%-40s %0.3e [%0.4fx]", name .. " - " .. case, t, t / base))
|
||||
end
|
||||
|
||||
fmt("Empty function", "Simple", base)
|
||||
fmt("Empty function", "Interpolation", interp)
|
||||
for k, v in pairs(tests) do
|
||||
local t1 = timeit(v, callSimple)
|
||||
fmt(k, "Simple", t1)
|
||||
local t2 = timeit(v, callInterp)
|
||||
fmt(k, "Interpolation", t2)
|
||||
end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
local Logger = require(script.Parent.Parent.Parent.Logger)
|
||||
|
||||
local PrintSink = {}
|
||||
|
||||
function PrintSink.new(level)
|
||||
local printer = {
|
||||
maxLevel = level
|
||||
}
|
||||
|
||||
setmetatable(printer, PrintSink)
|
||||
return printer
|
||||
end
|
||||
|
||||
function PrintSink:log(message, context)
|
||||
if context.level == Logger.Levels.Error then
|
||||
error(message, 5)
|
||||
elseif context.level == Logger.Levels.Warning then
|
||||
warn(message)
|
||||
else
|
||||
print(message)
|
||||
end
|
||||
end
|
||||
|
||||
return PrintSink
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
local log = require(script.Parent.appLogger)
|
||||
-- The app has some way to import the page, but not vice versa.
|
||||
local page = require(script.Parent.Parent.page.page)
|
||||
local printer = require(script.Parent.PrintSink)
|
||||
|
||||
log:addSink(printer.new(log.Levels.Error))
|
||||
|
||||
return function()
|
||||
log:info("calling {root}")
|
||||
page.init(log)
|
||||
|
||||
return page.doSomething()
|
||||
end
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local Logger = require(script.Parent.Parent.Parent.Logger)
|
||||
local log = Logger.new()
|
||||
log:setContext({root = "root"})
|
||||
return log
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
return function()
|
||||
local app = require(script.Parent.app.app)
|
||||
local log = require(script.Parent.app.appLogger)
|
||||
|
||||
local function newSink(level)
|
||||
return {
|
||||
maxLevel = level,
|
||||
seen = {},
|
||||
log = function(self, message, context)
|
||||
table.insert(self.seen, {message=message, context=context})
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
it("should generate expected messages", function()
|
||||
local sink = newSink(log.Levels.Info)
|
||||
log:addSink(sink)
|
||||
|
||||
local result = app()
|
||||
expect(result).to.equal("done")
|
||||
expect(#sink.seen).to.equal(3)
|
||||
expect(sink.seen[1].message).to.equal("calling root")
|
||||
expect(sink.seen[2].message).to.equal("calling root foo")
|
||||
expect(sink.seen[3].message).to.equal("calling root foo bar")
|
||||
end)
|
||||
end
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local log = require(script.Parent.pageLogger):new()
|
||||
log:setContext({bar = "bar"})
|
||||
|
||||
return function()
|
||||
log:info("calling {root} {foo} {bar}")
|
||||
return "done"
|
||||
end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
-- foo does not need to require root
|
||||
local log = require(script.Parent.pageLogger)
|
||||
local component = require(script.Parent.component)
|
||||
|
||||
return {
|
||||
init = function(logParent)
|
||||
log:setParent(logParent)
|
||||
end,
|
||||
doSomething = function()
|
||||
log:info("calling {root} {foo}")
|
||||
return component()
|
||||
end
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
local Logger = require(script.Parent.Parent.Parent.Logger)
|
||||
local log = Logger.new()
|
||||
log:setContext({foo = "foo"})
|
||||
return log
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
Logger = require(script.Logger),
|
||||
}
|
||||
Reference in New Issue
Block a user