integration_test/gce-testing-internal/logging/logging.go (61 lines of code) (raw):

// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package logging has utilities to aid in recording logs for tests. */ package logging import ( "fmt" "log" "os" "path/filepath" "sync" "go.uber.org/multierr" ) // DirectoryLogger manages a set of log.Loggers that each write to a different file // within a single directory. To write to a file named foo.txt in that directory, // call dirLog.ToFile("foo.txt") and use the log.Logger that it returns. // The ToMainLog method is a shorthand that logs to a file called main_log.txt. // // It is the caller's responsibility to call Close() when done with a DirectoryLogger. // After calling Close(), the DirectoryLogger and the log.Loggers it returns should // not be used anymore. type DirectoryLogger struct { Directory string mutex sync.Mutex loggers map[string]*log.Logger openFiles []*os.File } // NewDirectoryLogger returns a new DirectoryLogger managing the files in the given // directory. func NewDirectoryLogger(dir string) (*DirectoryLogger, error) { if err := os.MkdirAll(dir, 0777); err != nil { return nil, fmt.Errorf("NewDirectoryLogger() could not create dir %v for writing: %v", dir, err) } logger := &DirectoryLogger{ Directory: dir, loggers: make(map[string]*log.Logger), } return logger, nil } const ( mainLogName = "main_log.txt" ) // ToMainLog returns a Logger that writes to the main log (main_log.txt). func (dirLog *DirectoryLogger) ToMainLog() *log.Logger { return dirLog.ToFile(mainLogName) } // ToFile returns a Logger that writes to a file with the given path // inside the directory managed by this DirectoryLogger. func (dirLog *DirectoryLogger) ToFile(file string) *log.Logger { dirLog.mutex.Lock() defer dirLog.mutex.Unlock() if logger, ok := dirLog.loggers[file]; ok { return logger } fullPath := filepath.Join(dirLog.Directory, file) // Handle a value of 'file' with slashes in it by creating the necessary subdirectories. if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { log.Printf("Failed to create parent directories to log to %v (%v), dumping to backup instead", fullPath, err) return log.Default() } f, err := os.OpenFile(fullPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Printf("Failed to open %v for logging (%v), dumping to backup instead", fullPath, err) return log.Default() } dirLog.openFiles = append(dirLog.openFiles, f) logger := log.New(f, "", log.LstdFlags) dirLog.loggers[file] = logger return logger } // Close closes all open files that have been used for logging so far. func (dirLog *DirectoryLogger) Close() error { var err error for _, f := range dirLog.openFiles { err = multierr.Append(err, f.Close()) } // Reset the DirectoryLogger, to avoid confusing errors if somebody does end // up using the DirectoryLogger after Close()ing it (which they are not // supposed to do). dirLog.loggers = make(map[string]*log.Logger) dirLog.openFiles = nil return err }