注意 io.MultiWriter 的返回值

本文最后由 森林生灵 于 2020/07/04 01:58:54 编辑

文章目录 (?) [+]

    golang 的 io.MultiWriter() 函数可以将多个实现 io.Writer 接口实例包装为一个,实现多实例统一写入。但在开发自定义日志库时使用此函数遇到了这样一个问提,传入的多个接口实例总会有几个不会生效,而且好似是否生效还跟入传参位置有关。参考了网络上的案例,测试可以正常工作。

    func test() {
        file, err := os.Create("tmp.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        writer := io.MultiWriter(file, os.Stdout)
        writer.Write([]byte("Hello"))
    }

    有问题的部分代码如下:

    // 控制台打印彩色日志
    type Console struct {}
    
    func (c *Console) Write(p []byte) (n int, err error) {
        prefix := ""
        msg := string(p)
        length := len("[INFO]")
    
        if len(msg) >= length {
            prefix = msg[:length]
            switch prefix {
            case "[INFO]":
                prefix = StringBlue(prefix)
            case "[WARN]":
                prefix = StringYellow(prefix)
            case "[ERRO]":
                prefix = StringRed(prefix)
            case "[HTTP]":
                prefix = StringGreen(prefix)
            }
        }
    
        if prefix != "" {
            // 控制台打印彩色字符前缀
            return fmt.Fprintln(os.Stdout, prefix, msg[length:])
        } else {
            // 没有前缀直接输出原始消息
            return fmt.Fprintln(os.Stdout, msg)
        }
    }
    
    // 发送严重错误通知
    type Logger struct {}
    
    func (l *Logger) Write(p []byte) (n int, err error) {
        // TODO://
        file, err := os.Create("tmp.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        file.Write(p)
    
        // 由于尚未完成的该部分功能,为了测试 io.MultiWriter() 直接返回默认值
        return 0, nil
    }
    
    
    // 测试 io.MultiWriter()
    func test() {
        writer := io.MultiWriter(os.Stderr, &console.Console{}, &reporter.Reporter{})
        writer.Write([]byte("[INFO]Test Msg"))
        writer.Write([]byte("Raw Msg"))
    }

    表面看似两段代码没有什么错误,而且还把接口方法返回值的 error 硬性定义为 nil,但运行起来却大相径庭,经过一番查找发现异常原因在于 io.MultiWriter() 它不仅仅是判断返回值的 error ,还判断返回值的长度是否和传入 []byte 的长度一致。

    func (t *multiWriter) Write(p []byte) (n int, err error) {
       for _, w := range t.writers {
          n, err = w.Write(p)
          if err != nil {
             return
          }
          // 处理后的切片长度要与原传入切片的长度一样,否则直接跳出循环,不在执行之后的 io.Write
          if n != len(p) {
             err = ErrShortWrite
             return
          }
       }
       return len(p), nil
    }

    再回头看一下实现的那两个实例,很明显 &console.Console{} 在有符合的 prefix 时会对原始数据进行了修改,返回 fmt.Fprintln() 中的长度固然比原来的长;同时又由于 &reporter.Reporter{} 中强制返回的长度为 0,也会触发错误而终止循环。

    心得,在本案例中参考了网传案例,先入为主,并未输出 io.MultiWriter() 的返回值,不曾考虑在 errornil 时亦会导致程序中途跳出,故开发时一定要输出调用函数的返回值,即使上层函数 errornil,也要输出一下,可(我)以(还)避(是)免(太)走(菜)弯(了)路 .....


    本文标题:注意 io.MultiWriter 的返回值
    本文链接:https://www.lanseyujie.com/post/golang-development-note-io-multiwriter.html
    版权声明:本文使用「署名-非商业性使用-相同方式共享」创作共享协议,转载或使用请遵守署名协议。
    点赞 0 分享 0