mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-29 15:02:30 +00:00
7a30982bf0
Today, trace.Printf and trace.Debugf format the message more than once: at least once for logging, and once for tracing. Some of that formatting can be expensive for large messages, causing CPU and RAM spikes due to transient allocations. So this patch makes those trace functions only format once, and avoid the logging calls if they're below the logging level.
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package trace
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"blitiri.com.ar/go/chasquid/internal/nettrace"
|
|
"blitiri.com.ar/go/log"
|
|
)
|
|
|
|
type nopCloser struct{ io.Writer }
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
// setupLog replaces the default logger with one that writes to the returned
|
|
// buffer, at the given level. It is restored when the test finishes.
|
|
func setupLog(t *testing.T, level log.Level) *bytes.Buffer {
|
|
t.Helper()
|
|
buf := &bytes.Buffer{}
|
|
orig := log.Default
|
|
log.Default = log.New(nopCloser{buf})
|
|
log.Default.Level = level
|
|
log.Default.LogTime = false
|
|
log.Default.LogCaller = false
|
|
t.Cleanup(func() { log.Default = orig })
|
|
return buf
|
|
}
|
|
|
|
// traceEvents returns the rendered (HTML) active traces for the family.
|
|
func traceEvents(t *testing.T, family string) string {
|
|
t.Helper()
|
|
req := httptest.NewRequest("GET", "/debug/traces?fam="+family+"&b=-1", nil)
|
|
w := httptest.NewRecorder()
|
|
nettrace.RenderTraces(w, req)
|
|
return w.Body.String()
|
|
}
|
|
|
|
// countingStringer counts how many times it was formatted.
|
|
type countingStringer struct{ n *int }
|
|
|
|
func (c countingStringer) String() string {
|
|
*c.n++
|
|
return "stringer"
|
|
}
|
|
|
|
func TestFormatOnce(t *testing.T) {
|
|
for _, level := range []log.Level{log.Info, log.Debug} {
|
|
setupLog(t, level)
|
|
tr := New("TestFormatOnce", "title")
|
|
|
|
n := 0
|
|
tr.Debugf("%v", countingStringer{&n})
|
|
if n != 1 {
|
|
t.Errorf("level %d: Debugf formatted %d times, expected 1",
|
|
level, n)
|
|
}
|
|
|
|
n = 0
|
|
tr.Printf("%v", countingStringer{&n})
|
|
if n != 1 {
|
|
t.Errorf("level %d: Printf formatted %d times, expected 1",
|
|
level, n)
|
|
}
|
|
|
|
tr.Finish()
|
|
}
|
|
}
|
|
|
|
func TestLog(t *testing.T) {
|
|
// At info level, debug messages are not logged.
|
|
buf := setupLog(t, log.Info)
|
|
tr := New("TestLog", "title")
|
|
tr.Printf("info %d", 1)
|
|
tr.Debugf("debug %d", 2)
|
|
tr.Errorf("error %d", 3)
|
|
tr.Error(errors.New("error 4"))
|
|
tr.Finish()
|
|
|
|
expected := "_ TestLog title: info 1\n" +
|
|
"_ TestLog title: error: error 3\n" +
|
|
"_ TestLog title: error: error 4\n"
|
|
if got := buf.String(); got != expected {
|
|
t.Errorf("unexpected log at info level:\n got: %q\nwant: %q",
|
|
got, expected)
|
|
}
|
|
|
|
// At debug level, they are. Messages are quoted.
|
|
buf = setupLog(t, log.Debug)
|
|
tr = New("TestLog", "title")
|
|
tr.Debugf("debug %q\n", "x")
|
|
tr.Finish()
|
|
|
|
expected = `. TestLog title: debug \"x\"\n` + "\n"
|
|
if got := buf.String(); got != expected {
|
|
t.Errorf("unexpected log at debug level:\n got: %q\nwant: %q",
|
|
got, expected)
|
|
}
|
|
}
|
|
|
|
func TestTraceEvents(t *testing.T) {
|
|
// Debug messages are added to the trace even if they are not logged.
|
|
setupLog(t, log.Info)
|
|
tr := New("TestTraceEvents", "title")
|
|
tr.Printf("info %d", 1)
|
|
tr.Debugf("debug %d", 2)
|
|
tr.Errorf("error %d", 3)
|
|
|
|
body := traceEvents(t, "TestTraceEvents")
|
|
for _, s := range []string{"info 1", "debug 2", "error: error 3"} {
|
|
if !strings.Contains(body, s) {
|
|
t.Errorf("trace does not contain %q:\n%s", s, body)
|
|
}
|
|
}
|
|
tr.Finish()
|
|
}
|