Files

232 lines
4.1 KiB
Go

// +build debug0 or debug
package pdebug
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"runtime"
"strconv"
"sync"
"time"
)
const Enabled = true
const indentPerLevel = 2
func init() {
Trace = true
}
type state struct {
clock Clock
indent int
mu sync.RWMutex
out io.Writer
prefix []byte
}
type mGuard struct {
errptr *error
indent int
msgFormat string
msgArgs []interface{}
start time.Time
}
var Trace bool
var st = &state{
clock: ClockFunc(time.Now),
out: os.Stderr,
prefix: []byte("|DEBUG|"),
}
func Configure(options ...Option) {
var clock Clock
var output io.Writer
for _, option := range options {
switch option.Ident() {
case identClock{}:
clock = option.Value().(Clock)
case identWriter{}:
output = option.Value().(io.Writer)
}
}
st.mu.Lock()
st.clock = clock
st.out = output
st.mu.Unlock()
}
func FuncMarker() MarkerGuard {
pc, _, _, ok := runtime.Caller(1)
if !ok {
panic("pdebug.FuncMarker could not determine the name of caller function")
}
f := runtime.FuncForPC(pc)
return Marker(f.Name())
}
var mGuardPool = sync.Pool{
New: allocMGuard,
}
func allocMGuard() interface{} {
return &mGuard{}
}
func getMGuard() *mGuard {
return mGuardPool.Get().(*mGuard)
}
func releaseMGuard(mg *mGuard) {
mg.indent = 0
mg.msgFormat = ""
mg.msgArgs = nil
mGuardPool.Put(mg)
}
func Marker(format string, args ...interface{}) MarkerGuard {
if !Trace {
return nil
}
var clock Clock
var indent int
var prefix []byte
st.mu.RLock()
clock = st.clock
prefix = st.prefix
indent = st.indent
st.mu.RUnlock()
mg := getMGuard()
mg.indent = indent
mg.msgFormat = format
mg.msgArgs = args
if clock := st.clock; clock != nil {
mg.start = clock.Now()
}
var buf []byte
formatMarkerMessage(&buf, "START "+mg.msgFormat, mg.msgArgs, prefix, nil, clock, mg.indent)
st.mu.Lock()
_, _ = st.out.Write(buf)
st.indent += indentPerLevel
st.mu.Unlock()
return mg
}
func (mg *mGuard) BindError(err *error) MarkerGuard {
if !Trace {
return nil
}
mg.errptr = err
return mg
}
func (mg *mGuard) End() {
if !Trace {
return
}
var clock Clock
var prefix []byte
st.mu.Lock()
st.indent -= indentPerLevel
if st.indent < 0 {
st.indent = 0
}
clock = st.clock
prefix = st.prefix
st.mu.Unlock()
var postfix []byte
if clock != nil || mg.errptr != nil {
postfix = append(postfix, '(')
if clock != nil {
postfix = append(postfix, []byte("elapsed=")...)
postfix = append(postfix, []byte(clock.Now().Sub(mg.start).String())...)
}
if errptr := mg.errptr; errptr != nil && *errptr != nil {
if clock != nil {
postfix = append(postfix, ", "...)
}
postfix = append(postfix, "error="...)
postfix = append(postfix, []byte((*errptr).Error())...)
}
postfix = append(postfix, ')')
}
var buf []byte
formatMarkerMessage(&buf, "END "+mg.msgFormat, mg.msgArgs, prefix, postfix, clock, mg.indent)
st.mu.Lock()
_, _ = st.out.Write(buf)
st.mu.Unlock()
releaseMGuard(mg)
}
func formatMarkerMessage(buf *[]byte, format string, args []interface{}, prefix, postfix []byte, clock Clock, indent int) {
// foo\nbar\n should be written as preamble foo\npreamble bar\n
var scratch bytes.Buffer
fmt.Fprintf(&scratch, format, args...)
scanner := bufio.NewScanner(&scratch)
for scanner.Scan() {
appendPreamble(buf, prefix, clock, indent)
line := scanner.Bytes()
*buf = append(*buf, line...)
*buf = append(*buf, postfix...)
*buf = append(*buf, '\n')
}
}
func appendPreamble(buf *[]byte, prefix []byte, clock Clock, indent int) {
*buf = append(*buf, prefix...)
*buf = append(*buf, ' ')
if clock != nil {
*buf = append(*buf,
[]byte(strconv.FormatFloat(float64(clock.Now().UnixNano())/1000000.0, 'f', 5, 64))...,
)
*buf = append(*buf, ' ')
}
for i := 0; i < indent; i++ {
*buf = append(*buf, ' ')
}
}
func Printf(format string, args ...interface{}) {
if !Trace {
return
}
var buf []byte
var clock Clock
var prefix []byte
var indent int
st.mu.RLock()
clock = st.clock
prefix = st.prefix
indent = st.indent
st.mu.RUnlock()
formatMarkerMessage(&buf, format, args, prefix, nil, clock, indent)
st.mu.Lock()
_, _ = st.out.Write(buf)
st.mu.Unlock()
}