golang代理io.Writer在日志中使用时的行为有所不同

时间:2018-10-12 21:24:30

标签: go

我正在尝试实现一个满足io.Writer的代理,因此可以将其插入记录器。这个想法是,它将像正常情况一样打印输出,但还会保留数据副本以供以后读取。

以下代码中的ProxyIO结构应该做到这一点,并且只要我直接调用其Write()方法,它就可以做到。但是,当我将其插入log.Logger实例时,输出是意外的。

(这是精简的代码,我想使用的原始实现是使用映射和圆形指针,而不是示例代码中使用的[][]byte buf。此外,我还删除了所有锁定。)

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

type ProxyIO struct {
    out io.Writer // the io we are proxying
    buf [][]byte
}

func newProxyIO(out io.Writer) *ProxyIO {
    return &ProxyIO{
        out: out,
        buf: [][]byte{},
    }
}

func (r *ProxyIO) Write(s []byte) (int, error) {
    r.out.Write(s)
    r.buf = append(r.buf, s)
    return len(s), nil
}

func main() {
    p := newProxyIO(ioutil.Discard)
    p.Write([]byte("test1\n"))
    p.Write([]byte("test2\n"))
    p.Write([]byte("test3\n"))
    l := log.New(p, "", 0)
    l.Print("test4")
    l.Print("test5")
    l.Print("test6")
    for i, e := range p.buf {
        fmt.Printf("%d: %s", i, e)
    }
}

(这是游乐场https://play.golang.org/p/UoOq4Nd-rmI上的代码)

我希望此代码提供以下输出:

0: test1
1: test2
2: test3
3: test4
4: test5
5: test6

但是,它将始终打印以下内容:

0: test1
1: test2
2: test3
3: test6
4: test6
5: test6

我的地图实现的行为是相同的。我还尝试使用container/list中的双向链表作为存储,它始终是相同的。因此,我必须在这里遗漏一些重要内容。

为什么我在缓冲区中看到的最后一个日志输出是三次,而不是最后三个日志输出?

1 个答案:

答案 0 :(得分:2)

如果您查看Logger.Print的源代码,则会看到它调用logger.Output。您会注意到它如何将字符串的值设置为l.buf,然后调用Write

如果您阅读this answer,即使一切都是按值传递的,您也会看到

  

当您将切片传递给函数时,将由此复制   标头,包括指针,它将指向相同的背景   数组。

因此,当您这样做时:

l.Print("test4")
l.Print("test5")
l.Print("test6")

记录器有效地重用了同一切片,并且您append对同一切片的引用有3次,因此在打印时自然会使用3次最新值集。

要解决此问题,您可以像这样使用之前复制[]byte

func (r *ProxyIO) Write(s []byte) (int, error) {
    c := make([]byte, len(s))
    copy(c, s)
    r.out.Write(c)
    r.buf = append(r.buf, c)
    return len(c), nil
}

更新后的游乐场:https://play.golang.org/p/DIWC1Xa6w0R