1package ext
2
3import (
4	"os"
5	"sync"
6	"sync/atomic"
7	"unsafe"
8
9	log "github.com/inconshreveable/log15"
10)
11
12// EscalateErrHandler wraps another handler and passes all records through
13// unchanged except if the logged context contains a non-nil error
14// value in its context. In that case, the record's level is raised
15// to LvlError unless it was already more serious (LvlCrit).
16//
17// This allows you to log the result of all functions for debugging
18// and still capture error conditions when in production with a single
19// log line. As an example, the following the log record will be written
20// out only if there was an error writing a value to redis:
21//
22//     logger := logext.EscalateErrHandler(
23//         log.LvlFilterHandler(log.LvlInfo, log.StdoutHandler))
24//
25//     reply, err := redisConn.Do("SET", "foo", "bar")
26//     logger.Debug("Wrote value to redis", "reply", reply, "err", err)
27//     if err != nil {
28//         return err
29//     }
30//
31func EscalateErrHandler(h log.Handler) log.Handler {
32	return log.FuncHandler(func(r *log.Record) error {
33		if r.Lvl > log.LvlError {
34			for i := 1; i < len(r.Ctx); i++ {
35				if v, ok := r.Ctx[i].(error); ok && v != nil {
36					r.Lvl = log.LvlError
37					break
38				}
39			}
40		}
41		return h.Log(r)
42	})
43}
44
45// SpeculativeHandler is a handler for speculative logging. It
46// keeps a ring buffer of the given size full of the last events
47// logged into it. When Flush is called, all buffered log records
48// are written to the wrapped handler. This is extremely for
49// continuosly capturing debug level output, but only flushing those
50// log records if an exceptional condition is encountered.
51func SpeculativeHandler(size int, h log.Handler) *Speculative {
52	return &Speculative{
53		handler: h,
54		recs:    make([]*log.Record, size),
55	}
56}
57
58// Speculative is the Log15.Handler. Read `SpeculativeHandler` for more information.
59type Speculative struct {
60	mu      sync.Mutex
61	idx     int
62	recs    []*log.Record
63	handler log.Handler
64	full    bool
65}
66
67// Log implements log15.Handler interface
68func (h *Speculative) Log(r *log.Record) error {
69	h.mu.Lock()
70	defer h.mu.Unlock()
71	h.recs[h.idx] = r
72	h.idx = (h.idx + 1) % len(h.recs)
73	h.full = h.full || h.idx == 0
74	return nil
75}
76
77// Flush logs all records on the handler.
78func (h *Speculative) Flush() {
79	recs := make([]*log.Record, 0)
80	func() {
81		h.mu.Lock()
82		defer h.mu.Unlock()
83		if h.full {
84			recs = append(recs, h.recs[h.idx:]...)
85		}
86		recs = append(recs, h.recs[:h.idx]...)
87
88		// reset state
89		h.full = false
90		h.idx = 0
91	}()
92
93	// don't hold the lock while we flush to the wrapped handler
94	for _, r := range recs {
95		h.handler.Log(r)
96	}
97}
98
99// HotSwapHandler wraps another handler that may swapped out
100// dynamically at runtime in a thread-safe fashion.
101// HotSwapHandler is the same functionality
102// used to implement the SetHandler method for the default
103// implementation of Logger.
104func HotSwapHandler(h log.Handler) *HotSwap {
105	hs := new(HotSwap)
106	hs.Swap(h)
107	return hs
108}
109
110// HotSwap is the Log15.Handler. Read `HotSwapHandler` for more information.
111type HotSwap struct {
112	handler unsafe.Pointer
113}
114
115// Log implements log15.Handler interface.
116func (h *HotSwap) Log(r *log.Record) error {
117	return (*(*log.Handler)(atomic.LoadPointer(&h.handler))).Log(r)
118}
119
120// Swap atomically the logger handler.
121func (h *HotSwap) Swap(newHandler log.Handler) {
122	atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler))
123}
124
125// FatalHandler makes critical errors exit the program
126// immediately, much like the log.Fatal* methods from the
127// standard log package
128func FatalHandler(h log.Handler) log.Handler {
129	return log.FuncHandler(func(r *log.Record) error {
130		err := h.Log(r)
131		if r.Lvl == log.LvlCrit {
132			os.Exit(1)
133		}
134		return err
135	})
136}
137