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  }