github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/logging/log.go (about)

     1  package logging
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  	"github.com/rs/zerolog/log"
    12  	"github.com/stretchr/testify/require"
    13  	tc "github.com/testcontainers/testcontainers-go"
    14  
    15  	"github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/config"
    16  )
    17  
    18  const afterTestEndedMsg = "LOG AFTER TEST ENDED"
    19  
    20  // CustomT wraps testing.T for two puposes:
    21  // 1. it implements Write to override the default logger
    22  // 2. it implements Printf to implement the testcontainers-go/Logging interface
    23  // The reason for both of these is that go parallel testing causes the logs to get mixed up,
    24  // so we need to override the default logger to *testing.T.Log to ensure that the logs are
    25  // properly associated with the tests running. The testcontainers-go/Logging interface complicates
    26  // this more since it needs a struct with L to hold the logger and needs to override Printf.
    27  type CustomT struct {
    28  	*testing.T
    29  	L     zerolog.Logger
    30  	ended bool
    31  }
    32  
    33  func (ct *CustomT) Write(p []byte) (n int, err error) {
    34  	str := string(p)
    35  	if strings.TrimSpace(str) == "" {
    36  		return len(p), nil
    37  	}
    38  	if ct.ended {
    39  		l := GetTestLogger(nil)
    40  		l.Error().Msgf("%s %s: %s", afterTestEndedMsg, ct.Name(), string(p))
    41  		return len(p), nil
    42  	}
    43  	ct.T.Log(strings.TrimSuffix(str, "\n"))
    44  	return len(p), nil
    45  }
    46  
    47  // Printf implements the testcontainers-go/Logging interface.
    48  func (ct CustomT) Printf(format string, v ...interface{}) {
    49  	if ct.ended {
    50  		s := "%s: "
    51  		formatted := fmt.Sprintf("%s %s%s", afterTestEndedMsg, s, format)
    52  		l := GetTestLogger(nil)
    53  		l.Error().Msgf(formatted, ct.Name(), v)
    54  	} else {
    55  		ct.L.Info().Msgf(format, v...)
    56  	}
    57  }
    58  
    59  func Init() {
    60  	l := GetLogger(nil, config.EnvVarLogLevel)
    61  	log.Logger = l
    62  }
    63  
    64  // GetLogger returns a logger that will write to the testing.T.Log function using the env var provided for the log level.
    65  // nil can be passed for t to get a logger that is not associated with a go test.
    66  func GetLogger(t *testing.T, envVarName string) zerolog.Logger {
    67  	lvlStr := os.Getenv(envVarName)
    68  	if lvlStr == "" {
    69  		lvlStr = "info"
    70  	}
    71  	lvl, err := zerolog.ParseLevel(lvlStr)
    72  	if err != nil {
    73  		if t != nil {
    74  			require.NoError(t, err, "error parsing log level")
    75  		} else {
    76  			panic(fmt.Sprintf("failed to parse log level: %s", err))
    77  		}
    78  	}
    79  	zerolog.TimeFieldFormat = time.RFC3339Nano
    80  	if t != nil {
    81  		ct := &CustomT{T: t}
    82  		// Use Cleanup function to set ended to true once the test completes
    83  		t.Cleanup(func() {
    84  			ct.ended = true
    85  		})
    86  		return zerolog.New(ct).Output(zerolog.ConsoleWriter{Out: ct, TimeFormat: "15:04:05.00"}).Level(lvl).With().Timestamp().Logger()
    87  	}
    88  	return log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05.00"}).Level(lvl).With().Timestamp().Logger()
    89  }
    90  
    91  // GetTestLogger returns a logger that will write to the testing.T.Log function using the env var for log level.
    92  // nil can be passed for t to get a logger that is not associated with a go test.
    93  func GetTestLogger(t *testing.T) zerolog.Logger {
    94  	return GetLogger(t, config.EnvVarLogLevel)
    95  }
    96  
    97  // GetTestContainersGoTestLogger returns a logger that will write to the testing.T.Log function using the env var for log level
    98  // for logs that testcontainers-go will log out. nil can be passed to this and it will be treated as the default tc.Logger
    99  func GetTestContainersGoTestLogger(t *testing.T) tc.Logging {
   100  	l := tc.Logger
   101  	if t != nil {
   102  		l = CustomT{
   103  			T: t,
   104  			L: GetTestLogger(t),
   105  		}
   106  	}
   107  	return l
   108  }
   109  
   110  // SplitStringIntoChunks takes a string and splits it into chunks of a specified size.
   111  func SplitStringIntoChunks(s string, chunkSize int) []string {
   112  	// Length of the string.
   113  	strLen := len(s)
   114  
   115  	// Number of chunks needed.
   116  	numChunks := (strLen + chunkSize - 1) / chunkSize
   117  
   118  	// Slice to hold the chunks.
   119  	chunks := make([]string, numChunks)
   120  
   121  	// Loop to create chunks.
   122  	for i := 0; i < numChunks; i++ {
   123  		// Calculate the start and end indices of the chunk.
   124  		start := i * chunkSize
   125  		end := start + chunkSize
   126  
   127  		// If the end index goes beyond the string length, adjust it to the string length.
   128  		if end > strLen {
   129  			end = strLen
   130  		}
   131  
   132  		// Slice the string and add the chunk to the slice.
   133  		chunks[i] = s[start:end]
   134  	}
   135  
   136  	return chunks
   137  }