Вы можете перехватить ведение журнала, указав io.Writer
- log.SetOutput .Там вы можете просто проверить, является ли записываемая строка тем, что вам нужно отследить и записать трассировку стека делегированному завернутому модулю записи.
Вот пример игровой площадки: https://play.golang.org/p/2bClt2JBuFs
Обратите внимание, чтополучение текущей трассировки стека не является бесплатным, оно вызывает остановку мира и может существенно замедлить работу вашего приложения.Если у вас есть приложение, которое создает большой объем журналов, я бы порекомендовал ограничить объем отслеживаемых данных (например, трассировать только первое совпадение или N раз в секунду / минуту)
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]\n", 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
}