From 11f7f536536e9cb4f575e84bdb5a46e9d4028eac Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Wed, 25 Dec 2019 13:32:59 +0000 Subject: [PATCH] initial commit --- LICENSE | 12 ++++++ README.md | 57 +++++++++++++++++++++++++ lua-ln-scm-1.rockspec | 17 ++++++++ spec/ln_spec.lua | 62 +++++++++++++++++++++++++++ src/example.lua | 8 ++++ src/ln.lua | 99 +++++++++++++++++++++++++++++++++++++++++++ test.sh | 3 ++ 7 files changed, 258 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lua-ln-scm-1.rockspec create mode 100644 spec/ln_spec.lua create mode 100644 src/example.lua create mode 100644 src/ln.lua create mode 100755 test.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87df039 --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2019 Christine Dodrill + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..74ec74d --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# ln: The Natural Logger for Lua + +This is a clone of [ln](https://github.com/Xe/ln) for Lua. ln works by using +key->value pairs to create structured logging output. By default, this outputs +logs formatted similar to [logfmt][logfmt]. + +[logfmt]: https://www.brandur.org/logfmt + +Example: + +```lua +local ln = require "ln" + +ln.log {foo = "bar"} +-- time="2019-12-25T13:24:00" foo=bar +``` + +It also supports multiple tables: + +```lua +local ln = require "ln" + +ln.log({foo = "bar"}, {needs_space = "a string value with spaces"}) +-- time="2019-12-25T13:24:00" foo=bar needs_space="a string value with spaces" +``` + +And logging errors: + +```lua +local ln = require "ln" + +local result, err = thing_that_could_fail() +if err ~= nil then + ln.err(err, {tried = "thing_that_could_fail()"}) +end +-- time="2019-12-25T13:27:32" err="vibe check failed" tried=thing_that_could_fail() +``` + +And outputting logs as JSON: + +``` +local ln = require "ln" + +ln.default_logger.formatter = ln.JSONFormatter:new() +ln.log {foo = "bar"} +-- {"foo":"bar","time":"2019-12-25T13:27:32"} +``` + +Or creating your own logger instance: + +```lua +local ln = require "ln" + +local lgr = ln.Logger:new() +lgr:log {foo = "bar"} +-- time="2019-12-25T13:27:32" foo=bar +``` diff --git a/lua-ln-scm-1.rockspec b/lua-ln-scm-1.rockspec new file mode 100644 index 0000000..20d8597 --- /dev/null +++ b/lua-ln-scm-1.rockspec @@ -0,0 +1,17 @@ +package = "ln" +version = "scm-1" +source = { + url = "git+ssh://ssh.tulpa.dev/cadey/lua-ln" +} +description = { + homepage = "https://tulpa.dev/cadey/lua-ln", + license = "0bsd" +} +dependencies = { + "lua ~> 5.3", + "dkjson" +} +build = { + type = "builtin", + modules = {} +} diff --git a/spec/ln_spec.lua b/spec/ln_spec.lua new file mode 100644 index 0000000..08e1eae --- /dev/null +++ b/spec/ln_spec.lua @@ -0,0 +1,62 @@ +local ln = require "ln" + +describe("ln", function() + describe("exports", function() + it("has Logger", function() + assert.truthy(ln.Logger) + end) + + it("has default_logger", function() + assert.truthy(ln.default_logger) + end) + + it("has LogfmtFormatter", function() + assert.truthy(ln.LogfmtFormatter) + end) + + it("has JSONFormatter", function() + assert.truthy(ln.JSONFormatter) + end) + + it("has log", function() + assert.truthy(ln.log) + end) + + it("has err", function() + assert.truthy(ln.err) + end) + end) + + describe("default logger operations", function() + local lgr = ln.default_logger + it("can log", function() + lgr:log {foo = "bar"} + end) + + it("can error", function() + lgr:err("vibe check failed", {foo = "bar"}) + end) + + it("takes multiple tables", function() + lgr:log({foo = "bar"}, {baz = "boz"}) + end) + + it("lets you override the formatter", function() + lgr.formatter = ln.JSONFormatter:new() + lgr:log {foo = "bar"} + end) + end) + + describe("logfmt", function() + local fmtr = ln.LogfmtFormatter:new() + + it("should return a non-nil message", function() + assert.truthy(fmtr:format({foo = "bar"})) + end) + + it("should have spaces when the input does", function() + local msg = fmtr:format {foo = "bar with spaces"} + assert.truthy(string.find(msg, 'foo="bar with spaces"')) + end) + end) +end) diff --git a/src/example.lua b/src/example.lua new file mode 100644 index 0000000..0531e81 --- /dev/null +++ b/src/example.lua @@ -0,0 +1,8 @@ +local ln = require "ln" +local lgr = ln.default_logger + +lgr:log { foo = "bar", needs_space = "a string value with spaces" } +lgr:err("foo bar", {foo = "bar"}) + +local jlgr = ln.Logger:new(ln.JSONFormatter:new()) +jlgr:log { foo = "bar", big = "scary space value" } diff --git a/src/ln.lua b/src/ln.lua new file mode 100644 index 0000000..b97f886 --- /dev/null +++ b/src/ln.lua @@ -0,0 +1,99 @@ +local json = require "dkjson" + +local function time() + return os.date "%Y-%m-%dT%H:%M:%S" +end + +local function quote(value) + if string.find(value, " ") then + return string.format("%q", value) + end + + return value +end + +local LogfmtFormatter = {} + +function LogfmtFormatter:format(tbl) + local message = "time=\"" .. time() .. "\" " + for k, v in pairs(tbl) do + message = message .. k .. "=" .. quote(v) .. " " + end + + return message +end + +function LogfmtFormatter:new() + local o = {} + setmetatable(o, self) + self.__index = self + return o +end + +local JSONFormatter = {} + +function JSONFormatter:format(tbl) + tbl["time"] = time() + + return json.encode(tbl) +end + +function JSONFormatter:new() + local o = {} + setmetatable(o, self) + self.__index = self + return o +end + +local Logger = {} + +function Logger:err(err, ...) + self:log({err = err}, ...) +end + +function Logger:log(...) + local result = {} + local args = table.pack(...) + + for i=1, args.n do + if type(args[i]) == "table" then + for k, v in pairs(args[i]) do + result[tostring(k)] = tostring(v) + end + end + end + + message = self.formatter:format(result) + + print(message) +end + +function Logger:new(formatter) + if formatter == nil then + formatter = LogfmtFormatter:new() + end + local o = { + formatter = formatter, + } + setmetatable(o, self) + self.__index = self + return o +end + +local default_logger = Logger:new() +local function log(...) + default_logger:log(...) +end + +local function err(why, ...) + default_logger:err(why, ...) +end + +return { + default_logger = default_logger, + Logger = Logger, + LogfmtFormatter = LogfmtFormatter, + JSONFormatter = JSONFormatter, + log = log, + err = err, +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..465d7de --- /dev/null +++ b/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +busted --defer-print