github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/utils/logging/logger_test.go (about)

     1  //  Copyright 2020 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package logging
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	daisy "github.com/GoogleCloudPlatform/compute-daisy"
    27  	"github.com/stretchr/testify/assert"
    28  
    29  	"github.com/GoogleCloudPlatform/compute-image-tools/proto/go/pb"
    30  	"github.com/GoogleCloudPlatform/compute-image-tools/proto/go/pbtesting"
    31  )
    32  
    33  var (
    34  	dateTime = time.Date(2009, 11, 10, 23, 10, 15, 0, time.UTC)
    35  )
    36  
    37  func Test_RedirectGlobalLogsToUser_CapturesStandardLog(t *testing.T) {
    38  	// Ensure that previous log settings are overwritten.
    39  	log.SetPrefix("old-prefix")
    40  	log.SetFlags(log.LstdFlags)
    41  	log.SetOutput(os.Stderr)
    42  
    43  	logger, buffer := setupTestLogger("prefix", dateTime)
    44  
    45  	RedirectGlobalLogsToUser(logger)
    46  	log.Print("hello world")
    47  	assert.Equal(t, "prefix: 2009-11-10T23:10:15Z hello world\n", buffer.String())
    48  }
    49  
    50  func Test_DefaultToolLogger_User_FormatsLikeDaisy(t *testing.T) {
    51  	fromDaisy := (&daisy.LogEntry{
    52  		LocalTimestamp: dateTime,
    53  		WorkflowName:   "image-import",
    54  		Message:        "msg",
    55  	}).String()
    56  
    57  	logger, written := setupTestLogger("[image-import]", dateTime)
    58  	logger.User("msg")
    59  	fromToolLogger := written.String()
    60  
    61  	assert.Equal(t, fromDaisy, fromToolLogger)
    62  }
    63  
    64  func Test_DefaultToolLogger_User_Prefixes(t *testing.T) {
    65  	// Using a colon after the prefix follows the pattern of Daisy's
    66  	// standard logger. See daisy.LogEntry.String
    67  	type test struct {
    68  		name          string
    69  		userPrefix    string
    70  		expectWritten string
    71  	}
    72  
    73  	tests := []test{
    74  		{
    75  			name:          "add colon after prefix",
    76  			userPrefix:    "import-image",
    77  			expectWritten: "import-image: 2009-11-10T23:10:15Z message\n",
    78  		},
    79  		{
    80  			name:          "don't add extra colon",
    81  			userPrefix:    "import-image:",
    82  			expectWritten: "import-image: 2009-11-10T23:10:15Z message\n",
    83  		},
    84  		{
    85  			name:          "no colon when empty prefix",
    86  			userPrefix:    "",
    87  			expectWritten: "2009-11-10T23:10:15Z message\n",
    88  		},
    89  	}
    90  
    91  	for _, tt := range tests {
    92  		t.Run(tt.name, func(t *testing.T) {
    93  			logger, written := setupTestLogger(tt.userPrefix, dateTime)
    94  			logger.User("message")
    95  			actual := written.String()
    96  			assert.Equal(t, tt.expectWritten, actual)
    97  		})
    98  	}
    99  }
   100  
   101  func Test_DefaultToolLogger_UserAndDebugInterleave(t *testing.T) {
   102  	type test struct {
   103  		name         string
   104  		logCalls     func(writer Logger)
   105  		expectedLogs string
   106  	}
   107  
   108  	tests := []test{
   109  		{
   110  			name:         "no logs when empty write",
   111  			logCalls:     func(writer Logger) {},
   112  			expectedLogs: "",
   113  		},
   114  		{
   115  			name: "prepend specified user prefix",
   116  			logCalls: func(writer Logger) {
   117  				writer.User("hello user")
   118  			},
   119  			expectedLogs: "[image-import]: 2009-11-10T23:10:15Z hello user\n",
   120  		},
   121  		{
   122  			name: "prepend a debug prefix",
   123  			logCalls: func(writer Logger) {
   124  				writer.Debug("hello debug")
   125  			},
   126  			expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug\n",
   127  		},
   128  		{
   129  			name: "maintain order when multiple writes",
   130  			logCalls: func(writer Logger) {
   131  				writer.Debug("hello debug1")
   132  				writer.User("hello user1")
   133  				writer.User("hello user2")
   134  				writer.Debug("hello debug2")
   135  			},
   136  			expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug1\n" +
   137  				"[image-import]: 2009-11-10T23:10:15Z hello user1\n" +
   138  				"[image-import]: 2009-11-10T23:10:15Z hello user2\n" +
   139  				"[debug]: 2009-11-10T23:10:15Z hello debug2\n",
   140  		},
   141  		{
   142  			name: "don't add extra newlines",
   143  			logCalls: func(writer Logger) {
   144  				writer.Debug("hello debug1\n")
   145  				writer.User("hello user1\n")
   146  			},
   147  			expectedLogs: "[debug]: 2009-11-10T23:10:15Z hello debug1\n" +
   148  				"[image-import]: 2009-11-10T23:10:15Z hello user1\n",
   149  		},
   150  	}
   151  
   152  	for _, tt := range tests {
   153  		t.Run(tt.name, func(t *testing.T) {
   154  			logger, written := setupTestLogger("[image-import]", dateTime)
   155  			tt.logCalls(logger)
   156  			assert.Equal(t, tt.expectedLogs, written.String())
   157  			if tt.expectedLogs == "" {
   158  				assert.Nil(t, logger.ReadOutputInfo().SerialOutputs,
   159  					"Only append SerialLog when there were logs written.")
   160  			} else {
   161  				assert.Equal(t, []string{tt.expectedLogs}, logger.ReadOutputInfo().SerialOutputs,
   162  					"Create a single SerialOutput member containing all debug and user logs.")
   163  			}
   164  		})
   165  	}
   166  }
   167  
   168  func Test_DefaultToolLogger_Metric_MergesNestedStruct(t *testing.T) {
   169  	logger := NewToolLogger("[user]")
   170  	logger.Metric(&pb.OutputInfo{IsUefiDetected: true})
   171  	logger.Metric(&pb.OutputInfo{InspectionResults: &pb.InspectionResults{
   172  		ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS}})
   173  	expected := &pb.OutputInfo{
   174  		IsUefiDetected: true,
   175  		InspectionResults: &pb.InspectionResults{
   176  			ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   177  		},
   178  	}
   179  	pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo())
   180  }
   181  
   182  func Test_DefaultToolLogger_Metric_AppendsSlices(t *testing.T) {
   183  	logger := NewToolLogger("[user]")
   184  	logger.Metric(&pb.OutputInfo{InflationTimeMs: []int64{30}})
   185  	logger.Metric(&pb.OutputInfo{InflationTimeMs: []int64{40}})
   186  	expected := &pb.OutputInfo{InflationTimeMs: []int64{30, 40}}
   187  
   188  	pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo())
   189  }
   190  
   191  func Test_DefaultToolLogger_Metric_DoesntClobberSingleValuesWithDefaultValues(t *testing.T) {
   192  	logger := NewToolLogger("[user]")
   193  	logger.Metric(&pb.OutputInfo{IsUefiDetected: true})
   194  	logger.Metric(&pb.OutputInfo{InflationType: "api"})
   195  	expected := &pb.OutputInfo{IsUefiDetected: true, InflationType: "api"}
   196  	pbtesting.AssertEqual(t, expected, logger.ReadOutputInfo())
   197  }
   198  
   199  func Test_DefaultToolLogger_ReadOutputInfo_ClearsState(t *testing.T) {
   200  	logger, _ := setupTestLogger("[user]", dateTime)
   201  
   202  	// 1. Use the logger; on first read, OutputInfo should contain all buffered information.
   203  	logger.Metric(&pb.OutputInfo{IsUefiDetected: true})
   204  	logger.User("hi")
   205  	logger.Trace("trace")
   206  	firstRead := logger.ReadOutputInfo()
   207  	pbtesting.AssertEqual(t, &pb.OutputInfo{
   208  		IsUefiDetected: true,
   209  		SerialOutputs:  []string{"[user]: 2009-11-10T23:10:15Z hi\n", "trace"},
   210  	}, firstRead)
   211  
   212  	// 2. On second read, the buffers should be cleared.
   213  	secondRead := logger.ReadOutputInfo()
   214  	pbtesting.AssertEqual(t, &pb.OutputInfo{}, secondRead)
   215  
   216  	// 3. Use the logger again and verify that the new information is kept.
   217  	logger.Metric(&pb.OutputInfo{InflationType: "daisy"})
   218  	logger.User("hi 1")
   219  	logger.Trace("trace 1")
   220  
   221  	thirdRead := logger.ReadOutputInfo()
   222  	pbtesting.AssertEqual(t, &pb.OutputInfo{
   223  		InflationType: "daisy",
   224  		SerialOutputs: []string{"[user]: 2009-11-10T23:10:15Z hi 1\n", "trace 1"},
   225  	}, thirdRead)
   226  }
   227  
   228  func Test_DefaultToolLogger_NewLogger_WritesToParent(t *testing.T) {
   229  	parent, _ := setupTestLogger("[parent-prefix]", dateTime)
   230  
   231  	parent.User("user-1")
   232  	parent.Debug("debug-1")
   233  	parent.Trace("trace-1")
   234  	parent.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{1}})
   235  
   236  	child := parent.NewLogger("[child-prefix]")
   237  	child.User("user-2")
   238  	child.Debug("debug-2")
   239  	child.Trace("trace-2")
   240  	child.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{2}})
   241  
   242  	parent.User("user-3")
   243  	parent.Debug("debug-3")
   244  	parent.Trace("trace-3")
   245  	parent.Metric(&pb.OutputInfo{SourcesSizeGb: []int64{3}})
   246  
   247  	expected := pb.OutputInfo{
   248  		SourcesSizeGb: []int64{1, 2, 3},
   249  		SerialOutputs: []string{
   250  			strings.Join([]string{
   251  				"[parent-prefix]: 2009-11-10T23:10:15Z user-1",
   252  				"[debug]: 2009-11-10T23:10:15Z debug-1",
   253  				"[child-prefix]: 2009-11-10T23:10:15Z user-2",
   254  				"[debug]: 2009-11-10T23:10:15Z debug-2",
   255  				"[parent-prefix]: 2009-11-10T23:10:15Z user-3",
   256  				"[debug]: 2009-11-10T23:10:15Z debug-3",
   257  				"",
   258  			}, "\n"),
   259  			"trace-1",
   260  			"trace-2",
   261  			"trace-3",
   262  		},
   263  	}
   264  	actual := parent.ReadOutputInfo()
   265  	pbtesting.AssertEqual(t, &expected, actual)
   266  }
   267  
   268  func setupTestLogger(userPrefix string, now time.Time) (ToolLogger, fmt.Stringer) {
   269  	var buffer bytes.Buffer
   270  	realLogger := NewToolLogger(userPrefix).(*defaultToolLogger)
   271  	realLogger.timeProvider = func() time.Time {
   272  		return now
   273  	}
   274  	realLogger.output.SetOutput(&buffer)
   275  	return realLogger, &buffer
   276  }