Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tflog+tfsdklog: Added WithAdditionalLocationOffset function #36

Merged
merged 6 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/36.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
tflog: Added `WithAdditionalLocationOffset` function, which allows implementations to adjust the location offset when using helper functions
```

```release-note:enhancement
tfsdklog: Added `WithAdditionalLocationOffset` function, which allows implementations to adjust the location offset when using helper functions
```
29 changes: 29 additions & 0 deletions internal/hclogutils/logger_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package hclogutils

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

// LoggerOptionsCopy will safely copy LoggerOptions. Manually implemented
// to save importing a dependency such as github.com/mitchellh/copystructure.
func LoggerOptionsCopy(src *hclog.LoggerOptions) *hclog.LoggerOptions {
if src == nil {
return nil
}

return &hclog.LoggerOptions{
AdditionalLocationOffset: src.AdditionalLocationOffset,
Color: src.Color,
DisableTime: src.DisableTime,
Exclude: src.Exclude,
IncludeLocation: src.IncludeLocation,
IndependentLevels: src.IndependentLevels,
JSONFormat: src.JSONFormat,
Level: src.Level,
Mutex: src.Mutex,
Name: src.Name,
Output: src.Output,
TimeFormat: src.TimeFormat,
TimeFn: src.TimeFn,
}
}
28 changes: 18 additions & 10 deletions internal/loggertest/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ import (
"context"
"io"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/terraform-plugin-log/internal/logging"
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
)

func ProviderRoot(ctx context.Context, output io.Writer) context.Context {
loggerOptions := &hclog.LoggerOptions{
DisableTime: true,
JSONFormat: true,
Level: hclog.Trace,
Output: output,
}

ctx = logging.SetProviderRootLogger(ctx, hclog.New(loggerOptions))
return tfsdklog.NewRootProviderLogger(
ctx,
logging.WithoutLocation(),
logging.WithoutTimestamp(),
logging.WithOutput(output),
)
}

return ctx
// ProviderRootWithLocation is for testing code that affects go-hclog's caller
// information (location offset). Most testing code should avoid this, since
// correctly checking differences including the location is extra effort
// with little benefit.
func ProviderRootWithLocation(ctx context.Context, output io.Writer) context.Context {
return tfsdklog.NewRootProviderLogger(
ctx,
logging.WithoutTimestamp(),
logging.WithOutput(output),
)
}
28 changes: 18 additions & 10 deletions internal/loggertest/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@ import (
"context"
"io"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/terraform-plugin-log/internal/logging"
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
)

func SDKRoot(ctx context.Context, output io.Writer) context.Context {
loggerOptions := &hclog.LoggerOptions{
DisableTime: true,
JSONFormat: true,
Level: hclog.Trace,
Output: output,
}

ctx = logging.SetSDKRootLogger(ctx, hclog.New(loggerOptions))
return tfsdklog.NewRootSDKLogger(
ctx,
logging.WithoutLocation(),
logging.WithoutTimestamp(),
logging.WithOutput(output),
)
}

return ctx
// SDKRootWithLocation is for testing code that affects go-hclog's caller
// information (location offset). Most testing code should avoid this, since
// correctly checking differences including the location is extra effort
// with little benefit.
func SDKRootWithLocation(ctx context.Context, output io.Writer) context.Context {
return tfsdklog.NewRootSDKLogger(
ctx,
logging.WithoutTimestamp(),
logging.WithOutput(output),
)
}
20 changes: 20 additions & 0 deletions internal/logging/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import (
"os"
)

const (
// Default provider root logger name.
DefaultProviderRootLoggerName string = "provider"

// Default SDK root logger name.
DefaultSDKRootLoggerName string = "sdk"
)

// loggerKey defines context keys for locating loggers in context.Context
// it's a private type to make sure no other packages can override the key
type loggerKey string
Expand All @@ -14,10 +22,22 @@ const (
// logger for writing logs from within provider code.
ProviderRootLoggerKey loggerKey = "provider"

// ProviderRootLoggerOptionsKey is the loggerKey that will hold the root
// logger options when the root provider logger is created. This is to
// assist creating subsystem loggers, as most options cannot be fetched and
// a logger does not provide set methods for these options.
ProviderRootLoggerOptionsKey loggerKey = "provider-options"

// SDKRootLoggerKey is the loggerKey that will hold the root logger for
// writing logs from with SDKs.
SDKRootLoggerKey loggerKey = "sdk"

// SDKRootLoggerOptionsKey is the loggerKey that will hold the root
// logger options when the SDK provider logger is created. This is to
// assist creating subsystem loggers, as most options cannot be fetched and
// a logger does not provide set methods for these options.
SDKRootLoggerOptionsKey loggerKey = "sdk-options"

// SinkKey is the loggerKey that will hold the logging sink used for
// test frameworks.
SinkKey loggerKey = ""
Expand Down
38 changes: 33 additions & 5 deletions internal/logging/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ type LoggerOpts struct {
// of the logging statement or not.
IncludeLocation bool

// AdditionalLocationOffset is the number of additional stack levels to
// skip when finding the file and line information for the log line.
// Defaults to 1 to account for the tflog and tfsdklog logging functions.
AdditionalLocationOffset int

// Output dictates where logs are written to. Output should only ever
// be set by tflog or tfsdklog, never by SDK authors or provider
// developers. Where logs get written to is complex and delicate and
Expand All @@ -37,21 +42,34 @@ type LoggerOpts struct {
}

// ApplyLoggerOpts generates a LoggerOpts out of a list of Option
// implementations. By default, IncludeLocation is true, IncludeTime is true,
// and Output is os.Stderr.
// implementations. By default, AdditionalLocationOffset is 1, IncludeLocation
// is true, IncludeTime is true, and Output is os.Stderr.
func ApplyLoggerOpts(opts ...Option) LoggerOpts {
// set some defaults
l := LoggerOpts{
IncludeLocation: true,
IncludeTime: true,
Output: os.Stderr,
AdditionalLocationOffset: 1,
IncludeLocation: true,
IncludeTime: true,
Output: os.Stderr,
}
for _, opt := range opts {
l = opt(l)
}
return l
}

// WithAdditionalLocationOffset sets the WithAdditionalLocationOffset
// configuration option, allowing implementations to fix location information
// when implementing helper functions. The default offset of 1 is automatically
// added to the provided value to account for the tflog and tfsdk logging
// functions.
func WithAdditionalLocationOffset(additionalLocationOffset int) Option {
return func(l LoggerOpts) LoggerOpts {
l.AdditionalLocationOffset = additionalLocationOffset + 1
return l
}
}

// WithOutput sets the Output configuration option, controlling where logs get
// written to. This is mostly used for testing (to write to os.Stdout, so the
// test framework can compare it against the example output) and as a helper
Expand All @@ -63,6 +81,16 @@ func WithOutput(output io.Writer) Option {
}
}

// WithoutLocation disables the location included with logging statements. It
// should only ever be used to make log output deterministic when testing
// terraform-plugin-log.
func WithoutLocation() Option {
return func(l LoggerOpts) LoggerOpts {
l.IncludeLocation = false
return l
}
}

// WithoutTimestamp disables the timestamp included with logging statements. It
// should only ever be used to make log output deterministic when testing
// terraform-plugin-log.
Expand Down
29 changes: 29 additions & 0 deletions internal/logging/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,41 @@ func GetProviderRootLogger(ctx context.Context) hclog.Logger {
return logger.(hclog.Logger)
}

// GetProviderRootLoggerOptions returns the root logger options used for
// creating the root provider logger. If the root logger has not been created
// or the options are not present, it will return nil.
func GetProviderRootLoggerOptions(ctx context.Context) *hclog.LoggerOptions {
if GetProviderRootLogger(ctx) == nil {
return nil
}

loggerOptionsRaw := ctx.Value(ProviderRootLoggerOptionsKey)

if loggerOptionsRaw == nil {
return nil
}

loggerOptions, ok := loggerOptionsRaw.(*hclog.LoggerOptions)

if !ok {
return nil
}

return loggerOptions
}

// SetProviderRootLogger sets `logger` as the root logger used for writing
// logs from a provider.
func SetProviderRootLogger(ctx context.Context, logger hclog.Logger) context.Context {
return context.WithValue(ctx, ProviderRootLoggerKey, logger)
}

// SetProviderRootLoggerOptions sets `loggerOptions` as the root logger options
// used for creating the provider root logger.
func SetProviderRootLoggerOptions(ctx context.Context, loggerOptions *hclog.LoggerOptions) context.Context {
return context.WithValue(ctx, ProviderRootLoggerOptionsKey, loggerOptions)
}

// NewProviderSubsystemLoggerWarning is the text included in log output when a
// subsystem is auto-generated by terraform-plugin-log because it was used
// before the provider instantiated it.
Expand Down
29 changes: 29 additions & 0 deletions internal/logging/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,41 @@ func GetSDKRootLogger(ctx context.Context) hclog.Logger {
return logger.(hclog.Logger)
}

// GetSDKRootLoggerOptions returns the root logger options used for
// creating the root SDK logger. If the root logger has not been created or
// the options are not present, it will return nil.
func GetSDKRootLoggerOptions(ctx context.Context) *hclog.LoggerOptions {
if GetSDKRootLogger(ctx) == nil {
return nil
}

loggerOptionsRaw := ctx.Value(SDKRootLoggerOptionsKey)

if loggerOptionsRaw == nil {
return nil
}

loggerOptions, ok := loggerOptionsRaw.(*hclog.LoggerOptions)

if !ok {
return nil
}

return loggerOptions
}

// SetSDKRootLogger sets `logger` as the root logger used for writing logs from
// an SDK.
func SetSDKRootLogger(ctx context.Context, logger hclog.Logger) context.Context {
return context.WithValue(ctx, SDKRootLoggerKey, logger)
}

// SetSDKRootLoggerOptions sets `loggerOptions` as the root logger options
// used for creating the SDK root logger.
func SetSDKRootLoggerOptions(ctx context.Context, loggerOptions *hclog.LoggerOptions) context.Context {
return context.WithValue(ctx, SDKRootLoggerOptionsKey, loggerOptions)
}

// NewSDKSubsystemLoggerWarning is the text included in log output when a
// subsystem is auto-generated by terraform-plugin-log because it was used
// before the SDK instantiated it.
Expand Down
8 changes: 8 additions & 0 deletions tflog/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import (
// to NewSubsystem prior to calling it.
type Options []logging.Option

// WithAdditionalLocationOffset returns an option that allowing implementations
// to fix location information when implementing helper functions. The default
// offset of 1 is automatically added to the provided value to account for the
// tflog logging functions.
func WithAdditionalLocationOffset(additionalLocationOffset int) logging.Option {
return logging.WithAdditionalLocationOffset(additionalLocationOffset)
}

// WithLevelFromEnv returns an option that will set the level of the logger
// based on the string in an environment variable. The environment variable
// checked will be `name` and `subsystems`, joined by _ and in all caps.
Expand Down
Loading