diff --git a/controller/watcher.go b/controller/watcher.go index 7e1e38958..ed092c31e 100644 --- a/controller/watcher.go +++ b/controller/watcher.go @@ -1,13 +1,17 @@ package controller import ( + "encoding/json" + "fmt" "math/rand" + "reflect" "time" "github.com/hashicorp/consul-terraform-sync/config" "github.com/hashicorp/consul-terraform-sync/logging" "github.com/hashicorp/consul-terraform-sync/retry" "github.com/hashicorp/hcat" + "github.com/hashicorp/hcat/events" ) const ( @@ -63,6 +67,7 @@ func newWatcher(conf *config.Config, maxRetries int) (*hcat.Watcher, error) { Clients: clients, Cache: hcat.NewStore(), ConsulRetryFunc: wr.retryConsul, + EventHandler: newWatcherEventHandler(logging.Global().Named(hcatLogSystemName)), }), nil } @@ -124,3 +129,40 @@ func setVaultClient(clients *hcat.ClientSet, conf *config.Config) error { return clients.AddVault(vault) } + +func newWatcherEventHandler(logger logging.Logger) events.EventHandler { + return func(e events.Event) { + // Log events at different log levels based on the type + var level logging.Level + switch e.(type) { + case events.Trace: + // Only show hcat Trace events when trace logging is enabled. + level = logging.Trace + default: + // Everything else is shown when debug is enabled. + level = logging.Debug + } + + // Default to emitting the go format for the event. + event := fmt.Sprintf("%+v", e) + + // If the output log level is Trace, then emit the json format for the event. + // This could be very large in certain circumstances, because it displays + // nested data structures. + if logger.IsTrace() { + b, err := json.Marshal(e) + if err != nil { + logger.Warn("Unexpected error marshalling event to json", "error", err, "event", e) + return + } + event = string(b) + } + + // Emit the log and include the type name, if possible. + tName := "nil" + if t := reflect.TypeOf(e); t != nil { + tName = t.Name() + } + logger.Log(level, "event received", "type", tName, "event", event) + } +} diff --git a/controller/watcher_test.go b/controller/watcher_test.go index aff034c5e..2f56009cd 100644 --- a/controller/watcher_test.go +++ b/controller/watcher_test.go @@ -1,11 +1,17 @@ package controller import ( + "bytes" + "encoding/json" + "fmt" "math/rand" "testing" "time" + "github.com/hashicorp/consul-terraform-sync/logging" + "github.com/hashicorp/hcat/events" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestWatchRetry_retryConsul(t *testing.T) { @@ -55,3 +61,42 @@ func TestWatchRetry_retryConsul(t *testing.T) { }) } } + +func Test_newWatcherEventHandler(t *testing.T) { + toJson := func(v any) string { + b, err := json.Marshal(v) + require.NoError(t, err) + return string(b) + } + toGo := func(v any) string { + return fmt.Sprintf("%+v", v) + } + + testCases := []struct { + logLevel string + shouldLog bool + findString func(v any) string + event events.Event + }{ + {"TRACE", true, toJson, events.Trace{ID: "logged"}}, + {"DEBUG", false, toGo, events.Trace{ID: "not logged"}}, + {"TRACE", true, toJson, events.NoNewData{ID: "logged"}}, + {"DEBUG", true, toGo, events.NewData{ID: "logged"}}, + {"INFO", false, toGo, events.RetryAttempt{ID: "not logged"}}, + {"DEBUG", true, func(v any) string { return "type=nil" }, nil}, + } + + for _, tc := range testCases { + var buf bytes.Buffer + logger := logging.NewTestLogger(tc.logLevel, &buf) + handler := newWatcherEventHandler(logger) + handler(tc.event) + toFind := tc.findString(tc.event) + if tc.shouldLog { + assert.Contains(t, buf.String(), toFind) + } else { + assert.NotContains(t, buf.String(), toFind) + assert.Equal(t, "", buf.String()) + } + } +} diff --git a/logging/logging.go b/logging/logging.go index cc41e91d8..f28bf51dd 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -17,8 +17,19 @@ var Levels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERR"} // it is set by the Setup() function and is shared with all other setup variations (i.e. SetupLocal()) var LogLevel = "" +// Alias the hclog levels so that the hclog package doesn't need to be exposed for `logger.Log(...)` calls. +type Level = hclog.Level + const ( defaultLogLevel = "INFO" + + NoLevel Level = hclog.NoLevel + Trace Level = hclog.Trace + Debug Level = hclog.Debug + Info Level = hclog.Info + Warn Level = hclog.Warn + Error Level = hclog.Error + Off Level = hclog.Off ) // Logger is a type alias for hclog.Logger