diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 1147a87e..8ba4ab19 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -9,10 +9,9 @@ import ( "os" "runtime" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/cmd" "cdr.dev/coder-cli/internal/x/xterminal" - - "go.coder.com/flog" ) // Using a global for the version so it can be set at build time using ldflags. @@ -31,7 +30,8 @@ func main() { stdoutState, err := xterminal.MakeOutputRaw(os.Stdout.Fd()) if err != nil { - flog.Fatal("set output to raw: %s", err) + clog.Log(clog.Fatal(fmt.Sprintf("set output to raw: %s", err))) + os.Exit(1) } defer func() { // Best effort. Would result in broken terminal on window but nothing we can do about it. @@ -42,6 +42,7 @@ func main() { app.Version = fmt.Sprintf("%s %s %s/%s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) if err := app.ExecuteContext(ctx); err != nil { - flog.Fatal("%v", err) + clog.Log(err) + os.Exit(1) } } diff --git a/coder-sdk/error.go b/coder-sdk/error.go index 2f365a47..91ebfa30 100644 --- a/coder-sdk/error.go +++ b/coder-sdk/error.go @@ -12,8 +12,8 @@ import ( // ErrNotFound describes an error case in which the requested resource could not be found var ErrNotFound = xerrors.Errorf("resource not found") -// apiError is the expected payload format for our errors. -type apiError struct { +// APIError is the expected payload format for our errors. +type APIError struct { Err struct { Msg string `json:"msg"` } `json:"error"` @@ -30,7 +30,7 @@ func (e *HTTPError) Error() string { return fmt.Sprintf("dump response: %+v", err) } - var msg apiError + var msg APIError // Try to decode the payload as an error, if it fails or if there is no error message, // return the response URL with the dump. if err := json.NewDecoder(e.Response.Body).Decode(&msg); err != nil || msg.Err.Msg == "" { @@ -38,7 +38,7 @@ func (e *HTTPError) Error() string { } // If the payload was a in the expected error format with a message, include it. - return fmt.Sprintf("%s\n%s%s", e.Response.Request.URL, dump, msg.Err.Msg) + return msg.Err.Msg } func bodyError(resp *http.Response) error { diff --git a/go.mod b/go.mod index 6b75ad80..b8918186 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/rjeczalik/notify v0.9.2 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 - go.coder.com/flog v0.0.0-20190906214207-47dd47ea0512 golang.org/x/crypto v0.0.0-20200422194213-44a606286825 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 diff --git a/internal/activity/pusher.go b/internal/activity/pusher.go index 7f0f0e00..6d0a224f 100644 --- a/internal/activity/pusher.go +++ b/internal/activity/pusher.go @@ -2,13 +2,13 @@ package activity import ( "context" + "fmt" "time" "golang.org/x/time/rate" "cdr.dev/coder-cli/coder-sdk" - - "go.coder.com/flog" + "cdr.dev/coder-cli/internal/clog" ) const pushInterval = time.Minute @@ -42,6 +42,6 @@ func (p *Pusher) Push(ctx context.Context) { } if err := p.client.PushActivity(ctx, p.source, p.envID); err != nil { - flog.Error("push activity: %s", err) + clog.Log(clog.Error(fmt.Sprintf("push activity: %s", err))) } } diff --git a/internal/clog/error.go b/internal/clog/error.go new file mode 100644 index 00000000..71183573 --- /dev/null +++ b/internal/clog/error.go @@ -0,0 +1,131 @@ +package clog + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/fatih/color" + "golang.org/x/xerrors" +) + +// CLIMessage provides a human-readable message for CLI errors and messages. +type CLIMessage struct { + Level string + Color color.Attribute + Header string + Lines []string +} + +// CLIError wraps a CLIMessage and allows consumers to treat it as a normal error. +type CLIError struct { + CLIMessage + error +} + +// String formats the CLI message for consumption by a human. +func (m CLIMessage) String() string { + var str strings.Builder + str.WriteString(fmt.Sprintf("%s: %s\n", + color.New(m.Color).Sprint(m.Level), + color.New(color.Bold).Sprint(m.Header)), + ) + for _, line := range m.Lines { + str.WriteString(fmt.Sprintf(" %s %s\n", color.New(m.Color).Sprint("|"), line)) + } + return str.String() +} + +// Log logs the given error to stderr, defaulting to "fatal" if the error is not a CLIError. +// If the error is a CLIError, the plain error chain is ignored and the CLIError +// is logged on its own. +func Log(err error) { + var cliErr CLIError + if !xerrors.As(err, &cliErr) { + cliErr = Fatal(err.Error()) + } + fmt.Fprintln(os.Stderr, cliErr.String()) +} + +// LogInfo prints the given info message to stderr. +func LogInfo(header string, lines ...string) { + fmt.Fprint(os.Stderr, CLIMessage{ + Level: "info", + Color: color.FgBlue, + Header: header, + Lines: lines, + }.String()) +} + +// LogSuccess prints the given info message to stderr. +func LogSuccess(header string, lines ...string) { + fmt.Fprint(os.Stderr, CLIMessage{ + Level: "success", + Color: color.FgGreen, + Header: header, + Lines: lines, + }.String()) +} + +// Warn creates an error with the level "warning". +func Warn(header string, lines ...string) CLIError { + return CLIError{ + CLIMessage: CLIMessage{ + Color: color.FgYellow, + Level: "warning", + Header: header, + Lines: lines, + }, + error: errors.New(header), + } +} + +// Error creates an error with the level "error". +func Error(header string, lines ...string) CLIError { + return CLIError{ + CLIMessage: CLIMessage{ + Color: color.FgRed, + Level: "error", + Header: header, + Lines: lines, + }, + error: errors.New(header), + } +} + +// Fatal creates an error with the level "fatal". +func Fatal(header string, lines ...string) CLIError { + return CLIError{ + CLIMessage: CLIMessage{ + Color: color.FgRed, + Level: "fatal", + Header: header, + Lines: lines, + }, + error: errors.New(header), + } +} + +// Bold provides a convenience wrapper around color.New for brevity when logging. +func Bold(a string) string { + return color.New(color.Bold).Sprint(a) +} + +// Tip formats according to the given format specifier and prepends a bolded "tip: " header. +func Tip(format string, a ...interface{}) string { + return fmt.Sprintf("%s %s", Bold("tip:"), fmt.Sprintf(format, a...)) +} + +// Hint formats according to the given format specifier and prepends a bolded "hint: " header. +func Hint(format string, a ...interface{}) string { + return fmt.Sprintf("%s %s", Bold("hint:"), fmt.Sprintf(format, a...)) +} + +// Cause formats according to the given format specifier and prepends a bolded "cause: " header. +func Cause(format string, a ...interface{}) string { + return fmt.Sprintf("%s %s", Bold("cause:"), fmt.Sprintf(format, a...)) +} + +// BlankLine is an empty string meant to be used in CLIMessage and CLIError construction. +const BlankLine = "" diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index 8297bbd6..ff789ca3 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -8,10 +8,14 @@ import ( "golang.org/x/xerrors" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/config" ) -var errNeedLogin = xerrors.New("failed to read session credentials: did you run \"coder login\"?") +var errNeedLogin = clog.Fatal( + "failed to read session credentials", + clog.Hint(`did you run "coder login [https://coder.domain.com]"?`), +) func newClient() (*coder.Client, error) { sessionToken, err := config.Session.Read() diff --git a/internal/cmd/ceapi.go b/internal/cmd/ceapi.go index e15b101d..f9b4ebe6 100644 --- a/internal/cmd/ceapi.go +++ b/internal/cmd/ceapi.go @@ -5,6 +5,7 @@ import ( "fmt" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "golang.org/x/xerrors" ) @@ -73,10 +74,12 @@ func findEnv(ctx context.Context, client *coder.Client, envName, userEmail strin found = append(found, env.Name) } - return nil, notFoundButDidFind{ - needle: envName, - haystack: found, - } + return nil, clog.Fatal( + "failed to find environment", + fmt.Sprintf("environment %q not found in %q", envName, found), + clog.BlankLine, + clog.Tip("run \"coder envs ls\" to view your environments"), + ) } type notFoundButDidFind struct { diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index b18be8a6..50020eba 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -2,15 +2,16 @@ package cmd import ( "encoding/json" + "fmt" "os" + "sync/atomic" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/x/xtabwriter" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "golang.org/x/xerrors" - - "go.coder.com/flog" ) func envsCommand() *cobra.Command { @@ -37,7 +38,7 @@ func envsCommand() *cobra.Command { return err } if len(envs) < 1 { - flog.Info("no environments found") + clog.LogInfo("no environments found") return nil } @@ -92,26 +93,33 @@ coder envs --user charlie@coder.com ls -o json \ } var egroup errgroup.Group + var fails int32 for _, envName := range args { envName := envName egroup.Go(func() error { env, err := findEnv(cmd.Context(), client, envName, *user) if err != nil { - flog.Error("failed to find environment by name \"%s\": %v", envName, err) - return xerrors.Errorf("find environment by name: %w", err) + atomic.AddInt32(&fails, 1) + clog.Log(err) + return xerrors.Errorf("find env by name: %w", err) } if err = client.StopEnvironment(cmd.Context(), env.ID); err != nil { - flog.Error("failed to stop environment \"%s\": %v", env.Name, err) - return xerrors.Errorf("stop environment: %w", err) + atomic.AddInt32(&fails, 1) + err = clog.Fatal(fmt.Sprintf("stop environment %q", env.Name), + clog.Cause(err.Error()), clog.BlankLine, + clog.Hint("current environment status is %q", env.LatestStat.ContainerStatus), + ) + clog.Log(err) + return err } - flog.Success("Successfully stopped environment %q", envName) + clog.LogSuccess(fmt.Sprintf("successfully stopped environment %q", envName)) return nil }) } if err = egroup.Wait(); err != nil { - return xerrors.Errorf("some stop operations failed") + return clog.Fatal(fmt.Sprintf("%d failure(s) emitted", fails)) } return nil }, diff --git a/internal/cmd/login.go b/internal/cmd/login.go index a1740b48..fdcbe765 100644 --- a/internal/cmd/login.go +++ b/internal/cmd/login.go @@ -9,14 +9,13 @@ import ( "strings" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/config" "cdr.dev/coder-cli/internal/loginsrv" "github.com/pkg/browser" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "golang.org/x/xerrors" - - "go.coder.com/flog" ) func makeLoginCmd() *cobra.Command { @@ -140,7 +139,7 @@ func login(cmd *cobra.Command, envURL *url.URL, urlCfg, sessionCfg config.File) return xerrors.Errorf("store config: %w", err) } - flog.Success("Logged in.") + clog.LogSuccess("logged in") return nil } diff --git a/internal/cmd/logout.go b/internal/cmd/logout.go index 621ed89e..b653d68f 100644 --- a/internal/cmd/logout.go +++ b/internal/cmd/logout.go @@ -3,11 +3,10 @@ package cmd import ( "os" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/config" "github.com/spf13/cobra" "golang.org/x/xerrors" - - "go.coder.com/flog" ) func makeLogoutCmd() *cobra.Command { @@ -22,11 +21,11 @@ func logout(_ *cobra.Command, _ []string) error { err := config.Session.Delete() if err != nil { if os.IsNotExist(err) { - flog.Info("no active session") + clog.LogInfo("no active session") return nil } return xerrors.Errorf("delete session: %w", err) } - flog.Success("logged out") + clog.LogSuccess("logged out") return nil } diff --git a/internal/cmd/rebuild.go b/internal/cmd/rebuild.go index a5d42a50..41f3913d 100644 --- a/internal/cmd/rebuild.go +++ b/internal/cmd/rebuild.go @@ -8,11 +8,11 @@ import ( "time" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "github.com/briandowns/spinner" "github.com/fatih/color" "github.com/manifoldco/promptui" "github.com/spf13/cobra" - "go.coder.com/flog" "golang.org/x/crypto/ssh/terminal" "golang.org/x/xerrors" ) @@ -56,7 +56,10 @@ coder envs rebuild backend-env --force`, return err } } else { - flog.Info("Use \"coder envs watch-build %s\" to follow the build logs", env.Name) + clog.LogSuccess( + "successfully started rebuild", + clog.Tip("run \"coder envs watch-build %s\" to follow the build logs", env.Name), + ) } return nil }, diff --git a/internal/cmd/resourcemanager.go b/internal/cmd/resourcemanager.go index f5366f21..d4ee20bb 100644 --- a/internal/cmd/resourcemanager.go +++ b/internal/cmd/resourcemanager.go @@ -8,8 +8,8 @@ import ( "text/tabwriter" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "github.com/spf13/cobra" - "go.coder.com/flog" "golang.org/x/xerrors" ) @@ -212,8 +212,10 @@ func printResourceTop(writer io.Writer, groups []groupable, labeler envLabeler, _, _ = fmt.Fprint(tabwriter, "\n") } if len(userResources) == 0 { - flog.Info("No groups for the given filters exist with active environments.") - flog.Info("Use \"--show-empty\" to see groups with no resources.") + clog.LogInfo( + "no groups for the given filters exist with active environments", + clog.Tip("run \"--show-empty\" to see groups with no resources."), + ) } return nil } diff --git a/internal/cmd/secrets.go b/internal/cmd/secrets.go index 9d98faac..879d6bde 100644 --- a/internal/cmd/secrets.go +++ b/internal/cmd/secrets.go @@ -10,9 +10,8 @@ import ( "golang.org/x/xerrors" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/x/xtabwriter" - - "go.coder.com/flog" ) func makeSecretsCmd() *cobra.Command { @@ -154,7 +153,7 @@ func listSecrets(userEmail *string) func(cmd *cobra.Command, _ []string) error { } if len(secrets) < 1 { - flog.Info("No secrets found") + clog.LogInfo("no secrets found") return nil } @@ -212,10 +211,12 @@ func makeRemoveSecrets(userEmail *string) func(c *cobra.Command, args []string) for _, n := range args { err := client.DeleteSecretByName(cmd.Context(), n, user.ID) if err != nil { - flog.Error("failed to delete secret %q: %v", n, err) + clog.Log(clog.Error( + fmt.Sprintf("failed to delete secret %q: %v", n, err), + )) errorSeen = true } else { - flog.Success("successfully deleted secret %q", n) + clog.LogSuccess("successfully deleted secret: %q", n) } } if errorSeen { diff --git a/internal/cmd/shell.go b/internal/cmd/shell.go index 4e08edf0..76c2c8d4 100644 --- a/internal/cmd/shell.go +++ b/internal/cmd/shell.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "fmt" "io" "os" "strings" @@ -15,10 +16,9 @@ import ( "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/activity" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/x/xterminal" "cdr.dev/wsep" - - "go.coder.com/flog" ) func getEnvsForCompletion(user string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -153,7 +153,7 @@ func runCommand(ctx context.Context, envName, command string, args []string) err if err != nil { var closeErr websocket.CloseError if xerrors.As(err, &closeErr) { - return xerrors.Errorf("network error, is %q online?", envName) + return networkErr(client, env) } return xerrors.Errorf("start remote command: %w", err) } @@ -179,7 +179,6 @@ func runCommand(ctx context.Context, envName, command string, args []string) err } }() go func() { - if _, err := io.Copy(os.Stderr, process.Stderr()); err != nil { cancel() } @@ -188,13 +187,26 @@ func runCommand(ctx context.Context, envName, command string, args []string) err if err := process.Wait(); err != nil { var closeErr websocket.CloseError if xerrors.Is(err, ctx.Err()) || xerrors.As(err, &closeErr) { - return xerrors.Errorf("network error, is %q online?", envName) + return networkErr(client, env) } return err } return nil } +func networkErr(client *coder.Client, env *coder.Environment) error { + if env.LatestStat.ContainerStatus != coder.EnvironmentOn { + return clog.Fatal( + "environment is not running", + fmt.Sprintf("environment %q is not running", env.Name), + fmt.Sprintf("its current status is %q", env.LatestStat.ContainerStatus), + clog.BlankLine, + clog.Tip("run \"coder envs rebuild %s --follow\" to start the environment", env.Name), + ) + } + return xerrors.Errorf("network error, is %q online?", env.Name) +} + func heartbeat(ctx context.Context, conn *websocket.Conn, interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() @@ -204,9 +216,10 @@ func heartbeat(ctx context.Context, conn *websocket.Conn, interval time.Duration case <-ctx.Done(): return case <-ticker.C: - if err := conn.Ping(ctx); err != nil { - // NOTE: Prefix with \r\n to attempt to have clearer output as we might still be in raw mode. - flog.Fatal("\r\nFailed to ping websocket: %s, exiting.", err) + if err := conn.Ping(ctx); err != nil || true { + // don't try to do multi-line here because the raw mode makes things weird + clog.Log(clog.Fatal("failed to ping websocket, exiting: " + err.Error())) + os.Exit(1) } } } diff --git a/internal/cmd/sync.go b/internal/cmd/sync.go index 0ba32095..05d9cd00 100644 --- a/internal/cmd/sync.go +++ b/internal/cmd/sync.go @@ -9,11 +9,10 @@ import ( "strings" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/sync" "github.com/spf13/cobra" "golang.org/x/xerrors" - - "go.coder.com/flog" ) func makeSyncCmd() *cobra.Command { @@ -96,7 +95,7 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error { remoteVersion, rsyncErr := s.Version() if rsyncErr != nil { - flog.Info("Unable to determine remote rsync version. Proceeding cautiously.") + clog.LogInfo("unable to determine remote rsync version: proceeding cautiously") } else if localVersion != remoteVersion { return xerrors.Errorf("rsync protocol mismatch: local = %s, remote = %s", localVersion, remoteVersion) } diff --git a/internal/cmd/urls.go b/internal/cmd/urls.go index b79ddaa8..c91427da 100644 --- a/internal/cmd/urls.go +++ b/internal/cmd/urls.go @@ -14,9 +14,8 @@ import ( "golang.org/x/xerrors" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/coder-cli/internal/x/xtabwriter" - - "go.coder.com/flog" ) func makeURLCmd() *cobra.Command { @@ -70,7 +69,7 @@ var urlAccessLevel = map[string]string{ func validatePort(port string) (int, error) { p, err := strconv.ParseUint(port, 10, 16) if err != nil { - flog.Error("Invalid port") + clog.Log(clog.Error("invalid port")) return 0, err } if p < 1 { @@ -83,7 +82,7 @@ func validatePort(port string) (int, error) { func accessLevelIsValid(level string) bool { _, ok := urlAccessLevel[level] if !ok { - flog.Error("Invalid access level") + clog.Log(clog.Error("invalid access level")) } return ok } @@ -101,7 +100,7 @@ func makeListDevURLs(outputFmt *string) func(cmd *cobra.Command, args []string) switch *outputFmt { case "human": if len(devURLs) < 1 { - flog.Info("No devURLs found for environment %q", envName) + clog.LogInfo(fmt.Sprintf("no devURLs found for environment %q", envName)) return nil } err := xtabwriter.WriteTable(len(devURLs), func(i int) interface{} { @@ -168,13 +167,13 @@ func makeCreateDevURL() *cobra.Command { urlID, found := devURLID(portNum, urls) if found { - flog.Info("Updating devurl for port %v", port) + clog.LogInfo(fmt.Sprintf("updating devurl for port %v", port)) err := client.UpdateDevURL(cmd.Context(), env.ID, urlID, portNum, urlname, access) if err != nil { return xerrors.Errorf("update DevURL: %w", err) } } else { - flog.Info("Adding devurl for port %v", port) + clog.LogInfo(fmt.Sprintf("Adding devurl for port %v", port)) err := client.InsertDevURL(cmd.Context(), env.ID, portNum, urlname, access) if err != nil { return xerrors.Errorf("insert DevURL: %w", err) @@ -236,7 +235,7 @@ func removeDevURL(cmd *cobra.Command, args []string) error { urlID, found := devURLID(portNum, urls) if found { - flog.Info("Deleting devurl for port %v", port) + clog.LogInfo(fmt.Sprintf("deleting devurl for port %v", port)) } else { return xerrors.Errorf("No devurl found for port %v", port) } diff --git a/internal/sync/eventcache.go b/internal/sync/eventcache.go index 85a5e8ac..f4e757f8 100644 --- a/internal/sync/eventcache.go +++ b/internal/sync/eventcache.go @@ -5,8 +5,6 @@ import ( "time" "github.com/rjeczalik/notify" - - "go.coder.com/flog" ) type timedEvent struct { @@ -17,18 +15,14 @@ type timedEvent struct { type eventCache map[string]timedEvent func (cache eventCache) Add(ev timedEvent) { - log := flog.New() - log.Prefix = ev.Path() + ": " lastEvent, ok := cache[ev.Path()] if ok { switch { // If the file was quickly created and then destroyed, pretend nothing ever happened. case lastEvent.Event() == notify.Create && ev.Event() == notify.Remove: delete(cache, ev.Path()) - log.Info("ignored Create then Remove") return } - log.Info("replaced %s with %s", lastEvent.Event(), ev.Event()) } // Only let the latest event for a path have action. cache[ev.Path()] = ev diff --git a/internal/sync/sync.go b/internal/sync/sync.go index 83e6ca85..177ff9c9 100644 --- a/internal/sync/sync.go +++ b/internal/sync/sync.go @@ -21,10 +21,9 @@ import ( "golang.org/x/sync/semaphore" "golang.org/x/xerrors" - "go.coder.com/flog" - "cdr.dev/coder-cli/coder-sdk" "cdr.dev/coder-cli/internal/activity" + "cdr.dev/coder-cli/internal/clog" "cdr.dev/wsep" ) @@ -119,7 +118,7 @@ func (s Sync) remoteCmd(ctx context.Context, prog string, args ...string) error // initSync performs the initial synchronization of the directory. func (s Sync) initSync() error { - flog.Info("doing initial sync (%s -> %s)", s.LocalDir, s.RemoteDir) + clog.LogInfo(fmt.Sprintf("doing initial sync (%s -> %s)", s.LocalDir, s.RemoteDir)) start := time.Now() // Delete old files on initial sync (e.g git checkout). @@ -128,7 +127,9 @@ func (s Sync) initSync() error { if err := s.syncPaths(true, s.LocalDir+"/.", s.RemoteDir); err != nil { return err } - flog.Success("finished initial sync (%s)", time.Since(start).Truncate(time.Millisecond)) + clog.LogSuccess( + fmt.Sprintf("finished initial sync (%s)", time.Since(start).Truncate(time.Millisecond)), + ) return nil } @@ -193,16 +194,16 @@ func (s Sync) work(ev timedEvent) { case notify.Remove: err = s.handleDelete(localPath) default: - flog.Info("unhandled event %+v %s", ev.Event(), ev.Path()) + clog.LogInfo(fmt.Sprintf("unhandled event %+v %s", ev.Event(), ev.Path())) } log := fmt.Sprintf("%v %s (%s)", ev.Event(), filepath.Base(localPath), time.Since(ev.CreatedAt).Truncate(time.Millisecond*10), ) if err != nil { - flog.Error(log+": %s", err) + clog.Log(clog.Error(fmt.Sprintf("%s: %s", log, err))) } else { - flog.Success(log) + clog.LogSuccess(log) } } @@ -331,7 +332,7 @@ func (s Sync) Run() error { return nil } - flog.Info("watching %s for changes", s.LocalDir) + clog.LogInfo(fmt.Sprintf("watching %s for changes", s.LocalDir)) var droppedEvents uint64 // Timed events lets us track how long each individual file takes to @@ -347,7 +348,7 @@ func (s Sync) Run() error { }: default: if atomic.AddUint64(&droppedEvents, 1) == 1 { - flog.Info("dropped event, sync should restart soon") + clog.LogInfo("dropped event, sync should restart soon") } } }