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 }