go/mqtt/internal/log_utils.go (97 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package internal import ( "context" "fmt" "log/slog" "reflect" "github.com/Azure/iot-operations-sdks/go/internal/log" "github.com/eclipse/paho.golang/paho" "github.com/iancoleman/strcase" ) type Logger struct{ log.Logger } func (l Logger) Packet(ctx context.Context, name string, packet any) { // This is expensive; bail out if we don't need it. if !l.Enabled(ctx, slog.LevelDebug) { return } val := realValue(reflect.ValueOf(packet)) if missingValue(val) { l.Log(ctx, slog.LevelWarn, fmt.Sprintf("%s not available", name)) } else { l.Log(ctx, slog.LevelDebug, name, reflectAttrs(val)...) } } func reflectAttrs(val reflect.Value) []slog.Attr { typ := val.Type() num := typ.NumField() var attrs []slog.Attr for i := range num { f := typ.Field(i) if !f.IsExported() { continue } attrs = append(attrs, reflectAttr( strcase.ToSnake(f.Name), realValue(val.Field(i)), )...) } return attrs } func reflectAttr(name string, val reflect.Value) []slog.Attr { // Ignore zero values to keep the log cleaner. if missingValue(val) { return nil } switch name { // Paho's struct nesting is not particularly useful to log. case "properties": return reflectAttrs(val) // Subscriptions are one-at-a-time for the session client. case "subscriptions": if subs, ok := val.Interface().([]paho.SubscribeOptions); ok { return reflectAttrs(reflect.ValueOf(subs[0])) } case "topics": if topics, ok := val.Interface().([]string); ok { return []slog.Attr{slog.String("topic", topics[0])} } case "reasons": if reasons, ok := val.Interface().([]byte); ok { return []slog.Attr{slog.Int("reason_code", int(reasons[0]))} } // Fix QoS not being actually PascalCased. case "qo_s": return []slog.Attr{slog.Any("qos", val.Interface())} // Do not log secrets. case "password", "auth_data": return nil } switch v := val.Interface().(type) { case []byte: return []slog.Attr{slog.String(name, string(v))} case paho.UserProperties: if len(v) == 0 { return nil } attrs := make([]any, len(v)) for i, p := range v { attrs[i] = slog.String(p.Key, p.Value) } return []slog.Attr{slog.Group(name, attrs...)} } if val.Kind() == reflect.Struct { as := reflectAttrs(val) if len(as) == 0 { return nil } cpy := make([]any, len(as)) for i, a := range as { cpy[i] = a } return []slog.Attr{slog.Group(name, cpy...)} } return []slog.Attr{slog.Any(name, val.Interface())} } func realValue(val reflect.Value) reflect.Value { for val.Kind() == reflect.Pointer { val = val.Elem() } return val } func missingValue(val reflect.Value) bool { return val.Kind() == reflect.Invalid || val.IsZero() }