-
Notifications
You must be signed in to change notification settings - Fork 43
/
logging.go
196 lines (172 loc) · 6.54 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
package pango
import (
"fmt"
"log/slog"
"math/bits"
)
// LogCategory is a bitmask describing what categories of logging to enable.
//
// Available bit-wise flags are as follows:
//
// - LogCategoryPango: Basic library-level logging
// - LogCategoryOp: Logging of operation commands (Op functions)
// - LogCategorySend: Logging of the data being sent to the server. All sensitive
// data will be scrubbed from the response unless LogCategorySensitive
// is explicitly added to the mask
// - LogCategoryReceive: Logging of the data being received from the server. All
// sensitive data will be scrubbed from the response unless LogCategorySensitive
// is explicitly added to the mask
// - LogCategoryCurl: When used along with LogCategorySend, an equivalent curl
// command will be logged
// - LogCategoryAll: A meta-category, enabling all above categories at once
// - LogCategorySensitive: Logging of sensitive data like hostnames, logins,
// passwords or API keys of any sort
type LogCategory uint
const (
LogCategoryPango LogCategory = 1 << iota
LogCategoryOp
LogCategorySend
LogCategoryReceive
LogCategoryCurl
LogCategoryAll = LogCategoryPango | LogCategoryOp | LogCategorySend |
LogCategoryReceive | LogCategoryCurl
// Make sure that LogCategorySensitive is always last, explicitly set to 1 << 32
LogCategorySensitive LogCategory = 1 << 16
)
var logCategoryToString = map[LogCategory]string{
LogCategoryPango: "pango",
LogCategoryOp: "op",
LogCategorySend: "send",
LogCategoryReceive: "receive",
LogCategoryCurl: "curl",
LogCategoryAll: "all",
LogCategorySensitive: "sensitive",
}
func createStringToCategoryMap(categories map[LogCategory]string) map[string]LogCategory {
// Instead of keeping two maps for two way association, we
// just generate reversed map on the fly. This function is not
// going to be used outside of the initial library setup, so
// the slight performance penalty is not an issue.
stringsMap := make(map[string]LogCategory, len(logCategoryToString))
for category, sym := range logCategoryToString {
stringsMap[sym] = category
}
return stringsMap
}
// LogCategoryFromStrings transforms list with categories into its bitmask equivalent.
//
// This function takes a list of strings, representing log categories
// that can be used to filter what gets logged by pango library. This list
// can change over time as more categories are added to the library.
//
// It returns LogCategory bitmask which can be then used to configure
// logger. If unknown log category string is given as part of the
// list, error is returned instead.
func LogCategoryFromStrings(symbols []string) (LogCategory, error) {
stringsMap := createStringToCategoryMap(logCategoryToString)
var logCategoriesMask LogCategory
for _, elt := range symbols {
category, ok := stringsMap[elt]
if !ok {
return 0, fmt.Errorf("unknown log category: %s", elt)
}
logCategoriesMask |= category
slog.Info("logCategoriesMask", "equal", logCategoriesMask)
}
return logCategoriesMask, nil
}
// LogCategoryAsStrings interprets given LogCategory bitmask into its string representation.
//
// This function takes LogCategory bitmask as argument, and converts
// it into a list of strings, where each element represents a single
// category. LogCategoryAll is converted into a list of enabled
// categories, without "all".
//
// It returns a list of categories as strings, or error if invalid
// LogCategory mask has been provided.
func LogCategoryAsStrings(categories LogCategory) ([]string, error) {
symbols := make([]string, 0)
// Calculate a number of high bits in the categories mask, to make
// sure all categories other than LogCategoryAll have been matched.
highBits := bits.OnesCount(uint(categories))
// Iterate over all available log categories, skipping
// LogCategoryAll as we can't distinguish between explicitly
// ORing all LogCategories and using LogCategoryAll.
for key, value := range logCategoryToString {
if key == LogCategoryAll {
continue
}
if categories&key == key {
symbols = append(symbols, value)
}
}
// Return an error if number of high bits in the categories
// mask is lower than length of the symbols list
if len(symbols) < highBits && (categories&LogCategoryAll != LogCategoryAll) {
return nil, fmt.Errorf("invalid LogCategory bitmask")
}
return symbols, nil
}
// LogCategoryToSymbol returns string representation of the given LogCategory
//
// The given LogCategory can only have single bit set high, and cannot
// match LogCategoryAll. To convert LogCategory bitmask into a list of categories,
// use LogCategoryToStrings instead.
//
// It returns string representation of the log category, or error if
// unknown category has been provided.
func LogCategoryToString(category LogCategory) (string, error) {
if category&LogCategoryAll == LogCategoryAll {
return "", fmt.Errorf("cannot convert LogCategoryAll into a category string.")
}
symbol, ok := logCategoryToString[category]
if ok {
return symbol, nil
}
return "", fmt.Errorf("unknown LogCategory: %d", category)
}
// StringToLogCategory returns LogCategory mask matching given string category.
//
// The given string should be a single category, and not "all". To convert "all"
// into a list of enabled log categories, use LogCategoryFromStrings.
//
// It returns LogCategory representation of the given category string, or en
// error if either "all" or unknown string has been given.
func StringToLogCategory(sym string) (LogCategory, error) {
if sym == logCategoryToString[LogCategoryAll] {
return 0, fmt.Errorf("cannot convert \"all\" category string into LogCategory")
}
for key, value := range logCategoryToString {
if value == sym {
return key, nil
}
}
return 0, fmt.Errorf("Unknown logging symbol: %s", sym)
}
type categoryLogger struct {
logger *slog.Logger
discardLogger *slog.Logger
categories LogCategory
}
func newCategoryLogger(logger *slog.Logger, categories LogCategory) *categoryLogger {
return &categoryLogger{
logger: logger,
discardLogger: slog.New(discardHandler{}),
categories: categories,
}
}
func (l *categoryLogger) WithLogCategory(category LogCategory) *slog.Logger {
matched, ok := logCategoryToString[category]
// If the category cannot be matched, instead of returning
// error we use "unknown" instead.
if !ok {
matched = "unknown"
}
if l.categories&category == category {
return l.logger.WithGroup(matched)
}
return l.discardLogger.WithGroup(matched)
}
func (l *categoryLogger) enabledFor(category LogCategory) bool {
return l.categories&category == category
}