mirror of
https://github.com/rwinkhart/go-winio.git
synced 2026-09-02 15:17:38 -04:00
Previously, the only constructor for the etwlogrus hook accepted a name, and created a new ETW provider with that name. With this change, there is another constructor which takes an already created ETW provider. This is to allow the use of the ETW provider for other things, such as if the application wants to support ETW capture state.
203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
package etwlogrus
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/Microsoft/go-winio/pkg/etw"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Hook is a Logrus hook which logs received events to ETW.
|
|
type Hook struct {
|
|
provider *etw.Provider
|
|
closeProvider bool
|
|
}
|
|
|
|
// NewHook registers a new ETW provider and returns a hook to log from it. The
|
|
// provider will be closed when the hook is closed.
|
|
func NewHook(providerName string) (*Hook, error) {
|
|
provider, err := etw.NewProvider(providerName, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Hook{provider, true}, nil
|
|
}
|
|
|
|
// NewHookFromProvider creates a new hook based on an existing ETW provider. The
|
|
// provider will not be closed when the hook is closed.
|
|
func NewHookFromProvider(provider *etw.Provider) (*Hook, error) {
|
|
return &Hook{provider, false}, nil
|
|
}
|
|
|
|
// Levels returns the set of levels that this hook wants to receive log entries
|
|
// for.
|
|
func (h *Hook) Levels() []logrus.Level {
|
|
return []logrus.Level{
|
|
logrus.TraceLevel,
|
|
logrus.DebugLevel,
|
|
logrus.InfoLevel,
|
|
logrus.WarnLevel,
|
|
logrus.ErrorLevel,
|
|
logrus.FatalLevel,
|
|
logrus.PanicLevel,
|
|
}
|
|
}
|
|
|
|
// Fire receives each Logrus entry as it is logged, and logs it to ETW.
|
|
func (h *Hook) Fire(e *logrus.Entry) error {
|
|
level := etw.Level(e.Level)
|
|
if !h.provider.IsEnabledForLevel(level) {
|
|
return nil
|
|
}
|
|
|
|
// Reserve extra space for the message field.
|
|
fields := make([]etw.FieldOpt, 0, len(e.Data)+1)
|
|
|
|
fields = append(fields, etw.StringField("Message", e.Message))
|
|
|
|
for k, v := range e.Data {
|
|
fields = append(fields, getFieldOpt(k, v))
|
|
}
|
|
|
|
// We could try to map Logrus levels to ETW levels, but we would lose some
|
|
// fidelity as there are fewer ETW levels. So instead we use the level
|
|
// directly.
|
|
return h.provider.WriteEvent(
|
|
"LogrusEntry",
|
|
etw.WithEventOpts(etw.WithLevel(level)),
|
|
fields)
|
|
}
|
|
|
|
// Currently, we support logging basic builtin types (int, string, etc), slices
|
|
// of basic builtin types, error, types derived from the basic types (e.g. "type
|
|
// foo int"), and structs (recursively logging their fields). We do not support
|
|
// slices of derived types (e.g. "[]foo").
|
|
//
|
|
// For types that we don't support, the value is formatted via fmt.Sprint, and
|
|
// we also log a message that the type is unsupported along with the formatted
|
|
// type. The intent of this is to make it easier to see which types are not
|
|
// supported in traces, so we can evaluate adding support for more types in the
|
|
// future.
|
|
func getFieldOpt(k string, v interface{}) etw.FieldOpt {
|
|
switch v := v.(type) {
|
|
case bool:
|
|
return etw.BoolField(k, v)
|
|
case []bool:
|
|
return etw.BoolArray(k, v)
|
|
case string:
|
|
return etw.StringField(k, v)
|
|
case []string:
|
|
return etw.StringArray(k, v)
|
|
case int:
|
|
return etw.IntField(k, v)
|
|
case []int:
|
|
return etw.IntArray(k, v)
|
|
case int8:
|
|
return etw.Int8Field(k, v)
|
|
case []int8:
|
|
return etw.Int8Array(k, v)
|
|
case int16:
|
|
return etw.Int16Field(k, v)
|
|
case []int16:
|
|
return etw.Int16Array(k, v)
|
|
case int32:
|
|
return etw.Int32Field(k, v)
|
|
case []int32:
|
|
return etw.Int32Array(k, v)
|
|
case int64:
|
|
return etw.Int64Field(k, v)
|
|
case []int64:
|
|
return etw.Int64Array(k, v)
|
|
case uint:
|
|
return etw.UintField(k, v)
|
|
case []uint:
|
|
return etw.UintArray(k, v)
|
|
case uint8:
|
|
return etw.Uint8Field(k, v)
|
|
case []uint8:
|
|
return etw.Uint8Array(k, v)
|
|
case uint16:
|
|
return etw.Uint16Field(k, v)
|
|
case []uint16:
|
|
return etw.Uint16Array(k, v)
|
|
case uint32:
|
|
return etw.Uint32Field(k, v)
|
|
case []uint32:
|
|
return etw.Uint32Array(k, v)
|
|
case uint64:
|
|
return etw.Uint64Field(k, v)
|
|
case []uint64:
|
|
return etw.Uint64Array(k, v)
|
|
case uintptr:
|
|
return etw.UintptrField(k, v)
|
|
case []uintptr:
|
|
return etw.UintptrArray(k, v)
|
|
case float32:
|
|
return etw.Float32Field(k, v)
|
|
case []float32:
|
|
return etw.Float32Array(k, v)
|
|
case float64:
|
|
return etw.Float64Field(k, v)
|
|
case []float64:
|
|
return etw.Float64Array(k, v)
|
|
case error:
|
|
return etw.StringField(k, v.Error())
|
|
default:
|
|
switch rv := reflect.ValueOf(v); rv.Kind() {
|
|
case reflect.Bool:
|
|
return getFieldOpt(k, rv.Bool())
|
|
case reflect.Int:
|
|
return getFieldOpt(k, int(rv.Int()))
|
|
case reflect.Int8:
|
|
return getFieldOpt(k, int8(rv.Int()))
|
|
case reflect.Int16:
|
|
return getFieldOpt(k, int16(rv.Int()))
|
|
case reflect.Int32:
|
|
return getFieldOpt(k, int32(rv.Int()))
|
|
case reflect.Int64:
|
|
return getFieldOpt(k, int64(rv.Int()))
|
|
case reflect.Uint:
|
|
return getFieldOpt(k, uint(rv.Uint()))
|
|
case reflect.Uint8:
|
|
return getFieldOpt(k, uint8(rv.Uint()))
|
|
case reflect.Uint16:
|
|
return getFieldOpt(k, uint16(rv.Uint()))
|
|
case reflect.Uint32:
|
|
return getFieldOpt(k, uint32(rv.Uint()))
|
|
case reflect.Uint64:
|
|
return getFieldOpt(k, uint64(rv.Uint()))
|
|
case reflect.Uintptr:
|
|
return getFieldOpt(k, uintptr(rv.Uint()))
|
|
case reflect.Float32:
|
|
return getFieldOpt(k, float32(rv.Float()))
|
|
case reflect.Float64:
|
|
return getFieldOpt(k, float64(rv.Float()))
|
|
case reflect.String:
|
|
return getFieldOpt(k, rv.String())
|
|
case reflect.Struct:
|
|
fields := make([]etw.FieldOpt, 0, rv.NumField())
|
|
for i := 0; i < rv.NumField(); i++ {
|
|
field := rv.Field(i)
|
|
if field.CanInterface() {
|
|
fields = append(fields, getFieldOpt(k, field.Interface()))
|
|
}
|
|
}
|
|
return etw.Struct(k, fields...)
|
|
}
|
|
}
|
|
|
|
return etw.StringField(k, fmt.Sprintf("(Unsupported: %T) %v", v, v))
|
|
}
|
|
|
|
// Close cleans up the hook and closes the ETW provider. If the provder was
|
|
// registered by etwlogrus, it will be closed as part of `Close`. If the
|
|
// provider was passed in, it will not be closed.
|
|
func (h *Hook) Close() error {
|
|
if h.closeProvider {
|
|
return h.provider.Close()
|
|
}
|
|
return nil
|
|
}
|