Skip to content

Commit

Permalink
[TT-1862] remove logstream. add function to dump Docker container log…
Browse files Browse the repository at this point in the history
…s to files (#1414)
  • Loading branch information
Tofel authored Dec 6, 2024
1 parent a85e897 commit dce4e1c
Show file tree
Hide file tree
Showing 21 changed files with 138 additions and 3,930 deletions.
117 changes: 9 additions & 108 deletions book/src/lib.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,38 +252,13 @@ Builder will read the location of chain configuration from env var named `PRIVAT

`net` is an instance of `blockchain.EVMNetwork`, which contains characteristics of the network and can be used to connect to it using an EVM client. `rpc` variable contains arrays of public and private RPC endpoints, where "private" means URL that's accessible from the same Docker network as the chain is running in.

# Using LogStream
## Logs
By default, we will save logs of all Docker containers running on the host machine, when the test ends (regardless whether it failed or succeeded). They will be available in the `./logs/<test-name><date>` directory. Same goes for dumping the databases of PostgresDBs
used by the Chainlink nodes. These will be saves in the `./db_dumps/<test-name><date>` directory.

LogStream is a package that allows to connect to a Docker container and then flush logs to configured targets. Currently 3 targets are supported:
## Loki and Grafana

- `file` - saves logs to a file in `./logs` folder
- `loki` - sends logs to Loki
- `in-memory` - stores logs in memory

It can be configured to use multiple targets at once. If no target is specified, it becomes a no-op.

LogStream has to be configured by passing an instance of `LoggingConfig` to the constructor.

When you connect a container LogStream will create a new consumer and start a detached goroutine that listens to logs emitted by that container and which reconnects and re-requests logs if listening fails for whatever reason. Retry limit and timeout can both be configured using functional options. In most cases one container should have one consumer, but it's possible to have multiple consumers for one container.

LogStream stores all logs in gob temporary file. To actually send/save them, you need to flush them. When you do it, LogStream will decode the file and send logs to configured targets. If log handling results in an error it won't be retried and processing of logs for given consumer will stop (if you think we should add a retry mechanism please let us know).

_Important:_ Flushing and accepting logs is blocking operation. That's because they both share the same cursor to temporary file and otherwise it's position would be racey and could result in mixed up logs.

## Configuration

Basic `LogStream` TOML configuration is following:

```toml
[LogStream]
log_targets=["file"]
log_producer_timeout="10s"
log_producer_retry_limit=10
```

You can find it here: [logging_default.toml](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/tomls/logging_default.toml)

When using `in-memory` or `file` target no other configuration variables are required. When using `loki` target, following ones must be set:
If you need to pass Loki or Grafana configuration to your tests you can do that by providing the following config:

```toml
[Logging.Loki]
Expand All @@ -295,87 +270,13 @@ bearer_token_secret="bearer-token"

Also, do remember that different URL should be used when running in CI and everywhere else. In CI it should be a public endpoint, while in local environment it should be a private one.

If your test has a Grafana dashboard in order for the url to be correctly printed you should provide the following config:
If your test has a Grafana dashboard you should provide the following config:

```toml
[Logging.Grafana]
url="http://grafana.somwhere.com/my_dashboard"
```

## Initialisation

First you need to create a new instance:

```golang
// t - instance of *testing.T (can be nil)
// testConfig.Logging - pointer to logging part of TestConfig
ls := logstream.NewLogStream(t, testConfig.Logging)
```

## Listening to logs

If using `testcontainers-go` Docker containers it is recommended to use life cycle hooks for connecting and disconnecting LogStream from the container. You can do that when creating `ContainerRequest` in the following way:

```golang
containerRequest := &tc.ContainerRequest{
LifecycleHooks: []tc.ContainerLifecycleHooks{
{PostStarts: []tc.ContainerHook{
func(ctx context.Context, c tc.Container) error {
if ls != nil {
return n.ls.ConnectContainer(ctx, c, "custom-container-prefix-can-be-empty")
}
return nil
},
},
PostStops: []tc.ContainerHook{
func(ctx context.Context, c tc.Container) error {
if ls != nil {
return n.ls.DisconnectContainer(c)
}
return nil
},
}},
},
}
```

You can print log location for each target using this function: `(m *LogStream) PrintLogTargetsLocations()`. For `file` target it will print relative folder path, for `loki` it will print URL of a Grafana Dashboard scoped to current execution and container ids. For `in-memory` target it's no-op.

It is recommended to shutdown LogStream at the end of your tests. Here's an example:

```golang
t.Cleanup(func() {
l.Warn().Msg("Shutting down Log Stream")

if t.Failed() || os.Getenv("TEST_LOG_COLLECT") == "true" {
// we can't do much if this fails, so we just log the error
_ = logStream.FlushLogsToTargets()
// this will log log locations for each target (for file it will be a folder, for Loki Grafana dashboard -- remember to provide it's url in config!)
logStream.PrintLogTargetsLocations()
// this will save log locations in test summary, so that they can be easily accessed in GH's step summary
logStream.SaveLogLocationInTestSummary()
}

// we can't do much if this fails, so we just log the error
_ = logStream.Shutdown(testcontext.Get(b.t))
})
```

or in a bit shorter way:

```golang
t.Cleanup(func() {
l.Warn().Msg("Shutting down Log Stream")

if t.Failed() || os.Getenv("TEST_LOG_COLLECT") == "true" {
// this will log log locations for each target (for file it will be a folder, for Loki Grafana dashboard -- remember to provide it's url in config!)
logStream.PrintLogTargetsLocations()
// this will save log locations in test summary, so that they can be easily accessed in GH's step summary
}

// we can't do much if this fails
_ = logStream.FlushAndShutdown()
})
base_url="https://your-grafana-url"
dashboard_url="/my-dashboard"
dashboard_uid="my-dashboard-uid" # optional
```

## Grouping test execution
Expand Down
14 changes: 0 additions & 14 deletions book/src/lib/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ convert_to_toml_array() {
}
selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS")
log_targets=$(convert_to_toml_array "$LOGSTREAM_LOG_TARGETS")
if [ -n "$PYROSCOPE_SERVER" ]; then
pyroscope_enabled=true
Expand All @@ -292,12 +291,6 @@ else
execution_layer="geth"
fi
if [ -n "$TEST_LOG_COLLECT" ]; then
test_log_collect=true
else
test_log_collect=false
fi
cat << EOF > config.toml
[Network]
selected_networks=$selected_networks
Expand All @@ -312,13 +305,6 @@ server_url="$PYROSCOPE_SERVER"
environment="$PYROSCOPE_ENVIRONMENT"
key_secret="$PYROSCOPE_KEY"
[Logging]
test_log_collect=$test_log_collect
run_id="$RUN_ID"
[Logging.LogStream]
log_targets=$log_targets
[Logging.Loki]
tenant_id="$LOKI_TENANT_ID"
url="$LOKI_URL"
Expand Down
12 changes: 0 additions & 12 deletions lib/config/examples/example.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
# Example of full config with all fields
# General part
[Logging]
# if set to true will save logs even if test did not fail
test_log_collect=false

[Logging.LogStream]
# supported targets: file, loki, in-memory. if empty no logs will be persistet
log_targets=["file"]
# context timeout for starting log producer and also time-frame for requesting logs
log_producer_timeout="10s"
# number of retries before log producer gives up and stops listening to logs
log_producer_retry_limit=10

[Logging.Loki]
tenant_id="tenant_id"
# full URL of Loki ingest endpoint
Expand All @@ -21,7 +10,6 @@ basic_auth_secret="loki-basic-auth"
# only needed for cloud grafana
bearer_token_secret="bearer_token"

# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set)
[Logging.Grafana]
# grafana url (trailing "/" will be stripped)
base_url="http://grafana.url"
Expand Down
44 changes: 4 additions & 40 deletions lib/config/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,17 @@ import (

"github.com/pkg/errors"

"github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain"
"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/net"
)

type LoggingConfig struct {
TestLogCollect *bool `toml:"test_log_collect,omitempty"`
ShowHTMLCoverageReport *bool `toml:"show_html_coverage_report,omitempty"` // Show reports with go coverage data
RunId *string `toml:"run_id,omitempty"`
Loki *LokiConfig `toml:"-"`
Grafana *GrafanaConfig `toml:"Grafana,omitempty"`
LogStream *LogStreamConfig `toml:"LogStream,omitempty"`
ShowHTMLCoverageReport *bool `toml:"show_html_coverage_report,omitempty"` // Show reports with go coverage data
Loki *LokiConfig `toml:"-"`
Grafana *GrafanaConfig `toml:"Grafana,omitempty"`
}

// Validate executes config validation for LogStream, Grafana and Loki
// Validate executes config validation for Grafana and Loki
func (l *LoggingConfig) Validate() error {
if l.LogStream != nil {
if err := l.LogStream.Validate(); err != nil {
return fmt.Errorf("invalid log stream config: %w", err)
}
}

if l.Grafana != nil {
if err := l.Grafana.Validate(); err != nil {
return fmt.Errorf("invalid grafana config: %w", err)
Expand All @@ -41,32 +31,6 @@ func (l *LoggingConfig) Validate() error {
return nil
}

type LogStreamConfig struct {
LogTargets []string `toml:"log_targets"`
LogProducerTimeout *blockchain.StrDuration `toml:"log_producer_timeout"`
LogProducerRetryLimit *uint `toml:"log_producer_retry_limit"`
}

// Validate checks that the log stream config is valid, which means that
// log targets are valid and log producer timeout is greater than 0
func (l *LogStreamConfig) Validate() error {
if len(l.LogTargets) > 0 {
for _, target := range l.LogTargets {
if target != "loki" && target != "file" && target != "in-memory" {
return fmt.Errorf("invalid log target %s", target)
}
}
}

if l.LogProducerTimeout != nil {
if l.LogProducerTimeout.Duration == 0 {
return errors.New("log producer timeout must be greater than 0")
}
}

return nil
}

type LokiConfig struct {
TenantId *string `toml:"-"`
Endpoint *string `toml:"-"`
Expand Down
30 changes: 0 additions & 30 deletions lib/config/testconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,36 +54,6 @@ type TestConfig struct {
func (c *TestConfig) ReadFromEnvVar() error {
logger := logging.GetTestLogger(nil)

testLogCollect := MustReadEnvVar_Boolean(E2E_TEST_LOG_COLLECT_ENV)
if testLogCollect != nil {
if c.Logging == nil {
c.Logging = &LoggingConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.TestLogCollect", E2E_TEST_LOG_COLLECT_ENV)
c.Logging.TestLogCollect = testLogCollect
}

loggingRunID := MustReadEnvVar_String(E2E_TEST_LOGGING_RUN_ID_ENV)
if loggingRunID != "" {
if c.Logging == nil {
c.Logging = &LoggingConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.RunID", E2E_TEST_LOGGING_RUN_ID_ENV)
c.Logging.RunId = &loggingRunID
}

logstreamLogTargets := MustReadEnvVar_Strings(E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV, ",")
if len(logstreamLogTargets) > 0 {
if c.Logging == nil {
c.Logging = &LoggingConfig{}
}
if c.Logging.LogStream == nil {
c.Logging.LogStream = &LogStreamConfig{}
}
logger.Debug().Msgf("Using %s env var to override Logging.LogStream.LogTargets", E2E_TEST_LOG_STREAM_LOG_TARGETS_ENV)
c.Logging.LogStream.LogTargets = logstreamLogTargets
}

lokiTenantID := MustReadEnvVar_String(E2E_TEST_LOKI_TENANT_ID_ENV)
if lokiTenantID != "" {
if c.Logging == nil {
Expand Down
4 changes: 0 additions & 4 deletions lib/config/tomls/logging_default.toml

This file was deleted.

Loading

0 comments on commit dce4e1c

Please sign in to comment.