fix: optimize report command to avoid creating timestamp directories

- Implement lazy loading for directory creation in config.go
- Add logFile parameter to InitLogger for better control
- Use dynamic directory existence check instead of flags
- Report command now uses console-only logging to prevent directory creation
- Support both JSON and colorized console output formats
- Maintain backward compatibility for all other commands

Changes:
- config.go: Convert directory paths to getter methods with lazy creation
- logger.go: Add logFile parameter and improve logging control
- cmd/root.go: Detect report command and disable file logging
- uixt/*: Update all references to use new getter methods

Fixes the issue where 'hrp report results/' would create unwanted timestamp directories
This commit is contained in:
lilong.129
2025-06-10 12:06:08 +08:00
parent 6588d95154
commit f5f6d177ab
9 changed files with 84 additions and 36 deletions

View File

@@ -46,7 +46,9 @@ GitHub: https://github.com/httprunner/httprunner
Copyright © 2017-present debugtalk. Apache-2.0 License.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
hrp.InitLogger(logLevel, logJSON)
// For report command, don't create log files to avoid creating directories
enableLogFile := cmd.Name() != "report"
hrp.InitLogger(logLevel, logJSON, enableLogFile)
},
Version: version.GetVersionInfo(),
TraverseChildren: true, // parses flags on all parents before executing child command

View File

@@ -21,12 +21,13 @@ const (
type Config struct {
RootDir string
ResultsDir string
ResultsPath string
DownloadsPath string
ScreenShotsPath string
resultsPath string
downloadsPath string
screenShotsPath string
StartTime time.Time
ActionLogFilePath string
DeviceActionLogFilePath string
mu sync.Mutex
}
var (
@@ -48,25 +49,58 @@ func GetConfig() *Config {
startTimeStr := cfg.StartTime.Format("20060102150405")
cfg.ResultsDir = filepath.Join(ResultsDirName, startTimeStr)
cfg.ResultsPath = filepath.Join(cfg.RootDir, cfg.ResultsDir)
cfg.DownloadsPath = filepath.Join(cfg.RootDir, filepath.Join(DownloadsDirName, startTimeStr))
cfg.ScreenShotsPath = filepath.Join(cfg.ResultsPath, ScreenshotsDirName)
cfg.resultsPath = filepath.Join(cfg.RootDir, cfg.ResultsDir)
cfg.downloadsPath = filepath.Join(cfg.RootDir, filepath.Join(DownloadsDirName, startTimeStr))
cfg.screenShotsPath = filepath.Join(cfg.resultsPath, ScreenshotsDirName)
cfg.ActionLogFilePath = filepath.Join(cfg.ResultsDir, ActionLogDirName)
cfg.DeviceActionLogFilePath = "/sdcard/Android/data/io.appium.uiautomator2.server/files/hodor"
// create results directory
if err := builtin.EnsureFolderExists(cfg.ResultsPath); err != nil {
log.Fatal().Err(err).Msg("create results directory failed")
}
if err := builtin.EnsureFolderExists(cfg.DownloadsPath); err != nil {
log.Fatal().Err(err).Msg("create downloads directory failed")
}
if err := builtin.EnsureFolderExists(cfg.ScreenShotsPath); err != nil {
log.Fatal().Err(err).Msg("create screenshots directory failed")
}
globalConfig = cfg
})
return globalConfig
}
// ResultsPath returns the results path and creates the directory if it doesn't exist
func (c *Config) ResultsPath() string {
c.mu.Lock()
defer c.mu.Unlock()
// Check if directory exists, create if it doesn't
if _, err := os.Stat(c.resultsPath); os.IsNotExist(err) {
if err := builtin.EnsureFolderExists(c.resultsPath); err != nil {
log.Error().Err(err).Str("path", c.resultsPath).Msg("failed to create results directory")
} else {
log.Info().Str("path", c.resultsPath).Msg("create folder")
}
}
return c.resultsPath
}
// DownloadsPath returns the downloads path and creates the directory if it doesn't exist
func (c *Config) DownloadsPath() string {
c.mu.Lock()
defer c.mu.Unlock()
// Check if directory exists, create if it doesn't
if _, err := os.Stat(c.downloadsPath); os.IsNotExist(err) {
if err := builtin.EnsureFolderExists(c.downloadsPath); err != nil {
log.Error().Err(err).Str("path", c.downloadsPath).Msg("failed to create downloads directory")
}
}
return c.downloadsPath
}
// ScreenShotsPath returns the screenshots path and creates the directory if it doesn't exist
func (c *Config) ScreenShotsPath() string {
c.mu.Lock()
defer c.mu.Unlock()
// Check if directory exists, create if it doesn't
if _, err := os.Stat(c.screenShotsPath); os.IsNotExist(err) {
if err := builtin.EnsureFolderExists(c.screenShotsPath); err != nil {
log.Error().Err(err).Str("path", c.screenShotsPath).Msg("failed to create screenshots directory")
}
}
return c.screenShotsPath
}

View File

@@ -1 +1 @@
v5.0.0-beta-2506101103
v5.0.0-beta-2506101206

View File

@@ -15,7 +15,7 @@ import (
"github.com/httprunner/httprunner/v5/internal/config"
)
func InitLogger(logLevel string, logJSON bool) {
func InitLogger(logLevel string, logJSON bool, logFile bool) {
// Error Logging with Stacktrace
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
@@ -39,40 +39,52 @@ func InitLogger(logLevel string, logJSON bool) {
TimeFormat: time.RFC3339Nano,
NoColor: noColor,
}
msg = "log with colorized console and file output"
if logFile {
msg = "log with colorized console and file output"
} else {
msg = "log with colorized console output only"
}
} else {
// default logger
consoleWriter = os.Stderr
msg = "log with json console and file output"
if logFile {
msg = "log with json console and file output"
} else {
msg = "log with json console output only"
}
}
// parse console log level
consoleLevel := parseLogLevel(logLevel)
// If logFile is false, use console-only logger
if !logFile {
log.Logger = zerolog.New(consoleWriter).With().Timestamp().Logger().Level(consoleLevel)
log.Info().Msg(msg)
return
}
// file writer - write to results/taskID/hrp.log
cfg := config.GetConfig()
logFilePath := filepath.Join(cfg.ResultsPath, "hrp.log")
logFilePath := filepath.Join(cfg.ResultsPath(), "hrp.log")
// create or open log file
logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
logFileWriter, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
if err != nil {
// if file creation failed, use console logger only
log.Logger = zerolog.New(consoleWriter).With().Timestamp().Logger().Level(consoleLevel)
log.Error().Err(err).Str("logFilePath", logFilePath).Msg("create log file failed")
log.Error().Err(err).Str("logFilePath", logFilePath).Msg(msg)
} else {
// create a custom writer that applies different log levels
multiWriter := &leveledMultiWriter{
consoleWriter: consoleWriter,
consoleLevel: consoleLevel,
fileWriter: logFile,
fileWriter: logFileWriter,
fileLevel: zerolog.DebugLevel,
}
log.Logger = zerolog.New(multiWriter).With().Timestamp().Logger()
log.Info().Str("logFilePath", logFilePath).Msg("log file created successfully")
log.Info().Str("logFilePath", logFilePath).Msg(msg)
}
log.Info().Msg(msg)
log.Info().Str("console_log_level", strings.ToUpper(logLevel)).Str("file_log_level", "DEBUG").Msg("logger initialized with different levels")
}
// parseLogLevel converts string log level to zerolog.Level

View File

@@ -807,7 +807,7 @@ func (ad *ADBDriver) ScreenRecord(opts ...option.ActionOption) (videoPath string
filePath = options.ScreenRecordPath
} else {
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
filePath = filepath.Join(config.GetConfig().ScreenShotsPath, fmt.Sprintf("%s.mp4", timestamp))
filePath = filepath.Join(config.GetConfig().ScreenShotsPath(), fmt.Sprintf("%s.mp4", timestamp))
}
var ctx context.Context

View File

@@ -70,7 +70,7 @@ func (dExt *XTDriver) GetScreenResult(opts ...option.ActionOption) (screenResult
fileName = builtin.GenNameWithTimestamp("%d_screenshot")
}
imagePath := filepath.Join(
config.GetConfig().ScreenShotsPath,
config.GetConfig().ScreenShotsPath(),
fmt.Sprintf("%s.%s", fileName, "jpeg"),
)
go func() {
@@ -436,7 +436,7 @@ func MarkUIOperation(driver IDriver, actionType option.ActionName, actionCoordin
// create screenshot save path
timestamp := builtin.GenNameWithTimestamp("%d")
imagePath := filepath.Join(
config.GetConfig().ScreenShotsPath,
config.GetConfig().ScreenShotsPath(),
fmt.Sprintf("action_%s_pre_%s.png", timestamp, actionType),
)

View File

@@ -141,7 +141,7 @@ func postHandler(driver IDriver, actionType option.ActionName, options *option.A
// save compressed screenshot to file
timestamp := builtin.GenNameWithTimestamp("%d")
imagePath := filepath.Join(
config.GetConfig().ScreenShotsPath,
config.GetConfig().ScreenShotsPath(),
fmt.Sprintf("action_%s_post_%s.png", timestamp, actionType),
)

View File

@@ -305,7 +305,7 @@ var (
func DownloadFileByUrl(fileUrl string) (filePath string, err error) {
hash := md5.Sum([]byte(fileUrl))
fileName := fmt.Sprintf("%x", hash)
filePath = filepath.Join(config.GetConfig().DownloadsPath, fileName)
filePath = filepath.Join(config.GetConfig().DownloadsPath(), fileName)
// get or create file lock
lockI, _ := fileLocks.LoadOrStore(filePath, &sync.Mutex{})

View File

@@ -910,7 +910,7 @@ func (wd *WDADriver) triggerWDALog(data map[string]interface{}) (rawResp []byte,
func (wd *WDADriver) ScreenRecord(opts ...option.ActionOption) (videoPath string, err error) {
log.Info().Msg("WDADriver.ScreenRecord")
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
fileName := filepath.Join(config.GetConfig().ScreenShotsPath, fmt.Sprintf("%s.mp4", timestamp))
fileName := filepath.Join(config.GetConfig().ScreenShotsPath(), fmt.Sprintf("%s.mp4", timestamp))
options := option.NewActionOptions(opts...)
duration := time.Duration(options.Duration * float64(time.Second))