diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index e9f35bcaff..fb701906dc 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -31,6 +31,29 @@ func (g *TestLogConsumer) Accept(l Log) { } ``` +#### WithLogger + +- Not available until the next release of testcontainers-go :material-tag: main + +If you need to either pass logger to a container, you can use `testcontainers.WithLogger`. + +!!!info + Consider calling this before other "With" functions as these may generate logs. + +In this example we also use `TestLogger` which writes to the passed in `testing.TB` using `Logf`. +The result is that we capture all logging from the container into the test context meaning its +hidden behind `go test -v` and is associated with the relevant test, providing the user with +useful context instead of appearing out of band. + +```golang +func TestHandler(t *testing.T) { + logger := TestLogger(t) + _, err := postgresModule.RunContainer(ctx, testcontainers.WithLogger(logger)) + require.NoError(t, err) + // Do something with container. +} +``` + Please read the [Following Container Logs](/features/follow_logs) documentation for more information about creating log consumers. #### Wait Strategies diff --git a/logger.go b/logger.go index 5236ac4640..b137fdca66 100644 --- a/logger.go +++ b/logger.go @@ -12,6 +12,14 @@ import ( // Logger is the default log instance var Logger Logging = log.New(os.Stderr, "", log.LstdFlags) +// Validate our types implement the required interfaces. +var ( + _ Logging = (*log.Logger)(nil) + _ ContainerCustomizer = LoggerOption{} + _ GenericProviderOption = LoggerOption{} + _ DockerProviderOption = LoggerOption{} +) + // Logging defines the Logger interface type Logging interface { Printf(format string, v ...interface{}) @@ -24,37 +32,51 @@ func LogDockerServerInfo(ctx context.Context, client client.APIClient, logger Lo } // TestLogger returns a Logging implementation for testing.TB -// This way logs from testcontainers are part of the test output of a test suite or test case +// This way logs from testcontainers are part of the test output of a test suite or test case. func TestLogger(tb testing.TB) Logging { tb.Helper() return testLogger{TB: tb} } -// WithLogger is a generic option that implements GenericProviderOption, DockerProviderOption -// It replaces the global Logging implementation with a user defined one e.g. to aggregate logs from testcontainers -// with the logs of specific test case +// WithLogger returns a generic option that sets the logger to be used. +// +// Consider calling this before other "With functions" as these may generate logs. +// +// This can be given a TestLogger to collect the logs from testcontainers into a +// test case. func WithLogger(logger Logging) LoggerOption { return LoggerOption{ logger: logger, } } +// LoggerOption is a generic option that sets the logger to be used. +// +// It can be used to set the logger for providers and containers. type LoggerOption struct { logger Logging } +// ApplyGenericTo implements GenericProviderOption. func (o LoggerOption) ApplyGenericTo(opts *GenericProviderOptions) { opts.Logger = o.logger } +// ApplyDockerTo implements DockerProviderOption. func (o LoggerOption) ApplyDockerTo(opts *DockerProviderOptions) { opts.Logger = o.logger } +// Customize implements ContainerCustomizer. +func (o LoggerOption) Customize(req *GenericContainerRequest) { + req.Logger = o.logger +} + type testLogger struct { testing.TB } +// Printf implements Logging. func (t testLogger) Printf(format string, v ...interface{}) { t.Helper() t.Logf(format, v...) diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000000..d4debef016 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,31 @@ +package testcontainers + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestWithLogger(t *testing.T) { + logger := TestLogger(t) + logOpt := WithLogger(logger) + t.Run("container", func(t *testing.T) { + var req GenericContainerRequest + logOpt.Customize(&req) + require.Equal(t, logger, req.Logger) + }) + + t.Run("provider", func(t *testing.T) { + var opts GenericProviderOptions + logOpt.ApplyGenericTo(&opts) + require.Equal(t, logger, opts.Logger) + }) + + t.Run("docker", func(t *testing.T) { + opts := &DockerProviderOptions{ + GenericProviderOptions: &GenericProviderOptions{}, + } + logOpt.ApplyDockerTo(opts) + require.Equal(t, logger, opts.Logger) + }) +}