diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go new file mode 100644 index 00000000..c5ba4aae --- /dev/null +++ b/cmd/sing-box/cmd_run.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/goccy/go-json" + "github.com/sagernet/sing-box" + "github.com/sagernet/sing-box/option" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var commandRun = &cobra.Command{ + Use: "run", + Short: "run service", + Run: run, +} + +func run(cmd *cobra.Command, args []string) { + configContent, err := os.ReadFile(configPath) + if err != nil { + logrus.Fatal("read config: ", err) + } + var options option.Options + err = json.Unmarshal(configContent, &options) + if err != nil { + logrus.Fatal("decode config: ", err) + } + if disableColor { + if options.Log == nil { + options.Log = &option.LogOption{} + } + options.Log.DisableColor = true + } + + ctx, cancel := context.WithCancel(context.Background()) + service, err := box.NewService(ctx, options) + if err != nil { + logrus.Fatal("create service: ", err) + } + err = service.Start() + if err != nil { + logrus.Fatal("start service: ", err) + } + osSignals := make(chan os.Signal, 1) + signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM) + <-osSignals + cancel() + service.Close() +} diff --git a/cmd/sing-box/main.go b/cmd/sing-box/main.go index af29dd03..72b5370a 100644 --- a/cmd/sing-box/main.go +++ b/cmd/sing-box/main.go @@ -1,83 +1,45 @@ package main import ( - "context" "os" - "os/signal" - "syscall" - "github.com/goccy/go-json" - "github.com/sagernet/sing-box" - "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) func init() { logrus.StandardLogger().SetLevel(logrus.TraceLevel) - logrus.StandardLogger().Formatter.(*logrus.TextFormatter).ForceColors = true + logrus.StandardLogger().SetFormatter(&log.LogrusTextFormatter{}) } var ( configPath string workingDir string - formatConfig bool + disableColor bool ) func main() { command := &cobra.Command{ - Use: "sing-box", - Run: run, + Use: "sing-box", + PersistentPreRun: preRun, } - command.Flags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path") - command.Flags().StringVarP(&workingDir, "directory", "D", "", "set working directory") - command.Flags().BoolVarP(&formatConfig, "format", "f", false, "print formatted configuration file") + command.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path") + command.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") + command.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") + command.AddCommand(commandRun) if err := command.Execute(); err != nil { logrus.Fatal(err) } } -func run(cmd *cobra.Command, args []string) { +func preRun(cmd *cobra.Command, args []string) { + if disableColor { + logrus.StandardLogger().SetFormatter(&log.LogrusTextFormatter{DisableColors: true}) + } if workingDir != "" { if err := os.Chdir(workingDir); err != nil { logrus.Fatal(err) } } - - configContent, err := os.ReadFile(configPath) - if err != nil { - logrus.Fatal("read config: ", err) - } - var options option.Options - err = json.Unmarshal(configContent, &options) - if err != nil { - logrus.Fatal("decode config: ", err) - } - - ctx, cancel := context.WithCancel(context.Background()) - service, err := box.NewService(ctx, options) - if err != nil { - logrus.Fatal("create service: ", err) - } - - if formatConfig { - cancel() - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - err = encoder.Encode(options) - if err != nil { - logrus.Fatal("encode config: ", err) - } - return - } - - err = service.Start() - if err != nil { - logrus.Fatal("start service: ", err) - } - osSignals := make(chan os.Signal, 1) - signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM) - <-osSignals - cancel() - service.Close() } diff --git a/log/logrus.go b/log/logrus.go index 828a9b5f..9191b47e 100644 --- a/log/logrus.go +++ b/log/logrus.go @@ -27,7 +27,11 @@ type abstractLogrusLogger interface { func NewLogrusLogger(options option.LogOption) (*logrusLogger, error) { logger := logrus.New() logger.SetLevel(logrus.TraceLevel) - logger.Formatter.(*logrus.TextFormatter).ForceColors = true + logger.SetFormatter(&LogrusTextFormatter{ + DisableColors: options.DisableColor || options.Output != "", + DisableTimestamp: !options.Timestamp && options.Output != "", + FullTimestamp: options.Timestamp, + }) logger.AddHook(new(logrusHook)) var err error if options.Level != "" { diff --git a/log/logrus_text_formatter.go b/log/logrus_text_formatter.go new file mode 100644 index 00000000..35ae5ec9 --- /dev/null +++ b/log/logrus_text_formatter.go @@ -0,0 +1,83 @@ +package log + +import ( + "bytes" + "fmt" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +const ( + red = 31 + yellow = 33 + blue = 36 + gray = 37 +) + +var baseTimestamp time.Time + +func init() { + baseTimestamp = time.Now() +} + +type LogrusTextFormatter struct { + DisableColors bool + DisableTimestamp bool + FullTimestamp bool + TimestampFormat string +} + +func (f *LogrusTextFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = "-0700 2006-01-02 15:04:05" + } + f.print(b, entry, timestampFormat) + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *LogrusTextFormatter) print(b *bytes.Buffer, entry *logrus.Entry, timestampFormat string) { + var levelColor int + switch entry.Level { + case logrus.DebugLevel, logrus.TraceLevel: + levelColor = gray + case logrus.WarnLevel: + levelColor = yellow + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + levelColor = red + case logrus.InfoLevel: + levelColor = blue + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String()) + if !f.DisableColors { + switch { + case f.DisableTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s", levelColor, levelText, entry.Message) + case !f.FullTimestamp: + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message) + default: + fmt.Fprintf(b, "%s \x1b[%dm%s\x1b[0m %-44s", entry.Time.Format(timestampFormat), levelColor, levelText, entry.Message) + } + } else { + switch { + case f.DisableTimestamp: + fmt.Fprintf(b, "%s %-44s", levelText, entry.Message) + case !f.FullTimestamp: + fmt.Fprintf(b, "%s[%04d] %-44s", levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message) + default: + fmt.Fprintf(b, "[%s] %s %-44s", entry.Time.Format(timestampFormat), levelText, entry.Message) + } + } +} diff --git a/option/config.go b/option/config.go index 4946f742..9dc2f318 100644 --- a/option/config.go +++ b/option/config.go @@ -30,7 +30,9 @@ func (o Options) Equals(other Options) bool { } type LogOption struct { - Disabled bool `json:"disabled,omitempty"` - Level string `json:"level,omitempty"` - Output string `json:"output,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Level string `json:"level,omitempty"` + Output string `json:"output,omitempty"` + Timestamp bool `json:"timestamp,omitempty"` + DisableColor bool `json:"-"` }