forked from etcd-io/etcd
/
logging.go
259 lines (226 loc) · 7.12 KB
/
logging.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2013, Cong Ding. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// author: Cong Ding <dinggnu@gmail.com>
// Package logging implements log library for other applications. It provides
// functions Debug, Info, Warning, Error, Critical, and formatting version
// Logf.
//
// Example:
//
// logger := logging.SimpleLogger("main")
// logger.SetLevel(logging.WARNING)
// logger.Error("test for error")
// logger.Warning("test for warning", "second parameter")
// logger.Debug("test for debug")
//
package logging
import (
"github.com/ccding/go-config-reader/config"
"io"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
)
// Pre-defined formats
const (
DefaultFileName = "logging.log" // default logging filename
DefaultConfigFile = "logging.conf" // default logging configuration file
DefaultTimeFormat = "2006-01-02 15:04:05.999999999" // defaulttime format
bufSize = 1000 // buffer size for writer
queueSize = 10000 // chan queue size in async logging
reqSize = 10000 // chan queue size in async logging
)
// Logger is the logging struct.
type Logger struct {
// Be careful of the alignment issue of the variable seqid because it
// uses the sync/atomic.AddUint64() operation. If the alignment is
// wrong, it will cause a panic. To solve the alignment issue in an
// easy way, we put seqid to the beginning of the structure.
// seqid is only visiable internally.
seqid uint64 // last used sequence number in record
// These variables can be configured by users.
name string // logger name
level Level // record level higher than this will be printed
recordFormat string // format of the record
recordArgs []string // arguments to be used in the recordFormat
out io.Writer // writer
sync bool // use sync or async way to record logs
timeFormat string // format for time
// These variables are visible to users.
startTime time.Time // start time of the logger
// Internally used variables, which don't have get and set functions.
wlock sync.Mutex // writer lock
queue chan string // queue used in async logging
request chan request // queue used in non-runtime logging
flush chan bool // flush signal for the watcher to write
quit chan bool // quit signal for the watcher to quit
fd *os.File // file handler, used to close the file on destroy
runtime bool // with runtime operation or not
}
// SimpleLogger creates a new logger with simple configuration.
func SimpleLogger(name string) (*Logger, error) {
return createLogger(name, WARNING, BasicFormat, DefaultTimeFormat, os.Stdout, false)
}
// BasicLogger creates a new logger with basic configuration.
func BasicLogger(name string) (*Logger, error) {
return FileLogger(name, WARNING, BasicFormat, DefaultTimeFormat, DefaultFileName, false)
}
// RichLogger creates a new logger with simple configuration.
func RichLogger(name string) (*Logger, error) {
return FileLogger(name, NOTSET, RichFormat, DefaultTimeFormat, DefaultFileName, false)
}
// FileLogger creates a new logger with file output.
func FileLogger(name string, level Level, format string, timeFormat string, file string, sync bool) (*Logger, error) {
out, err := os.Create(file)
if err != nil {
return nil, err
}
logger, err := createLogger(name, level, format, timeFormat, out, sync)
if err == nil {
logger.fd = out
return logger, nil
} else {
return nil, err
}
}
// WriterLogger creates a new logger with a writer
func WriterLogger(name string, level Level, format string, timeFormat string, out io.Writer, sync bool) (*Logger, error) {
return createLogger(name, level, format, timeFormat, out, sync)
}
// WriterLogger creates a new logger from a configuration file
func ConfigLogger(filename string) (*Logger, error) {
conf := config.NewConfig(filename)
err := conf.Read()
if err != nil {
return nil, err
}
name := conf.Get("", "name")
slevel := conf.Get("", "level")
if slevel == "" {
slevel = "0"
}
l, err := strconv.Atoi(slevel)
if err != nil {
return nil, err
}
level := Level(l)
format := conf.Get("", "format")
if format == "" {
format = BasicFormat
}
timeFormat := conf.Get("", "timeFormat")
if timeFormat == "" {
timeFormat = DefaultTimeFormat
}
ssync := conf.Get("", "sync")
if ssync == "" {
ssync = "0"
}
file := conf.Get("", "file")
if file == "" {
file = DefaultFileName
}
sync := true
if ssync == "0" {
sync = false
} else if ssync == "1" {
sync = true
} else {
return nil, err
}
return FileLogger(name, level, format, timeFormat, file, sync)
}
// createLogger create a new logger
func createLogger(name string, level Level, format string, timeFormat string, out io.Writer, sync bool) (*Logger, error) {
logger := new(Logger)
err := logger.parseFormat(format)
if err != nil {
return nil, err
}
// asign values to logger
logger.name = name
logger.level = level
logger.out = out
logger.seqid = 0
logger.sync = sync
logger.queue = make(chan string, queueSize)
logger.request = make(chan request, reqSize)
logger.flush = make(chan bool)
logger.quit = make(chan bool)
logger.startTime = time.Now()
logger.fd = nil
logger.timeFormat = timeFormat
// start watcher to write logs if it is async or no runtime field
if !logger.sync {
go logger.watcher()
}
return logger, nil
}
// Destroy sends quit signal to watcher and releases all the resources.
func (logger *Logger) Destroy() {
if !logger.sync {
// quit watcher
logger.quit <- true
// wait for watcher quit
<-logger.quit
}
// clean up
if logger.fd != nil {
logger.fd.Close()
}
}
// Flush the writer
func (logger *Logger) Flush() {
if !logger.sync {
// send flush signal
logger.flush <- true
// wait for flush finish
<-logger.flush
}
}
// Getter functions
func (logger *Logger) Name() string {
return logger.name
}
func (logger *Logger) StartTime() int64 {
return logger.startTime.UnixNano()
}
func (logger *Logger) TimeFormat() string {
return logger.timeFormat
}
func (logger *Logger) Level() Level {
return Level(atomic.LoadInt32((*int32)(&logger.level)))
}
func (logger *Logger) RecordFormat() string {
return logger.recordFormat
}
func (logger *Logger) RecordArgs() []string {
return logger.recordArgs
}
func (logger *Logger) Writer() io.Writer {
return logger.out
}
func (logger *Logger) Sync() bool {
return logger.sync
}
// Setter functions
func (logger *Logger) SetLevel(level Level) {
atomic.StoreInt32((*int32)(&logger.level), int32(level))
}
func (logger *Logger) SetWriter(out ...io.Writer) {
logger.out = io.MultiWriter(out...)
}