Skip to content

Commit 84c8d23

Browse files
committed
Add flags mechanism and callsite options.
Flags have been added to include the file name/path and line number of the log callsite in all log messages. Flags can be configured using the LOGFLAGS environment variable as well as explicitly setting the flags on a backend in code using WithFlags. Inspired by the Lshortfile/Llongfile flags for the stdlib log package.
1 parent 30bef3d commit 84c8d23

File tree

2 files changed

+112
-8
lines changed

2 files changed

+112
-8
lines changed

doc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,15 @@ The default implementation in this package must be created by the Backend type.
1313
Backends can write to any io.Writer, including multi-writers created by
1414
io.MultiWriter. Multi-writers allow log output to be written to many writers,
1515
including standard output and log files.
16+
17+
Optional logging behavior can be specified by using the LOGFLAGS environment
18+
variable and overridden per-Backend by using the WithFlags call option. Multiple
19+
LOGFLAGS options can be specified, separated by commas. The following options
20+
are recognized:
21+
22+
longfile: Include the full filepath and line number in all log messages
23+
24+
shortfile: Include the filename and line number in all log messages.
25+
Overrides longfile.
1626
*/
1727
package btclog

log.go

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,43 @@ import (
3737
"fmt"
3838
"io"
3939
"io/ioutil"
40+
"os"
41+
"runtime"
4042
"strings"
4143
"sync"
4244
"sync/atomic"
4345
"time"
4446
)
4547

48+
// defaultFlags specifies changes to the default logger behavior. It is set
49+
// during package init and configured using the LOGFLAGS environment variable.
50+
// New logger backends can override these default flags using WithFlags.
51+
var defaultFlags uint32
52+
53+
// Flags to modify Backend's behavior.
54+
const (
55+
// Llongfile modifies the logger output to include full path and line number
56+
// of the logging callsite, e.g. /a/b/c/main.go:123.
57+
Llongfile uint32 = 1 << iota
58+
59+
// Lshortfile modifies the logger output to include filename and line number
60+
// of the logging callsite, e.g. main.go:123. Overrides Llongfile.
61+
Lshortfile
62+
)
63+
64+
// Read logger flags from the LOGFLAGS environment variable. Multiple flags can
65+
// be set at once, separated by commas.
66+
func init() {
67+
for _, f := range strings.Split(os.Getenv("LOGFLAGS"), ",") {
68+
switch f {
69+
case "longfile":
70+
defaultFlags |= Llongfile
71+
case "shortfile":
72+
defaultFlags |= Lshortfile
73+
}
74+
}
75+
}
76+
4677
// Level is the level at which a logger is configured. All messages sent
4778
// to a level which is below the current level are filtered.
4879
type Level uint32
@@ -95,16 +126,33 @@ func (l Level) String() string {
95126
}
96127

97128
// NewBackend creates a logger backend from a Writer.
98-
func NewBackend(w io.Writer) *Backend {
99-
return &Backend{w: w}
129+
func NewBackend(w io.Writer, opts ...BackendOption) *Backend {
130+
b := &Backend{w: w, flag: defaultFlags}
131+
for _, o := range opts {
132+
o(b)
133+
}
134+
return b
100135
}
101136

102137
// Backend is a logging backend. Subsystems created from the backend write to
103138
// the backend's Writer. Backend provides atomic writes to the Writer from all
104139
// subsystems.
105140
type Backend struct {
106-
w io.Writer
107-
mu sync.Mutex // ensures atomic writes
141+
w io.Writer
142+
mu sync.Mutex // ensures atomic writes
143+
flag uint32
144+
}
145+
146+
// BackendOption is a function used to modify the behavior of a Backend.
147+
type BackendOption func(b *Backend)
148+
149+
// WithFlags configures a Backend to use the specified flags rather than using
150+
// the package's defaults as determined through the LOGFLAGS environment
151+
// variable.
152+
func WithFlags(flags uint32) BackendOption {
153+
return func(b *Backend) {
154+
b.flag = flags
155+
}
108156
}
109157

110158
// bufferPool defines a concurrent safe free list of byte slices used to provide
@@ -150,8 +198,10 @@ func itoa(buf *[]byte, i int, wid int) {
150198
*buf = append(*buf, b[bp:]...)
151199
}
152200

153-
// Appends a header in the format 'YYYY-MM-DD hh:mm:ss.sss [LVL] TAG: '.
154-
func formatHeader(buf *[]byte, t time.Time, lvl, tag string) {
201+
// Appends a header in the default format 'YYYY-MM-DD hh:mm:ss.sss [LVL] TAG: '.
202+
// If either of the Lshortfile or Llongfile flags are specified, the file named
203+
// and line number are included after the tag and before the final colon.
204+
func formatHeader(buf *[]byte, t time.Time, lvl, tag string, file string, line int) {
155205
year, month, day := t.Date()
156206
hour, min, sec := t.Clock()
157207
ms := t.Nanosecond() / 1e6
@@ -173,9 +223,41 @@ func formatHeader(buf *[]byte, t time.Time, lvl, tag string) {
173223
*buf = append(*buf, lvl...)
174224
*buf = append(*buf, "] "...)
175225
*buf = append(*buf, tag...)
226+
if file != "" {
227+
*buf = append(*buf, ' ')
228+
*buf = append(*buf, file...)
229+
*buf = append(*buf, ':')
230+
itoa(buf, line, -1)
231+
}
176232
*buf = append(*buf, ": "...)
177233
}
178234

235+
// calldepth is the call depth of the callsite function relative to the
236+
// caller of the subsystem logger. It is used to recover the filename and line
237+
// number of the logging call if either the short or long file flags are
238+
// specified.
239+
const calldepth = 3
240+
241+
// callsite returns the file name and line number of the callsite to the
242+
// subsystem logger.
243+
func callsite(flag uint32) (string, int) {
244+
_, file, line, ok := runtime.Caller(calldepth)
245+
if !ok {
246+
return "???", 0
247+
}
248+
if flag&Lshortfile != 0 {
249+
short := file
250+
for i := len(file) - 1; i > 0; i-- {
251+
if os.IsPathSeparator(file[i]) {
252+
short = file[i+1:]
253+
break
254+
}
255+
}
256+
file = short
257+
}
258+
return file, line
259+
}
260+
179261
// print outputs a log message to the writer associated with the backend after
180262
// creating a prefix for the given level and tag according to the formatHeader
181263
// function and formatting the provided arguments using the default formatting
@@ -185,7 +267,13 @@ func (b *Backend) print(lvl, tag string, args ...interface{}) {
185267

186268
bytebuf := buffer()
187269

188-
formatHeader(bytebuf, t, lvl, tag)
270+
var file string
271+
var line int
272+
if b.flag&(Lshortfile|Llongfile) != 0 {
273+
file, line = callsite(b.flag)
274+
}
275+
276+
formatHeader(bytebuf, t, lvl, tag, file, line)
189277
buf := bytes.NewBuffer(*bytebuf)
190278
fmt.Fprintln(buf, args...)
191279
*bytebuf = buf.Bytes()
@@ -206,7 +294,13 @@ func (b *Backend) printf(lvl, tag string, format string, args ...interface{}) {
206294

207295
bytebuf := buffer()
208296

209-
formatHeader(bytebuf, t, lvl, tag)
297+
var file string
298+
var line int
299+
if b.flag&(Lshortfile|Llongfile) != 0 {
300+
file, line = callsite(b.flag)
301+
}
302+
303+
formatHeader(bytebuf, t, lvl, tag, file, line)
210304
buf := bytes.NewBuffer(*bytebuf)
211305
fmt.Fprintf(buf, format, args...)
212306
*bytebuf = append(buf.Bytes(), '\n')

0 commit comments

Comments
 (0)