English | 中文
A lightweight elevate I/O with Asynchronous io.Writer
Boost performance and efficiency effortlessly for logging, streaming, and more.
Log Asynchronous Writer is a lightweight log asynchronous writer designed for high-concurrency scenarios, such as HTTP servers and gRPC servers.
LAW utilizes a double buffer design, allowing it to write data to the deque asynchronously and flush the buffer to the io.Writer when it is full. This design significantly improves the writer's performance and reduces pressure on the io.Writer.
With just two APIs, Write and Stop, LAW offers simplicity and ease of use. The Write API is used to write log data to the buffer, while the Stop API is used to stop the writer.
LAW can be used with any implementation of the io.Writer interface that requires asynchronous writing, such as zap, logrus, klog, zerolog, and more.
- Simple and user-friendly
- No external dependencies required
- High performance with minimal memory usage
- Optimized for efficient garbage collection
- Supports customizable action callback functions
go get github.com/shengyanli1982/lawLAW is designed to be straightforward and user-friendly. To begin, create a writer and use the Write method to write log data to the buffer. When you're ready to stop the writer, simply call the Stop method.
Additionally, LAW offers a Config struct that enables customization of the writer's behavior. You can utilize the WithXXX methods to configure various features. For more information, refer to the Features section.
package main
import (
"os"
"time"
"strconv"
law "github.com/shengyanli1982/law"
)
func main() {
// 创建一个新的配置
// Create a new configuration
conf := NewConfig()
// 使用 os.Stdout 和配置创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout and the configuration
w := NewWriteAsyncer(os.Stdout, conf)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer w.Stop()
// 循环 10 次,每次都将一个数字写入 WriteAsyncer
// Loop 10 times, each time write a number to WriteAsyncer
for i := 0; i < 10; i++ {
_, _ = w.Write([]byte(strconv.Itoa(i))) // 将当前的数字写入 WriteAsyncer
}
// 等待 1 秒,以便我们可以看到 WriteAsyncer 的输出
// Wait for 1 second so we can see the output of WriteAsyncer
time.Sleep(time.Second)
}LAW also has some interesting features. It is designed to be easily extensible, allowing you to write your own asynchronous writer effortlessly.
LAW supports action callback functions. You can specify a callback function when creating a writer, and this function will be called when the writer performs certain actions.
// Callback 是一个接口,定义了队列操作和写操作的回调函数。
// Callback is an interface that defines callback functions for queue operations and write operations.
type Callback interface {
// OnWriteFailed 是一个方法,当写操作失败时会被调用。
// 它接受两个参数:一个字节切片(表示写入内容)和一个错误(表示失败的原因)。
// OnWriteFailed is a method that is called when a write operation fails.
// It takes two parameters: a byte slice (indicating the content to be written) and an error (indicating the reason for the failure).
OnWriteFailed(content []byte, reason error)
}Tip
Callback functions are optional. If you don't need them, you can pass nil when creating a writer, and the callback function will not be called.
You can use the WithCallback method to set the callback function.
package main
import (
"os"
"time"
"strconv"
law "github.com/shengyanli1982/law"
)
// callback 是一个实现了 law.Callback 接口的结构体
// callback is a struct that implements the law.Callback interface
type callback struct{}
// OnWriteFailed 是当数据写入失败时的回调函数
// OnWriteFailed is the callback function when data writing fails
func (c *callback) OnWriteFailed(b []byte, err error) {
fmt.Printf("write failed msg: %s, err: %v\n", string(b), err) // 输出写入失败的消息和错误
}
func main() {
// 创建一个新的配置,并设置回调函数
// Create a new configuration and set the callback function
conf := NewConfig().WithCallback(&callback{})
// 使用 os.Stdout 和配置创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout and the configuration
w := NewWriteAsyncer(os.Stdout, conf)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer w.Stop()
// 循环 10 次,每次都将一个数字写入 WriteAsyncer
// Loop 10 times, each time write a number to WriteAsyncer
for i := 0; i < 10; i++ {
_, _ = w.Write([]byte(strconv.Itoa(i))) // 将当前的数字写入 WriteAsyncer
}
// 等待 1 秒,以便我们可以看到 WriteAsyncer 的输出
// Wait for 1 second so we can see the output of WriteAsyncer
time.Sleep(time.Second)
}LAW utilizes a double buffer to write log data, allowing you to specify the buffer's capacity when creating a writer.
Tip
- The default capacity of the
dequeis unlimited, meaning it can hold an unlimited amount of log data. - The default capacity of the
bufferIois2k, meaning it can hold up to2klog data. If the buffer becomes full,LAWwill automatically flush the buffer to theio.Writer.2kis a recommended choice, but you can customize it.
You can use the WithBufferSize method to adjust the size of the bufferIo.
package main
import (
"os"
"time"
"strconv"
law "github.com/shengyanli1982/law"
)
func main() {
// 创建一个新的配置,并设置缓冲区大小为 1024
// Create a new configuration and set the buffer size to 1024
conf := NewConfig().WithBufferSize(1024)
// 使用 os.Stdout 和配置创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout and the configuration
w := NewWriteAsyncer(os.Stdout, conf)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer w.Stop()
// 循环 10 次,每次都将一个数字写入 WriteAsyncer
// Loop 10 times, each time write a number to WriteAsyncer
for i := 0; i < 10; i++ {
_, _ = w.Write([]byte(strconv.Itoa(i))) // 将当前的数字写入 WriteAsyncer
}
// 等待 1 秒,以便我们可以看到 WriteAsyncer 的输出
// Wait for 1 second so we can see the output of WriteAsyncer
time.Sleep(time.Second)
}LAW allows you to configure the heartbeat interval and idle timeout for the writer. The heartbeat interval determines how often the writer checks for new data, and the idle timeout determines how long the writer waits before flushing the buffer when there's no new data.
Tip
- The default heartbeat interval is
500ms, meaning the writer checks for new data every 500 milliseconds. - The default idle timeout is
5s, meaning the writer waits for 5 seconds of inactivity before flushing the buffer.
You can use the WithHeartbeatInterval and WithIdleTimeout methods to customize these values.
package main
import (
"os"
"time"
"strconv"
law "github.com/shengyanli1982/law"
)
func main() {
// 创建一个新的配置,并设置心跳间隔和闲置超时
// Create a new configuration and set the heartbeat interval and idle timeout
conf := NewConfig().
WithHeartbeatInterval(200 * time.Millisecond).
WithIdleTimeout(3 * time.Second)
// 使用 os.Stdout 和配置创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout and the configuration
w := NewWriteAsyncer(os.Stdout, conf)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer w.Stop()
// 循环 10 次,每次都将一个数字写入 WriteAsyncer
// Loop 10 times, each time write a number to WriteAsyncer
for i := 0; i < 10; i++ {
_, _ = w.Write([]byte(strconv.Itoa(i))) // 将当前的数字写入 WriteAsyncer
}
// 等待 1 秒,以便我们可以看到 WriteAsyncer 的输出
// Wait for 1 second so we can see the output of WriteAsyncer
time.Sleep(time.Second)
}LAW provides the flexibility to customize the queue used for storing log data. You have the option to implement your own queue and pass it to the writer during initialization.
Tip
By default, LAW uses an MPSC queue built on mutex/cond + linked list + sync.Pool.
You can use the WithQueue method to set a custom queue.
Queue Interface
// Queue 是一个接口,定义了队列的基本操作:Push 和 Pop。
// Queue is an interface that defines the basic operations of a queue: Push and Pop.
type Queue interface {
// Push 方法用于将值添加到队列中。
// The Push method is used to add a value to the queue.
Push(value *bytes.Buffer)
// Pop 方法用于从队列中取出一个值。
// The Pop method is used to take a value out of the queue.
Pop() *bytes.Buffer
}Here are some examples of how to use LAW. For more examples, you can also refer to the examples directory.
LAW can be used to write log data to zap asynchronously.
Code
package main
import (
"os"
"strconv"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
law "github.com/shengyanli1982/law"
)
func main() {
// 使用 os.Stdout 创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout
aw := law.NewWriteAsyncer(os.Stdout, nil)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer aw.Stop()
// 创建一个 zapcore.EncoderConfig 实例,用于配置 zap 的编码器
// Create a zapcore.EncoderConfig instance to configure the encoder of zap
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg", // 消息的键名
LevelKey: "level", // 级别的键名
NameKey: "logger", // 记录器名的键名
EncodeLevel: zapcore.LowercaseLevelEncoder, // 级别的编码器
EncodeTime: zapcore.ISO8601TimeEncoder, // 时间的编码器
EncodeDuration: zapcore.StringDurationEncoder, // 持续时间的编码器
}
// 使用 WriteAsyncer 创建一个 zapcore.WriteSyncer 实例
// Create a zapcore.WriteSyncer instance using WriteAsyncer
zapAsyncWriter := zapcore.AddSync(aw)
// 使用编码器配置和 WriteSyncer 创建一个 zapcore.Core 实例
// Create a zapcore.Core instance using the encoder configuration and WriteSyncer
zapCore := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), zapAsyncWriter, zapcore.DebugLevel)
// 使用 Core 创建一个 zap.Logger 实例
// Create a zap.Logger instance using Core
zapLogger := zap.New(zapCore)
// 循环 10 次,每次都使用 zapLogger 输出一个数字
// Loop 10 times, each time output a number using zapLogger
for i := 0; i < 10; i++ {
zapLogger.Info(strconv.Itoa(i)) // 输出当前的数字
}
// 等待 3 秒,以便我们可以看到 zapLogger 的输出
// Wait for 3 seconds so we can see the output of zapLogger
time.Sleep(3 * time.Second)
}Results
$ go run demo.go
{"level":"info","msg":"0"}
{"level":"info","msg":"1"}
{"level":"info","msg":"2"}
{"level":"info","msg":"3"}
{"level":"info","msg":"4"}
{"level":"info","msg":"5"}
{"level":"info","msg":"6"}
{"level":"info","msg":"7"}
{"level":"info","msg":"8"}
{"level":"info","msg":"9"}LAW can be used to write log data to logrus asynchronously.
Code
package main
import (
"os"
"time"
law "github.com/shengyanli1982/law"
"github.com/sirupsen/logrus"
)
func main() {
// 使用 os.Stdout 创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout
aw := law.NewWriteAsyncer(os.Stdout, nil)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer aw.Stop()
// 将 logrus 的输出设置为我们创建的 WriteAsyncer
// Set the output of logrus to the WriteAsyncer we created
logrus.SetOutput(aw)
// 循环 10 次,每次都使用 logrus 输出一个数字
// Loop 10 times, each time output a number using logrus
for i := 0; i < 10; i++ {
logrus.Info(i) // 输出当前的数字
}
// 等待 3 秒,以便我们可以看到 logrus 的输出
// Wait for 3 seconds so we can see the output of logrus
time.Sleep(3 * time.Second)
}Results
$ go run demo.go
time="2023-12-16T12:38:13+08:00" level=info msg=0
time="2023-12-16T12:38:13+08:00" level=info msg=1
time="2023-12-16T12:38:13+08:00" level=info msg=2
time="2023-12-16T12:38:13+08:00" level=info msg=3
time="2023-12-16T12:38:13+08:00" level=info msg=4
time="2023-12-16T12:38:13+08:00" level=info msg=5
time="2023-12-16T12:38:13+08:00" level=info msg=6
time="2023-12-16T12:38:13+08:00" level=info msg=7
time="2023-12-16T12:38:13+08:00" level=info msg=8
time="2023-12-16T12:38:13+08:00" level=info msg=9LAW can be used to write log data to klog asynchronously.
Code
package main
import (
"os"
"time"
law "github.com/shengyanli1982/law"
"k8s.io/klog/v2"
)
func main() {
// 使用 os.Stdout 创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout
aw := law.NewWriteAsyncer(os.Stdout, nil)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer aw.Stop()
// 将 klog 的输出设置为我们创建的 WriteAsyncer
// Set the output of klog to the WriteAsyncer we created
klog.SetOutput(aw)
// 循环 10 次,每次都使用 klog 输出一个数字
// Loop 10 times, each time output a number using klog
for i := 0; i < 10; i++ {
klog.Info(i) // 输出当前的数字
}
// 等待 3 秒,以便我们可以看到 klog 的输出
// Wait for 3 seconds so we can see the output of klog
time.Sleep(3 * time.Second)
}Results
$ go run demo.go
I1216 12:36:07.637943 17388 demo.go:18] 0
I1216 12:36:07.638105 17388 demo.go:18] 1
I1216 12:36:07.638109 17388 demo.go:18] 2
I1216 12:36:07.638113 17388 demo.go:18] 3
I1216 12:36:07.638117 17388 demo.go:18] 4
I1216 12:36:07.638121 17388 demo.go:18] 5
I1216 12:36:07.638125 17388 demo.go:18] 6
I1216 12:36:07.638128 17388 demo.go:18] 7
I1216 12:36:07.638132 17388 demo.go:18] 8
I1216 12:36:07.638136 17388 demo.go:18] 9LAW can be used to write log data to zerolog asynchronously.
Code
package main
import (
"os"
"time"
"github.com/rs/zerolog"
law "github.com/shengyanli1982/law"
)
func main() {
// 使用 os.Stdout 创建一个新的 WriteAsyncer 实例
// Create a new WriteAsyncer instance using os.Stdout
aw := law.NewWriteAsyncer(os.Stdout, nil)
// 使用 defer 语句确保在 main 函数退出时停止 WriteAsyncer
// Use a defer statement to ensure that WriteAsyncer is stopped when the main function exits
defer aw.Stop()
// 使用 WriteAsyncer 创建一个新的 zerolog.Logger 实例,并添加时间戳
// Create a new zerolog.Logger instance using WriteAsyncer and add a timestamp
log := zerolog.New(aw).With().Timestamp().Logger()
// 循环 10 次,每次都使用 log 输出一个数字和一条消息
// Loop 10 times, each time output a number and a message using log
for i := 0; i < 10; i++ {
log.Info().Int("i", i).Msg("hello") // 输出当前的数字和一条消息
}
// 等待 3 秒,以便我们可以看到 log 的输出
// Wait for 3 seconds so we can see the output of log
time.Sleep(3 * time.Second)
}Results
$ go run demo.go
{"level":"info","i":0,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":1,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":2,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":3,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":4,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":5,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":6,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":7,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":8,"time":"2023-12-16T12:39:45+08:00","message":"hello"}
{"level":"info","i":9,"time":"2023-12-16T12:39:45+08:00","message":"hello"}$ go test -benchmem -run=^$ -bench ^Benchmark* github.com/shengyanli1982/law/benchmark
goos: windows
goarch: amd64
pkg: github.com/shengyanli1982/law/benchmark
cpu: 12th Gen Intel(R) Core(TM) i5-12400F
BenchmarkBlackHoleWriter-12 1000000000 0.1278 ns/op 0 B/op 0 allocs/op
BenchmarkBlackHoleWriterParallel-12 1000000000 0.04558 ns/op 0 B/op 0 allocs/op
BenchmarkLogAsyncWriter-12 6480730 212.0 ns/op 122 B/op 1 allocs/op
BenchmarkLogAsyncWriterParallel-12 4032622 276.7 ns/op 260 B/op 2 allocs/op
BenchmarkZapSyncWriter-12 9245127 128.7 ns/op 0 B/op 0 allocs/op
BenchmarkZapSyncWriterParallel-12 52751426 26.14 ns/op 0 B/op 0 allocs/op
BenchmarkZapAsyncWriter-12 3765366 311.0 ns/op 129 B/op 1 allocs/op
BenchmarkZapAsyncWriterParallel-12 3039962 375.1 ns/op 234 B/op 2 allocs/opLAW employs a double buffer strategy for logging, which may slightly impact performance compared to zapcore.AddSync(BlackHoleWriter). This is because zap, when integrated with LAW, utilizes zap's writer buffer indirectly. zap passes the data to LAW through a deque before flushing it to the io.Writer (BlackHoleWriter). As a result, the performance of LAW is the sum of BenchmarkZapSyncWriter and BenchmarkLogAsyncWriter, equivalent to BenchmarkZapAsyncWriter.
Integrate law into the HTTP server to simulate real-world business scenarios and compare its performance with other loggers.
SyncWriter: os.Stdout
package main
import (
"net/http"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// 创建一个zapcore.EncoderConfig,用于配置日志编码器
// Create a zapcore.EncoderConfig to configure the log encoder
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg", // 消息的键名,Key name for the message
LevelKey: "level", // 日志级别的键名,Key name for the log level
NameKey: "logger", // 记录器名称的键名,Key name for the logger name
EncodeLevel: zapcore.LowercaseLevelEncoder, // 日志级别的编码器,Encoder for the log level
EncodeTime: zapcore.ISO8601TimeEncoder, // 时间的编码器,Encoder for the time
EncodeDuration: zapcore.StringDurationEncoder, // 持续时间的编码器,Encoder for the duration
}
// 创建一个zapcore.WriteSyncer,将日志写入标准输出
// Create a zapcore.WriteSyncer that writes logs to the standard output
zapSyncWriter := zapcore.AddSync(os.Stdout)
// 创建一个zapcore.Core,使用JSON编码器和标准输出
// Create a zapcore.Core using the JSON encoder and the standard output
zapCore := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), zapSyncWriter, zapcore.DebugLevel)
// 创建一个zap.Logger,使用上面创建的zapcore.Core
// Create a zap.Logger using the zapcore.Core created above
zapLogger := zap.New(zapCore)
// 注册一个HTTP处理函数,当访问"/"时,记录一条信息日志
// Register an HTTP handler function, when accessing "/", log an info message
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
zapLogger.Info("hello")
})
// 启动HTTP服务器,监听8080端口
// Start the HTTP server, listen on port 8080
_ = http.ListenAndServe(":8080", nil)
}Use wrk to test the performance of the http server.
#!/bin/bash
times=0
while [ $times -lt 5 ]
do
wrk -c 500 -t 10 http://127.0.0.1:8080
times=$[$times+1]
sleep 2
echo "--------------------------------------"
doneResults:
LAW: NewWriteAsyncer(os.Stdout, nil)
package main
import (
"net/http"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
x "github.com/shengyanli1982/law"
)
func main() {
// 创建一个新的异步写入器,输出到标准输出
// Create a new asynchronous writer that outputs to standard output
aw := x.NewWriteAsyncer(os.Stdout, nil)
// 确保在程序结束时停止异步写入器
// Ensure the asynchronous writer is stopped when the program ends
defer aw.Stop()
// 创建一个zapcore.EncoderConfig,用于配置日志编码器
// Create a zapcore.EncoderConfig to configure the log encoder
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg", // 消息的键名,Key name for the message
LevelKey: "level", // 日志级别的键名,Key name for the log level
NameKey: "logger", // 记录器名称的键名,Key name for the logger name
EncodeLevel: zapcore.LowercaseLevelEncoder, // 日志级别的编码器,Encoder for the log level
EncodeTime: zapcore.ISO8601TimeEncoder, // 时间的编码器,Encoder for the time
EncodeDuration: zapcore.StringDurationEncoder, // 持续时间的编码器,Encoder for the duration
}
// 创建一个zapcore.WriteSyncer,将日志写入异步写入器
// Create a zapcore.WriteSyncer that writes logs to the asynchronous writer
zapSyncWriter := zapcore.AddSync(aw)
// 创建一个zapcore.Core,使用JSON编码器和异步写入器
// Create a zapcore.Core using the JSON encoder and the asynchronous writer
zapCore := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), zapSyncWriter, zapcore.DebugLevel)
// 创建一个zap.Logger,使用上面创建的zapcore.Core
// Create a zap.Logger using the zapcore.Core created above
zapLogger := zap.New(zapCore)
// 注册一个HTTP处理函数,当访问"/"时,记录一条信息日志
// Register an HTTP handler function, when accessing "/", log an info message
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
zapLogger.Info("hello")
})
// 启动HTTP服务器,监听8080端口
// Start the HTTP server, listen on port 8080
_ = http.ListenAndServe(":8080", nil)
}Use wrk to test the performance of the http server.
#!/bin/bash
times=0
while [ $times -lt 5 ]
do
wrk -c 500 -t 10 http://127.0.0.1:8080
times=$[$times+1]
sleep 2
echo "--------------------------------------"
doneResults:

