# phuslu/log: a structured JSON logger for Go with a writer for every destination

> phuslu/log is a dependency-free structured logging library for Go that emits JSON, and the interesting part is the set of writers around it: a console writer that colourises, a rotating file writer, an async writer, a syslog writer, journal and eventlog writers, and a bridge that turns a logger into a standard log instance or a slog one. Its pitch is speed, and the API shape is what makes that claim plausible.

**phuslu/log** — Fastest structured logging

- Repository: https://github.com/phuslu/log
- Stars: 878 · Forks: 64
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/phuslu-log

## A logger struct, not a level interface

The Logger type is a struct rather than an interface, and that is the first thing to understand about the design. It holds the Level, the Caller setting, the TimeField, the TimeFormat, the TimeLocation and the Writer. The Caller field has a useful convention, since a negative value adds the full path to the file and line of the caller key, a zero value adds nothing, and a positive value adds the file and line. The TimeField names the time field in the output, defaulting to time, and the TimeFormat is RFC3339 with milliseconds when empty, with the documentation strongly recommending leaving it empty for performance. The TimeLocation applies to that format and defaults to time.Local. The Writer defaults to a wrapped os.Stderr writer when it is empty. A package-level DefaultLogger is provided as a global, initialised at DebugLevel with a caller setting of zero and the standard error writer.

## The writer is the extension point, and there are eight of them

The list of writers is the heart of the library. IOWriter is a plain io.Writer wrapper. ConsoleWriter parses the JSON and writes it in a colourised, human friendly format. FileWriter is rotating and effective. AsyncWriter is asynchronous and performant. MultiLevelWriter dispatches on multiple levels, so you can send debug to one place and errors to another. SyslogWriter is a memory efficient syslog writer, JournalWriter handles Linux journal logging and EventlogWriter handles Windows eventlog logging. The two native system log writers are what make this more than a JSON printer, because a systemd deployment wants its output in the journal with proper metadata rather than as text on standard error. The module has no dependencies at all beyond the standard library, and the go.mod is three lines with a Go 1.22 requirement, so the dependency-free claim is literal.

## The logging call chain is a builder, not a format string

The usage example shows the calling convention, and it is the shape most Go logging libraries have converged on. You call Info with no arguments, which returns a builder, and then chain typed field setters before the message:

```go
package main

import (
	"github.com/phuslu/log"
)

func main() {
	log.Info().Str("foo", "bar").Int("number", 42).Msg("hi, phuslog")
	log.Info().Msgf("foo=%s number=%d error=%+v", "bar", 42, "an error")
}
```

The fields are typed, so the JSON output carries the number as a number rather than as a quoted string, and the output in the example shows the shape with the time, the level, the fields and the message. The second line shows the alternative form, where a single formatted message carries everything, which is what you would use when the content is already a string. By default the library writes to os.Stderr, which is noted directly after the example, and the level set on the logger is what filters the chain.

## Standard library interoperability, in both directions

The interoperability section is what makes the library usable in an existing codebase rather than only in a new one. Logger.Std transforms the logger into standard log instances, and Logger.Slog transforms it into slog instances. There is also SlogNewJSONHandler, which is a drop-in replacement for the slog.NewJSONHandler. So a project that already has slog logging can swap the handler and keep the rest of the code, and a project that has code calling the standard log package can be given a logger that satisfies it. The otel directory in the repository root is the OpenTelemetry side of the same idea, and the repository has a separate README_zh.md, so the documentation is bilingual. The repository topics list slog and structured-logging alongside the usual logging terms, which reflects where the project sits relative to the standard library's own logging package.

## Utility functions that exist because the standard library is slow

The utility list is unusual for a logging library and it tells you what the performance work involved. Goid returns the goroutine id that matches the stack trace, which is the thing you want in a log line when a request is handled by an unknown goroutine. Now is a fast time.Now implementation without monotonic seconds, which is a deliberate trade against the monotonic clock reading for the sake of the timestamp format. NewXID creates a tracing id. Fastrandn returns a fast pseudorandom uint32 in the range zero to n, which is how the library avoids the global lock in the standard random source on a hot path. IsTerminal is an isatty check for Go. Printf is printf-style logging for the cases where a builder is overkill. EnableTimeCache caches timestamps for loggers on a given interval, which is the single biggest win in a logger that stamps every line with a time, and it is the kind of feature that only makes sense in a library whose selling point is throughput.

## FileWriter rotates on size, not on the date in the name

The FileWriter has the usual knobs, including the filename, the file mode, the maximum size in bytes before rotation, the maximum number of backups to retain, the time format used in the filename, and switches for local time, the hostname and the process id, plus a header function and a cleaner function for the backups. The highlights underneath are the part to read, because they are the sharp edges. FileWriter uses a symlink to point to the current log file with a timestamp, instead of renaming the file for rotation, and on Windows that may require administrator privileges. The Rotate method does not rotate on broad time formats such as daily or monthly until the file reaches its MaxSize, which surprises people who expect a daily log file. And FileWriter combined with AsyncWriter maximises performance and throughput on Linux, which is the combination the author points at if you are writing that much log output. The FileMode default is 0644, the time format default is the 2006-01-02T15-04-05 shape, and the backup retention default is to keep all old files.

## ConsoleWriter is for humans, and the documentation says so

The ConsoleWriter carries a warning in its own documentation comment, which is worth repeating because it is the kind of thing that gets missed. It parses the JSON input and writes it in a colourised, human friendly format, and it is important not to use it on the critical path of a high concurrency and low latency application. The reason is structural rather than incidental, since a console writer has to parse the JSON it is given, colourise the fields and format the line, whereas an IOWriter just writes bytes. The default output format is given as a template with the time, the level, the goroutine id and the caller followed by the message and then the key and value pairs. The type has three switches, ColorOutput for colourised output, QuoteString for quoting string values, and EndWithMessage for putting the message at the end of the line instead of the start. A custom Formatter function can be set, and if it is, those three switches are ignored, which is the escape hatch for a house style.

## Conclusion

phuslu/log suits a Go service that wants JSON logs on the standard library's own terms, that has to write to a journal or to the Windows event log, or that needs to hand a logger to a library that expects the standard log interface. It does not suit a team that wants a structured logging interface with pluggable levels the way slog has one, since the interface here is the writer rather than the level, and the console writer is explicitly not for a latency critical path. Before you adopt it, read the FileWriter notes, because rotation is driven by size rather than by the timestamp format and the symlink approach needs administrator rights on Windows.

## FAQ

### How do I log with phuslu/log?

Call the level function with no arguments to get a builder, chain typed field setters, and finish with Msg or Msgf. For example log.Info().Str("foo", "bar").Int("number", 42).Msg("hi, phuslog"), or use Msgf alone when the content is already a formatted string. By default the library writes to os.Stderr.

### Which output destinations does phuslu/log support?

Eight writers: IOWriter as a plain io.Writer wrapper, ConsoleWriter for colourised human output, FileWriter for rotating files, AsyncWriter for asynchronous writing, MultiLevelWriter for dispatching by level, SyslogWriter, JournalWriter for the Linux journal and EventlogWriter for the Windows eventlog.

### Can phuslu/log work with the standard library's log and slog packages?

Yes. Logger.Std transforms the logger into standard log instances and Logger.Slog transforms it into slog instances, and SlogNewJSONHandler is a drop-in replacement for slog.NewJSONHandler. The repository also carries an otel directory for the OpenTelemetry side.

### When does FileWriter rotate a log file?

Not on the date. The Rotate method does not rotate on broad time formats such as daily or monthly until the file reaches its MaxSize, and rotation uses a symlink pointing at the current timestamped file rather than renaming, which on Windows may need administrator privileges. Combining FileWriter with AsyncWriter is the configuration the documentation points at for maximum throughput on Linux.

## Sources

- [Issues](https://github.com/phuslu/log/issues)
- [License: MIT](https://github.com/phuslu/log/blob/master/LICENSE)
- [phuslu/log on GitHub](https://github.com/phuslu/log)
- [README](https://github.com/phuslu/log/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/phuslu-log
