logs.um

A logging library.

Every log has a tag. The tag is used to distinguish between different parts of the program. Every tag can have specific log level, which is different from the global log level. This allows you to enable the debug logs just for the code you are debugging.

This library offers following loggin backends:

  • ConsoleBackend - log to stdout
  • FileBackend - log to file

interface Backend

Defines a logging backend.

type Backend* = interface {

enum LogLevel

type LogLevel* = int
const (
LogLevelDbg* = LogLevel(0)
LogLevelInf* = LogLevel(1)
LogLevelWrn* = LogLevel(2)
LogLevelSus* = LogLevel(3)
LogLevelErr* = LogLevel(4)
)

struct ConsoleBackend

Backend which logs to the console. This is the default backend.

type ConsoleBackend* = struct { }

struct FileBackend

Backend which logs to a file. Created using mkFileBackend.

type FileBackend* = struct {
// Contains private fields

fn FileBackend.close

Closes the internal file.

fn (this: ^FileBackend) close*() {

fn mkFileBackend

Creates a FileBackend. If rewrite is false, the file will be appended.

fn mkFileBackend*(path: str, rewrite: bool = false): (FileBackend, std::Err) {

fn init

Initialize logs.um. You need to call this before using any other functions.

fn init*() {

fn setBackend

Set the logging backend.

fn setBackend*(b: Backend) {

fn setGlobalLogLevel

Set the global log level.

fn setGlobalLogLevel*(level: LogLevel) {

fn setTagLogLevel

Set the tag specific log level.

fn setTagLogLevel*(tag: str, level: LogLevel) {

fn dbg

fn dbg*(tag: str, fmt: str, args: ..any) {

fn inf

fn inf*(tag: str, fmt: str, args: ..any) {

fn wrn

fn wrn*(tag: str, fmt: str, args: ..any) {

fn sus

fn sus*(tag: str, fmt: str, args: ..any) {

fn err

fn err*(tag: str, fmt: str, args: ..any) {


import (
	"std.um"
	"umbox/fmt/fmt.um"
)

//~~
// A logging library.
//
// Every log has a tag. The tag is used to distinguish between different parts
// of the program. Every tag can have specific log level, which is different
// from the global log level. This allows you to enable the debug logs just for
// the code you are debugging.
//
// This library offers following loggin backends:
// * `ConsoleBackend` - log to `stdout`
// * `FileBackend` - log to file
//~~


//~~interface Backend
// Defines a logging backend.
type Backend* = interface {
//~~
	write(s: str)
}

//~~enum LogLevel
type LogLevel* = int
const (
	LogLevelDbg* = LogLevel(0)
	LogLevelInf* = LogLevel(1)
	LogLevelWrn* = LogLevel(2)
	LogLevelSus* = LogLevel(3)
	LogLevelErr* = LogLevel(4)
)
//~~

type Config = struct {
	level: LogLevel
	tagLevel: map[str]LogLevel
	backend: Backend
}

//~~struct ConsoleBackend
// Backend which logs to the console. This is the default backend.
type ConsoleBackend* = struct { }
//~~

fn (this: ^ConsoleBackend) write*(s: str) {
	printf("%s", s)
}

//~~struct FileBackend
// Backend which logs to a file. Created using `mkFileBackend`.
type FileBackend* = struct {
	// Contains private fields
//~~
	f: std::File
}

fn (this: ^FileBackend) write*(s: str) {
	fprintf(this.f, "%s", s)
}

//~~fn FileBackend.close
// Closes the internal file.
fn (this: ^FileBackend) close*() {
//~~
	std::fclose(this.f)
}

//~~fn mkFileBackend
// Creates a `FileBackend`. If `rewrite` is false, the file will be appended.
fn mkFileBackend*(path: str, rewrite: bool = false): (FileBackend, std::Err) {
//~~
	f, err := std::fopen(path, rewrite ? "w" : "a")
	return { f }, err
}

var (
	conf: Config = { }
)

//~~fn init
// Initialize `logs.um`. You need to call this before using any other functions.
fn init*() {
//~~
	conf = {
		level: LogLevelInf,
		tagLevel: {},
		backend: ConsoleBackend{}
	}
}

//~~fn setBackend
// Set the logging backend.
fn setBackend*(b: Backend) {
//~~
	conf.backend = b
}

//~~fn setGlobalLogLevel
// Set the global log level.
fn setGlobalLogLevel*(level: LogLevel) {
//~~
	conf.level = level
}

//~~fn setTagLogLevel
// Set the tag specific log level.
fn setTagLogLevel*(tag: str, level: LogLevel) {
//~~
	conf.tagLevel[tag] = level
}

fn getLogLevel(tag: str): LogLevel {
	if validkey(conf.tagLevel, tag) {
		return conf.tagLevel[tag]
	}

	return conf.level
}

fn logRaw(levelStr: str, tag: str, fmtStr: str, args: []any) {
	dt := std::localtime(std::time())

	conf.backend.write(sprintf("[%s]: %04d-%02d-%02dT%02d:%02d:%02d: %s: %s\n",
		levelStr, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, tag,
		fmt::vsfmt(fmtStr, args))
	)
}

//~~fn dbg
fn dbg*(tag: str, fmt: str, args: ..any) {
//~~
	if getLogLevel(tag) > LogLevelDbg {
		return
	}

	logRaw("DBG", tag, fmt, args)
}

//~~fn inf
fn inf*(tag: str, fmt: str, args: ..any) {
//~~
	if getLogLevel(tag) > LogLevelInf {
		return
	}

	logRaw("INF", tag, fmt, args)
}

//~~fn wrn
fn wrn*(tag: str, fmt: str, args: ..any) {
//~~
	if getLogLevel(tag) > LogLevelWrn {
		return
	}

	logRaw("WRN", tag, fmt, args)
}

//~~fn sus
fn sus*(tag: str, fmt: str, args: ..any) {
//~~
	if getLogLevel(tag) > LogLevelSus {
		return
	}

	logRaw("SUS", tag, fmt, args)
}

//~~fn err
fn err*(tag: str, fmt: str, args: ..any) {
//~~
	if getLogLevel(tag) > LogLevelErr {
		return
	}

	logRaw("ERR", tag, fmt, args)
}

fn main() {
	init()
	setTagLogLevel("debug.um", LogLevelDbg)

	const TAG = "logs.um"

	dbg("debug.um", "Debug log")
	dbg(TAG, "Debug log")
	inf(TAG, "Information log")
	inf(TAG, "Testing the formatting \\{} {}", 3.141)
	wrn(TAG, "Warning log")
	sus(TAG, "Suspicious log")
	err(TAG, "Error log")

	fb, e := mkFileBackend("log.txt", true)
	std::exitif(e)
	setBackend(fb)
	dbg("debug.um", "Debug log")
	dbg(TAG, "Debug log")
	inf(TAG, "Information log")
	wrn(TAG, "Warning log")
	sus(TAG, "Suspicious log")
	err(TAG, "Error log")
}