-
Notifications
You must be signed in to change notification settings - Fork 0
/
logSubsystem.go
202 lines (165 loc) · 6.06 KB
/
logSubsystem.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
package logger
import (
"io"
"os"
"strings"
"sync"
)
var logMut = &sync.RWMutex{}
var loggers map[string]*logger
var defaultLogOut LogOutputHandler
var defaultLogLevel = LogInfo
var logPattern = ""
var withLoggerName bool
var mutDisplayByteSlice = &sync.RWMutex{}
var displayByteSlice func(slice []byte) string
func init() {
logPattern = "*:INFO"
loggers = make(map[string]*logger)
defaultLogOut = NewLogOutputSubject()
_ = defaultLogOut.AddObserver(os.Stdout, &ConsoleFormatter{})
displayByteSlice = ToHex
}
// GetOrCreate returns a log based on the name provided, generating a new log if there is no log with provided name
func GetOrCreate(name string) *logger {
logMut.Lock()
defer logMut.Unlock()
loggerFromMap, ok := loggers[name]
if !ok {
loggerFromMap = NewLogger(name, defaultLogLevel, defaultLogOut)
loggers[name] = loggerFromMap
}
return loggerFromMap
}
// SetLogLevel changes the log level of the contained loggers. The expected format is
// "MATCHING_STRING1:LOG_LEVEL1,MATCHING_STRING2:LOG_LEVEL2".
// If matching string is *, it will change the log levels of all contained loggers and will also set the
// defaultLogLevelProperty. Otherwise, the log level will be modified only on those loggers that will contain the
// matching string on any position.
// For example, having the parameter "DEBUG|process" will set the DEBUG level on all loggers that will contain
// the "process" string in their name ("process/sync", "process/interceptors", "process" and so on).
// The rules are applied in the exact manner as they are provided, starting from left to the right part of the string
// Example: *:INFO,p2p:ERROR,*:DEBUG,data:INFO will result in having the data package logger(s) on INFO log level
// and all other packages on DEBUG level
func SetLogLevel(logLevelAndPattern string) error {
logLevels, patterns, err := ParseLogLevelAndMatchingString(logLevelAndPattern)
if err != nil {
return err
}
logMut.Lock()
setLogLevelOnMap(loggers, &defaultLogLevel, logLevels, patterns)
logPattern = logLevelAndPattern
logMut.Unlock()
return nil
}
// GetLogLevelPattern returns the last set log level pattern.
// The format returned is MATCHING_STRING1:LOG_LEVEL1,MATCHING_STRING2:LOG_LEVEL2".
func GetLogLevelPattern() string {
logMut.RLock()
defer logMut.RUnlock()
return logPattern
}
// GetLoggerLogLevel gets the log level of the specified logger
func GetLoggerLogLevel(loggerName string) LogLevel {
logMut.RLock()
defer logMut.RUnlock()
loggerFromMap, ok := loggers[loggerName]
if !ok {
return LogNone
}
logLevel := loggerFromMap.GetLevel()
return logLevel
}
// ToggleLoggerName enables / disables logger name
func ToggleLoggerName(enable bool) {
logMut.Lock()
withLoggerName = enable
logMut.Unlock()
}
// IsEnabledLoggerName returns whether logger name is enabled
func IsEnabledLoggerName() bool {
logMut.RLock()
withLogName := withLoggerName
logMut.RUnlock()
return withLogName
}
// GetLogOutputSubject returns the default log output subject
func GetLogOutputSubject() LogOutputHandler {
return defaultLogOut
}
// AddLogObserver adds a new observer (writer + formatter) to the already built-in log observers queue
// This method is useful when adding a new output device for logs is needed (such as files, streams, API routes and so on)
func AddLogObserver(w io.Writer, formatter Formatter) error {
return defaultLogOut.AddObserver(w, formatter)
}
// RemoveLogObserver removes an exiting observer by providing the writer pointer.
func RemoveLogObserver(w io.Writer) error {
return defaultLogOut.RemoveObserver(w)
}
// ClearLogObservers clears the observers lists
func ClearLogObservers() {
defaultLogOut.ClearObservers()
}
func setLogLevelOnMap(loggers map[string]*logger, dest *LogLevel, logLevels []LogLevel, patterns []string) {
for i := 0; i < len(logLevels); i++ {
pattern := patterns[i]
logLevel := logLevels[i]
for name, log := range loggers {
isMatching := pattern == "*" || strings.Contains(name, pattern)
if isMatching {
log.SetLevel(logLevel)
}
}
if pattern == "*" {
*dest = logLevel
}
}
}
// ParseLogLevelAndMatchingString can parse a string in the form "MATCHING_STRING1:LOG_LEVEL1,MATCHING_STRING2:LOG_LEVEL2" into its
// corresponding log level and matching string. Errors if something goes wrong.
// For example, having the parameter "DEBUG|process" will set the DEBUG level on all loggers that will contain
// the "process" string in their name ("process/sync", "process/interceptors", "process" and so on).
// The rules are applied in the exact manner as they are provided, starting from left to the right part of the string
// Example: *:INFO,p2p:ERROR,*:DEBUG,data:INFO will result in having the data package logger(s) on INFO log level
// and all other packages on DEBUG level
func ParseLogLevelAndMatchingString(logLevelAndPatterns string) ([]LogLevel, []string, error) {
splitLevelPatterns := strings.Split(logLevelAndPatterns, ",")
levels := make([]LogLevel, len(splitLevelPatterns))
patterns := make([]string, len(splitLevelPatterns))
for i, levelPattern := range splitLevelPatterns {
level, pattern, err := parseLevelPattern(levelPattern)
if err != nil {
return nil, nil, err
}
levels[i] = level
patterns[i] = pattern
}
return levels, patterns, nil
}
func parseLevelPattern(logLevelAndPattern string) (LogLevel, string, error) {
input := strings.Split(logLevelAndPattern, ":")
if len(input) != 2 {
return LogTrace, "", ErrInvalidLogLevelPattern
}
logLevel, err := GetLogLevel(input[1])
return logLevel, input[0], err
}
// SetDisplayByteSlice sets the converter function from byte slice to string
// default, this will call hex.EncodeToString
func SetDisplayByteSlice(f func(slice []byte) string) error {
if f == nil {
return ErrNilDisplayByteSliceHandler
}
mutDisplayByteSlice.Lock()
displayByteSlice = f
mutDisplayByteSlice.Unlock()
return nil
}
// DisplayByteSlice converts the provided byte slice to its string representation using
// displayByteSlice function pointer
func DisplayByteSlice(slice []byte) string {
mutDisplayByteSlice.RLock()
f := displayByteSlice
mutDisplayByteSlice.RUnlock()
return f(slice)
}