multilog

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2024 License: MIT Imports: 3 Imported by: 0

README

multilog

multilog is a simple wrapper around charm/log that enables creating loggers with multiple outputs.

Installation

go get github.com/kociumba/multilog

Basic usage

multilog works exatly the same as charm/log only difference being the returned logger can write to multiple io.Writer interfaces simultaneously.

package main

import (
    "os"

    "github.com/kociumba/multilog"
)

func main() {
    // Create a log file
    logFile, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer logFile.Close()

    // Create a new multilog logger that writes to stdout and the log file
    log := multilog.NewMulti(os.Stdout, logFile)

    log.Info("logging info into stdout and a file 😎")
}

[!IMPORTANT] Due to the limitations of the io.Writer interface, which only returns a single error, multilog can only surface one error at a time when multiple writes fail. This means if multiple writers encounter errors, only the first error will be returned through charm/log's error handling.

Using with options

Just like in charm/log you can create a logger with options like this:

package main

import (
    "os"
    "time"

    "github.com/charmbracelet/log"
    "github.com/kociumba/multilog"
)

func main() {
    // Create a log file
    logFile, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer logFile.Close()

    // Create a new multilog logger with custom options
    log := multilog.NewMultiWithOptions(log.Options{
        ReportCaller:      true,
        ReportTimestamp:   true,
        TimeFormat:        time.RFC3339,
        Prefix:            "multilogging :3",
    }, os.Stdout, logFile)

    log.Info("logging info into stdout and a file 😎")
}

[!NOTE] Due to Go's requirement that variadic arguments must be the last parameter, the parameter order differs from charm/log: options, writers... instead of writer, options.

The returned loggers are standard charm/log log.Logger types so options can also be changed after creatation like this:

log.Info("the calling function will not be reported")
log.SetReportCaller(true)
log.Info("the calling function will be reported")

The multiwriter

multilog simply creates new log.Logger instances using the internal multilog/multiwriter package.

If you want to use this multiwriter by itself for other purposes without the charm/log wrapper, you can use the NewMultiWriter function from the multilog/multiwriter package.

package main

import (
    "bytes"
    "fmt"
    "net"
    "os"

    "github.com/kociumba/multilog/multiwriter"
)

func main() {
    // Create a buffer, a file writer, and a TCP writer
    buf := &bytes.Buffer{}
    logFile, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer logFile.Close()

    // Connect to a remote TCP server
    tcpConn, err := net.Dial("tcp", "remote-dashboard.example.com:12345")
    if err != nil {
        panic(err)
    }
    defer tcpConn.Close()

    // Create a new MultiWriter instance with the local and remote writers
    multi := multiwriter.NewMultiWriter(os.Stdout, buf, logFile, tcpConn)

    // Write some data to the MultiWriter
    n, err := multi.Write([]byte("Writing to multiple writers including a remote TCP server 😎"))
    if err != nil {
        fmt.Println("Error:", err)
    }
    fmt.Println("Wrote", n, "bytes")
}

You can also provide an ErrorHandler callback to be called for each write error that occurs.

package main

import (
    "bytes"
    "fmt"
    "net"
    "os"

    "github.com/kociumba/multilog/multiwriter"
)

func main() {
    // Create a buffer, a file writer, and a TCP writer
    buf := &bytes.Buffer{}
    logFile, err := os.Create("log.txt")
    if err != nil {
        panic(err)
    }
    defer logFile.Close()

    // Connect to a remote TCP server
    tcpConn, err := net.Dial("tcp", "remote-dashboard.example.com:12345")
    if err != nil {
        panic(err)
    }
    defer tcpConn.Close()

    // Create a new MultiWriter instance with an error handler
    multi := multiwriter.NewMultiWriter(os.Stdout, buf, logFile, tcpConn, &failingWriter{})
    multi.ErrorHandler = func(we multiwriter.WriteError) {

        // You can check for error types in the ErrorHandler callback
        if we.Err == io.ErrShortWrite {
            fmt.Println("short write detected")
        }
        fmt.Printf("Write failed for writer %T: %v\n", we.Writer, we.Err)
    }

    // Write some data to the MultiWriter
    n, err := multi.Write([]byte("Writing to multiple writers with error handling 😎"))
    if err != nil {
        fmt.Println("Error:", err)
    }
    fmt.Println("Wrote", n, "bytes")
}

type failingWriter struct{}

func (fw *failingWriter) Write(p []byte) (int, error) {
    return 0, fmt.Errorf("this writer always fails")
}

[!IMPORTANT] Keep in mind all of the writers that are in the multiwriter will always be called, even if there is an error in one or more. If you want to terminate the multilogger after an error you will need to handle that yourself.

FAQ

Q: Why wrap charm/log instead of creating a custom logger?

A: Simple anwser is that i really like the simplicity and design of charm/log and this is a very niche use case that propably isn't worth making a whole new logging library for. TLDR - if not a wrapper around charm/log then a wrapper around std/log

Q: What about that error handling from earlier ?

A: The only half deacent idea I had for fixing this issue is to create an error channel that you as the user could listen to for errors during logging and handle them accordingly. But this brings a whole lot of complexity to this otherwise tiny wrapper so it may or may not happen at some pont.

This is now resolved when using the multilog/multiwriter package. You can set a custom callback function that will execute for each write error that occurs.

Q: Where in gods name would I need this ?

A: This isn't usefull for 99% of projects out there, but these are just some ideas I had while coding this:

  • Windows services that need to log to both a file and stdout during development
  • Applications that need to log locally while also sending logs to a remote system (e.g., via a TCP Writer). This includes for example servers where you could send the logs to a remote dashboard and to a local file at the same time.

Documentation

Overview

multilog is a simple wrapper around github.com/charmbracelet/log that enables creating loggers with multiple outputs.

for example:

logFile, err := os.Create("log.txt")

log := multilog.NewMulti(os.Stdout, logFile)
log.Info("logging info into stdout and a file!")

The one big issue stemming from this package using the io.Writer interface is that multiple errors can not be handled as io.Writer returns a single error.

This means only the first error is returned when charmbracelet/log tries to write the log. Making it possibly hard to debug as charmbracelet/log does not provide very good error info out of the io.Writer.

I did consider returning an error channel with the log.Logger but that would require the user to deal with handling wirter errors on their own and asynchronously, which isn't ideal as I want to keep this as simple as the original charmbracelet/log.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewMulti

func NewMulti(writers ...io.Writer) *log.Logger

NewMulti returns a new logger using the default options.

func NewMultiWithOptions

func NewMultiWithOptions(o log.Options, writers ...io.Writer) *log.Logger

NewMultiWithOptions returns a new logger using the provided options.

The writers need to be passed in as the last parameter because of go's limitations around variadic arguments.

Types

This section is empty.

Directories

Path Synopsis
A small implementation of the io.Writer interface that allows simultaneous writing to multiple io.Writers
A small implementation of the io.Writer interface that allows simultaneous writing to multiple io.Writers

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL