Skip to content

Commit

Permalink
[TT-1952] Write application logs for in-memory tests to file and safe…
Browse files Browse the repository at this point in the history
… in artifacts (#16029)

* try to split logs by level in in-memory tests

* pass split logger to CL node

* sync logs only if it's supported

* add Sync() debug

* add logger name to debug

* do not add console writer

* single file logger

* use different path for logs file, use new version for run-integration-tests.yml

* add missing secrets to run-integration-tests.yml invocation

* update to relative path

* modify action version and path of logs file

* update version of action and use abs path

* update action version

* develop version of action

* revert some changes, delete old logger

* use experimental version of action

* change version

* change version

* change version

* change version

* update version

* clean up

* remove new tests

* take into account dir from test name

* fix typo

* fix the lint error

---------

Co-authored-by: Gheorghe Strimtu <[email protected]>
  • Loading branch information
Tofel and gheorghestrimtu authored Feb 6, 2025
1 parent b92a304 commit e420274
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 6 deletions.
5 changes: 1 addition & 4 deletions .github/integration-in-memory-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ runner-test-matrix:
triggers:
- PR Integration CCIP Tests
test_cmd: cd integration-tests/smoke/ccip && go test ccip_messaging_test.go -timeout 12m -test.parallel=2 -count=1 -json

- id: smoke/ccip/ccip_message_limitations_test.go:*
path: integration-tests/smoke/ccip/ccip_message_limitations_test.go
test_env_type: in-memory
Expand All @@ -46,15 +45,14 @@ runner-test-matrix:
triggers:
- PR Integration CCIP Tests
test_cmd: cd integration-tests/smoke/ccip && go test ccip_fee_boosting_test.go -timeout 12m -test.parallel=2 -count=1 -json

- id: smoke/ccip/ccip_batching_test.go:*
path: integration-tests/smoke/ccip/ccip_batching_test.go
test_env_type: in-memory
runs_on: ubuntu-latest
triggers:
- PR Integration CCIP Tests
test_cmd: cd integration-tests/smoke/ccip && go test ccip_batching_test.go -timeout 25m -test.parallel=2 -count=1 -json

- id: smoke/ccip/ccip_add_chain_test.go:*
path: integration-tests/smoke/ccip/ccip_add_chain_test.go
test_env_type: in-memory
Expand Down Expand Up @@ -119,5 +117,4 @@ runner-test-matrix:
- PR Integration CCIP Tests
test_cmd: cd integration-tests/smoke/ccip/ && go test ccip_disable_lane_test.go -timeout 10m -test.parallel=1 -count=1 -json


# END: CCIP tests
4 changes: 2 additions & 2 deletions deployment/environment/memory/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import (
solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/logger"

"github.com/smartcontractkit/chainlink/v2/core/capabilities"
"github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm"
configv2 "github.com/smartcontractkit/chainlink/v2/core/config/toml"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/v2/core/logger/audit"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore"
Expand Down Expand Up @@ -147,7 +147,7 @@ func NewNode(
})

// Set logging.
lggr := logger.TestLogger(t)
lggr := logger.NewSingleFileLogger(t)
lggr.SetLogLevel(logLevel)

// Create clients for the core node backed by sim.
Expand Down
150 changes: 150 additions & 0 deletions deployment/logger/single_file_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package logger

import (
"fmt"
"log"
"os"
"path/filepath"
"runtime/debug"
"testing"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"

// The Chainlink logger interface we're implementing:
corelogger "github.com/smartcontractkit/chainlink/v2/core/logger"
)

// SingleFileLogger is a wrapper around a zap SugaredLogger that implements
// Chainlink’s corelogger.Logger interface.
type SingleFileLogger struct {
*zap.SugaredLogger
}

// Ensure it truly implements the interface at compile time:
var _ corelogger.Logger = (*SingleFileLogger)(nil)

// NewSingleFileLogger creates a zap-based logger that writes everything to one file.
// The file name includes the test name + timestamp so that parallel tests don’t collide.
func NewSingleFileLogger(tb testing.TB) *SingleFileLogger {
// Our logs will go here so GH can upload them:
baseDir := "logs"

// For uniqueness, include test name + timestamp
filename := fmt.Sprintf("%s_%d.log", tb.Name(), time.Now().UnixNano())
dirOfFilename := filepath.Dir(filename)

dir, err := filepath.Abs(filepath.Join(baseDir, dirOfFilename))
if err != nil {
log.Fatalf("Failed to get absolute path for %q: %v", filepath.Join(baseDir, dirOfFilename), err)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Fatalf("Failed to create logs dir %q: %v", dir, err)
}

fullPath, err := filepath.Abs(filepath.Join(baseDir, filename))
if err != nil {
log.Fatalf("Failed to get absolute path for %q: %v", fullPath, err)
}
// Log the full path
log.Printf("Logging to %q", fullPath)
f, err := os.Create(fullPath)
if err != nil {
log.Fatalf("Failed to create %q: %v", fullPath, err)
}

// Auto-flush writer so we don't lose logs if the test fails abruptly
writer := &autoFlushWriter{file: f}

encCfg := zap.NewDevelopmentEncoderConfig()
encCfg.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05.000000000")

core := zapcore.NewCore(
zapcore.NewJSONEncoder(encCfg),
zapcore.AddSync(writer),
zap.DebugLevel,
)

zapLogger := zap.New(core, zap.AddCaller())
return &SingleFileLogger{zapLogger.Sugar()}
}

// autoFlushWriter flushes on every log write, so we don’t lose logs if the test crashes.
type autoFlushWriter struct {
file *os.File
}

func (w *autoFlushWriter) Write(p []byte) (n int, err error) {
n, err = w.file.Write(p)
if err == nil {
_ = w.file.Sync()
}
return n, err
}

func (w *autoFlushWriter) Sync() error {
return w.file.Sync()
}

// ----------------------------------------------------------------------------
// Implement the corelogger.Logger interface
// ----------------------------------------------------------------------------

func (l *SingleFileLogger) Name() string {
return l.Desugar().Name()
}

func (l *SingleFileLogger) Named(name string) corelogger.Logger {
// zap’s Named(...) returns a new *zap.Logger. We return a new SingleFileLogger wrapping it.
return &SingleFileLogger{l.SugaredLogger.Named(name)}
}

func (l *SingleFileLogger) Trace(args ...interface{}) {
// “Trace” in Chainlink’s interface is basically a debug-level call
l.Debug(args...)
}

func (l *SingleFileLogger) Tracef(format string, values ...interface{}) {
l.Debugf(format, values...)
}

func (l *SingleFileLogger) Tracew(msg string, keysAndValues ...interface{}) {
l.Debugw(msg, keysAndValues...)
}

func (l *SingleFileLogger) Critical(args ...interface{}) {
// “Critical” is typically mapped to an error-level call or fatal if you prefer
l.Error(args...)
}

func (l *SingleFileLogger) Criticalf(format string, values ...interface{}) {
l.Errorf(format, values...)
}

func (l *SingleFileLogger) Criticalw(msg string, keysAndValues ...interface{}) {
l.Errorw(msg, keysAndValues...)
}

func (l *SingleFileLogger) Helper(skip int) corelogger.Logger {
// Helper() is for skipping extra stack frames in error logs. We can do AddCallerSkip here:
return &SingleFileLogger{l.SugaredLogger.Desugar().WithOptions(zap.AddCallerSkip(skip)).Sugar()}
}

func (l *SingleFileLogger) Recover(panicErr interface{}) {
// Called on panics, typically you might add custom logic or stacktrace
if panicErr != nil {
l.Errorf("Recovering from panic: %v", panicErr)
debug.PrintStack()
}
}

func (l *SingleFileLogger) SetLogLevel(zapLevel zapcore.Level) {
// If you need dynamic level changes, you can store an AtomicLevel.
// If not, you can just do nothing or rebuild a new logger.
}

func (l *SingleFileLogger) With(args ...interface{}) corelogger.Logger {
// Adds extra fields to the logger. Return a new instance with them.
return &SingleFileLogger{l.SugaredLogger.With(args...)}
}

0 comments on commit e420274

Please sign in to comment.