github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/audit/audit.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package audit 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/wiggin77/logr" 11 "github.com/wiggin77/logr/format" 12 ) 13 14 type Level logr.Level 15 16 type Audit struct { 17 lgr *logr.Logr 18 logger logr.Logger 19 20 // OnQueueFull is called on an attempt to add an audit record to a full queue. 21 // On return the calling goroutine will block until the audit record can be added. 22 OnQueueFull func(qname string, maxQueueSize int) 23 24 // OnError is called when an error occurs while writing an audit record. 25 OnError func(err error) 26 } 27 28 func (a *Audit) Init(maxQueueSize int) { 29 a.lgr = &logr.Logr{MaxQueueSize: maxQueueSize} 30 a.logger = a.lgr.NewLogger() 31 32 a.lgr.OnQueueFull = a.onQueueFull 33 a.lgr.OnTargetQueueFull = a.onTargetQueueFull 34 a.lgr.OnLoggerError = a.onLoggerError 35 } 36 37 // MakeFilter creates a filter which only allows the specified audit levels to be output. 38 func (a *Audit) MakeFilter(level ...Level) *logr.CustomFilter { 39 filter := &logr.CustomFilter{} 40 for _, l := range level { 41 filter.Add(logr.Level(l)) 42 } 43 return filter 44 } 45 46 // MakeJSONFormatter creates a formatter that outputs JSON suitable for audit records. 47 func (a *Audit) MakeJSONFormatter() *format.JSON { 48 f := &format.JSON{ 49 DisableTimestamp: true, 50 DisableMsg: true, 51 DisableStacktrace: true, 52 DisableLevel: true, 53 ContextSorter: sortAuditFields, 54 } 55 return f 56 } 57 58 // LogRecord emits an audit record with complete info. 59 func (a *Audit) LogRecord(level Level, rec Record) { 60 flds := logr.Fields{} 61 flds[KeyAPIPath] = rec.APIPath 62 flds[KeyEvent] = rec.Event 63 flds[KeyStatus] = rec.Status 64 flds[KeyUserID] = rec.UserID 65 flds[KeySessionID] = rec.SessionID 66 flds[KeyClient] = rec.Client 67 flds[KeyIPAddress] = rec.IPAddress 68 69 for k, v := range rec.Meta { 70 flds[k] = v 71 } 72 73 l := a.logger.WithFields(flds) 74 l.Log(logr.Level(level)) 75 } 76 77 // Log emits an audit record based on minimum required info. 78 func (a *Audit) Log(level Level, path string, evt string, status string, userID string, sessionID string, meta Meta) { 79 a.LogRecord(level, Record{ 80 APIPath: path, 81 Event: evt, 82 Status: status, 83 UserID: userID, 84 SessionID: sessionID, 85 Meta: meta, 86 }) 87 } 88 89 // AddTarget adds a Logr target to the list of targets each audit record will be output to. 90 func (a *Audit) AddTarget(target logr.Target) { 91 a.lgr.AddTarget(target) 92 } 93 94 // Shutdown cleanly stops the audit engine after making best efforts to flush all targets. 95 func (a *Audit) Shutdown() { 96 err := a.lgr.Shutdown() 97 if err != nil { 98 a.onLoggerError(err) 99 } 100 } 101 102 func (a *Audit) onQueueFull(rec *logr.LogRec, maxQueueSize int) bool { 103 if a.OnQueueFull != nil { 104 a.OnQueueFull("main", maxQueueSize) 105 } 106 // block until record can be added. 107 return false 108 } 109 110 func (a *Audit) onTargetQueueFull(target logr.Target, rec *logr.LogRec, maxQueueSize int) bool { 111 if a.OnQueueFull != nil { 112 a.OnQueueFull(fmt.Sprintf("%v", target), maxQueueSize) 113 } 114 // block until record can be added. 115 return false 116 } 117 118 func (a *Audit) onLoggerError(err error) { 119 if a.OnError != nil { 120 a.OnError(err) 121 } 122 } 123 124 // sortAuditFields sorts the context fields of an audit record such that some fields 125 // are prepended in order, some are appended in order, and the rest are sorted alphabetically. 126 // This is done to make reading the records easier since common fields will appear in the same order. 127 func sortAuditFields(fields logr.Fields) []format.ContextField { 128 prependKeys := []string{KeyEvent, KeyStatus, KeyUserID, KeySessionID, KeyIPAddress} 129 appendKeys := []string{KeyClusterID, KeyClient} 130 131 // sort alphabetically any fields not in the prepend/append lists. 132 keys := make([]string, 0, len(fields)) 133 for k := range fields { 134 if !findIn(k, prependKeys, appendKeys) { 135 keys = append(keys, k) 136 } 137 } 138 sort.Strings(keys) 139 140 allKeys := make([]string, 0, len(fields)) 141 142 // add any prepends that exist in fields 143 for _, k := range prependKeys { 144 if _, ok := fields[k]; ok { 145 allKeys = append(allKeys, k) 146 } 147 } 148 149 // sorted 150 allKeys = append(allKeys, keys...) 151 152 // add any appends that exist in fields 153 for _, k := range appendKeys { 154 if _, ok := fields[k]; ok { 155 allKeys = append(allKeys, k) 156 } 157 } 158 159 cfs := make([]format.ContextField, 0, len(allKeys)) 160 for _, k := range allKeys { 161 cfs = append(cfs, format.ContextField{Key: k, Val: fields[k]}) 162 } 163 return cfs 164 } 165 166 func findIn(s string, arrs ...[]string) bool { 167 for _, list := range arrs { 168 for _, key := range list { 169 if s == key { 170 return true 171 } 172 } 173 } 174 return false 175 }