Skip to content

Commit 561793d

Browse files
authored
tflog+tfsdklog: Added WithAdditionalLocationOffset function (#36)
Reference: #29 Allows implementations to fix the location offset of caller information when using helper functions. While typically expected for subsystem loggers, SDKs could also offer the configuration of the root provider logger as well. Since each `hclog.Logger` is mainly immutable once its created (except for level and creating named sub-loggers), the `hclog.LoggerOptions` are saved to the context as well, allowing `NewSubsystem` to appropriately have all the root logger options available to create new sub-loggers. Maybe this can be changed upstream in the future, although that would technically be a breaking change since the interface would need to be modified. This changeset also includes setting the name of the test root SDK/provider loggers, so that subsystem naming (and therefore the unit testing) matches real world usage.
1 parent 34e012b commit 561793d

File tree

19 files changed

+603
-77
lines changed

19 files changed

+603
-77
lines changed

.changelog/36.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:enhancement
2+
tflog: Added `WithAdditionalLocationOffset` function, which allows implementations to adjust the location offset when using helper functions
3+
```
4+
5+
```release-note:enhancement
6+
tfsdklog: Added `WithAdditionalLocationOffset` function, which allows implementations to adjust the location offset when using helper functions
7+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package hclogutils
2+
3+
import (
4+
"github.com/hashicorp/go-hclog"
5+
)
6+
7+
// LoggerOptionsCopy will safely copy LoggerOptions. Manually implemented
8+
// to save importing a dependency such as github.com/mitchellh/copystructure.
9+
func LoggerOptionsCopy(src *hclog.LoggerOptions) *hclog.LoggerOptions {
10+
if src == nil {
11+
return nil
12+
}
13+
14+
return &hclog.LoggerOptions{
15+
AdditionalLocationOffset: src.AdditionalLocationOffset,
16+
Color: src.Color,
17+
DisableTime: src.DisableTime,
18+
Exclude: src.Exclude,
19+
IncludeLocation: src.IncludeLocation,
20+
IndependentLevels: src.IndependentLevels,
21+
JSONFormat: src.JSONFormat,
22+
Level: src.Level,
23+
Mutex: src.Mutex,
24+
Name: src.Name,
25+
Output: src.Output,
26+
TimeFormat: src.TimeFormat,
27+
TimeFn: src.TimeFn,
28+
}
29+
}

internal/loggertest/provider.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,27 @@ import (
44
"context"
55
"io"
66

7-
"github.com/hashicorp/go-hclog"
87
"github.com/hashicorp/terraform-plugin-log/internal/logging"
8+
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
99
)
1010

1111
func ProviderRoot(ctx context.Context, output io.Writer) context.Context {
12-
loggerOptions := &hclog.LoggerOptions{
13-
DisableTime: true,
14-
JSONFormat: true,
15-
Level: hclog.Trace,
16-
Output: output,
17-
}
18-
19-
ctx = logging.SetProviderRootLogger(ctx, hclog.New(loggerOptions))
12+
return tfsdklog.NewRootProviderLogger(
13+
ctx,
14+
logging.WithoutLocation(),
15+
logging.WithoutTimestamp(),
16+
logging.WithOutput(output),
17+
)
18+
}
2019

21-
return ctx
20+
// ProviderRootWithLocation is for testing code that affects go-hclog's caller
21+
// information (location offset). Most testing code should avoid this, since
22+
// correctly checking differences including the location is extra effort
23+
// with little benefit.
24+
func ProviderRootWithLocation(ctx context.Context, output io.Writer) context.Context {
25+
return tfsdklog.NewRootProviderLogger(
26+
ctx,
27+
logging.WithoutTimestamp(),
28+
logging.WithOutput(output),
29+
)
2230
}

internal/loggertest/sdk.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,27 @@ import (
44
"context"
55
"io"
66

7-
"github.com/hashicorp/go-hclog"
87
"github.com/hashicorp/terraform-plugin-log/internal/logging"
8+
"github.com/hashicorp/terraform-plugin-log/tfsdklog"
99
)
1010

1111
func SDKRoot(ctx context.Context, output io.Writer) context.Context {
12-
loggerOptions := &hclog.LoggerOptions{
13-
DisableTime: true,
14-
JSONFormat: true,
15-
Level: hclog.Trace,
16-
Output: output,
17-
}
18-
19-
ctx = logging.SetSDKRootLogger(ctx, hclog.New(loggerOptions))
12+
return tfsdklog.NewRootSDKLogger(
13+
ctx,
14+
logging.WithoutLocation(),
15+
logging.WithoutTimestamp(),
16+
logging.WithOutput(output),
17+
)
18+
}
2019

21-
return ctx
20+
// SDKRootWithLocation is for testing code that affects go-hclog's caller
21+
// information (location offset). Most testing code should avoid this, since
22+
// correctly checking differences including the location is extra effort
23+
// with little benefit.
24+
func SDKRootWithLocation(ctx context.Context, output io.Writer) context.Context {
25+
return tfsdklog.NewRootSDKLogger(
26+
ctx,
27+
logging.WithoutTimestamp(),
28+
logging.WithOutput(output),
29+
)
2230
}

internal/logging/log.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import (
55
"os"
66
)
77

8+
const (
9+
// Default provider root logger name.
10+
DefaultProviderRootLoggerName string = "provider"
11+
12+
// Default SDK root logger name.
13+
DefaultSDKRootLoggerName string = "sdk"
14+
)
15+
816
// loggerKey defines context keys for locating loggers in context.Context
917
// it's a private type to make sure no other packages can override the key
1018
type loggerKey string
@@ -14,10 +22,22 @@ const (
1422
// logger for writing logs from within provider code.
1523
ProviderRootLoggerKey loggerKey = "provider"
1624

25+
// ProviderRootLoggerOptionsKey is the loggerKey that will hold the root
26+
// logger options when the root provider logger is created. This is to
27+
// assist creating subsystem loggers, as most options cannot be fetched and
28+
// a logger does not provide set methods for these options.
29+
ProviderRootLoggerOptionsKey loggerKey = "provider-options"
30+
1731
// SDKRootLoggerKey is the loggerKey that will hold the root logger for
1832
// writing logs from with SDKs.
1933
SDKRootLoggerKey loggerKey = "sdk"
2034

35+
// SDKRootLoggerOptionsKey is the loggerKey that will hold the root
36+
// logger options when the SDK provider logger is created. This is to
37+
// assist creating subsystem loggers, as most options cannot be fetched and
38+
// a logger does not provide set methods for these options.
39+
SDKRootLoggerOptionsKey loggerKey = "sdk-options"
40+
2141
// SinkKey is the loggerKey that will hold the logging sink used for
2242
// test frameworks.
2343
SinkKey loggerKey = ""

internal/logging/options.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ type LoggerOpts struct {
2222
// of the logging statement or not.
2323
IncludeLocation bool
2424

25+
// AdditionalLocationOffset is the number of additional stack levels to
26+
// skip when finding the file and line information for the log line.
27+
// Defaults to 1 to account for the tflog and tfsdklog logging functions.
28+
AdditionalLocationOffset int
29+
2530
// Output dictates where logs are written to. Output should only ever
2631
// be set by tflog or tfsdklog, never by SDK authors or provider
2732
// developers. Where logs get written to is complex and delicate and
@@ -37,21 +42,34 @@ type LoggerOpts struct {
3742
}
3843

3944
// ApplyLoggerOpts generates a LoggerOpts out of a list of Option
40-
// implementations. By default, IncludeLocation is true, IncludeTime is true,
41-
// and Output is os.Stderr.
45+
// implementations. By default, AdditionalLocationOffset is 1, IncludeLocation
46+
// is true, IncludeTime is true, and Output is os.Stderr.
4247
func ApplyLoggerOpts(opts ...Option) LoggerOpts {
4348
// set some defaults
4449
l := LoggerOpts{
45-
IncludeLocation: true,
46-
IncludeTime: true,
47-
Output: os.Stderr,
50+
AdditionalLocationOffset: 1,
51+
IncludeLocation: true,
52+
IncludeTime: true,
53+
Output: os.Stderr,
4854
}
4955
for _, opt := range opts {
5056
l = opt(l)
5157
}
5258
return l
5359
}
5460

61+
// WithAdditionalLocationOffset sets the WithAdditionalLocationOffset
62+
// configuration option, allowing implementations to fix location information
63+
// when implementing helper functions. The default offset of 1 is automatically
64+
// added to the provided value to account for the tflog and tfsdk logging
65+
// functions.
66+
func WithAdditionalLocationOffset(additionalLocationOffset int) Option {
67+
return func(l LoggerOpts) LoggerOpts {
68+
l.AdditionalLocationOffset = additionalLocationOffset + 1
69+
return l
70+
}
71+
}
72+
5573
// WithOutput sets the Output configuration option, controlling where logs get
5674
// written to. This is mostly used for testing (to write to os.Stdout, so the
5775
// test framework can compare it against the example output) and as a helper
@@ -63,6 +81,16 @@ func WithOutput(output io.Writer) Option {
6381
}
6482
}
6583

84+
// WithoutLocation disables the location included with logging statements. It
85+
// should only ever be used to make log output deterministic when testing
86+
// terraform-plugin-log.
87+
func WithoutLocation() Option {
88+
return func(l LoggerOpts) LoggerOpts {
89+
l.IncludeLocation = false
90+
return l
91+
}
92+
}
93+
6694
// WithoutTimestamp disables the timestamp included with logging statements. It
6795
// should only ever be used to make log output deterministic when testing
6896
// terraform-plugin-log.

internal/logging/provider.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,41 @@ func GetProviderRootLogger(ctx context.Context) hclog.Logger {
1616
return logger.(hclog.Logger)
1717
}
1818

19+
// GetProviderRootLoggerOptions returns the root logger options used for
20+
// creating the root provider logger. If the root logger has not been created
21+
// or the options are not present, it will return nil.
22+
func GetProviderRootLoggerOptions(ctx context.Context) *hclog.LoggerOptions {
23+
if GetProviderRootLogger(ctx) == nil {
24+
return nil
25+
}
26+
27+
loggerOptionsRaw := ctx.Value(ProviderRootLoggerOptionsKey)
28+
29+
if loggerOptionsRaw == nil {
30+
return nil
31+
}
32+
33+
loggerOptions, ok := loggerOptionsRaw.(*hclog.LoggerOptions)
34+
35+
if !ok {
36+
return nil
37+
}
38+
39+
return loggerOptions
40+
}
41+
1942
// SetProviderRootLogger sets `logger` as the root logger used for writing
2043
// logs from a provider.
2144
func SetProviderRootLogger(ctx context.Context, logger hclog.Logger) context.Context {
2245
return context.WithValue(ctx, ProviderRootLoggerKey, logger)
2346
}
2447

48+
// SetProviderRootLoggerOptions sets `loggerOptions` as the root logger options
49+
// used for creating the provider root logger.
50+
func SetProviderRootLoggerOptions(ctx context.Context, loggerOptions *hclog.LoggerOptions) context.Context {
51+
return context.WithValue(ctx, ProviderRootLoggerOptionsKey, loggerOptions)
52+
}
53+
2554
// NewProviderSubsystemLoggerWarning is the text included in log output when a
2655
// subsystem is auto-generated by terraform-plugin-log because it was used
2756
// before the provider instantiated it.

internal/logging/sdk.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,41 @@ func GetSDKRootLogger(ctx context.Context) hclog.Logger {
1616
return logger.(hclog.Logger)
1717
}
1818

19+
// GetSDKRootLoggerOptions returns the root logger options used for
20+
// creating the root SDK logger. If the root logger has not been created or
21+
// the options are not present, it will return nil.
22+
func GetSDKRootLoggerOptions(ctx context.Context) *hclog.LoggerOptions {
23+
if GetSDKRootLogger(ctx) == nil {
24+
return nil
25+
}
26+
27+
loggerOptionsRaw := ctx.Value(SDKRootLoggerOptionsKey)
28+
29+
if loggerOptionsRaw == nil {
30+
return nil
31+
}
32+
33+
loggerOptions, ok := loggerOptionsRaw.(*hclog.LoggerOptions)
34+
35+
if !ok {
36+
return nil
37+
}
38+
39+
return loggerOptions
40+
}
41+
1942
// SetSDKRootLogger sets `logger` as the root logger used for writing logs from
2043
// an SDK.
2144
func SetSDKRootLogger(ctx context.Context, logger hclog.Logger) context.Context {
2245
return context.WithValue(ctx, SDKRootLoggerKey, logger)
2346
}
2447

48+
// SetSDKRootLoggerOptions sets `loggerOptions` as the root logger options
49+
// used for creating the SDK root logger.
50+
func SetSDKRootLoggerOptions(ctx context.Context, loggerOptions *hclog.LoggerOptions) context.Context {
51+
return context.WithValue(ctx, SDKRootLoggerOptionsKey, loggerOptions)
52+
}
53+
2554
// NewSDKSubsystemLoggerWarning is the text included in log output when a
2655
// subsystem is auto-generated by terraform-plugin-log because it was used
2756
// before the SDK instantiated it.

tflog/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import (
1212
// to NewSubsystem prior to calling it.
1313
type Options []logging.Option
1414

15+
// WithAdditionalLocationOffset returns an option that allowing implementations
16+
// to fix location information when implementing helper functions. The default
17+
// offset of 1 is automatically added to the provided value to account for the
18+
// tflog logging functions.
19+
func WithAdditionalLocationOffset(additionalLocationOffset int) logging.Option {
20+
return logging.WithAdditionalLocationOffset(additionalLocationOffset)
21+
}
22+
1523
// WithLevelFromEnv returns an option that will set the level of the logger
1624
// based on the string in an environment variable. The environment variable
1725
// checked will be `name` and `subsystems`, joined by _ and in all caps.

0 commit comments

Comments
 (0)