A => +test.ha +50 -0
@@ 1,50 @@
+use log;
+use io;
+use os;
+use strings;
+use fmt;
+
+@test fn logfmt() void = {
+ let s = teststream_open();
+
+ let l = new(&s);
+
+ log::setlogger(&l);
+ log::println("request_uri", "/", "method", "POST", "user_id", 123);
+
+ let sbuf = strings::fromutf8(s.buf);
+
+ assert(strings::contains(sbuf, "request_uri=/ method=POST user_id=123"));
+
+ free(sbuf);
+};
+
+const teststream_vtable: io::vtable = io::vtable {
+ reader = &teststream_read,
+ writer = &teststream_write,
+ ...
+};
+
+type teststream = struct {
+ stream: io::stream,
+ buf: []u8,
+};
+
+fn teststream_open() teststream = teststream {
+ stream = &teststream_vtable,
+ ...
+};
+
+fn teststream_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
+ let stream = s: *teststream;
+ buf = stream.buf;
+ return len(buf);
+};
+
+fn teststream_write(s: *io::stream, buf: const []u8) (size | io::error) = {
+ let stream = s: *teststream;
+
+ append(stream.buf, buf...);
+
+ return len(buf);
+};<
\ No newline at end of file
A => LICENSE +21 -0
@@ 1,21 @@
+MIT License
+
+Copyright (c) 2022 Blain Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.<
\ No newline at end of file
A => Makefile +28 -0
@@ 1,28 @@
+.POSIX:
+.SUFFIXES:
+HARE=hare
+HAREFLAGS=
+
+DESTDIR=
+PREFIX=/usr/local
+SRCDIR=$(PREFIX)/src
+HARESRCDIR=$(SRCDIR)/hare
+THIRDPARTYDIR=$(HARESRCDIR)/third-party
+
+all:
+ # no-op
+
+clean:
+ # no-op
+
+check:
+ $(HARE) test
+
+install:
+ mkdir -p $(DESTDIR)$(THIRDPARTYDIR)/logfmt
+ install -m644 * $(DESTDIR)$(THIRDPARTYDIR)/logfmt
+
+uninstall:
+ rm -rf $(DESTDIR)$(THIRDPARTYDIR)/logfmt
+
+.PHONY: all clean check install uninstall<
\ No newline at end of file
A => README.md +42 -0
@@ 1,42 @@
+# hare-logfmt
+
+A logfmt logger that can be used in [`log::setlogger(*logger) void`](https://docs.harelang.org/log#setlogger) in Hare.
+
+## Usage
+
+```hare
+use logfmt;
+use log;
+
+export fn main() void = {
+ let l = logfmt::new(os::stderr);
+
+ log::setlogger(&l);
+
+ log::println(&l, "request_uri", "/", "method", "POST", "user_id", 123);
+ log::println(&l, "request_uri", "/sign-in", "method", "GET");
+ log::println(&l, "request_uri", "/dashboard", "method", "GET", "user_id", 123);
+};
+```
+
+**Output**
+
+```console
+ts=2022-05-12T09:36:27-0400 request_uri=/ method=POST user_id=123
+ts=2022-05-12T09:42:27-0400 request_uri=/sign-in method=GET
+ts=2022-05-12T09:52:10-0400 request_uri=/dashboard method=GET user_id=123
+```
+
+You can also run `haredoc` to read the module documentation.
+
+```console
+> haredoc
+// Implements the log::logger for outputting logs in Logfmt format.
+type logfmtlogger = struct {
+ log::logger,
+ handle: io::handle,
+};
+
+// creates a new instace of logfmtlogger to be use with [[log::setlogger]].
+fn new(handle: io::handle) logfmtlogger;
+```<
\ No newline at end of file
A => logfmt.ha +64 -0
@@ 1,64 @@
+use io;
+use log;
+use fmt;
+use datetime;
+use os;
+use encoding::utf8;
+use strings;
+
+// Implements the log::logger for outputting logs in Logfmt format.
+export type logfmtlogger = struct {
+ log::logger,
+ handle: io::handle,
+};
+
+// creates a new instace of logfmtlogger to be use with [[log::setlogger]].
+export fn new(handle: io::handle) logfmtlogger = {
+ return logfmtlogger {
+ println = &log_println,
+ printfln = &log_printfln,
+ handle = handle,
+ };
+};
+
+fn log_println(logger: *log::logger, fields: fmt::formattable...) void = {
+ const logger = logger: *logfmtlogger;
+ assert(logger.println == &log_println);
+
+ const now = datetime::now();
+ fmt::fprint(logger.handle, "ts="): void;
+ datetime::format(logger.handle, datetime::RFC3339, &now): void;
+ fmt::fprint(logger.handle, " "): void;
+
+ for (let i = 0z; i < len(fields); i+= 1) {
+ if (i % 2 == 0) {
+ fmt::fprint(logger.handle, fields[i]): void;
+ fmt::fprint(logger.handle, "="): void;
+ } else {
+ fmt::fprint(logger.handle, fields[i]): void;
+ fmt::fprint(logger.handle, " "): void;
+ };
+ };
+ fmt::fprintln(logger.handle, ""): void;
+};
+
+fn log_printfln(logger: *log::logger, fmt: str, fields: fmt::field...) void = {
+ const logger = logger: *logfmtlogger;
+ assert(logger.printfln == &log_printfln);
+
+ const now = datetime::now();
+ fmt::fprint(logger.handle, "ts="): void;
+ datetime::format(logger.handle, datetime::RFC3339, &now): void;
+ fmt::fprint(logger.handle, " "): void;
+
+ for (let i = 0z; i < len(fields); i+= 1) {
+ if (i % 2 == 0) {
+ fmt::fprintf(logger.handle, "", fields[i]): void;
+ fmt::fprint(logger.handle, "="): void;
+ } else {
+ fmt::fprintf(logger.handle, "", fields[i]): void;
+ fmt::fprint(logger.handle, " "): void;
+ };
+ };
+ fmt::fprintln(logger.handle, ""): void;
+};<
\ No newline at end of file