{"id":24514928,"url":"https://github.com/kociumba/multilog","last_synced_at":"2025-11-03T18:59:46.124Z","repository":{"id":262549916,"uuid":"887511771","full_name":"kociumba/multilog","owner":"kociumba","description":"multilog is a simple wrapper around https://github.com/charmbracelet/log that enables creating loggers with multiple outputs.","archived":false,"fork":false,"pushed_at":"2024-11-13T23:17:28.000Z","size":12,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-22T01:15:01.316Z","etag":null,"topics":["go","golang","logging","simple","utility","wrapper"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/kociumba/multilog","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kociumba.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-12T21:11:40.000Z","updated_at":"2024-11-13T23:16:48.000Z","dependencies_parsed_at":"2024-11-13T03:20:20.095Z","dependency_job_id":"263dfc3e-53aa-4bfe-94cd-a7a78ab62479","html_url":"https://github.com/kociumba/multilog","commit_stats":null,"previous_names":["kociumba/multilog"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kociumba%2Fmultilog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kociumba%2Fmultilog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kociumba%2Fmultilog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kociumba%2Fmultilog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kociumba","download_url":"https://codeload.github.com/kociumba/multilog/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243716245,"owners_count":20336111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","logging","simple","utility","wrapper"],"created_at":"2025-01-22T01:15:15.340Z","updated_at":"2025-11-03T18:59:46.098Z","avatar_url":"https://github.com/kociumba.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# multilog\n\nmultilog is a simple wrapper around [charm/log](https://github.com/charmbracelet/log) that enables creating loggers with multiple outputs.\n\n\n## Installation\n\n```bash\ngo get github.com/kociumba/multilog\n```\n    \n## Basic usage\n\nmultilog works exatly the same as [charm/log](https://github.com/charmbracelet/log) only difference being the returned logger can write to multiple `io.Writer` interfaces simultaneously.\n\n```go\npackage main\n\nimport (\n    \"os\"\n\n    \"github.com/kociumba/multilog\"\n)\n\nfunc main() {\n    // Create a log file\n    logFile, err := os.Create(\"log.txt\")\n    if err != nil {\n        panic(err)\n    }\n    defer logFile.Close()\n\n    // Create a new multilog logger that writes to stdout and the log file\n    log := multilog.NewMulti(os.Stdout, logFile)\n\n    log.Info(\"logging info into stdout and a file 😎\")\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e 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](https://github.com/charmbracelet/log) error handling.\n\n\n## Using with options\n\nJust like in [charm/log](https://github.com/charmbracelet/log) you can create a logger with options like this:\n\n```go\npackage main\n\nimport (\n    \"os\"\n    \"time\"\n\n    \"github.com/charmbracelet/log\"\n    \"github.com/kociumba/multilog\"\n)\n\nfunc main() {\n    // Create a log file\n    logFile, err := os.Create(\"log.txt\")\n    if err != nil {\n        panic(err)\n    }\n    defer logFile.Close()\n\n    // Create a new multilog logger with custom options\n    log := multilog.NewMultiWithOptions(log.Options{\n        ReportCaller:      true,\n        ReportTimestamp:   true,\n        TimeFormat:        time.RFC3339,\n        Prefix:            \"multilogging :3\",\n    }, os.Stdout, logFile)\n\n    log.Info(\"logging info into stdout and a file 😎\")\n}\n```\n\n\u003e [!NOTE]\n\u003e Due to Go's requirement that variadic arguments must be the last parameter, the parameter order differs from [charm/log](https://github.com/charmbracelet/log): `options, writers...` instead of `writer, options`.\n\nThe returned loggers are standard [charm/log](https://github.com/charmbracelet/log) `log.Logger` types so options can also be changed after creatation like this:\n\n```go\nlog.Info(\"the calling function will not be reported\")\nlog.SetReportCaller(true)\nlog.Info(\"the calling function will be reported\")\n```\n\n\n## The multiwriter\n\nmultilog simply creates new `log.Logger` instances using the internal [multilog/multiwriter](https://github.com/kociumba/multilog/tree/main/multiwriter) package.\n\nIf you want to use this multiwriter by itself for other purposes without the [charm/log](https://github.com/charmbracelet/log) wrapper, you can use the `NewMultiWriter` function from the [multilog/multiwriter](https://github.com/kociumba/multilog/tree/main/multiwriter) package.\n\n```go\npackage main\n\nimport (\n    \"bytes\"\n    \"fmt\"\n    \"net\"\n    \"os\"\n\n    \"github.com/kociumba/multilog/multiwriter\"\n)\n\nfunc main() {\n    // Create a buffer, a file writer, and a TCP writer\n    buf := \u0026bytes.Buffer{}\n    logFile, err := os.Create(\"log.txt\")\n    if err != nil {\n        panic(err)\n    }\n    defer logFile.Close()\n\n    // Connect to a remote TCP server\n    tcpConn, err := net.Dial(\"tcp\", \"remote-dashboard.example.com:12345\")\n    if err != nil {\n        panic(err)\n    }\n    defer tcpConn.Close()\n\n    // Create a new MultiWriter instance with the local and remote writers\n    multi := multiwriter.NewMultiWriter(os.Stdout, buf, logFile, tcpConn)\n\n    // Write some data to the MultiWriter\n    n, err := multi.Write([]byte(\"Writing to multiple writers including a remote TCP server 😎\"))\n    if err != nil {\n        fmt.Println(\"Error:\", err)\n    }\n    fmt.Println(\"Wrote\", n, \"bytes\")\n}\n```\n\nYou can also provide an `ErrorHandler` callback to be called for each write error that occurs.\n\n```go\npackage main\n\nimport (\n    \"bytes\"\n    \"fmt\"\n    \"net\"\n    \"os\"\n\n    \"github.com/kociumba/multilog/multiwriter\"\n)\n\nfunc main() {\n    // Create a buffer, a file writer, and a TCP writer\n    buf := \u0026bytes.Buffer{}\n    logFile, err := os.Create(\"log.txt\")\n    if err != nil {\n        panic(err)\n    }\n    defer logFile.Close()\n\n    // Connect to a remote TCP server\n    tcpConn, err := net.Dial(\"tcp\", \"remote-dashboard.example.com:12345\")\n    if err != nil {\n        panic(err)\n    }\n    defer tcpConn.Close()\n\n    // Create a new MultiWriter instance with an error handler\n    multi := multiwriter.NewMultiWriter(os.Stdout, buf, logFile, tcpConn, \u0026failingWriter{})\n    multi.ErrorHandler = func(we multiwriter.WriteError) {\n\n        // You can check for error types in the ErrorHandler callback\n        if we.Err == io.ErrShortWrite {\n            fmt.Println(\"short write detected\")\n        }\n        fmt.Printf(\"Write failed for writer %T: %v\\n\", we.Writer, we.Err)\n    }\n\n    // Write some data to the MultiWriter\n    n, err := multi.Write([]byte(\"Writing to multiple writers with error handling 😎\"))\n    if err != nil {\n        fmt.Println(\"Error:\", err)\n    }\n    fmt.Println(\"Wrote\", n, \"bytes\")\n}\n\ntype failingWriter struct{}\n\nfunc (fw *failingWriter) Write(p []byte) (int, error) {\n    return 0, fmt.Errorf(\"this writer always fails\")\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e 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.\n\n## FAQ\n\n#### Q: Why wrap [charm/log](https://github.com/charmbracelet/log) instead of creating a custom logger?\n\nA: Simple anwser is that i really like the simplicity and design of [charm/log](https://github.com/charmbracelet/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](https://github.com/charmbracelet/log) then a wrapper around std/log\n\n#### Q: What about that error handling from earlier ?\n\nA: ~~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.~~\n\nThis is now resolved when using the [multilog/multiwriter](https://github.com/kociumba/multilog/tree/main/multiwriter) package. You can set a custom callback function that will execute for each write error that occurs.\n\n#### Q: Where in gods name would I need this ?\n\nA: This isn't usefull for 99% of projects out there, but these are just some ideas I had while coding this:\n\n- Windows services that need to log to both a file and stdout during development\n- 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.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkociumba%2Fmultilog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkociumba%2Fmultilog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkociumba%2Fmultilog/lists"}