...

Source file src/github.com/Microsoft/go-winio/pkg/etwlogrus/hook.go

Documentation: github.com/Microsoft/go-winio/pkg/etwlogrus

     1  //go:build windows
     2  // +build windows
     3  
     4  package etwlogrus
     5  
     6  import (
     7  	"errors"
     8  	"sort"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/Microsoft/go-winio/pkg/etw"
    13  )
    14  
    15  const defaultEventName = "LogrusEntry"
    16  
    17  // ErrNoProvider is returned when a hook is created without a provider being configured.
    18  var ErrNoProvider = errors.New("no ETW registered provider")
    19  
    20  // HookOpt is an option to change the behavior of the Logrus ETW hook.
    21  type HookOpt func(*Hook) error
    22  
    23  // Hook is a Logrus hook which logs received events to ETW.
    24  type Hook struct {
    25  	provider      *etw.Provider
    26  	closeProvider bool
    27  	// allows setting the entry name
    28  	getName func(*logrus.Entry) string
    29  	// returns additional options to add to the event
    30  	getEventsOpts func(*logrus.Entry) []etw.EventOpt
    31  }
    32  
    33  // NewHook registers a new ETW provider and returns a hook to log from it.
    34  // The provider will be closed when the hook is closed.
    35  func NewHook(providerName string, opts ...HookOpt) (*Hook, error) {
    36  	opts = append(opts, WithNewETWProvider(providerName))
    37  
    38  	return NewHookFromOpts(opts...)
    39  }
    40  
    41  // NewHookFromProvider creates a new hook based on an existing ETW provider.
    42  // The provider will not be closed when the hook is closed.
    43  func NewHookFromProvider(provider *etw.Provider, opts ...HookOpt) (*Hook, error) {
    44  	opts = append(opts, WithExistingETWProvider(provider))
    45  
    46  	return NewHookFromOpts(opts...)
    47  }
    48  
    49  // NewHookFromOpts creates a new hook with the provided options.
    50  // An error is returned if the hook does not have a valid provider.
    51  func NewHookFromOpts(opts ...HookOpt) (*Hook, error) {
    52  	h := defaultHook()
    53  
    54  	for _, o := range opts {
    55  		if err := o(h); err != nil {
    56  			return nil, err
    57  		}
    58  	}
    59  	return h, h.validate()
    60  }
    61  
    62  func defaultHook() *Hook {
    63  	h := &Hook{}
    64  	return h
    65  }
    66  
    67  func (h *Hook) validate() error {
    68  	if h.provider == nil {
    69  		return ErrNoProvider
    70  	}
    71  	return nil
    72  }
    73  
    74  // Levels returns the set of levels that this hook wants to receive log entries
    75  // for.
    76  func (*Hook) Levels() []logrus.Level {
    77  	return logrus.AllLevels
    78  }
    79  
    80  var logrusToETWLevelMap = map[logrus.Level]etw.Level{
    81  	logrus.PanicLevel: etw.LevelAlways,
    82  	logrus.FatalLevel: etw.LevelCritical,
    83  	logrus.ErrorLevel: etw.LevelError,
    84  	logrus.WarnLevel:  etw.LevelWarning,
    85  	logrus.InfoLevel:  etw.LevelInfo,
    86  	logrus.DebugLevel: etw.LevelVerbose,
    87  	logrus.TraceLevel: etw.LevelVerbose,
    88  }
    89  
    90  // Fire receives each Logrus entry as it is logged, and logs it to ETW.
    91  func (h *Hook) Fire(e *logrus.Entry) error {
    92  	// Logrus defines more levels than ETW typically uses, but analysis is
    93  	// easiest when using a consistent set of levels across ETW providers, so we
    94  	// map the Logrus levels to ETW levels.
    95  	level := logrusToETWLevelMap[e.Level]
    96  	if !h.provider.IsEnabledForLevel(level) {
    97  		return nil
    98  	}
    99  
   100  	name := defaultEventName
   101  	if h.getName != nil {
   102  		if n := h.getName(e); n != "" {
   103  			name = n
   104  		}
   105  	}
   106  
   107  	// extra room for two more options in addition to log level to avoid repeated reallocations
   108  	// if the user also provides options
   109  	opts := make([]etw.EventOpt, 0, 3)
   110  	opts = append(opts, etw.WithLevel(level))
   111  	if h.getEventsOpts != nil {
   112  		opts = append(opts, h.getEventsOpts(e)...)
   113  	}
   114  
   115  	// Sort the fields by name so they are consistent in each instance
   116  	// of an event. Otherwise, the fields don't line up in WPA.
   117  	names := make([]string, 0, len(e.Data))
   118  	hasError := false
   119  	for k := range e.Data {
   120  		if k == logrus.ErrorKey {
   121  			// Always put the error last because it is optional in some events.
   122  			hasError = true
   123  		} else {
   124  			names = append(names, k)
   125  		}
   126  	}
   127  	sort.Strings(names)
   128  
   129  	// Reserve extra space for the message and time fields.
   130  	fields := make([]etw.FieldOpt, 0, len(e.Data)+2)
   131  	fields = append(fields, etw.StringField("Message", e.Message))
   132  	fields = append(fields, etw.Time("Time", e.Time))
   133  	for _, k := range names {
   134  		fields = append(fields, etw.SmartField(k, e.Data[k]))
   135  	}
   136  	if hasError {
   137  		fields = append(fields, etw.SmartField(logrus.ErrorKey, e.Data[logrus.ErrorKey]))
   138  	}
   139  
   140  	// Firing an ETW event is essentially best effort, as the event write can
   141  	// fail for reasons completely out of the control of the event writer (such
   142  	// as a session listening for the event having no available space in its
   143  	// buffers). Therefore, we don't return the error from WriteEvent, as it is
   144  	// just noise in many cases.
   145  	_ = h.provider.WriteEvent(name, opts, fields)
   146  
   147  	return nil
   148  }
   149  
   150  // Close cleans up the hook and closes the ETW provider. If the provder was
   151  // registered by etwlogrus, it will be closed as part of `Close`. If the
   152  // provider was passed in, it will not be closed.
   153  func (h *Hook) Close() error {
   154  	if h.closeProvider {
   155  		return h.provider.Close()
   156  	}
   157  	return nil
   158  }
   159  

View as plain text