forked from balacode/udpt
/
log.go
148 lines (132 loc) · 4.38 KB
/
log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// -----------------------------------------------------------------------------
// github.com/balacode/udpt /[log.go]
// (c) balarabe@protonmail.com License: MIT
// -----------------------------------------------------------------------------
package udpt
import (
"fmt"
"io"
"os"
"strings"
"time"
)
// logChanSize specifies the number of log messages buffered in logChan.
//
// To disable log buffering, set it to 1. This can be useful if you want
// to see log messages in-order with code execution, not after the fact.
// But this will slow it down while it waits for log messages to be written.
//
const logChanSize = 1024
// logTimeNow should always be set to time.Now,
// except when mocking during testing.
var logTimeNow = time.Now
// logWriter provides a buffered logger that outputs
// to standard output and/or a log file.
type logWriter struct {
Print bool
LogFile string
// logChan is the channel into which log messages are sent.
logChan chan logEntry
}
// MakeLogWriter is a convenience function that creates and returns
// a Writer to use for logging during developing or debugging.
//
// You can assign this function to Config.LogFunc:
// E.g. sender.Config.LogWriter = MakeLogWriter(true, "udpt.log")
//
// printMsg determines if each log entry should be printed to standard output.
//
// logFile specifies the log file into which to append.
//
func MakeLogWriter(printMsg bool, logFile string) io.Writer {
return &logWriter{Print: printMsg, LogFile: logFile}
} // MakeLogWriter
// Write writes 'b' to the log and implements io.Writer
func (lw *logWriter) Write(b []byte) (n int, err error) {
lw.logEnter(string(b))
return len(b), nil
}
// initChan initializes the logging queue (logChan) and launches
// a goroutine that receives and outputs log messages.
func (lw *logWriter) initChan() {
lw.logChan = make(chan logEntry, logChanSize)
go func() {
for entry := range lw.logChan {
entry.Output()
}
}()
} // initChan
// logEnter sends a log message to log channel 'logChan'.
// Prefixes each line in the message with a timestamp.
//
// If logChanSize is 1, outputs the message immediately.
func (lw *logWriter) logEnter(msg string) {
//
// prefix each line with a timestamp
tms := logTimeNow().String()[:19]
lines := strings.Split(msg, "\n")
for i, line := range lines {
lines[i] = tms + " " + line
}
msg = strings.Join(lines, "\n")
entry := logEntry{
printMsg: lw.Print,
logFile: lw.LogFile,
msg: msg,
}
if lw.logChan == nil {
lw.initChan()
}
lw.logChan <- entry
} // logEnter
// -----------------------------------------------------------------------------
// logEntry contains a message to be printed and/or written to a log file.
type logEntry struct {
printMsg bool
logFile string
msg string
} // logEntry
// Output immediately prints msg to standard output and if
// logFile is not blank, appends the message to logFile.
func (le *logEntry) Output() {
le.outputDI(os.Stdout, openLogFile)
} // Output
// outputDI is only used by Output() and provides parameters
// for dependency injection, to enable mocking during testing.
func (le *logEntry) outputDI(
dest io.Writer,
openLogFile func(filename string) (io.WriteCloser, error),
) {
if le.printMsg {
fmt.Fprintln(dest, le.msg)
}
path := le.logFile
if path == "" {
return
}
wr, err := openLogFile(path)
if err != nil {
fmt.Fprintln(dest, err)
return
}
if wr == nil {
return
}
n, err := wr.Write([]byte(le.msg + "\n"))
if n == 0 || err != nil {
fmt.Fprintln(dest, "ERROR 0xE81F3D:", err)
}
err = wr.Close()
if err != nil {
fmt.Fprintln(dest, "ERROR 0xE50F96:", err)
}
} // outputDI
// openLogFile opens a file for outputDI().
func openLogFile(filename string) (io.WriteCloser, error) {
fl, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, makeError(0xE2DA59, err)
}
return fl, nil
} // openLogFile
// end