From 34f40f81c54cad79dbb8c027f02164c13acbec65 Mon Sep 17 00:00:00 2001 From: tycho garen Date: Tue, 10 Nov 2020 15:54:14 -0500 Subject: [PATCH] add support for logging functions --- go.sum | 2 - message/composer_test.go | 82 +++++---- message/error.go | 1 + message/error_message.go | 1 + message/fields.go | 51 +++--- message/function.go | 376 +++++++++++++++++++++++++++++++++++++++ message/function_test.go | 7 + message/interface.go | 67 ++++++- send/buffered.go | 2 +- send/interface.go | 8 +- 10 files changed, 533 insertions(+), 64 deletions(-) create mode 100644 message/function.go create mode 100644 message/function_test.go diff --git a/go.sum b/go.sum index c96dbb2..b2f533a 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shirou/gopsutil v2.20.4+incompatible h1:cMT4rxS55zx9NVUnCkrmXCsEB/RNfG9SwHY9evtX8Ng= -github.com/shirou/gopsutil v2.20.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.8+incompatible h1:8c7Atn0FAUZJo+f4wYbN0iVpdWniCQk7IYwGtgdh1mY= github.com/shirou/gopsutil v2.20.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/message/composer_test.go b/message/composer_test.go index 72ca1a3..7b62bb2 100644 --- a/message/composer_test.go +++ b/message/composer_test.go @@ -18,34 +18,40 @@ func TestPopulatedMessageComposerConstructors(t *testing.T) { assert := assert.New(t) // map objects to output cases := map[Composer]string{ - NewString(testMsg): testMsg, - NewDefaultMessage(level.Error, testMsg): testMsg, - NewBytes([]byte(testMsg)): testMsg, - NewBytesMessage(level.Error, []byte(testMsg)): testMsg, - NewError(errors.New(testMsg)): testMsg, - NewErrorMessage(level.Error, errors.New(testMsg)): testMsg, - NewErrorWrap(errors.New(testMsg), ""): testMsg, - NewErrorWrapMessage(level.Error, errors.New(testMsg), ""): testMsg, - NewFormatted(string(testMsg[0])+"%s", testMsg[1:]): testMsg, - NewFormattedMessage(level.Error, string(testMsg[0])+"%s", testMsg[1:]): testMsg, - WrapError(errors.New(testMsg), ""): testMsg, - WrapErrorf(errors.New(testMsg), ""): testMsg, - NewLine(testMsg, ""): testMsg, - NewLineMessage(level.Error, testMsg, ""): testMsg, - NewLine(testMsg): testMsg, - NewLineMessage(level.Error, testMsg): testMsg, - MakeGroupComposer(NewString(testMsg)): testMsg, - NewGroupComposer([]Composer{NewString(testMsg)}): testMsg, - MakeJiraMessage(&JiraIssue{Summary: testMsg, Type: "Something"}): testMsg, - NewJiraMessage("", testMsg, JiraField{Key: "type", Value: "Something"}): testMsg, - NewFieldsMessage(level.Error, testMsg, Fields{}): fmt.Sprintf("[message='%s']", testMsg), - NewFields(level.Error, Fields{"test": testMsg}): fmt.Sprintf("[test='%s']", testMsg), - MakeFieldsMessage(testMsg, Fields{}): fmt.Sprintf("[message='%s']", testMsg), - MakeFields(Fields{"test": testMsg}): fmt.Sprintf("[test='%s']", testMsg), - NewErrorWrappedComposer(errors.New("hello"), NewString("world")): "world: hello", - When(true, testMsg): testMsg, - Whenf(true, testMsg): testMsg, - Whenln(true, testMsg): testMsg, + NewString(testMsg): testMsg, + NewDefaultMessage(level.Error, testMsg): testMsg, + NewBytes([]byte(testMsg)): testMsg, + NewBytesMessage(level.Error, []byte(testMsg)): testMsg, + NewError(errors.New(testMsg)): testMsg, + NewErrorMessage(level.Error, errors.New(testMsg)): testMsg, + NewErrorWrap(errors.New(testMsg), ""): testMsg, + NewErrorWrapMessage(level.Error, errors.New(testMsg), ""): testMsg, + NewFormatted(string(testMsg[0])+"%s", testMsg[1:]): testMsg, + NewFormattedMessage(level.Error, string(testMsg[0])+"%s", testMsg[1:]): testMsg, + WrapError(errors.New(testMsg), ""): testMsg, + WrapErrorf(errors.New(testMsg), ""): testMsg, + NewLine(testMsg, ""): testMsg, + NewLineMessage(level.Error, testMsg, ""): testMsg, + NewLine(testMsg): testMsg, + NewLineMessage(level.Error, testMsg): testMsg, + MakeGroupComposer(NewString(testMsg)): testMsg, + NewGroupComposer([]Composer{NewString(testMsg)}): testMsg, + MakeJiraMessage(&JiraIssue{Summary: testMsg, Type: "Something"}): testMsg, + NewJiraMessage("", testMsg, JiraField{Key: "type", Value: "Something"}): testMsg, + NewFieldsMessage(level.Error, testMsg, Fields{}): fmt.Sprintf("[message='%s']", testMsg), + NewFields(level.Error, Fields{"test": testMsg}): fmt.Sprintf("[test='%s']", testMsg), + MakeFieldsMessage(testMsg, Fields{}): fmt.Sprintf("[message='%s']", testMsg), + MakeFields(Fields{"test": testMsg}): fmt.Sprintf("[test='%s']", testMsg), + NewErrorWrappedComposer(errors.New("hello"), NewString("world")): "world: hello", + When(true, testMsg): testMsg, + Whenf(true, testMsg): testMsg, + Whenln(true, testMsg): testMsg, + Whenln(true, testMsg): testMsg, + MakeComposerProducerMessage(func() Composer { return NewString(testMsg) }): testMsg, + NewComposerProducerMessage(level.Error, func() Composer { return NewString(testMsg) }): testMsg, + MakeErrorProducerMessage(func() error { return errors.New(testMsg) }): testMsg, + NewErrorProducerMessage(level.Error, func() error { return errors.New(testMsg) }): testMsg, + NewFieldsProducerMessage(level.Error, func() Fields { return Fields{"pro": "ducer"} }): "[pro='ducer']", NewEmailMessage(level.Error, Email{ Recipients: []string{"someone@example.com"}, Subject: "Test msg", @@ -133,6 +139,13 @@ func TestUnpopulatedMessageComposers(t *testing.T) { NewGithubStatusMessageWithRepo(level.Error, GithubStatus{}), NewJIRACommentMessage(level.Error, "", ""), NewSlackMessage(level.Error, "", "", nil), + MakeComposerProducerMessage(nil), + MakeComposerProducerMessage(func() Composer { return nil }), + MakeFieldsProducerMessage(nil), + MakeFieldsProducerMessage(func() Fields { return nil }), + MakeFieldsProducerMessage(func() Fields { return Fields{} }), + MakeErrorProducerMessage(nil), + MakeErrorProducerMessage(func() error { return nil }), } for idx, msg := range cases { @@ -413,15 +426,22 @@ type causer interface { Cause() error } +type unwrapper interface { + Unwrap() error +} + func TestErrors(t *testing.T) { for name, cmp := range map[string]Composer{ - "Wrapped": WrapError(errors.New("err"), "msg"), - "Plain": NewError(errors.New("err")), + "Wrapped": WrapError(errors.New("err"), "wrap"), + "Plain": NewError(errors.New("err")), + "Producer": MakeErrorProducerMessage(func() error { return errors.New("message") }), + "WrapperProducer": WrapErrorFunc(func() error { return errors.New("message") }, Fields{"op": "wrap"}), } { t.Run(name, func(t *testing.T) { t.Run("Interfaces", func(t *testing.T) { assert.Implements(t, (*error)(nil), cmp) - assert.Implements(t, (*causer)(nil), cmp) + assert.Implements(t, (*error)(nil), cmp) + assert.Implements(t, (*unwrapper)(nil), cmp) }) t.Run("Value", func(t *testing.T) { assert.Equal(t, cmp.(error).Error(), cmp.String()) diff --git a/message/error.go b/message/error.go index b719403..d225d4b 100644 --- a/message/error.go +++ b/message/error.go @@ -69,6 +69,7 @@ func (e *errorMessage) Raw() interface{} { func (e *errorMessage) Error() string { return e.String() } func (e *errorMessage) Cause() error { return e.err } +func (e *errorMessage) Unwrap() error { return e.err } func (e *errorMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/message/error_message.go b/message/error_message.go index abb90f2..764dc73 100644 --- a/message/error_message.go +++ b/message/error_message.go @@ -68,6 +68,7 @@ func (m *errorComposerWrap) String() string { func (m *errorComposerWrap) Error() string { return m.String() } func (m *errorComposerWrap) Cause() error { return m.err } +func (m *errorComposerWrap) Unwrap() error { return m.err } func (m *errorComposerWrap) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/message/fields.go b/message/fields.go index 893315c..ea3e0ce 100644 --- a/message/fields.go +++ b/message/fields.go @@ -47,23 +47,6 @@ func NewFields(p level.Priority, f Fields) Composer { return m } -// MakeFieldsMessage constructs a fields Composer from a message string and -// Fields object, without specifying the priority of the message. -func MakeFieldsMessage(message string, f Fields) Composer { - m := &fieldMessage{message: message, fields: f} - m.setup() - - return m -} - -// MakeSimpleFields returns a structured Composer that does -// not attach basic logging metadata. -func MakeSimpleFields(f Fields) Composer { - m := &fieldMessage{fields: f, skipMetadata: true} - m.setup() - return m -} - // NewSimpleFields returns a structured Composer that does not // attach basic logging metadata and allows callers to configure the // messages' log level. @@ -96,6 +79,29 @@ func NewSimpleFieldsMessage(p level.Priority, msg string, f Fields) Composer { return m } +// MakeFields creates a composer interface from *just* a Fields instance. +func MakeFields(f Fields) Composer { + m := &fieldMessage{fields: f} + m.setup() + return m +} + +// MakeFieldsMessage constructs a fields Composer from a message string and +// Fields object, without specifying the priority of the message. +func MakeFieldsMessage(message string, f Fields) Composer { + m := &fieldMessage{message: message, fields: f} + m.setup() + return m +} + +// MakeSimpleFields returns a structured Composer that does +// not attach basic logging metadata. +func MakeSimpleFields(f Fields) Composer { + m := &fieldMessage{fields: f, skipMetadata: true} + m.setup() + return m +} + // GetDefaultFieldsMessage returns a "short" message form, to avoid // needing to call .String() on the type, which produces a string form // of the message. If the message has a short form (either in the map, @@ -130,6 +136,10 @@ func GetDefaultFieldsMessage(msg Composer, val string) string { //////////////////////////////////////////////////////////////////////// func (m *fieldMessage) setup() { + if m.fields == nil { + m.fields = Fields{} + } + if _, ok := m.fields[FieldsMsgName]; !ok && m.message != "" { m.fields[FieldsMsgName] = m.message } @@ -147,13 +157,6 @@ func (m *fieldMessage) setup() { } } -// MakeFields creates a composer interface from *just* a Fields instance. -func MakeFields(f Fields) Composer { - m := &fieldMessage{fields: f} - m.setup() - return m -} - func (m *fieldMessage) Loggable() bool { if m.message == "" && len(m.fields) == 0 { return false diff --git a/message/function.go b/message/function.go new file mode 100644 index 0000000..83242d7 --- /dev/null +++ b/message/function.go @@ -0,0 +1,376 @@ +// Functional Messages +// +// Grip can automatically convert three types of functions into +// messages: +// +// func() Fields +// func() Composer +// func() error +// +// The benefit of these functions is that they're only called if +// the message is above the logging threshold. In the case of +// conditional logging (i.e. When), if the conditional is false, then +// the function is never called. +// +// in the case of all the buffered sending implementation, the +// function call can be deferred and run outside of the main thread, +// and so may be an easy way to defer message production outside in +// cases where messages may be complicated. +// +// Additionally, the message conversion in grip's logging method can +// take these function types and convert them to these messages, which +// can clean up some call-site operations, and makes it possible to +// use defer with io.Closer methods without wrapping the method in an +// additional function, as in: +// +// defer grip.Error(file.Close) +// +// Although the WrapErrorFunc method, as in the following may permit +// useful annotation, as follows, which has the same "lazy" semantics. +// +// defer grip.Error(message.WrapErrorFunc(file.Close, message.Fields{})) +// +package message + +import ( + "fmt" + "io" + + "github.com/deciduosity/grip/level" + "github.com/pkg/errors" +) + +// FieldsProducer is a function that returns a structured message body +// as a way of writing simple Composer implementations in the form +// anonymous functions, as in: +// +// grip.Info(func() message.Fields {return message.Fields{"message": "hello world!"}}) +// +// Grip can automatically convert these functions when passed to a +// logging function. +// +// If the Fields object is nil or empty then no message is logged. +type FieldsProducer func() Fields + +type fieldsProducerMessage struct { + fp FieldsProducer + cached Composer + level level.Priority +} + +// NewFieldsProducerMessage constructs a lazy FieldsProducer wrapping +// message at the specified level. +// +// FieldsProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func NewFieldsProducerMessage(p level.Priority, fp FieldsProducer) Composer { + return &fieldsProducerMessage{level: p, fp: fp} +} + +// MakeFieldsProducerMessage constructs a lazy FieldsProducer wrapping +// message at the specified level. +// +// FieldsProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func MakeFieldsProducerMessage(fp FieldsProducer) Composer { + return &fieldsProducerMessage{fp: fp} +} + +func (fp *fieldsProducerMessage) resolve() { + if fp.cached == nil { + if fp.fp == nil { + fp.cached = NewFields(fp.level, Fields{}) + } else { + fp.cached = NewFields(fp.level, fp.fp()) + } + } +} + +func (fp *fieldsProducerMessage) Annotate(k string, v interface{}) error { + fp.resolve() + return fp.cached.Annotate(k, v) +} + +func (fp *fieldsProducerMessage) SetPriority(p level.Priority) error { + if !p.IsValid() { + return errors.New("invalid level") + } + fp.level = p + if fp.cached != nil { + return fp.cached.SetPriority(fp.level) + } + + return nil +} + +func (fp *fieldsProducerMessage) Loggable() bool { + if fp.fp == nil { + return false + } + + fp.resolve() + return fp.cached.Loggable() +} + +func (fp *fieldsProducerMessage) Priority() level.Priority { return fp.level } +func (fp *fieldsProducerMessage) String() string { fp.resolve(); return fp.cached.String() } +func (fp *fieldsProducerMessage) Raw() interface{} { fp.resolve(); return fp.cached.Raw() } + +//////////////////////////////////////////////////////////////////////// + +// ComposerProducer constructs a lazy composer, and makes it easy to +// implement new Composers as functions returning an existing composer +// type. Consider the following: +// +// grip.Info(func() message.Composer { return WrapError(validateRequest(req), message.Fields{"op": "name"})}) +// +// Grip can automatically convert these functions when passed to a +// logging function. +// +// If the Fields object is nil or empty then no message is ever logged. +type ComposerProducer func() Composer + +type composerProducerMessage struct { + cp ComposerProducer + cached Composer + level level.Priority +} + +// NewComposerMessage constructs a message, with the given priority, +// that will call the ComposerProducer function lazily during logging. +// +// ComposerProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func NewComposerProducerMessage(p level.Priority, cp ComposerProducer) Composer { + return &composerProducerMessage{level: p, cp: cp} +} + +// MakeComposerMessage constructs a message that will call the +// ComposerProducer function lazily during logging. +// +// ComposerProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func MakeComposerProducerMessage(cp ComposerProducer) Composer { + return &composerProducerMessage{cp: cp} +} + +func (cp *composerProducerMessage) resolve() { + if cp.cached == nil { + cp.cached = cp.cp() + if cp.cached == nil { + cp.cached = NewSimpleFields(cp.level, Fields{}) + } else { + cp.cached.SetPriority(cp.level) + } + } +} + +func (cp *composerProducerMessage) Annotate(k string, v interface{}) error { + cp.resolve() + return cp.cached.Annotate(k, v) +} + +func (cp *composerProducerMessage) SetPriority(p level.Priority) error { + if !p.IsValid() { + return errors.New("invalid level") + } + + cp.level = p + if cp.cached != nil { + return cp.cached.SetPriority(cp.level) + } + + return nil +} + +func (cp *composerProducerMessage) Loggable() bool { + if cp.cp == nil { + return false + } + + cp.resolve() + return cp.cached.Loggable() +} + +func (cp *composerProducerMessage) Priority() level.Priority { return cp.level } +func (cp *composerProducerMessage) String() string { cp.resolve(); return cp.cached.String() } +func (cp *composerProducerMessage) Raw() interface{} { cp.resolve(); return cp.cached.Raw() } + +//////////////////////////////////////////////////////////////////////// + +// ErrorProducer is a function that returns an error, and is used for +// constructing message that lazily wraps the resulting function which +// is called when the message is dispatched. +// +// If you pass one of these functions to a logging method, the +// ConvertToComposer operation will construct a lazy Composer based on +// this function, as in: +// +// grip.Error(func() error { return errors.New("error message") }) +// +// It may be useful also to pass a "closer" function in this form, as +// in: +// +// grip.Error(file.Close) +// +// As a special case the WrapErrorFunc method has the same semantics +// as other ErrorProducer methods, but makes it possible to annotate +// an error. +type ErrorProducer func() error + +type errorProducerMessage struct { + ep ErrorProducer + cached Composer + level level.Priority +} + +// NewErrorProducerMessage returns a mesage that wrapps an error +// producing function, at the specified level. If the function returns +// then there is never a message logged. +// +// ErrorProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func NewErrorProducerMessage(p level.Priority, ep ErrorProducer) Composer { + return &errorProducerMessage{level: p, ep: ep} +} + +// MakeErrorProducerMessage returns a mesage that wrapps an error +// producing function. If the function returns then there is never a +// message logged. +// +// ErrorProducer functions are only called, before calling the +// Loggable, String, Raw, or Annotate methods. Changing the priority +// does not call the function. In practice, if the priority of the +// message is below the logging threshold, then the function will +// never be called. +func MakeErrorProducerMessage(ep ErrorProducer) Composer { + return &errorProducerMessage{ep: ep} +} + +func (ep *errorProducerMessage) resolve() { + if ep.cached == nil { + ep.cached = NewErrorMessage(ep.level, ep.ep()) + } +} + +func (ep *errorProducerMessage) Annotate(k string, v interface{}) error { + ep.resolve() + return ep.cached.Annotate(k, v) +} + +func (ep *errorProducerMessage) SetPriority(p level.Priority) error { + if !p.IsValid() { + return errors.New("invalid level") + } + + ep.level = p + if ep.cached != nil { + return ep.cached.SetPriority(ep.level) + } + + return nil +} + +func (ep *errorProducerMessage) Loggable() bool { + if ep.ep == nil { + return false + } + + ep.resolve() + return ep.cached.Loggable() +} + +func (ep *errorProducerMessage) Priority() level.Priority { return ep.level } +func (ep *errorProducerMessage) String() string { ep.resolve(); return ep.cached.String() } +func (ep *errorProducerMessage) Raw() interface{} { ep.resolve(); return ep.cached.Raw() } +func (ep *errorProducerMessage) Error() string { return ep.String() } +func (ep *errorProducerMessage) Unwrap() error { return ep.Cause() } + +func (ep *errorProducerMessage) Cause() error { + ep.resolve() + + switch err := ep.cached.(type) { + case *errorComposerWrap: + return err.err + case *errorMessage: + return err.err + case error: + return err + default: + return nil + } +} + +func (ep *errorProducerMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", errors.Cause(ep)) + _, _ = io.WriteString(s, ep.String()) + return + } + fallthrough + case 's', 'q': + _, _ = io.WriteString(s, ep.Error()) + } +} + +// WrapErrorFunc produces a lazily-composed wrapped error message. The +// function is only called is +// +// The resulting method itself implements the "error" interface +// (supporing unwrapping,) as well as the composer type, so you can +// return the result of this function as an error to avoid needing to +// manage multiple error annotations. +func WrapErrorFunc(ep ErrorProducer, m interface{}) Composer { + return errorComposerShim{MakeComposerProducerMessage(func() Composer { return WrapError(ep(), m) })} +} + +type errorComposerShim struct { + Composer +} + +func (ecs errorComposerShim) Error() string { return ecs.Composer.String() } +func (ecs errorComposerShim) Unwrap() error { return ecs.Cause() } + +func (ecs errorComposerShim) Cause() error { + switch err := ecs.Composer.(type) { + case *errorComposerWrap: + return err.err + case *errorMessage: + return err.err + case error: + return err + default: + return nil + } +} + +func (ecs errorComposerShim) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", errors.Cause(ecs)) + _, _ = io.WriteString(s, ecs.String()) + return + } + fallthrough + case 's', 'q': + _, _ = io.WriteString(s, ecs.Error()) + } +} diff --git a/message/function_test.go b/message/function_test.go new file mode 100644 index 0000000..7c9e63b --- /dev/null +++ b/message/function_test.go @@ -0,0 +1,7 @@ +package message + +import "testing" + +func TestFunctionMessage(t *testing.T) { + +} diff --git a/message/interface.go b/message/interface.go index d70f675..95e0f10 100644 --- a/message/interface.go +++ b/message/interface.go @@ -68,6 +68,18 @@ func convert(p level.Priority, message interface{}, overRideLevel bool) Composer return NewDefaultMessage(p, message) case error: return NewErrorMessage(p, message) + case FieldsProducer: + return NewFieldsProducerMessage(p, message) + case func() Fields: + return NewFieldsProducerMessage(p, message) + case ComposerProducer: + return NewComposerProducerMessage(p, message) + case func() Composer: + return NewComposerProducerMessage(p, message) + case ErrorProducer: + return NewErrorProducerMessage(p, message) + case func() error: + return NewErrorProducerMessage(p, message) case []string: return newLinesFromStrings(p, message) case []interface{}: @@ -83,15 +95,13 @@ func convert(p level.Priority, message interface{}, overRideLevel bool) Composer for idx := range message { grp[idx] = newLinesFromStrings(p, message[idx]) } - out := NewGroupComposer(grp) - return out + return NewGroupComposer(grp) case [][]byte: grp := make([]Composer, len(message)) for idx := range message { grp[idx] = NewBytesMessage(p, message[idx]) } - out := NewGroupComposer(grp) - return out + return NewGroupComposer(grp) case []map[string]interface{}: grp := make([]Composer, len(message)) for idx := range message { @@ -106,6 +116,42 @@ func convert(p level.Priority, message interface{}, overRideLevel bool) Composer } out := NewGroupComposer(grp) return out + case []FieldsProducer: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewFieldsProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) + case []func() Fields: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewFieldsProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) + case []ComposerProducer: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewComposerProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) + case []func() Composer: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewComposerProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) + case []ErrorProducer: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewErrorProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) + case []func() error: + grp := make([]Composer, len(message)) + for idx := range message { + grp[idx] = NewErrorProducerMessage(p, message[idx]) + } + return NewGroupComposer(grp) case nil: return NewLineMessage(p) default: @@ -119,7 +165,7 @@ func convert(p level.Priority, message interface{}, overRideLevel bool) Composer // Additionally, returns true for all unknown types, including all types not // produced by this package. func IsStructured(msg Composer) bool { - switch msg.(type) { + switch m := msg.(type) { case *stringMessage: return false case *formatMessenger: @@ -148,6 +194,17 @@ func IsStructured(msg Composer) bool { return true case *GroupComposer: return true + case *fieldsProducerMessage: + m.resolve() + return IsStructured(m.cached) + case *composerProducerMessage: + m.resolve() + return IsStructured(m.cached) + case *errorProducerMessage: + m.resolve() + return IsStructured(m.cached) + case errorComposerShim: + return IsStructured(m.Composer) default: return true } diff --git a/send/buffered.go b/send/buffered.go index c234305..26351bd 100644 --- a/send/buffered.go +++ b/send/buffered.go @@ -57,7 +57,7 @@ func NewBufferedSender(sender Sender, interval time.Duration, size int) Sender { } func (s *bufferedSender) Send(msg message.Composer) { - if !s.Level().ShouldLog(msg) { + if !s.Level().Loggable(msg.Priority()) { return } diff --git a/send/interface.go b/send/interface.go index 911df57..15d416a 100644 --- a/send/interface.go +++ b/send/interface.go @@ -75,11 +75,17 @@ func (l LevelInfo) Valid() bool { return l.Default.IsValid() && l.Threshold.IsVa // ShouldLog checks to see if the log message should be logged, and returns // false if there is no message or if the message's priority is below the // logging threshold. +// +// This calls the messages' Loggable method. func (l LevelInfo) ShouldLog(m message.Composer) bool { // priorities are 0 = Emergency; 7 = debug - return m.Loggable() && (m.Priority() >= l.Threshold) + return m.Loggable() && l.Loggable(m.Priority()) } +// Loggable returns true only when passed an argument that is equal to +// or greater than the threshold. +func (l LevelInfo) Loggable(p level.Priority) bool { return p >= l.Threshold } + func setup(s Sender, name string, l LevelInfo) (Sender, error) { if err := s.SetLevel(l); err != nil { return nil, err