如何找到Golang程序中向stderr的流氓写入发生在哪里?

I've got a rather large Golang program with rather many dependencies. Somewhere, someone writes rogue data to stderr.

I want to find where this write is occurring so I can remove it.

I know I can intercept stderr using Dup2 and Pipes, so I can detect the write as it occurs; but this does not let me get a stack from the goroutine doing the write.

In other languages, I'd set os.Stderr to a custom File with a hooked Write function. Is there a way to do this in Go that I'm not aware of? If not, what would be a good way to find where this method call is happening?

You can intercept logging by providing io.Writer to log.SetOutput. There you can simply check if string that is being loged is what you need to trace and write stacktrace to the delegated wrapped writer.

Here is an example playground: https://play.golang.org/p/2bClt2JBuFs

Please note that getting current stacktrace is not free, it causes stop-of-the-world and can drastically slow your application down. In case you have an application that produces a high volume of logs I would recommend limit amount of data being traced (e.g. trace only first match or N times per second/minute)

package main

import (
    "fmt"
    "log"
    "os"
    "regexp"
    "runtime/debug"
)

func main() {
    log.SetOutput(NewLogInterceptor(LogInterceptionCheck{Pattern: ".*Some.*", Description: "with 'Some' substring"}))
    f1()
    log.Println("message that is not traced")
}

func f1() {
    log.Println("Some message")
}



type LogInterceptor struct {
    target *os.File
    checks []LogInterceptionCheck
}

type LogInterceptionCheck struct {
    regexp      *regexp.Regexp
    Pattern     string
    Description string
}

func NewLogInterceptor(checks ...LogInterceptionCheck) *LogInterceptor {
    for i := 0; i < len(checks); i++ {
        each := checks[i]
        compiled, e := regexp.Compile(each.Pattern)
        if e != nil {
            log.Fatalf("cannot compile regexpt [%s]: %s", each, e)
        }
        checks[i].regexp = compiled
    }
    return &LogInterceptor{os.Stderr, checks}
}

func (interceptor *LogInterceptor) Write(p []byte) (n int, err error) {
    i, err := interceptor.target.Write(p)
    // use loop because it is faster and generates less garbage compared to for-range loop
    for i := 0; i < len(interceptor.checks); i++ {
        check := interceptor.checks[i]
        if check.regexp.Match(p) {
            _, e := fmt.Fprintf(interceptor.target, ">>>> Printing stacktrace [%s]
", check.Description)
            if e != nil {
                log.Fatalf("cannot write: %s", e)
            }
            _, e = interceptor.target.Write(debug.Stack())
            if e != nil {
                log.Fatalf("cannot write stacktrace: %s", e)
            }
            break
        }
    }
    return i, err
}