1// Copyright © 2016 Steve Francia <spf@spf13.com>.
2//
3// Use of this source code is governed by an MIT-style
4// license that can be found in the LICENSE file.
5
6package jwalterweatherman
7
8import (
9	"fmt"
10	"io"
11	"log"
12)
13
14type Threshold int
15
16func (t Threshold) String() string {
17	return prefixes[t]
18}
19
20const (
21	LevelTrace Threshold = iota
22	LevelDebug
23	LevelInfo
24	LevelWarn
25	LevelError
26	LevelCritical
27	LevelFatal
28)
29
30var prefixes map[Threshold]string = map[Threshold]string{
31	LevelTrace:    "TRACE",
32	LevelDebug:    "DEBUG",
33	LevelInfo:     "INFO",
34	LevelWarn:     "WARN",
35	LevelError:    "ERROR",
36	LevelCritical: "CRITICAL",
37	LevelFatal:    "FATAL",
38}
39
40// Notepad is where you leave a note!
41type Notepad struct {
42	TRACE    *log.Logger
43	DEBUG    *log.Logger
44	INFO     *log.Logger
45	WARN     *log.Logger
46	ERROR    *log.Logger
47	CRITICAL *log.Logger
48	FATAL    *log.Logger
49
50	LOG      *log.Logger
51	FEEDBACK *Feedback
52
53	loggers         [7]**log.Logger
54	logHandle       io.Writer
55	outHandle       io.Writer
56	logThreshold    Threshold
57	stdoutThreshold Threshold
58	prefix          string
59	flags           int
60
61	// One per Threshold
62	logCounters [7]*logCounter
63}
64
65// NewNotepad create a new notepad.
66func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad {
67	n := &Notepad{}
68
69	n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL}
70	n.outHandle = outHandle
71	n.logHandle = logHandle
72	n.stdoutThreshold = outThreshold
73	n.logThreshold = logThreshold
74
75	if len(prefix) != 0 {
76		n.prefix = "[" + prefix + "] "
77	} else {
78		n.prefix = ""
79	}
80
81	n.flags = flags
82
83	n.LOG = log.New(n.logHandle,
84		"LOG:   ",
85		n.flags)
86	n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG}
87
88	n.init()
89	return n
90}
91
92// init creates the loggers for each level depending on the notepad thresholds.
93func (n *Notepad) init() {
94	logAndOut := io.MultiWriter(n.outHandle, n.logHandle)
95
96	for t, logger := range n.loggers {
97		threshold := Threshold(t)
98		counter := &logCounter{}
99		n.logCounters[t] = counter
100		prefix := n.prefix + threshold.String() + " "
101
102		switch {
103		case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
104			*logger = log.New(io.MultiWriter(counter, logAndOut), prefix, n.flags)
105
106		case threshold >= n.logThreshold:
107			*logger = log.New(io.MultiWriter(counter, n.logHandle), prefix, n.flags)
108
109		case threshold >= n.stdoutThreshold:
110			*logger = log.New(io.MultiWriter(counter, n.outHandle), prefix, n.flags)
111
112		default:
113			// counter doesn't care about prefix and flags, so don't use them
114			// for performance.
115			*logger = log.New(counter, "", 0)
116		}
117	}
118}
119
120// SetLogThreshold changes the threshold above which messages are written to the
121// log file.
122func (n *Notepad) SetLogThreshold(threshold Threshold) {
123	n.logThreshold = threshold
124	n.init()
125}
126
127// SetLogOutput changes the file where log messages are written.
128func (n *Notepad) SetLogOutput(handle io.Writer) {
129	n.logHandle = handle
130	n.init()
131}
132
133// GetStdoutThreshold returns the defined Treshold for the log logger.
134func (n *Notepad) GetLogThreshold() Threshold {
135	return n.logThreshold
136}
137
138// SetStdoutThreshold changes the threshold above which messages are written to the
139// standard output.
140func (n *Notepad) SetStdoutThreshold(threshold Threshold) {
141	n.stdoutThreshold = threshold
142	n.init()
143}
144
145// GetStdoutThreshold returns the Treshold for the stdout logger.
146func (n *Notepad) GetStdoutThreshold() Threshold {
147	return n.stdoutThreshold
148}
149
150// SetPrefix changes the prefix used by the notepad. Prefixes are displayed between
151// brackets at the beginning of the line. An empty prefix won't be displayed at all.
152func (n *Notepad) SetPrefix(prefix string) {
153	if len(prefix) != 0 {
154		n.prefix = "[" + prefix + "] "
155	} else {
156		n.prefix = ""
157	}
158	n.init()
159}
160
161// SetFlags choose which flags the logger will display (after prefix and message
162// level). See the package log for more informations on this.
163func (n *Notepad) SetFlags(flags int) {
164	n.flags = flags
165	n.init()
166}
167
168// Feedback writes plainly to the outHandle while
169// logging with the standard extra information (date, file, etc).
170type Feedback struct {
171	out *log.Logger
172	log *log.Logger
173}
174
175func (fb *Feedback) Println(v ...interface{}) {
176	fb.output(fmt.Sprintln(v...))
177}
178
179func (fb *Feedback) Printf(format string, v ...interface{}) {
180	fb.output(fmt.Sprintf(format, v...))
181}
182
183func (fb *Feedback) Print(v ...interface{}) {
184	fb.output(fmt.Sprint(v...))
185}
186
187func (fb *Feedback) output(s string) {
188	if fb.out != nil {
189		fb.out.Output(2, s)
190	}
191	if fb.log != nil {
192		fb.log.Output(2, s)
193	}
194}
195