英文:
Deferred evaluation of github.com/rs/zerolog Fields
问题
Introduction
zerolog字段
我在我的Go语言项目中使用github.com/rs/zerolog
。
我知道可以通过以下方式向输出添加字段:
package main
import (
"os"
"github.com/rs/zerolog"
)
func main() {
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
logger.Int("myIntField", 42)
logger.Info("a regular log output") // 这个日志条目也将包含整数字段`myIntField`
}
但是我想要的是在运行时评估logger.Info("a regular log output")
行中字段myIntField
的值。
设置
我有一个生产者/消费者的设置(例如参见https://goplay.tools/snippet/hkoMAwqKcwj),其中包含Go协程,并且我有两个整数,它们以原子方式计算消费者和生产者Go协程的数量。在消费者和生产者关闭时,我想要在运行时显示这些数字。
以下是使用log而不是zerolog的代码:
package main
import (
"fmt"
"log"
"os"
"sync"
"sync/atomic"
)
func main() {
numProducers := int32(3)
numConsumers := int32(3)
producersRunning := numProducers
consumersRunning := numConsumers
var wg sync.WaitGroup
l := log.New(os.Stderr, "", 0)
// producers
for i := int32(0); i < numProducers; i++ {
idx := i
wg.Add(1)
go (func() {
// producer tear down
defer func() {
atomic.AddInt32(&producersRunning, -1)
l.Printf("producer-%3d . producersRunning: %3d\n", idx, producersRunning)
wg.Done()
}()
// this is where the actual producer works is happening
})()
}
// consumers
for i := int32(0); i < numConsumers; i++ {
idx := i
wg.Add(1)
go (func() {
// consumer tear down
defer func() {
atomic.AddInt32(&consumersRunning, -1)
l.Printf("consumer-%3d . consumersRunning: %3d\n", idx, consumersRunning)
wg.Done()
}()
// this is where the actual consumer works is happening
})()
}
fmt.Println("waiting")
wg.Wait()
}
它的输出类似于:
waiting
producer- 1 . producersRunning: 2
producer- 0 . producersRunning: 1
consumer- 1 . consumersRunning: 2
producer- 2 . producersRunning: 0
consumer- 2 . consumersRunning: 1
consumer- 0 . consumersRunning: 0
每个消费者/生产者一个日志记录器
使用zerolog,您可以创建日志记录器并将其传递给每个Go协程:
logger := zerolog.New(os.Stderr)
go myConsumer(logger.With().Str("is", "consumer").Logger())
go myProducer(logger.With().Str("is", "producer").Logger())
然后,您可以通过查看每个日志行中的is
字段,轻松确定消息来自消费者还是生产者。
但是,如果我想始终在每个日志行中打印当前活动的消费者/生产者数量怎么办?您可能会尝试这样做:
go myConsumer(logger.With().Str("is", "consumer").Int("consumersRunning", consumersRunning).Logger())
go myProducer(logger.With().Str("is", "producer").Int("producersRunning", producersRunning).Logger())
但是,这只会打印在创建Go协程时consumersRunning
和producersRunning
的瞬时值。相反,我希望日志输出反映日志输出时的值。
总结
我希望我的问题清楚明了。我不确定这是否违背了"零"的概念,但是像下面这样的函数
func (e *Event) DeferredInt(key string, i func()int) *Event
可能会起作用,只要它存在就可以。
是否有其他方法实现相同的效果?
潜在解决方法
我的意思是一种方法可能是将logger
变量替换为以下函数调用:
logFunc := func() zerolog.Logger {
return logger.With().Int("runningConcumers", runningConsumers).Logger()
}
然后可以使用logFunc().Msg("hello")
创建日志条目。这样延迟了runningConsumers
的评估,但也为每个日志条目创建了一个日志记录器,这感觉有点过度。
到目前为止,我希望没有让您困惑。
英文:
<!-- language-all: lang-go -->
Introduction
zerolog fields
I'm using github.com/rs/zerolog
in my golang project.
I know that I can add fields to the output by using something like this:
package main
import (
"os"
"github.com/rs/zerolog"
)
func main() {
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
logger.Int("myIntField", 42)
logger.Info("a regular log output") // this log entry will also contain the integer field `myIntField`
}
But what I would like to have is something evaluates at runtime of the line logger.Info("a regular log output")
what the value of a field myIntField
is.
The setting
I have a producer/consumer setup (for example see https://goplay.tools/snippet/hkoMAwqKcwj) with go-routines and I have two integers that are atomically counted down the number of consumer and producer go-routines still in business. Upon tear down of the consumer and producer I want to display these numbers at runtime.
Here's the code when using log instead of zerolog:
package main
import (
"fmt"
"log"
"os"
"sync"
"sync/atomic"
)
func main() {
numProducers := int32(3)
numConsumers := int32(3)
producersRunning := numProducers
consumersRunning := numConsumers
var wg sync.WaitGroup
l := log.New(os.Stderr, "", 0)
// producers
for i := int32(0); i < numProducers; i++ {
idx := i
wg.Add(1)
go (func() {
// producer tear down
defer func() {
atomic.AddInt32(&producersRunning, -1)
l.Printf("producer-%3d . producersRunning: %3d\n", idx, producersRunning)
wg.Done()
}()
// this is where the actual producer works is happening
})()
}
// consumers
for i := int32(0); i < numConsumers; i++ {
idx := i
wg.Add(1)
go (func() {
// consumer tear down
defer func() {
atomic.AddInt32(&consumersRunning, -1)
l.Printf("consumer-%3d . consumersRunning: %3d\n", idx, consumersRunning)
wg.Done()
}()
// this is where the actual consumer works is happening
})()
}
fmt.Println("waiting")
wg.Wait()
}
It outputs something like this:
waiting
producer- 1 . producersRunning: 2
producer- 0 . producersRunning: 1
consumer- 1 . consumersRunning: 2
producer- 2 . producersRunning: 0
consumer- 2 . consumersRunning: 1
consumer- 0 . consumersRunning: 0
A logger per consumer / producer
With zerolog you can create loggers an pass them to each go-rountine:
logger := zerolog.New(os.Stderr)
go myConsumer(logger.With().Str("is", "consumer").Logger())
go myProducer(logger.With().Str("is", "producer").Logger())
Then you can easily find out in the logs if a message came from a consumer or a producer just by looking at the is
field in each log line.
But what if I want to always print the number of currently active consumers/producers in each log line? You might be tempted to do something like this:
go myConsumer(logger.With().Str("is", "consumer").Int("consumersRunning", consumersRunning).Logger())
go myProducer(logger.With().Str("is", "producer").Int("producersRunning", producersRunning).Logger())
But of course, this will only print the momentary value of consumersRunning
and producersRunning
at the time of creating the go-routine. Instead I would like the log output to reflect the values at the time of the log output.
Summary
I hope my question is clear. I'm not sure if it is against the concept of zero-ness but a function like
func (e *Event) DeferredInt(key string, i func()int) *Event
would probably work, if only it existed.
Is there another way to achieve the same effect?
Potential workaround
I mean one way could be to replace the logger
variable with a function call like this:
logFunc := func() zerolog.Logger {
return logger.With().Int("runningConcumers", runningConsumers).Logger()
}
And then a log entry can be created with logFunc().Msg("hello")
. This defers the evaluation of runningConsumers
but also creates a logger for each log entry which feels like overkill.
By now I hope I haven't confused you.
答案1
得分: 4
你可以添加一个hook。每个日志事件都会对hook进行评估。
package main
import (
"os"
"github.com/rs/zerolog"
)
type IntHook struct {
Count int
}
func (h *IntHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
e.Int("count", h.Count)
h.Count++
}
func main() {
var intHook IntHook
log := zerolog.New(os.Stdout).Hook(&intHook)
log.Info().Msg("hello world")
log.Info().Msg("hello world one more time")
}
输出结果为:
{"level":"info","count":0,"message":"hello world"}
{"level":"info","count":1,"message":"hello world one more time"}
在Hook.Run
的调用之间需要使用指针来保存Count
的值。
也许对你来说,使用HookFunc
会更好。它是一个无状态函数,每个事件都会调用它。这里有一个示例,展示了一个在每条消息中调用PRNG的函数hook:https://go.dev/play/p/xu6aXpUmE0v
package main
import (
"math/rand"
"os"
"github.com/rs/zerolog"
)
func RandomHook(e *zerolog.Event, l zerolog.Level, msg string) {
e.Int("random", rand.Intn(100))
}
func main() {
var randomHook zerolog.HookFunc = RandomHook
log := zerolog.New(os.Stdout).Hook(randomHook)
log.Info().Msg("hello world")
log.Info().Msg("hello world one more time")
}
输出结果为:
{"level":"info","random":81,"message":"hello world"}
{"level":"info","random":87,"message":"hello world one more time"}
英文:
You can add a hook. Hook is evaluated for each logging event
https://go.dev/play/p/Q7doafJGaeE
package main
import (
"os"
"github.com/rs/zerolog"
)
type IntHook struct {
Count int
}
func (h *IntHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
e.Int("count", h.Count)
h.Count++
}
func main() {
var intHook IntHook
log := zerolog.New(os.Stdout).Hook(&intHook)
log.Info().Msg("hello world")
log.Info().Msg("hello world one more time")
}
Output is
{"level":"info","count":0,"message":"hello world"}
{"level":"info","count":1,"message":"hello world one more time"}
Pointer is required to save Count
between calls to Hook.Run
May be for you a HookFunc
is better. It is a stateless function that is called for each event. Here is an example of a function hook that calls PRNG for each message: https://go.dev/play/p/xu6aXpUmE0v
package main
import (
"math/rand"
"os"
"github.com/rs/zerolog"
)
func RandomHook(e *zerolog.Event, l zerolog.Level, msg string) {
e.Int("random", rand.Intn(100))
}
func main() {
var randomHook zerolog.HookFunc = RandomHook
log := zerolog.New(os.Stdout).Hook(randomHook)
log.Info().Msg("hello world")
log.Info().Msg("hello world one more time")
}
Output
{"level":"info","random":81,"message":"hello world"}
{"level":"info","random":87,"message":"hello world one more time"}
答案2
得分: 0
你可以使用 zerolog Hook 来实现这个功能。Hook 是一个接口,其中包含一个 Run
方法,在将事件数据写入给定的 io.Writer
(在你的情况下是 os.Stderr
)之前调用该方法。
以下是一些示例代码:
type counter struct {
name string
value int32
}
func (c *counter) inc() { atomic.AddInt32(&c.value, 1) }
func (c *counter) dec() { atomic.AddInt32(&c.value, -1) }
func (c *counter) get() { atomic.LoadInt32(&c.value) }
func (c *counter) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
e.Int32(c.name, c.get())
}
func main() {
numConsumers, numProducers := 3, 3
consumersRunning := &counter{
name: "consumersRunning",
value: int32(numConsumers),
}
producersRunning := &counter{
name: "producersRunning",
value: int32(numProducers),
}
logger := zerolog.New(os.Stderr)
consumerLogger := logger.With().Str("is", "consumer").Logger().Hook(consumersRunning)
producerLogger := logger.With().Str("is", "producer").Logger().Hook(producersRunning)
// your other code
}
你可以使用计数器的 inc
和 dec
方法来修改运行的消费者/生产者数量。
英文:
You can use a zerolog Hook to achieve this. Hooks are interfaces with a Run
method which is called before the event data is written to the given io.Writer
(in your case os.Stderr
).
Here is some example code:
type counter struct {
name string
value int32
}
func (c *counter) inc() { atomic.AddInt32(&c.value, 1) }
func (c *counter) dec() { atomic.AddInt32(&c.value, -1) }
func (c *counter) get() { atomic.LoadInt32(&c.value) }
func (c *counter) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
e.Int32(c.name, c.get())
}
int main() {
numConsumers, numProducers := 3, 3
consumersRunning := &counter{
name: "consumersRunning",
value: int32(numConsumers),
}
producersRunning := &counter{
name: "producersRunning",
value: int32(numProducers),
}
logger := zerolog.New(os.Stderr)
consumerLogger := logger.With().Str("is", "consumer").Logger().Hook(consumersRunning)
producerLogger := logger.With().Str("is", "producer").Logger().Hook(producersRunning)
// your other code
}
You will use the inc
and dec
methods of the counters to modify the numbers of consumers/producers running.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论