910ccdb092
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.26.1 to 2.27.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.26.1...v2.27.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
437 lines
17 KiB
Go
437 lines
17 KiB
Go
package tfsdklog
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/terraform-plugin-log/internal/hclogutils"
|
|
"github.com/hashicorp/terraform-plugin-log/internal/logging"
|
|
)
|
|
|
|
// NewRootSDKLogger returns a new context.Context that contains an SDK logger
|
|
// configured with the passed options.
|
|
func NewRootSDKLogger(ctx context.Context, options ...logging.Option) context.Context {
|
|
opts := logging.ApplyLoggerOpts(options...)
|
|
if opts.Name == "" {
|
|
opts.Name = logging.DefaultSDKRootLoggerName
|
|
}
|
|
if sink := logging.GetSink(ctx); sink != nil {
|
|
logger := sink.Named(opts.Name)
|
|
sinkLoggerOptions := logging.GetSinkOptions(ctx)
|
|
sdkLoggerOptions := hclogutils.LoggerOptionsCopy(sinkLoggerOptions)
|
|
sdkLoggerOptions.Name = opts.Name
|
|
|
|
if opts.Level != hclog.NoLevel {
|
|
logger.SetLevel(opts.Level)
|
|
sdkLoggerOptions.Level = opts.Level
|
|
}
|
|
|
|
ctx = logging.SetSDKRootLogger(ctx, logger)
|
|
ctx = logging.SetSDKRootLoggerOptions(ctx, sdkLoggerOptions)
|
|
|
|
return ctx
|
|
}
|
|
if opts.Level == hclog.NoLevel {
|
|
opts.Level = hclog.Trace
|
|
}
|
|
|
|
// Cache root logger level outside context for performance reasons.
|
|
rootLevelMutex.Lock()
|
|
|
|
rootLevel = opts.Level
|
|
|
|
rootLevelMutex.Unlock()
|
|
|
|
loggerOptions := &hclog.LoggerOptions{
|
|
Name: opts.Name,
|
|
Level: opts.Level,
|
|
JSONFormat: true,
|
|
IndependentLevels: true,
|
|
IncludeLocation: opts.IncludeLocation,
|
|
DisableTime: !opts.IncludeTime,
|
|
Output: opts.Output,
|
|
AdditionalLocationOffset: opts.AdditionalLocationOffset,
|
|
}
|
|
|
|
ctx = logging.SetSDKRootLogger(ctx, hclog.New(loggerOptions))
|
|
ctx = logging.SetSDKRootLoggerOptions(ctx, loggerOptions)
|
|
|
|
return ctx
|
|
}
|
|
|
|
// NewRootProviderLogger returns a new context.Context that contains a provider
|
|
// logger configured with the passed options.
|
|
func NewRootProviderLogger(ctx context.Context, options ...logging.Option) context.Context {
|
|
opts := logging.ApplyLoggerOpts(options...)
|
|
if opts.Name == "" {
|
|
opts.Name = logging.DefaultProviderRootLoggerName
|
|
}
|
|
if sink := logging.GetSink(ctx); sink != nil {
|
|
logger := sink.Named(opts.Name)
|
|
sinkLoggerOptions := logging.GetSinkOptions(ctx)
|
|
providerLoggerOptions := hclogutils.LoggerOptionsCopy(sinkLoggerOptions)
|
|
providerLoggerOptions.Name = opts.Name
|
|
|
|
if opts.Level != hclog.NoLevel {
|
|
logger.SetLevel(opts.Level)
|
|
providerLoggerOptions.Level = opts.Level
|
|
}
|
|
|
|
ctx = logging.SetProviderRootLogger(ctx, logger)
|
|
ctx = logging.SetProviderRootLoggerOptions(ctx, providerLoggerOptions)
|
|
|
|
return ctx
|
|
}
|
|
if opts.Level == hclog.NoLevel {
|
|
opts.Level = hclog.Trace
|
|
}
|
|
loggerOptions := &hclog.LoggerOptions{
|
|
Name: opts.Name,
|
|
Level: opts.Level,
|
|
JSONFormat: true,
|
|
IndependentLevels: true,
|
|
IncludeLocation: opts.IncludeLocation,
|
|
DisableTime: !opts.IncludeTime,
|
|
Output: opts.Output,
|
|
AdditionalLocationOffset: opts.AdditionalLocationOffset,
|
|
}
|
|
|
|
ctx = logging.SetProviderRootLogger(ctx, hclog.New(loggerOptions))
|
|
ctx = logging.SetProviderRootLoggerOptions(ctx, loggerOptions)
|
|
|
|
return ctx
|
|
}
|
|
|
|
// SetField returns a new context.Context that has a modified logger in it which
|
|
// will include key and value as fields in all its log output.
|
|
//
|
|
// In case of the same key is used multiple times (i.e. key collision),
|
|
// the last one set is the one that gets persisted and then outputted with the logs.
|
|
func SetField(ctx context.Context, key string, value interface{}) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithField(key, value)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// Trace logs `msg` at the trace level to the logger in `ctx`, with optional
|
|
// `additionalFields` structured key-value fields in the log output. Fields are
|
|
// shallow merged with any defined on the logger, e.g. by the `SetField()` function,
|
|
// and across multiple maps.
|
|
func Trace(ctx context.Context, msg string, additionalFields ...map[string]interface{}) {
|
|
logger := logging.GetSDKRootLogger(ctx)
|
|
if logger == nil {
|
|
// this essentially should never happen in production the root
|
|
// logger for code should be injected by the in
|
|
// question, so really this is only likely in unit tests, at
|
|
// most so just making this a no-op is fine
|
|
return
|
|
}
|
|
|
|
additionalArgs, shouldOmit := logging.OmitOrMask(logging.GetSDKRootTFLoggerOpts(ctx), &msg, additionalFields)
|
|
if shouldOmit {
|
|
return
|
|
}
|
|
|
|
logger.Trace(msg, additionalArgs...)
|
|
}
|
|
|
|
// Debug logs `msg` at the debug level to the logger in `ctx`, with optional
|
|
// `additionalFields` structured key-value fields in the log output. Fields are
|
|
// shallow merged with any defined on the logger, e.g. by the `SetField()` function,
|
|
// and across multiple maps.
|
|
func Debug(ctx context.Context, msg string, additionalFields ...map[string]interface{}) {
|
|
logger := logging.GetSDKRootLogger(ctx)
|
|
if logger == nil {
|
|
// this essentially should never happen in production the root
|
|
// logger for code should be injected by the in
|
|
// question, so really this is only likely in unit tests, at
|
|
// most so just making this a no-op is fine
|
|
return
|
|
}
|
|
|
|
additionalArgs, shouldOmit := logging.OmitOrMask(logging.GetSDKRootTFLoggerOpts(ctx), &msg, additionalFields)
|
|
if shouldOmit {
|
|
return
|
|
}
|
|
|
|
logger.Debug(msg, additionalArgs...)
|
|
}
|
|
|
|
// Info logs `msg` at the info level to the logger in `ctx`, with optional
|
|
// `additionalFields` structured key-value fields in the log output. Fields are
|
|
// shallow merged with any defined on the logger, e.g. by the `SetField()` function,
|
|
// and across multiple maps.
|
|
func Info(ctx context.Context, msg string, additionalFields ...map[string]interface{}) {
|
|
logger := logging.GetSDKRootLogger(ctx)
|
|
if logger == nil {
|
|
// this essentially should never happen in production the root
|
|
// logger for code should be injected by the in
|
|
// question, so really this is only likely in unit tests, at
|
|
// most so just making this a no-op is fine
|
|
return
|
|
}
|
|
|
|
additionalArgs, shouldOmit := logging.OmitOrMask(logging.GetSDKRootTFLoggerOpts(ctx), &msg, additionalFields)
|
|
if shouldOmit {
|
|
return
|
|
}
|
|
|
|
logger.Info(msg, additionalArgs...)
|
|
}
|
|
|
|
// Warn logs `msg` at the warn level to the logger in `ctx`, with optional
|
|
// `additionalFields` structured key-value fields in the log output. Fields are
|
|
// shallow merged with any defined on the logger, e.g. by the `SetField()` function,
|
|
// and across multiple maps.
|
|
func Warn(ctx context.Context, msg string, additionalFields ...map[string]interface{}) {
|
|
logger := logging.GetSDKRootLogger(ctx)
|
|
if logger == nil {
|
|
// this essentially should never happen in production the root
|
|
// logger for code should be injected by the in
|
|
// question, so really this is only likely in unit tests, at
|
|
// most so just making this a no-op is fine
|
|
return
|
|
}
|
|
|
|
additionalArgs, shouldOmit := logging.OmitOrMask(logging.GetSDKRootTFLoggerOpts(ctx), &msg, additionalFields)
|
|
if shouldOmit {
|
|
return
|
|
}
|
|
|
|
logger.Warn(msg, additionalArgs...)
|
|
}
|
|
|
|
// Error logs `msg` at the error level to the logger in `ctx`, with optional
|
|
// `additionalFields` structured key-value fields in the log output. Fields are
|
|
// shallow merged with any defined on the logger, e.g. by the `SetField()` function,
|
|
// and across multiple maps.
|
|
func Error(ctx context.Context, msg string, additionalFields ...map[string]interface{}) {
|
|
logger := logging.GetSDKRootLogger(ctx)
|
|
if logger == nil {
|
|
// this essentially should never happen in production the root
|
|
// logger for code should be injected by the in
|
|
// question, so really this is only likely in unit tests, at
|
|
// most so just making this a no-op is fine
|
|
return
|
|
}
|
|
|
|
additionalArgs, shouldOmit := logging.OmitOrMask(logging.GetSDKRootTFLoggerOpts(ctx), &msg, additionalFields)
|
|
if shouldOmit {
|
|
return
|
|
}
|
|
|
|
logger.Error(msg, additionalArgs...)
|
|
}
|
|
|
|
// OmitLogWithFieldKeys returns a new context.Context that has a modified logger
|
|
// that will omit to write any log when any of the given keys is found
|
|
// within its fields.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the keys to omit by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `['foo', 'baz']`
|
|
//
|
|
// log1 = `{ msg = "...", fields = { 'foo': '...', 'bar': '...' }` -> omitted
|
|
// log2 = `{ msg = "...", fields = { 'bar': '...' }` -> printed
|
|
// log3 = `{ msg = "...", fields = { 'baz': '...', 'boo': '...' }` -> omitted
|
|
func OmitLogWithFieldKeys(ctx context.Context, keys ...string) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithOmitLogWithFieldKeys(keys...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// OmitLogWithMessageRegexes returns a new context.Context that has a modified logger
|
|
// that will omit to write any log that has a message matching any of the
|
|
// given *regexp.Regexp.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the regexp to omit by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `[regexp.MustCompile("(foo|bar)")]`
|
|
//
|
|
// log1 = `{ msg = "banana apple foo", fields = {...}` -> omitted
|
|
// log2 = `{ msg = "pineapple mango", fields = {...}` -> printed
|
|
// log3 = `{ msg = "pineapple mango bar", fields = {...}` -> omitted
|
|
func OmitLogWithMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithOmitLogWithMessageRegexes(expressions...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// OmitLogWithMessageStrings returns a new context.Context that has a modified logger
|
|
// that will omit to write any log that matches any of the given string.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the string to omit by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `['foo', 'bar']`
|
|
//
|
|
// log1 = `{ msg = "banana apple foo", fields = {...}` -> omitted
|
|
// log2 = `{ msg = "pineapple mango", fields = {...}` -> printed
|
|
// log3 = `{ msg = "pineapple mango bar", fields = {...}` -> omitted
|
|
func OmitLogWithMessageStrings(ctx context.Context, matchingStrings ...string) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithOmitLogWithMessageStrings(matchingStrings...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskFieldValuesWithFieldKeys returns a new context.Context that has a modified logger
|
|
// that masks (replaces) with asterisks (`***`) any field value where the
|
|
// key matches one of the given keys.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the keys to mask by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `['foo', 'baz']`
|
|
//
|
|
// log1 = `{ msg = "...", fields = { 'foo': '***', 'bar': '...' }` -> masked value
|
|
// log2 = `{ msg = "...", fields = { 'bar': '...' }` -> as-is value
|
|
// log3 = `{ msg = "...", fields = { 'baz': '***', 'boo': '...' }` -> masked value
|
|
func MaskFieldValuesWithFieldKeys(ctx context.Context, keys ...string) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithMaskFieldValuesWithFieldKeys(keys...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskAllFieldValuesRegexes returns a new context.Context that has a modified logger
|
|
// that masks (replaces) with asterisks (`***`) all field value substrings,
|
|
// matching one of the given *regexp.Regexp.
|
|
//
|
|
// Note that the replacement will happen, only for field values that are of type string.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the regexp to mask by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `[regexp.MustCompile("(foo|bar)")]`
|
|
//
|
|
// log1 = `{ msg = "...", fields = { 'k1': '***', 'k2': '***', 'k3': 'baz' }` -> masked value
|
|
// log2 = `{ msg = "...", fields = { 'k1': 'boo', 'k2': 'far', 'k3': 'baz' }` -> as-is value
|
|
// log2 = `{ msg = "...", fields = { 'k1': '*** *** baz' }` -> masked value
|
|
func MaskAllFieldValuesRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithMaskAllFieldValuesRegexes(expressions...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskAllFieldValuesStrings returns a new context.Context that has a modified logger
|
|
// that masks (replaces) with asterisks (`***`) all field value substrings,
|
|
// equal to one of the given strings.
|
|
//
|
|
// Note that the replacement will happen, only for field values that are of type string.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the regexp to mask by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `[regexp.MustCompile("(foo|bar)")]`
|
|
//
|
|
// log1 = `{ msg = "...", fields = { 'k1': '***', 'k2': '***', 'k3': 'baz' }` -> masked value
|
|
// log2 = `{ msg = "...", fields = { 'k1': 'boo', 'k2': 'far', 'k3': 'baz' }` -> as-is value
|
|
// log2 = `{ msg = "...", fields = { 'k1': '*** *** baz' }` -> masked value
|
|
func MaskAllFieldValuesStrings(ctx context.Context, matchingStrings ...string) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithMaskAllFieldValuesStrings(matchingStrings...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskMessageRegexes returns a new context.Context that has a modified logger
|
|
// that masks (replaces) with asterisks (`***`) all message substrings,
|
|
// matching one of the given *regexp.Regexp.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the regexp to mask by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `[regexp.MustCompile("(foo|bar)")]`
|
|
//
|
|
// log1 = `{ msg = "banana apple ***", fields = {...}` -> masked portion
|
|
// log2 = `{ msg = "pineapple mango", fields = {...}` -> as-is
|
|
// log3 = `{ msg = "pineapple mango ***", fields = {...}` -> masked portion
|
|
func MaskMessageRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithMaskMessageRegexes(expressions...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskMessageStrings returns a new context.Context that has a modified logger
|
|
// that masks (replace) with asterisks (`***`) all message substrings,
|
|
// equal to one of the given strings.
|
|
//
|
|
// Each call to this function is additive:
|
|
// the string to mask by are added to the existing configuration.
|
|
//
|
|
// Example:
|
|
//
|
|
// configuration = `['foo', 'bar']`
|
|
//
|
|
// log1 = `{ msg = "banana apple ***", fields = { 'k1': 'foo, bar, baz' }` -> masked portion
|
|
// log2 = `{ msg = "pineapple mango", fields = {...}` -> as-is
|
|
// log3 = `{ msg = "pineapple mango ***", fields = {...}` -> masked portion
|
|
func MaskMessageStrings(ctx context.Context, matchingStrings ...string) context.Context {
|
|
lOpts := logging.GetSDKRootTFLoggerOpts(ctx)
|
|
|
|
// Copy to prevent slice/map aliasing issues.
|
|
// Reference: https://github.com/hashicorp/terraform-plugin-log/issues/131
|
|
lOpts = logging.WithMaskMessageStrings(matchingStrings...)(lOpts.Copy())
|
|
|
|
return logging.SetSDKRootTFLoggerOpts(ctx, lOpts)
|
|
}
|
|
|
|
// MaskLogRegexes is a shortcut to invoke MaskMessageRegexes and MaskAllFieldValuesRegexes using the same input.
|
|
// Refer to those functions for details.
|
|
func MaskLogRegexes(ctx context.Context, expressions ...*regexp.Regexp) context.Context {
|
|
return MaskMessageRegexes(MaskAllFieldValuesRegexes(ctx, expressions...), expressions...)
|
|
}
|
|
|
|
// MaskLogStrings is a shortcut to invoke MaskMessageStrings and MaskAllFieldValuesStrings using the same input.
|
|
// Refer to those functions for details.
|
|
func MaskLogStrings(ctx context.Context, matchingStrings ...string) context.Context {
|
|
return MaskMessageStrings(MaskAllFieldValuesStrings(ctx, matchingStrings...), matchingStrings...)
|
|
}
|