Skip to content

Commit

Permalink
Merge pull request #605 from hashicorp/f-support-hclog
Browse files Browse the repository at this point in the history
Add Support for logging with `hc-log`
  • Loading branch information
gdavison committed Aug 17, 2023
2 parents 2c505f6 + 072a666 commit 261844c
Show file tree
Hide file tree
Showing 14 changed files with 543 additions and 58 deletions.
15 changes: 13 additions & 2 deletions aws_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ func configCommonLogging(ctx context.Context) context.Context {

func GetAwsConfig(ctx context.Context, c *Config) (context.Context, aws.Config, diag.Diagnostics) {
var diags diag.Diagnostics

var logger logging.Logger = logging.NullLogger{}
if c.Logger != nil {
logger = c.Logger
}
ctx = logging.RegisterLogger(ctx, logger)
ctx = configCommonLogging(ctx)

baseCtx, logger := logging.New(ctx, loggerName)
baseCtx, logger := logger.SubLogger(ctx, loggerName)
baseCtx = logging.RegisterLogger(baseCtx, logger)

logger.Trace(baseCtx, "Resolving AWS configuration")
Expand Down Expand Up @@ -209,8 +215,13 @@ func (r *networkErrorShortcutter) RetryDelay(attempt int, err error) (time.Durat

func GetAwsAccountIDAndPartition(ctx context.Context, awsConfig aws.Config, c *Config) (string, string, diag.Diagnostics) {
var diags diag.Diagnostics

var logger logging.Logger = logging.NullLogger{}
if c.Logger != nil {
logger = c.Logger
}
ctx = configCommonLogging(ctx)
ctx, logger := logging.New(ctx, loggerName)
ctx, logger = logger.SubLogger(ctx, loggerName)
ctx = logging.RegisterLogger(ctx, logger)

if !c.SkipCredsValidation {
Expand Down
151 changes: 149 additions & 2 deletions aws_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
Expand All @@ -31,9 +32,12 @@ import (
"github.com/hashicorp/aws-sdk-go-base/v2/internal/awsconfig"
"github.com/hashicorp/aws-sdk-go-base/v2/internal/constants"
"github.com/hashicorp/aws-sdk-go-base/v2/internal/test"
"github.com/hashicorp/aws-sdk-go-base/v2/logging"
"github.com/hashicorp/aws-sdk-go-base/v2/mockdata"
"github.com/hashicorp/aws-sdk-go-base/v2/servicemocks"
"github.com/hashicorp/aws-sdk-go-base/v2/useragent"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-log/tflogtest"
)

Expand Down Expand Up @@ -3466,15 +3470,19 @@ func (r *withNoDelay) RetryDelay(attempt int, err error) (time.Duration, error)
return 0 * time.Second, nil
}

func TestLogger(t *testing.T) {
func TestLogger_TfLog(t *testing.T) {
ctx := context.Background()
var buf bytes.Buffer
ctx := tflogtest.RootLogger(context.Background(), &buf)
ctx = tflogtest.RootLogger(ctx, &buf)

oldEnv := servicemocks.InitSessionTestEnv()
defer servicemocks.PopEnv(oldEnv)

ctx, logger := logging.NewTfLogger(ctx)

config := &Config{
AccessKey: servicemocks.MockStaticAccessKey,
Logger: logger,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
}
Expand All @@ -3497,6 +3505,9 @@ func TestLogger(t *testing.T) {
t.Fatalf("GetAwsConfig: decoding log lines: %s", err)
}

if len(lines) == 0 {
t.Fatalf("expected log entries, had none")
}
for i, line := range lines {
if a, e := line["@module"], expectedName; a != e {
t.Errorf("GetAwsConfig: line %d: expected module %q, got %q", i+1, e, a)
Expand All @@ -3513,9 +3524,145 @@ func TestLogger(t *testing.T) {
t.Fatalf("GetAwsAccountIDAndPartition: decoding log lines: %s", err)
}

if len(lines) == 0 {
t.Fatalf("expected log entries, had none")
}
for i, line := range lines {
if a, e := line["@module"], expectedName; a != e {
t.Errorf("GetAwsAccountIDAndPartition: line %d: expected module %q, got %q", i+1, e, a)
}
}
}

func TestLoggerDefaultMasking_TfLog(t *testing.T) {
ctx := context.Background()
var buf bytes.Buffer
ctx = tflogtest.RootLogger(ctx, &buf)

oldEnv := servicemocks.InitSessionTestEnv()
defer servicemocks.PopEnv(oldEnv)

config := &Config{
AccessKey: servicemocks.MockStaticAccessKey,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
}

ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
defer ts.Close()
config.StsEndpoint = ts.URL

ctx, _, diags := GetAwsConfig(ctx, config)
if diags.HasError() {
t.Fatalf("error in GetAwsConfig(): %v", diags)
}

buf.Reset()

tflog.Info(ctx, "message", map[string]any{
"id": "AKIAI44QH8DHBEXAMPLE",
})

lines, err := tflogtest.MultilineJSONDecode(&buf)
if err != nil {
t.Fatalf("decoding log lines: %s", err)
}

if l := len(lines); l != 1 {
t.Fatalf("expected 1 log entry, got %d", l)
}

line := lines[0]
if a, e := line["id"], "***"; a != e {
t.Errorf("expected %q, got %q", e, a)
}
}

func TestLogger_HcLog(t *testing.T) {
ctx := context.Background()

rootName := "hc-log-test"
expectedName := rootName + "." + loggerName

var buf bytes.Buffer
hclogger := configureHcLogger(rootName, &buf)

oldEnv := servicemocks.InitSessionTestEnv()
defer servicemocks.PopEnv(oldEnv)

ctx, logger := logging.NewHcLogger(ctx, hclogger)

config := &Config{
AccessKey: servicemocks.MockStaticAccessKey,
Logger: logger,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
}

ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
defer ts.Close()
config.StsEndpoint = ts.URL

ctx, awsConfig, diags := GetAwsConfig(ctx, config)
if diags.HasError() {
t.Fatalf("error in GetAwsConfig(): %v", diags)
}

lines, err := tflogtest.MultilineJSONDecode(&buf)
if err != nil {
t.Fatalf("GetAwsConfig: decoding log lines: %s", err)
}

if len(lines) == 0 {
t.Fatalf("expected log entries, had none")
}
for i, line := range lines {
if a, e := line["@module"], expectedName; a != e {
t.Errorf("GetAwsConfig: line %d: expected module %q, got %q", i+1, e, a)
}
}

_, _, diags = GetAwsAccountIDAndPartition(ctx, awsConfig, config)
if diags.HasError() {
t.Fatalf("GetAwsAccountIDAndPartition: unexpected '%[1]T': %[1]s", err)
}

lines, err = tflogtest.MultilineJSONDecode(&buf)
if err != nil {
t.Fatalf("GetAwsAccountIDAndPartition: decoding log lines: %s", err)
}

if len(lines) == 0 {
t.Fatalf("expected log entries, had none")
}
for i, line := range lines {
if a, e := line["@module"], expectedName; a != e {
t.Errorf("GetAwsAccountIDAndPartition: line %d: expected module %q, got %q", i+1, e, a)
}
}
}

// configureHcLogger configures the default logger with settings suitable for testing:
//
// - Log level set to TRACE
// - Written to the io.Writer passed in, such as a bytes.Buffer
// - Log entries are in JSON format, and can be decoded using multilineJSONDecode
// - Caller information is not included
// - Timestamp is not included
func configureHcLogger(name string, output io.Writer) hclog.Logger {
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: name,
Level: hclog.Trace,
Output: output,
IndependentLevels: true,
JSONFormat: true,
IncludeLocation: false,
DisableTime: true,
})

return logger
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2
github.com/aws/smithy-go v1.14.1
github.com/google/go-cmp v0.5.9
github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/mitchellh/go-homedir v1.1.0
Expand All @@ -27,7 +28,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/hashicorp/aws-sdk-go-base/v2/internal/expand"
"github.com/hashicorp/aws-sdk-go-base/v2/logging"
)

type Config struct {
Expand All @@ -33,6 +34,7 @@ type Config struct {
HTTPProxy string
IamEndpoint string
Insecure bool
Logger logging.Logger
MaxRetries int
Profile string
Region string
Expand Down
8 changes: 4 additions & 4 deletions logging/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ type loggerKeyT string

const loggerKey loggerKeyT = "logger-key"

func RegisterLogger(ctx context.Context, logger TfLogger) context.Context {
func RegisterLogger(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, loggerKey, logger)
}

func RetrieveLogger(ctx context.Context) TfLogger {
logger, ok := ctx.Value(loggerKey).(TfLogger)
func RetrieveLogger(ctx context.Context) Logger {
logger, ok := ctx.Value(loggerKey).(Logger)
if !ok {
return TfLogger("")
return NullLogger{}
}
return logger
}
71 changes: 71 additions & 0 deletions logging/hc_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package logging

import (
"context"

"github.com/hashicorp/go-hclog"
)

type HcLogger struct{}

var _ Logger = HcLogger{}

func NewHcLogger(ctx context.Context, logger hclog.Logger) (context.Context, HcLogger) {
ctx = hclog.WithContext(ctx, logger)

return ctx, HcLogger{}
}

func (l HcLogger) SubLogger(ctx context.Context, name string) (context.Context, Logger) {
logger := hclog.FromContext(ctx)
logger = logger.Named(name)
ctx = hclog.WithContext(ctx, logger)

return ctx, HcLogger{}
}

func (l HcLogger) Warn(ctx context.Context, msg string, fields ...map[string]any) {
logger := hclog.FromContext(ctx)
logger.Warn(msg, flattenFields(fields...)...)
}

func (l HcLogger) Info(ctx context.Context, msg string, fields ...map[string]any) {
logger := hclog.FromContext(ctx)
logger.Info(msg, flattenFields(fields...)...)
}

func (l HcLogger) Debug(ctx context.Context, msg string, fields ...map[string]any) {
logger := hclog.FromContext(ctx)
logger.Debug(msg, flattenFields(fields...)...)
}

func (l HcLogger) Trace(ctx context.Context, msg string, fields ...map[string]any) {
logger := hclog.FromContext(ctx)
logger.Trace(msg, flattenFields(fields...)...)
}

// TODO: how to handle duplicates
func flattenFields(fields ...map[string]any) []any {
var totalLen int
for _, m := range fields {
totalLen = len(m)
}
f := make([]any, 0, totalLen*2) //nolint:gomnd

for _, m := range fields {
for k, v := range m {
f = append(f, k, v)
}
}
return f
}

func (l HcLogger) SetField(ctx context.Context, key string, value any) context.Context {
logger := hclog.FromContext(ctx)
logger = logger.With(key, value)
ctx = hclog.WithContext(ctx, logger)
return ctx
}
Loading

0 comments on commit 261844c

Please sign in to comment.