go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/logging/memlogger/memory.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memlogger
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"reflect"
    23  	"sync"
    24  
    25  	"go.chromium.org/luci/common/logging"
    26  )
    27  
    28  // LogEntry is a single entry in a MemLogger, containing a message and a
    29  // severity.
    30  type LogEntry struct {
    31  	Level     logging.Level
    32  	Msg       string
    33  	Data      map[string]any
    34  	CallDepth int
    35  }
    36  
    37  // MemLogger is an implementation of Logger.
    38  // Zero value is a valid logger.
    39  type MemLogger struct {
    40  	lock   *sync.Mutex
    41  	data   *[]LogEntry
    42  	fields map[string]any
    43  }
    44  
    45  var _ logging.Logger = (*MemLogger)(nil)
    46  
    47  // Debugf implements the logging.Logger interface.
    48  func (m *MemLogger) Debugf(format string, args ...any) {
    49  	m.LogCall(logging.Debug, 1, format, args)
    50  }
    51  
    52  // Infof implements the logging.Logger interface.
    53  func (m *MemLogger) Infof(format string, args ...any) {
    54  	m.LogCall(logging.Info, 1, format, args)
    55  }
    56  
    57  // Warningf implements the logging.Logger interface.
    58  func (m *MemLogger) Warningf(format string, args ...any) {
    59  	m.LogCall(logging.Warning, 1, format, args)
    60  }
    61  
    62  // Errorf implements the logging.Logger interface.
    63  func (m *MemLogger) Errorf(format string, args ...any) {
    64  	m.LogCall(logging.Error, 1, format, args)
    65  }
    66  
    67  // LogCall implements the logging.Logger interface.
    68  func (m *MemLogger) LogCall(lvl logging.Level, calldepth int, format string, args []any) {
    69  	if m.lock != nil {
    70  		m.lock.Lock()
    71  		defer m.lock.Unlock()
    72  	}
    73  	if m.data == nil {
    74  		m.data = new([]LogEntry)
    75  	}
    76  	*m.data = append(*m.data, LogEntry{lvl, fmt.Sprintf(format, args...), m.fields, calldepth + 1})
    77  }
    78  
    79  // Messages returns all of the log messages that this memory logger has
    80  // recorded.
    81  func (m *MemLogger) Messages() []LogEntry {
    82  	if m.lock != nil {
    83  		m.lock.Lock()
    84  		defer m.lock.Unlock()
    85  	}
    86  	if m.data == nil || len(*m.data) == 0 {
    87  		return nil
    88  	}
    89  	ret := make([]LogEntry, len(*m.data))
    90  	copy(ret, *m.data)
    91  	return ret
    92  }
    93  
    94  // Reset resets the logged messages recorded so far.
    95  func (m *MemLogger) Reset() {
    96  	if m.lock != nil {
    97  		m.lock.Lock()
    98  		defer m.lock.Unlock()
    99  	}
   100  	if m.data != nil {
   101  		*m.data = nil
   102  	}
   103  }
   104  
   105  // GetFunc returns the first matching log entry.
   106  func (m *MemLogger) GetFunc(f func(*LogEntry) bool) *LogEntry {
   107  	if m.lock != nil {
   108  		m.lock.Lock()
   109  		defer m.lock.Unlock()
   110  	}
   111  	if m.data == nil {
   112  		return nil
   113  	}
   114  	for _, ent := range *m.data {
   115  		if f(&ent) {
   116  			clone := ent
   117  			return &clone
   118  		}
   119  	}
   120  	return nil
   121  }
   122  
   123  // Get returns the first matching log entry.
   124  // Note that lvl, msg and data have to match the entry precisely.
   125  func (m *MemLogger) Get(lvl logging.Level, msg string, data map[string]any) *LogEntry {
   126  	return m.GetFunc(func(ent *LogEntry) bool {
   127  		return ent.Level == lvl && ent.Msg == msg && reflect.DeepEqual(data, ent.Data)
   128  	})
   129  }
   130  
   131  // HasFunc returns true iff the MemLogger contains a matching log message.
   132  func (m *MemLogger) HasFunc(f func(*LogEntry) bool) bool {
   133  	return m.GetFunc(f) != nil
   134  }
   135  
   136  // Has returns true iff the MemLogger contains the specified log message.
   137  // Note that lvl, msg and data have to match the entry precisely.
   138  func (m *MemLogger) Has(lvl logging.Level, msg string, data map[string]any) bool {
   139  	return m.Get(lvl, msg, data) != nil
   140  }
   141  
   142  // Dump dumps the current memory logger contents to the given writer in a
   143  // human-readable format.
   144  func (m *MemLogger) Dump(w io.Writer) (n int, err error) {
   145  	amt := 0
   146  	for i, msg := range m.Messages() {
   147  		if i == 0 {
   148  			amt, err = fmt.Fprintf(w, "\nDUMP LOG:\n")
   149  			n += amt
   150  			if err != nil {
   151  				return
   152  			}
   153  		}
   154  		if msg.Data == nil {
   155  			amt, err = fmt.Fprintf(w, "  %s: %s\n", msg.Level, msg.Msg)
   156  			n += amt
   157  			if err != nil {
   158  				return
   159  			}
   160  		} else {
   161  			amt, err = fmt.Fprintf(w, "  %s: %s: %s\n", msg.Level, msg.Msg, logging.Fields(msg.Data))
   162  			n += amt
   163  			if err != nil {
   164  				return
   165  			}
   166  		}
   167  	}
   168  	return
   169  }
   170  
   171  // Use adds a memory backed Logger to Context, with concrete type
   172  // *MemLogger. Casting to the concrete type can be used to inspect the
   173  // log output after running a test case, for example.
   174  func Use(ctx context.Context) context.Context {
   175  	lock := sync.Mutex{}
   176  	data := []LogEntry{}
   177  	return logging.SetFactory(ctx, func(ctx context.Context) logging.Logger {
   178  		return &MemLogger{&lock, &data, logging.GetFields(ctx)}
   179  	})
   180  }
   181  
   182  // Reset is a convenience function to reset the current memory logger.
   183  //
   184  // This will panic if the current logger is not a memory logger.
   185  func Reset(ctx context.Context) {
   186  	logging.Get(ctx).(*MemLogger).Reset()
   187  }
   188  
   189  // Dump is a convenience function to dump the current contents of the memory
   190  // logger to the writer.
   191  //
   192  // This will panic if the current logger is not a memory logger.
   193  func Dump(ctx context.Context, w io.Writer) (n int, err error) {
   194  	return logging.Get(ctx).(*MemLogger).Dump(w)
   195  }
   196  
   197  // MustDumpStdout is a convenience function to dump the current contents of the
   198  // memory logger to stdout.
   199  //
   200  // This will panic if the current logger is not a memory logger.
   201  func MustDumpStdout(ctx context.Context) {
   202  	_, err := logging.Get(ctx).(*MemLogger).Dump(os.Stdout)
   203  	if err != nil {
   204  		panic(err)
   205  	}
   206  }