docs: add godoc and bilingual README

This commit is contained in:
2026-03-17 15:33:11 +03:00
parent 6112a707c7
commit badee2c598
10 changed files with 541 additions and 54 deletions

138
README.md
View File

@@ -1,2 +1,138 @@
# ScuroLogger (slog)
# slog
Small structured logger for Go with text and JSON output, multiple writers, and optional traceback metadata.
Russian version: [README_ru.md](README_ru.md)
## Features
- Fan out the same record to multiple destinations.
- Text and JSON writers.
- `stdout`, files, and arbitrary external `io.Writer` values.
- Optional timestamps for text output.
- Compact traceback metadata for text writers and full traceback slices for JSON.
- Explicit ownership rules for writer closing.
## Installation
```bash
go get git.nix13.pw/scuroneko/slog
```
## Quick start
```go
package main
import (
"log"
"git.nix13.pw/scuroneko/slog"
)
func main() {
logger := slog.CreateLogger().
Prefix("API").
Level(slog.DEBUG).
PrintTraceback(true).
JsonPretty(true)
text := logger.CreateTextStdoutWriter()
jsonFile, err := logger.CreateJsonFileWriter("logs/app.json")
if err != nil {
log.Fatal(err)
}
logger.AddWriters(text, jsonFile)
logger.Infoln("service started")
logger.Warnln("cache miss")
logger.Errorln("request failed")
logger.Debugln("debug details")
if err := logger.Close(); err != nil {
log.Fatal(err)
}
}
```
## Defaults
`CreateLogger()` starts with:
- `Prefix("LOG")`
- `Level(slog.FATAL)`
- `PrintTime(true)`
- `PrintTraceback(false)`
- `JsonPretty(false)`
Important: with the current level ordering, `Level(slog.FATAL)` allows `INFO`, `WARN`, `ERROR`, and `FATAL`, but not `DEBUG`. Use `Level(slog.DEBUG)` to enable every level.
## Writers and ownership
`Logger.Close()` only closes writers created by the logger itself:
- `CreateTextFileWriter(...)`
- `CreateJsonFileWriter(...)`
The following writers remain owned by the caller and are not closed by `Logger.Close()`:
- `CreateTextWriter(existingWriter)`
- `CreateJsonWriter(existingWriter)`
- `CreateTextStdoutWriter()`
- `CreateJsonStdoutWriter()`
This makes it safe to plug in `bytes.Buffer`, network writers, and other externally managed resources.
## Output formats
Text writers render records like:
```text
[API] [INFO] [main.go:main:27] [17.03.26 14:05:09] service started
```
The traceback field is included only when `PrintTraceback(true)` is enabled.
JSON writers emit objects with this shape:
```json
{
"time": "2026-03-17T14:05:09.123456789+03:00",
"level": "info",
"prefix": "API",
"message": "service started",
"traceback": [
{
"method": "main",
"filename": "main.go",
"line": 27,
"signature": "main.main",
"fullPath": "/path/to/main.go"
}
]
}
```
When `JsonPretty(true)` is enabled, JSON is indented.
## API summary
- `Info`, `Warn`, `Error`, `Debug`, and `Fatal` accept a list of values.
- `Infof`, `Warnf`, `Errorf`, `Debugf`, and `Fatalf` use `fmt.Sprintf`.
- The `*ln` methods preserve newline semantics, which is useful for `stdout`, Docker, and line-based collectors.
- `Fatal`, `Fatalf`, and `Fatalln` call `os.Exit(1)` after writing the message.
## Traceback behavior
- Text writers use the nearest user stack frame.
- JSON writers receive the full traceback slice.
- Internal `slog` frames and `runtime` frames are filtered out.
## Repository example
See [examples/main.go](examples/main.go).
## License
This project is licensed under GNU GPLv3. See [LICENSE](LICENSE).

138
README_ru.md Normal file
View File

@@ -0,0 +1,138 @@
# slog
Небольшой структурированный логгер для Go с текстовым и JSON-выводом, несколькими writer'ами и настраиваемыми traceback-метаданными.
English version: [README.md](README.md)
## Возможности
- Одновременная запись в несколько destinations.
- Текстовый и JSON-форматы.
- `stdout`, файлы и любые внешние `io.Writer`.
- Опциональные timestamp'ы для текстового вывода.
- Компактный traceback для текстовых writer'ов и полный traceback для JSON.
- Явные правила владения writer'ами при `Close()`.
## Установка
```bash
go get git.nix13.pw/scuroneko/slog
```
## Быстрый старт
```go
package main
import (
"log"
"git.nix13.pw/scuroneko/slog"
)
func main() {
logger := slog.CreateLogger().
Prefix("API").
Level(slog.DEBUG).
PrintTraceback(true).
JsonPretty(true)
text := logger.CreateTextStdoutWriter()
jsonFile, err := logger.CreateJsonFileWriter("logs/app.json")
if err != nil {
log.Fatal(err)
}
logger.AddWriters(text, jsonFile)
logger.Infoln("service started")
logger.Warnln("cache miss")
logger.Errorln("request failed")
logger.Debugln("debug details")
if err := logger.Close(); err != nil {
log.Fatal(err)
}
}
```
## Значения по умолчанию
`CreateLogger()` создает логгер со следующими настройками:
- `Prefix("LOG")`
- `Level(slog.FATAL)`
- `PrintTime(true)`
- `PrintTraceback(false)`
- `JsonPretty(false)`
Важно: в текущей модели уровней `Level(slog.FATAL)` пропускает `INFO`, `WARN`, `ERROR` и `FATAL`, но не `DEBUG`. Чтобы включить все сообщения, используйте `Level(slog.DEBUG)`.
## Writer'ы и владение
`Logger.Close()` закрывает только writer'ы, которые логгер создал сам:
- `CreateTextFileWriter(...)`
- `CreateJsonFileWriter(...)`
Внешние writer'ы не закрываются:
- `CreateTextWriter(existingWriter)`
- `CreateJsonWriter(existingWriter)`
- `CreateTextStdoutWriter()`
- `CreateJsonStdoutWriter()`
Это позволяет безопасно подключать `bytes.Buffer`, сетевые writer'ы и другие уже управляемые ресурсы.
## Форматы вывода
Текстовый writer формирует записи вида:
```text
[API] [INFO] [main.go:main:27] [17.03.26 14:05:09] service started
```
Поле traceback появляется только если включен `PrintTraceback(true)`.
JSON writer записывает объект со следующими полями:
```json
{
"time": "2026-03-17T14:05:09.123456789+03:00",
"level": "info",
"prefix": "API",
"message": "service started",
"traceback": [
{
"method": "main",
"filename": "main.go",
"line": 27,
"signature": "main.main",
"fullPath": "/path/to/main.go"
}
]
}
```
Если включен `JsonPretty(true)`, JSON выводится с отступами.
## API кратко
- `Info`, `Warn`, `Error`, `Debug`, `Fatal` принимают список значений.
- `Infof`, `Warnf`, `Errorf`, `Debugf`, `Fatalf` используют `fmt.Sprintf`.
- Методы `*ln` добавляют семантику перевода строки, что удобно для `stdout`, Docker и line-based collectors.
- `Fatal`, `Fatalf` и `Fatalln` вызывают `os.Exit(1)` после записи сообщения.
## Поведение traceback
- Текстовые writer'ы используют ближайший пользовательский stack frame.
- JSON writer'ы получают полный traceback.
- Внутренние frame'ы `slog` и `runtime` фильтруются из traceback.
## Пример из репозитория
См. [examples/main.go](examples/main.go).
## Лицензия
Проект распространяется под GNU GPLv3. См. [LICENSE](LICENSE).

42
doc.go Normal file
View File

@@ -0,0 +1,42 @@
// Package slog provides a small structured logger that can write the same
// record to text and JSON outputs.
//
// A Logger can fan out records to stdout, files, or any external io.Writer.
// Text writers can include timestamps and a compact traceback frame. JSON
// writers always emit a structured traceback slice.
//
// CreateLogger uses the following defaults:
// - prefix: "LOG"
// - level: FATAL, which enables info, warn, error, and fatal records
// - text timestamps: enabled
// - text traceback: disabled
// - pretty JSON: disabled
//
// Call Level(DEBUG) to enable all records, including debug messages.
//
// Basic usage:
//
// logger := slog.CreateLogger().
// Prefix("API").
// Level(slog.DEBUG).
// PrintTraceback(true).
// JsonPretty(true)
//
// text := logger.CreateTextStdoutWriter()
// jsonFile, err := logger.CreateJsonFileWriter("logs/app.json")
// if err != nil {
// panic(err)
// }
//
// logger.AddWriters(text, jsonFile)
// logger.Infoln("service started")
//
// if err := logger.Close(); err != nil {
// panic(err)
// }
//
// Writers created with CreateTextFileWriter or CreateJsonFileWriter are owned by
// the logger and are closed by Logger.Close. Writers created from existing
// io.Writer values through CreateTextWriter or CreateJsonWriter remain owned by
// the caller and are not closed by the logger.
package slog

View File

@@ -1,21 +1,53 @@
package main
import "git.nix13.pw/scuroneko/slog"
import (
"bytes"
"fmt"
"git.nix13.pw/scuroneko/slog"
)
func main() {
logger := slog.CreateLogger().JsonPretty(false)
defer logger.Close()
logger.AddWriter(logger.CreateTextStdoutWriter())
f, err := logger.CreateJsonFileWriter("main.log")
logger := slog.CreateLogger().
Prefix("EXAMPLE").
Level(slog.DEBUG).
JsonPretty(true)
textStdout := logger.CreateTextStdoutWriter()
jsonStdout := logger.CreateJsonStdoutWriter()
textFile, err := logger.CreateTextFileWriter("logs/text.log")
if err != nil {
panic(err)
}
logger.AddWriter(f)
logger.Infoln("Test")
logger.Warnln("Test")
logger.Errorln("Test")
logger.Debugln("Test") // No output
logger.Level(slog.DEBUG)
logger.Debugln("Test") // Now we have output
logger.Fatalln("Test") // Exit code 1
jsonFile, err := logger.CreateJsonFileWriter("logs/json.log")
if err != nil {
panic(err)
}
var externalBuffer bytes.Buffer
externalText := logger.CreateTextWriter(&externalBuffer)
externalJSON := logger.CreateJsonWriter(&externalBuffer)
logger.AddWriters(
textStdout,
jsonStdout,
textFile,
jsonFile,
externalText,
externalJSON,
)
logger.Infoln("service started")
logger.Warnln("cache miss")
logger.Errorln("request failed")
logger.Debugln("debug details")
if err := logger.Close(); err != nil {
panic(err)
}
fmt.Println("external buffer contents:")
fmt.Println(externalBuffer.String())
}

15
io.go
View File

@@ -5,55 +5,70 @@ import (
"os"
)
// Infof logs a formatted info message.
func (l *Logger) Infof(format string, args ...any) {
l.print(INFO, fmt.Sprintf(format, args...))
}
// Info logs an info message.
func (l *Logger) Info(m ...any) {
l.print(INFO, m...)
}
// Infoln logs an info message and appends a newline semantic for writers that need it.
func (l *Logger) Infoln(m ...any) {
l.println(INFO, m...)
}
// Warnf logs a formatted warning message.
func (l *Logger) Warnf(format string, args ...any) {
l.print(WARN, fmt.Sprintf(format, args...))
}
// Warn logs a warning message.
func (l *Logger) Warn(m ...any) {
l.print(WARN, m...)
}
// Warnln logs a warning message with newline semantic.
func (l *Logger) Warnln(m ...any) {
l.println(WARN, m...)
}
// Errorf logs a formatted error message.
func (l *Logger) Errorf(format string, args ...any) {
l.print(ERROR, fmt.Sprintf(format, args...))
}
// Error logs an error message.
func (l *Logger) Error(m ...any) {
l.print(ERROR, m...)
}
// Errorln logs an error message with newline semantic.
func (l *Logger) Errorln(m ...any) {
l.println(ERROR, m...)
}
// Fatalf logs a formatted fatal message and exits the process with code 1.
func (l *Logger) Fatalf(format string, args ...any) {
l.print(FATAL, fmt.Sprintf(format, args...))
os.Exit(1)
}
// Fatal logs a fatal message and exits the process with code 1.
func (l *Logger) Fatal(m ...any) {
l.print(FATAL, m...)
os.Exit(1)
}
// Fatalln logs a fatal message with newline semantic and exits the process with code 1.
func (l *Logger) Fatalln(m ...any) {
l.println(FATAL, m...)
os.Exit(1)
}
// Debugf logs a formatted debug message.
func (l *Logger) Debugf(format string, args ...any) {
l.print(DEBUG, fmt.Sprintf(format, args...))
}
// Debug logs a debug message.
func (l *Logger) Debug(m ...any) {
l.print(DEBUG, m...)
}
// Debugln logs a debug message with newline semantic.
func (l *Logger) Debugln(m ...any) {
l.println(DEBUG, m...)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/fatih/color"
)
// Logger routes log records to one or more configured writers.
type Logger struct {
prefix string
level LogLevel
@@ -20,16 +21,19 @@ type Logger struct {
jsonPretty bool
}
// LogLevel describes a logging severity.
type LogLevel struct {
n uint8
t string
c color.Attribute
}
// GetName returns the lowercase textual representation of the level.
func (l *LogLevel) GetName() string {
return l.t
}
// MethodTraceback describes a single stack frame attached to a log entry.
type MethodTraceback struct {
Method string `json:"method"`
Filename string `json:"filename"`
@@ -38,6 +42,7 @@ type MethodTraceback struct {
FullPath string `json:"fullPath"`
}
// Predefined log levels.
var (
INFO = LogLevel{n: 0, t: "info", c: color.FgWhite}
WARN = LogLevel{n: 1, t: "warn", c: color.FgHiYellow}
@@ -46,6 +51,7 @@ var (
DEBUG = LogLevel{n: 4, t: "debug", c: color.FgGreen}
)
// CreateLogger creates a logger with default settings.
func CreateLogger() *Logger {
return &Logger{
prefix: "LOG",
@@ -55,34 +61,42 @@ func CreateLogger() *Logger {
}
}
// Prefix sets the record prefix and returns the logger for chaining.
func (l *Logger) Prefix(prefix string) *Logger {
l.prefix = prefix
return l
}
// Level sets the maximum enabled level and returns the logger for chaining.
func (l *Logger) Level(level LogLevel) *Logger {
l.level = level
return l
}
// PrintTraceback enables traceback output for text writers.
func (l *Logger) PrintTraceback(b bool) *Logger {
l.printTraceback = b
return l
}
// PrintTime enables timestamps for text writers.
func (l *Logger) PrintTime(b bool) *Logger {
l.printTime = b
return l
}
// JsonPretty enables indented JSON output for JSON writers.
func (l *Logger) JsonPretty(b bool) *Logger {
l.jsonPretty = b
return l
}
// AddWriters appends multiple writers to the logger.
func (l *Logger) AddWriters(writers ...LoggerWriter) *Logger {
l.writers = append(l.writers, writers...)
return l
}
// AddWriter appends a single writer to the logger.
func (l *Logger) AddWriter(writer LoggerWriter) *Logger {
l.writers = append(l.writers, writer)
return l
}
// Close closes all owned writers and returns a joined error, if any.
func (l *Logger) Close() error {
var errs []error
for _, writer := range l.writers {
@@ -98,31 +112,40 @@ func (l *Logger) Close() error {
return errors.Join(errs...)
}
// CreateTextWriter wraps an external writer with the logger text settings.
func (l *Logger) CreateTextWriter(w io.Writer) *LoggerTextWriter {
return CreateTextWriter(w, l.printTraceback, l.printTime)
}
// CreateTextStdoutWriter creates a non-owning text writer for os.Stdout.
func (l *Logger) CreateTextStdoutWriter() *LoggerTextWriter {
return CreateTextStdoutWriter(l.printTraceback, l.printTime)
}
// CreateTextFileWriter creates an owning text writer for a file.
func (l *Logger) CreateTextFileWriter(filename string) (*LoggerTextWriter, error) {
return CreateTextFileWriter(filename, l.printTraceback, l.printTime)
}
// CreateJsonWriter wraps an external writer with the logger JSON settings.
func (l *Logger) CreateJsonWriter(w io.Writer) *LoggerJsonWriter {
return CreateJsonWriter(w, l.jsonPretty)
}
// CreateJsonStdoutWriter creates a non-owning JSON writer for os.Stdout.
func (l *Logger) CreateJsonStdoutWriter() *LoggerJsonWriter {
return CreateJsonStdoutWriter(l.jsonPretty)
}
// CreateJsonFileWriter creates an owning JSON writer for a file.
func (l *Logger) CreateJsonFileWriter(filename string) (*LoggerJsonWriter, error) {
return CreateJsonFileWriter(filename, l.jsonPretty)
}
// FormatTime converts time to the package text log timestamp format.
func FormatTime(t time.Time) string {
return fmt.Sprintf("%02d.%02d.%02d %02d:%02d:%02d", t.Day(), t.Month(), t.Year(), t.Hour(), t.Minute(), t.Second())
}
// FormatTraceback converts a traceback frame to a compact string.
func FormatTraceback(mt *MethodTraceback) string {
return fmt.Sprintf("%s:%s:%d", mt.Filename, mt.Method, mt.Line)
}
// FormatFullTraceback joins multiple traceback frames into one string.
func FormatFullTraceback(tracebacks []*MethodTraceback) string {
formatted := make([]string, 0)
for _, tb := range tracebacks {
@@ -130,6 +153,7 @@ func FormatFullTraceback(tracebacks []*MethodTraceback) string {
}
return strings.Join(formatted, "->")
}
// BuildString renders a text log record using the provided settings.
func BuildString(level LogLevel, prefix string, printTime, printTraceback bool, m ...any) string {
args := []string{
fmt.Sprintf("[%s]", prefix),

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"os"
"strings"
"testing"
)
@@ -153,6 +154,42 @@ func TestCreateJsonStdoutWriterDoesNotCloseStdout(t *testing.T) {
}
}
func TestGetFullTracebackSkipsRuntimeAndSlogFrames(t *testing.T) {
tracebacks := captureTracebackForTest()
if len(tracebacks) == 0 {
t.Fatal("expected at least one traceback frame")
}
first := tracebacks[0]
if first.Method != "captureTracebackForTest" {
t.Fatalf("first frame should be the nearest user frame, got %s", first.Method)
}
for _, tb := range tracebacks {
if strings.HasPrefix(tb.Signature, "runtime.") {
t.Fatalf("runtime frame should be filtered out, got %s", tb.Signature)
}
if _, ok := internalTracebackMethods[tb.Method]; ok {
t.Fatalf("internal slog frame should be filtered out, got %s", tb.Signature)
}
}
}
func TestGetTracebackReturnsNearestUserFrame(t *testing.T) {
tb := captureSingleTracebackForTest()
if tb == nil {
t.Fatal("expected traceback frame")
}
if tb.Method != "captureSingleTracebackForTest" {
t.Fatalf("expected nearest user frame, got %s", tb.Method)
}
if strings.HasPrefix(tb.Signature, "runtime.") {
t.Fatalf("runtime frame should be filtered out, got %s", tb.Signature)
}
if _, ok := internalTracebackMethods[tb.Method]; ok {
t.Fatalf("internal slog frame should be filtered out, got %s", tb.Signature)
}
}
func swapStdout(t *testing.T) *os.File {
t.Helper()
@@ -168,3 +205,11 @@ func swapStdout(t *testing.T) *os.File {
})
return stdoutFile
}
func captureTracebackForTest() []*MethodTraceback {
return getFullTraceback(0)
}
func captureSingleTracebackForTest() *MethodTraceback {
return getTraceback()
}

View File

@@ -2,59 +2,83 @@ package slog
import (
"runtime"
"sort"
"strings"
)
func getTraceback() *MethodTraceback {
caller, _, _, _ := runtime.Caller(5)
details := runtime.FuncForPC(caller)
signature := details.Name()
path, line := details.FileLine(caller)
splitPath := strings.Split(path, "/")
var internalTracebackMethods = map[string]struct{}{
"Infof": {},
"Info": {},
"Infoln": {},
"Warnf": {},
"Warn": {},
"Warnln": {},
"Errorf": {},
"Error": {},
"Errorln": {},
"Fatalf": {},
"Fatal": {},
"Fatalln": {},
"Debugf": {},
"Debug": {},
"Debugln": {},
"print": {},
"println": {},
"reportWriterError": {},
"getTraceback": {},
"getFullTraceback": {},
}
func getTraceback() *MethodTraceback {
tracebacks := getFullTraceback(0)
if len(tracebacks) == 0 {
return nil
}
return tracebacks[0]
}
func getFullTraceback(skip int) []*MethodTraceback {
pc := make([]uintptr, 15)
n := runtime.Callers(skip+2, pc)
list := make([]*MethodTraceback, 0)
frames := runtime.CallersFrames(pc[:n])
for {
frame, more := frames.Next()
tb := buildMethodTraceback(frame.PC)
if tb != nil && !isInternalTraceback(tb) {
list = append(list, tb)
}
if !more {
break
}
}
return list
}
func buildMethodTraceback(pc uintptr) *MethodTraceback {
details := runtime.FuncForPC(pc)
if details == nil {
return nil
}
signature := details.Name()
path, line := details.FileLine(pc)
splitPath := strings.Split(path, "/")
splitSignature := strings.Split(signature, ".")
method := splitSignature[len(splitSignature)-1]
tb := &MethodTraceback{
return &MethodTraceback{
Filename: splitPath[len(splitPath)-1],
FullPath: path,
Line: line,
Signature: signature,
Method: method,
}
return tb
}
func getFullTraceback(skip int) []*MethodTraceback {
pc := make([]uintptr, 15)
runtime.Callers(skip, pc)
list := make([]*MethodTraceback, 0)
frames := runtime.CallersFrames(pc)
for {
frame, more := frames.Next()
if !more {
break
}
details := runtime.FuncForPC(frame.PC)
signature := details.Name()
path, line := details.FileLine(frame.PC)
splitPath := strings.Split(path, "/")
splitSignature := strings.Split(signature, ".")
method := splitSignature[len(splitSignature)-1]
tb := &MethodTraceback{
Filename: splitPath[len(splitPath)-1],
FullPath: path,
Line: line,
Signature: signature,
Method: method,
}
list = append(list, tb)
func isInternalTraceback(tb *MethodTraceback) bool {
if strings.HasPrefix(tb.Signature, "runtime.") {
return true
}
sort.Slice(list, func(i, j int) bool {
return j < i
})
return list
_, ok := internalTracebackMethods[tb.Method]
return ok
}

View File

@@ -1,5 +1,6 @@
package slog
// Map applies f to each element of s and returns the resulting slice.
func Map[T, R any](s []T, f func(T) R) []R {
out := make([]R, len(s))
for i, el := range s {

View File

@@ -10,12 +10,14 @@ import (
"time"
)
// LoggerWriter is the common sink interface used by Logger.
type LoggerWriter interface {
Close() error
Write(p []byte) (n int, err error)
Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error
}
// LoggerTextWriter writes human-readable log records to an io.Writer.
type LoggerTextWriter struct {
LoggerWriter
writer io.Writer
@@ -24,14 +26,19 @@ type LoggerTextWriter struct {
printTime bool
}
// Write forwards raw bytes to the underlying writer.
func (w *LoggerTextWriter) Write(p []byte) (n int, err error) {
return w.writer.Write(p)
}
// Print formats the provided record as text and writes it to the underlying writer.
func (w *LoggerTextWriter) Print(level LogLevel, prefix string, _ []*MethodTraceback, messages ...any) error {
s := BuildString(level, prefix, w.printTime, w.printTraceback, messages...)
_, err := w.Write([]byte(s))
return err
}
// Close closes the owned writer, if any.
func (w *LoggerTextWriter) Close() error {
if w.closer == nil {
return nil
@@ -39,13 +46,15 @@ func (w *LoggerTextWriter) Close() error {
return w.closer.Close()
}
// LoggerJsonWriter write into writer JSON
// LoggerJsonWriter writes structured log records as JSON.
type LoggerJsonWriter struct {
LoggerWriter
writer io.Writer
closer io.Closer
pretty bool
}
// LoggerJsonMessage is the JSON payload emitted by LoggerJsonWriter.
type LoggerJsonMessage struct {
Time time.Time `json:"time"`
Level string `json:"level"`
@@ -54,9 +63,12 @@ type LoggerJsonMessage struct {
Traceback []*MethodTraceback `json:"traceback"`
}
// Write forwards raw bytes to the underlying writer.
func (w *LoggerJsonWriter) Write(data []byte) (int, error) {
return w.writer.Write(data)
}
// Print encodes the provided record as JSON and writes it to the underlying writer.
func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*MethodTraceback, messages ...any) error {
msg := Map(messages, func(el any) string {
return fmt.Sprintf("%v", el)
@@ -91,6 +103,8 @@ func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*Met
_, err = w.Write(data)
return err
}
// Close closes the owned writer, if any.
func (w *LoggerJsonWriter) Close() error {
if w.closer == nil {
return nil
@@ -98,12 +112,17 @@ func (w *LoggerJsonWriter) Close() error {
return w.closer.Close()
}
// CreateTextWriter wraps an external writer for text output without taking
// ownership of it.
func CreateTextWriter(w io.Writer, printTraceback, printTime bool) *LoggerTextWriter {
writer := &LoggerTextWriter{
writer: w, printTraceback: printTraceback, printTime: printTime,
}
return writer
}
// CreateTextFileWriter creates a text writer for path, creating parent
// directories as needed. The returned writer owns the opened file.
func CreateTextFileWriter(path string, printTraceback, printTime bool) (*LoggerTextWriter, error) {
err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
if err != nil {
@@ -117,23 +136,34 @@ func CreateTextFileWriter(path string, printTraceback, printTime bool) (*LoggerT
writer.closer = file
return writer, nil
}
// CreateTextStdoutWriter creates a text writer for os.Stdout without taking
// ownership of stdout.
func CreateTextStdoutWriter(printTraceback, printTime bool) *LoggerTextWriter {
writer := CreateTextWriter(os.Stdout, printTraceback, printTime)
writer.closer = nil
return writer
}
// CreateJsonWriter wraps an external writer for JSON output without taking
// ownership of it.
func CreateJsonWriter(w io.Writer, pretty bool) *LoggerJsonWriter {
writer := &LoggerJsonWriter{
writer: w, pretty: pretty,
}
return writer
}
// CreateJsonStdoutWriter creates a JSON writer for os.Stdout without taking
// ownership of stdout.
func CreateJsonStdoutWriter(pretty bool) *LoggerJsonWriter {
writer := CreateJsonWriter(os.Stdout, pretty)
writer.closer = nil
return writer
}
// CreateJsonFileWriter creates a JSON writer for path, creating parent
// directories as needed. The returned writer owns the opened file.
func CreateJsonFileWriter(path string, pretty bool) (*LoggerJsonWriter, error) {
err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
if err != nil {