github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/message/fields.go (about)

     1  package message
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/mongodb/grip/level"
     9  )
    10  
    11  // FieldsMsgName is the name of the default "message" field in the
    12  // fields structure.
    13  const FieldsMsgName = "message"
    14  
    15  type fieldMessage struct {
    16  	message                 string
    17  	fields                  Fields
    18  	cachedOutput            string
    19  	includeMetadata         bool
    20  	includeExtendedMetadata bool
    21  	Base
    22  }
    23  
    24  // Fields is a convince type that wraps map[string]interface{} and is
    25  // used for attaching structured metadata to a build request. For
    26  // example:
    27  //
    28  //	message.Fields{"key0", <value>, "key1", <value>}
    29  type Fields map[string]interface{}
    30  
    31  // NewFieldsMessage creates a fully configured Composer instance that will
    32  // attach basic metadata. This constructor allows you to include a string
    33  // message as well as Fields object.
    34  func NewFieldsMessage(p level.Priority, message string, f Fields) Composer {
    35  	m := MakeFieldsMessage(message, f)
    36  
    37  	_ = m.SetPriority(p)
    38  
    39  	return m
    40  }
    41  
    42  // NewFields constructs a full configured fields Composer with basic metadata.
    43  func NewFields(p level.Priority, f Fields) Composer {
    44  	m := MakeFields(f)
    45  	_ = m.SetPriority(p)
    46  
    47  	return m
    48  }
    49  
    50  // MakeFieldsMessage constructs a fields Composer from a message string and
    51  // Fields object, without specifying the priority of the message. This includes
    52  // basic metadata.
    53  func MakeFieldsMessage(message string, f Fields) Composer {
    54  	m := &fieldMessage{
    55  		message:         message,
    56  		fields:          f,
    57  		includeMetadata: true,
    58  	}
    59  	m.setup()
    60  	return m
    61  }
    62  
    63  // NewExtendedFieldsMessage is the same as NewFieldsMessage, but also collects
    64  // extended logging metadata.
    65  func NewExtendedFieldsMessage(p level.Priority, message string, f Fields) Composer {
    66  	m := MakeExtendedFieldsMessage(message, f)
    67  	_ = m.SetPriority(p)
    68  	return m
    69  }
    70  
    71  // MakeExtendedFields is the same as MakeFields but also collects extended
    72  // logging metadata.
    73  func MakeExtendedFields(f Fields) Composer {
    74  	m := &fieldMessage{
    75  		fields:                  f,
    76  		includeMetadata:         true,
    77  		includeExtendedMetadata: true,
    78  	}
    79  	m.setup()
    80  	return m
    81  }
    82  
    83  // NewExtendedFields is the same as NewFields but also collects extended logging
    84  // metadata.
    85  func NewExtendedFields(p level.Priority, f Fields) Composer {
    86  	m := MakeExtendedFields(f)
    87  	_ = m.SetPriority(p)
    88  	return m
    89  }
    90  
    91  // MakeExtendedFieldsMessage is the same as MakeFieldsMessage but also collects
    92  // extended logging metadata.
    93  func MakeExtendedFieldsMessage(msg string, f Fields) Composer {
    94  	m := &fieldMessage{
    95  		message:                 msg,
    96  		fields:                  f,
    97  		includeMetadata:         true,
    98  		includeExtendedMetadata: true,
    99  	}
   100  
   101  	m.setup()
   102  	return m
   103  }
   104  
   105  // MakeSimpleFields returns a structured Composer that does
   106  // not attach any logging metadata.
   107  func MakeSimpleFields(f Fields) Composer {
   108  	m := &fieldMessage{fields: f}
   109  	m.setup()
   110  	return m
   111  }
   112  
   113  // NewSimpleFields returns a structured Composer that does not
   114  // attach any logging metadata and allows callers to configure the
   115  // messages' log level.
   116  func NewSimpleFields(p level.Priority, f Fields) Composer {
   117  	m := MakeSimpleFields(f)
   118  	_ = m.SetPriority(p)
   119  	return m
   120  }
   121  
   122  // MakeSimpleFieldsMessage returns a structured Composer that does not attach
   123  // any logging metadata, but allows callers to specify the message
   124  // (the "message" field) as a string.
   125  func MakeSimpleFieldsMessage(msg string, f Fields) Composer {
   126  	m := &fieldMessage{
   127  		message: msg,
   128  		fields:  f,
   129  	}
   130  
   131  	m.setup()
   132  	return m
   133  }
   134  
   135  // NewSimpleFieldsMessage returns a structured Composer that does not attach
   136  // any logging metadata, but allows callers to specify the message
   137  // (the "message" field) as well as the message's log-level.
   138  func NewSimpleFieldsMessage(p level.Priority, msg string, f Fields) Composer {
   139  	m := MakeSimpleFieldsMessage(msg, f)
   140  	_ = m.SetPriority(p)
   141  	return m
   142  }
   143  
   144  ////////////////////////////////////////////////////////////////////////
   145  //
   146  // Implementation
   147  //
   148  ////////////////////////////////////////////////////////////////////////
   149  
   150  func (m *fieldMessage) setup() {
   151  	if _, ok := m.fields[FieldsMsgName]; !ok && m.message != "" {
   152  		m.fields[FieldsMsgName] = m.message
   153  	}
   154  
   155  	if !m.includeMetadata {
   156  		return
   157  	}
   158  	_ = m.Collect(m.includeExtendedMetadata)
   159  
   160  	if b, ok := m.fields["metadata"]; !ok {
   161  		m.fields["metadata"] = &m.Base
   162  	} else if _, ok = b.(*Base); ok {
   163  		m.fields["metadata"] = &m.Base
   164  	}
   165  }
   166  
   167  // MakeFields creates a composer interface from *just* a Fields instance.
   168  func MakeFields(f Fields) Composer {
   169  	m := &fieldMessage{fields: f, includeMetadata: true}
   170  	m.setup()
   171  	return m
   172  }
   173  
   174  func (m *fieldMessage) Loggable() bool {
   175  	if m.message == "" && len(m.fields) == 0 {
   176  		return false
   177  	}
   178  
   179  	if len(m.fields) == 1 {
   180  		if _, ok := m.fields["metadata"]; ok {
   181  			return false
   182  		}
   183  	}
   184  
   185  	return true
   186  }
   187  
   188  func (m *fieldMessage) String() string {
   189  	if !m.Loggable() {
   190  		return ""
   191  	}
   192  
   193  	if m.cachedOutput == "" {
   194  		out := []string{}
   195  		if m.message != "" {
   196  			out = append(out, fmt.Sprintf("%s='%s'", FieldsMsgName, m.message))
   197  		}
   198  
   199  		for k, v := range m.fields {
   200  			if k == FieldsMsgName && v == m.message {
   201  				continue
   202  			}
   203  			if k == "time" {
   204  				continue
   205  			}
   206  			if k == "metadata" {
   207  				continue
   208  			}
   209  
   210  			if str, ok := v.(fmt.Stringer); ok {
   211  				out = append(out, fmt.Sprintf("%s='%s'", k, str.String()))
   212  			} else {
   213  				out = append(out, fmt.Sprintf("%s='%v'", k, v))
   214  			}
   215  		}
   216  
   217  		sort.Strings(out)
   218  
   219  		m.cachedOutput = fmt.Sprintf("[%s]", strings.Join(out, " "))
   220  	}
   221  
   222  	return m.cachedOutput
   223  }
   224  
   225  func (m *fieldMessage) Raw() interface{} { return m.fields }
   226  
   227  func (m *fieldMessage) Annotate(key string, value interface{}) error {
   228  	if _, ok := m.fields[key]; ok {
   229  		return fmt.Errorf("key '%s' already exists", key)
   230  	}
   231  
   232  	m.fields[key] = value
   233  
   234  	return nil
   235  }