延迟评估 github.com/rs/zerolog Fields

huangapple go评论115阅读模式
英文:

Deferred evaluation of github.com/rs/zerolog Fields

问题

Introduction

zerolog字段

我在我的Go语言项目中使用github.com/rs/zerolog

我知道可以通过以下方式向输出添加字段:

  1. package main
  2. import (
  3. "os"
  4. "github.com/rs/zerolog"
  5. )
  6. func main() {
  7. logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
  8. logger.Int("myIntField", 42)
  9. logger.Info("a regular log output") // 这个日志条目也将包含整数字段`myIntField`
  10. }

但是我想要的是在运行时评估logger.Info("a regular log output")行中字段myIntField的值。

设置

我有一个生产者/消费者的设置(例如参见https://goplay.tools/snippet/hkoMAwqKcwj),其中包含Go协程,并且我有两个整数,它们以原子方式计算消费者和生产者Go协程的数量。在消费者和生产者关闭时,我想要在运行时显示这些数字。

以下是使用log而不是zerolog的代码:

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "sync"
  7. "sync/atomic"
  8. )
  9. func main() {
  10. numProducers := int32(3)
  11. numConsumers := int32(3)
  12. producersRunning := numProducers
  13. consumersRunning := numConsumers
  14. var wg sync.WaitGroup
  15. l := log.New(os.Stderr, "", 0)
  16. // producers
  17. for i := int32(0); i < numProducers; i++ {
  18. idx := i
  19. wg.Add(1)
  20. go (func() {
  21. // producer tear down
  22. defer func() {
  23. atomic.AddInt32(&producersRunning, -1)
  24. l.Printf("producer-%3d . producersRunning: %3d\n", idx, producersRunning)
  25. wg.Done()
  26. }()
  27. // this is where the actual producer works is happening
  28. })()
  29. }
  30. // consumers
  31. for i := int32(0); i < numConsumers; i++ {
  32. idx := i
  33. wg.Add(1)
  34. go (func() {
  35. // consumer tear down
  36. defer func() {
  37. atomic.AddInt32(&consumersRunning, -1)
  38. l.Printf("consumer-%3d . consumersRunning: %3d\n", idx, consumersRunning)
  39. wg.Done()
  40. }()
  41. // this is where the actual consumer works is happening
  42. })()
  43. }
  44. fmt.Println("waiting")
  45. wg.Wait()
  46. }

它的输出类似于:

  1. waiting
  2. producer- 1 . producersRunning: 2
  3. producer- 0 . producersRunning: 1
  4. consumer- 1 . consumersRunning: 2
  5. producer- 2 . producersRunning: 0
  6. consumer- 2 . consumersRunning: 1
  7. consumer- 0 . consumersRunning: 0

每个消费者/生产者一个日志记录器

使用zerolog,您可以创建日志记录器并将其传递给每个Go协程:

  1. logger := zerolog.New(os.Stderr)
  2. go myConsumer(logger.With().Str("is", "consumer").Logger())
  3. go myProducer(logger.With().Str("is", "producer").Logger())

然后,您可以通过查看每个日志行中的is字段,轻松确定消息来自消费者还是生产者。

但是,如果我想始终在每个日志行中打印当前活动的消费者/生产者数量怎么办?您可能会尝试这样做:

  1. go myConsumer(logger.With().Str("is", "consumer").Int("consumersRunning", consumersRunning).Logger())
  2. go myProducer(logger.With().Str("is", "producer").Int("producersRunning", producersRunning).Logger())

但是,这只会打印在创建Go协程时consumersRunningproducersRunning的瞬时值。相反,我希望日志输出反映日志输出时的值。

总结

我希望我的问题清楚明了。我不确定这是否违背了"零"的概念,但是像下面这样的函数

  1. func (e *Event) DeferredInt(key string, i func()int) *Event

可能会起作用,只要它存在就可以。

是否有其他方法实现相同的效果?

潜在解决方法

我的意思是一种方法可能是将logger变量替换为以下函数调用:

  1. logFunc := func() zerolog.Logger {
  2. return logger.With().Int("runningConcumers", runningConsumers).Logger()
  3. }

然后可以使用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:

  1. package main
  2. import (
  3. &quot;os&quot;
  4. &quot;github.com/rs/zerolog&quot;
  5. )
  6. func main() {
  7. logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
  8. logger.Int(&quot;myIntField&quot;, 42)
  9. logger.Info(&quot;a regular log output&quot;) // this log entry will also contain the integer field `myIntField`
  10. }

But what I would like to have is something evaluates at runtime of the line logger.Info(&quot;a regular log output&quot;) 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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;log&quot;
  5. &quot;os&quot;
  6. &quot;sync&quot;
  7. &quot;sync/atomic&quot;
  8. )
  9. func main() {
  10. numProducers := int32(3)
  11. numConsumers := int32(3)
  12. producersRunning := numProducers
  13. consumersRunning := numConsumers
  14. var wg sync.WaitGroup
  15. l := log.New(os.Stderr, &quot;&quot;, 0)
  16. // producers
  17. for i := int32(0); i &lt; numProducers; i++ {
  18. idx := i
  19. wg.Add(1)
  20. go (func() {
  21. // producer tear down
  22. defer func() {
  23. atomic.AddInt32(&amp;producersRunning, -1)
  24. l.Printf(&quot;producer-%3d . producersRunning: %3d\n&quot;, idx, producersRunning)
  25. wg.Done()
  26. }()
  27. // this is where the actual producer works is happening
  28. })()
  29. }
  30. // consumers
  31. for i := int32(0); i &lt; numConsumers; i++ {
  32. idx := i
  33. wg.Add(1)
  34. go (func() {
  35. // consumer tear down
  36. defer func() {
  37. atomic.AddInt32(&amp;consumersRunning, -1)
  38. l.Printf(&quot;consumer-%3d . consumersRunning: %3d\n&quot;, idx, consumersRunning)
  39. wg.Done()
  40. }()
  41. // this is where the actual consumer works is happening
  42. })()
  43. }
  44. fmt.Println(&quot;waiting&quot;)
  45. wg.Wait()
  46. }

It outputs something like this:

  1. waiting
  2. producer- 1 . producersRunning: 2
  3. producer- 0 . producersRunning: 1
  4. consumer- 1 . consumersRunning: 2
  5. producer- 2 . producersRunning: 0
  6. consumer- 2 . consumersRunning: 1
  7. consumer- 0 . consumersRunning: 0

A logger per consumer / producer

With zerolog you can create loggers an pass them to each go-rountine:

  1. logger := zerolog.New(os.Stderr)
  2. go myConsumer(logger.With().Str(&quot;is&quot;, &quot;consumer&quot;).Logger())
  3. go myProducer(logger.With().Str(&quot;is&quot;, &quot;producer&quot;).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:

  1. go myConsumer(logger.With().Str(&quot;is&quot;, &quot;consumer&quot;).Int(&quot;consumersRunning&quot;, consumersRunning).Logger())
  2. go myProducer(logger.With().Str(&quot;is&quot;, &quot;producer&quot;).Int(&quot;producersRunning&quot;, 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

  1. 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:

  1. logFunc := func() zerolog.Logger {
  2. return logger.With().Int(&quot;runningConcumers&quot;, runningConsumers).Logger()
  3. }

And then a log entry can be created with logFunc().Msg(&quot;hello&quot;). 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进行评估。

  1. package main
  2. import (
  3. "os"
  4. "github.com/rs/zerolog"
  5. )
  6. type IntHook struct {
  7. Count int
  8. }
  9. func (h *IntHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
  10. e.Int("count", h.Count)
  11. h.Count++
  12. }
  13. func main() {
  14. var intHook IntHook
  15. log := zerolog.New(os.Stdout).Hook(&intHook)
  16. log.Info().Msg("hello world")
  17. log.Info().Msg("hello world one more time")
  18. }

输出结果为:

  1. {"level":"info","count":0,"message":"hello world"}
  2. {"level":"info","count":1,"message":"hello world one more time"}

Hook.Run的调用之间需要使用指针来保存Count的值。

也许对你来说,使用HookFunc会更好。它是一个无状态函数,每个事件都会调用它。这里有一个示例,展示了一个在每条消息中调用PRNG的函数hook:https://go.dev/play/p/xu6aXpUmE0v

  1. package main
  2. import (
  3. "math/rand"
  4. "os"
  5. "github.com/rs/zerolog"
  6. )
  7. func RandomHook(e *zerolog.Event, l zerolog.Level, msg string) {
  8. e.Int("random", rand.Intn(100))
  9. }
  10. func main() {
  11. var randomHook zerolog.HookFunc = RandomHook
  12. log := zerolog.New(os.Stdout).Hook(randomHook)
  13. log.Info().Msg("hello world")
  14. log.Info().Msg("hello world one more time")
  15. }

输出结果为:

  1. {"level":"info","random":81,"message":"hello world"}
  2. {"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

  1. package main
  2. import (
  3. &quot;os&quot;
  4. &quot;github.com/rs/zerolog&quot;
  5. )
  6. type IntHook struct {
  7. Count int
  8. }
  9. func (h *IntHook) Run(e *zerolog.Event, l zerolog.Level, msg string) {
  10. e.Int(&quot;count&quot;, h.Count)
  11. h.Count++
  12. }
  13. func main() {
  14. var intHook IntHook
  15. log := zerolog.New(os.Stdout).Hook(&amp;intHook)
  16. log.Info().Msg(&quot;hello world&quot;)
  17. log.Info().Msg(&quot;hello world one more time&quot;)
  18. }

Output is

  1. {&quot;level&quot;:&quot;info&quot;,&quot;count&quot;:0,&quot;message&quot;:&quot;hello world&quot;}
  2. {&quot;level&quot;:&quot;info&quot;,&quot;count&quot;:1,&quot;message&quot;:&quot;hello world one more time&quot;}

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

  1. package main
  2. import (
  3. &quot;math/rand&quot;
  4. &quot;os&quot;
  5. &quot;github.com/rs/zerolog&quot;
  6. )
  7. func RandomHook(e *zerolog.Event, l zerolog.Level, msg string) {
  8. e.Int(&quot;random&quot;, rand.Intn(100))
  9. }
  10. func main() {
  11. var randomHook zerolog.HookFunc = RandomHook
  12. log := zerolog.New(os.Stdout).Hook(randomHook)
  13. log.Info().Msg(&quot;hello world&quot;)
  14. log.Info().Msg(&quot;hello world one more time&quot;)
  15. }

Output

  1. {&quot;level&quot;:&quot;info&quot;,&quot;random&quot;:81,&quot;message&quot;:&quot;hello world&quot;}
  2. {&quot;level&quot;:&quot;info&quot;,&quot;random&quot;:87,&quot;message&quot;:&quot;hello world one more time&quot;}

答案2

得分: 0

你可以使用 zerolog Hook 来实现这个功能。Hook 是一个接口,其中包含一个 Run 方法,在将事件数据写入给定的 io.Writer(在你的情况下是 os.Stderr)之前调用该方法。

以下是一些示例代码:

  1. type counter struct {
  2. name string
  3. value int32
  4. }
  5. func (c *counter) inc() { atomic.AddInt32(&c.value, 1) }
  6. func (c *counter) dec() { atomic.AddInt32(&c.value, -1) }
  7. func (c *counter) get() { atomic.LoadInt32(&c.value) }
  8. func (c *counter) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
  9. e.Int32(c.name, c.get())
  10. }
  11. func main() {
  12. numConsumers, numProducers := 3, 3
  13. consumersRunning := &counter{
  14. name: "consumersRunning",
  15. value: int32(numConsumers),
  16. }
  17. producersRunning := &counter{
  18. name: "producersRunning",
  19. value: int32(numProducers),
  20. }
  21. logger := zerolog.New(os.Stderr)
  22. consumerLogger := logger.With().Str("is", "consumer").Logger().Hook(consumersRunning)
  23. producerLogger := logger.With().Str("is", "producer").Logger().Hook(producersRunning)
  24. // your other code
  25. }

你可以使用计数器的 incdec 方法来修改运行的消费者/生产者数量。

英文:

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:

  1. type counter struct {
  2. name string
  3. value int32
  4. }
  5. func (c *counter) inc() { atomic.AddInt32(&amp;c.value, 1) }
  6. func (c *counter) dec() { atomic.AddInt32(&amp;c.value, -1) }
  7. func (c *counter) get() { atomic.LoadInt32(&amp;c.value) }
  8. func (c *counter) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
  9. e.Int32(c.name, c.get())
  10. }
  11. int main() {
  12. numConsumers, numProducers := 3, 3
  13. consumersRunning := &amp;counter{
  14. name: &quot;consumersRunning&quot;,
  15. value: int32(numConsumers),
  16. }
  17. producersRunning := &amp;counter{
  18. name: &quot;producersRunning&quot;,
  19. value: int32(numProducers),
  20. }
  21. logger := zerolog.New(os.Stderr)
  22. consumerLogger := logger.With().Str(&quot;is&quot;, &quot;consumer&quot;).Logger().Hook(consumersRunning)
  23. producerLogger := logger.With().Str(&quot;is&quot;, &quot;producer&quot;).Logger().Hook(producersRunning)
  24. // your other code
  25. }

You will use the inc and dec methods of the counters to modify the numbers of consumers/producers running.

huangapple
  • 本文由 发表于 2022年9月1日 20:53:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/73569678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定