Files
sarin/cmd/cli/main.go
T
aykhans 80a8ba1d59 feat(logging): add runtime log levels, response logging, and progress/log-file controls
- --log-level (comma-separated string: info, error): pick which logs are emitted;
  the level set is resolved once, up front
- Log responses at the info level: full self-contained JSON to a file or stderr,
  a compact "status duration | body" summary in the TUI log box
- Route logs independently of the progress display: the TUI log box when it runs,
  stderr when piped, or a file via --log-file/-w (parent dir validated up front)
- Replace --quiet with --progress (bar | none, default bar): progress and logs are
  now independent — progress=none still shows the log box; quiet == progress=none
  with an empty log level
- Rename the internal "message" concept to "log" (runtimeLog, runtimeLogger, ...)
2026-07-22 23:20:14 +04:00

98 lines
2.6 KiB
Go

package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"go.aykhans.me/sarin/internal/config"
"go.aykhans.me/sarin/internal/sarin"
"go.aykhans.me/sarin/internal/types"
utilsErr "go.aykhans.me/utils/errors"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
stopCtrl := sarin.NewStopController(cancel)
go listenForTermination(stopCtrl.Stop)
combinedConfig := config.ReadAllConfigs()
combinedConfig.SetDefaults()
if *combinedConfig.ShowConfig {
if !combinedConfig.Print() {
return
}
}
_ = utilsErr.MustHandle(combinedConfig.Validate(),
utilsErr.OnType(func(err types.FieldValidationErrors) error {
for _, fieldErr := range err.Errors {
if fieldErr.Value == "" {
fmt.Fprintln(os.Stderr,
config.StyleYellow.Render(fmt.Sprintf("[VALIDATION] Field '%s': ", fieldErr.Field))+fieldErr.Err.Error(),
)
} else {
fmt.Fprintln(os.Stderr,
config.StyleYellow.Render(fmt.Sprintf("[VALIDATION] Field '%s' (%s): ", fieldErr.Field, fieldErr.Value))+fieldErr.Err.Error(),
)
}
}
os.Exit(1)
return nil
}),
)
srn, err := sarin.NewSarin(
ctx,
combinedConfig.Methods, combinedConfig.URL, *combinedConfig.Timeout,
*combinedConfig.Concurrency, combinedConfig.Requests, combinedConfig.Duration,
*combinedConfig.Progress == config.ConfigProgressTypeBar, *combinedConfig.Insecure, combinedConfig.Params, combinedConfig.Headers,
combinedConfig.Cookies, combinedConfig.Bodies, combinedConfig.Proxies, combinedConfig.Values,
*combinedConfig.Output != config.ConfigOutputTypeNone,
*combinedConfig.DryRun, *combinedConfig.LogLevel, *combinedConfig.LogFile,
combinedConfig.Lua, combinedConfig.Js,
)
_ = utilsErr.MustHandle(err,
utilsErr.OnType(func(err types.ProxyDialError) error {
fmt.Fprintln(os.Stderr, config.StyleRed.Render("[PROXY] ")+err.Error())
os.Exit(1)
return nil
}),
utilsErr.OnSentinel(types.ErrScriptEmpty, func(err error) error {
fmt.Fprintln(os.Stderr, config.StyleRed.Render("[SCRIPT] ")+err.Error())
os.Exit(1)
return nil
}),
utilsErr.OnType(func(err types.ScriptLoadError) error {
fmt.Fprintln(os.Stderr, config.StyleRed.Render("[SCRIPT] ")+err.Error())
os.Exit(1)
return nil
}),
)
srn.Start(ctx, stopCtrl)
switch *combinedConfig.Output {
case config.ConfigOutputTypeNone:
return
case config.ConfigOutputTypeJSON:
srn.GetResponses().PrintJSON()
case config.ConfigOutputTypeYAML:
srn.GetResponses().PrintYAML()
default:
srn.GetResponses().PrintTable()
}
}
func listenForTermination(stop func()) {
sigChan := make(chan os.Signal, 4)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
for range sigChan {
stop()
}
}