diff --git a/README.md b/README.md index 8b35613c..e8138324 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,14 @@ There are four modes of operation (more on this in [User Guide][ug]): 1. Creating a Slack Export in Mattermost or Standard modes. 1. Emoji download mode. -Slackdump accepts two types of input (see `Dumping Conversations`_ section): +Slackdump accepts two types of input (see [Dumping +Conversations][usage-channels] section): 1. the URL/link of the channel or thread, OR 1. the ID of the channel. [ug]: doc/README.rst +[usage-channels]: doc/usage-channels.rst Quick Start =========== @@ -161,43 +163,16 @@ func main() { See [Package Documentation][godoc]. ### Using Custom Logger -Slackdump uses a simple [rusq/dlog][dlog] as a default logger (it is a wrapper around -the standard logger that adds `Debug*` functions). +Slackdump uses a "log/slog" package, it defaults to "slog.Default()". Set the +default slog logger to the one you want to use. -If you want to use the same default logger that Slackdump uses in your -application, it is available as `logger.Default`. +If you were using `logger.Silent` before, you would need to +[implement][slog-handler-guide] a discarding [Handler][godoc-slog-handler] for slog. -No doubts that everyone has their own favourite logger that is better than other -miserable loggers. Please read below for instructions on plugging your -favourite logger. - -[dlog]: https://github.com/rusq/dlog - -#### Logrus -Good news is [logrus] can be plugged in straight away, as it implements the -`logger.Interface` out of the box. +[slog-handler-guide]: https://github.com/golang/example/blob/master/slog-handler-guide/README.md +[godoc-slog-handler]: https://pkg.go.dev/log/slog#Handler ```go - lg := logrus.New() - sd, err := slackdump.New(context.Background(), provider, WithLogger(lg)) - if err != nil { - log.Print(err) - return - } -``` - -[logrus]: https://github.com/sirupsen/logrus - - -#### Glog and others -If you need to use some other logger, such as [glog], it is a matter of wrapping -the calls to satisfy the `logger.Interface` (defined in the [logger] -package), and then setting the `Logger` variable in `slackdump.Options` (see -[options.go]), or using `WithLogger` option. - -[glog]:https://github.com/golang/glog -[logger]: logger/logger.go -[options.go]: options.go ## FAQ @@ -205,7 +180,9 @@ package), and then setting the `Logger` variable in `slackdump.Options` (see No, you don't. Just run the application and EZ-Login 3000 will take care of the authentication or, alternatively, grab that token and -cookie from the browser Slack session. See `User Guide`_. +cookie from the browser Slack session. See [User's Guide][ug]. + + #### I'm getting "invalid_auth" error diff --git a/auth/browser/client.go b/auth/browser/client.go index 0a8a976f..99a88b3e 100644 --- a/auth/browser/client.go +++ b/auth/browser/client.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "os" "runtime" @@ -13,7 +14,6 @@ import ( "github.com/playwright-community/playwright-go" "github.com/rusq/slackdump/v3/auth/browser/pwcompat" - "github.com/rusq/slackdump/v3/logger" ) const ( @@ -30,7 +30,7 @@ type Client struct { verbose bool } -var Logger logger.Interface = logger.Default +var Logger = slog.Default() var ( installFn = playwright.Install @@ -51,7 +51,7 @@ func New(workspace string, opts ...Option) (*Client, error) { for _, opt := range opts { opt(cl) } - l().Debugf("browser=%s, timeout=%f", cl.br, cl.loginTimeout) + slog.Debug("New", "workspace", cl.workspace, "browser", cl.br, "timeout", cl.loginTimeout) runopts := &playwright.RunOptions{ Browsers: []string{cl.br.String()}, Verbose: cl.verbose, @@ -77,13 +77,14 @@ func (cl *Client) Authenticate(ctx context.Context) (string, []*http.Cookie, err _b = playwright.Bool ) + lg := slog.Default() pw, err := playwright.Run() if err != nil { return "", nil, err } defer func() { if err := pw.Stop(); err != nil { - l().Printf("failed to stop playwright: %v", err) + lg.ErrorContext(ctx, "failed to stop playwright", "error", err) } }() @@ -124,7 +125,7 @@ func (cl *Client) Authenticate(ctx context.Context) (string, []*http.Cookie, err page.On("close", func() { trace.Log(ctx, "user", "page closed"); close(cl.pageClosed) }) uri := fmt.Sprintf("https://%s"+slackDomain, cl.workspace) - l().Debugf("opening browser URL=%s", uri) + lg.Debug("opening browser", "URL", uri) if _, err := page.Goto(uri); err != nil { return "", nil, err @@ -219,13 +220,6 @@ func float2time(v float64) time.Time { return time.Unix(int64(v), 0) } -func l() logger.Interface { - if Logger == nil { - return logger.Default - } - return Logger -} - // pwRepair attempts to repair the playwright installation. func pwRepair(runopts *playwright.RunOptions) error { ad, err := pwcompat.NewAdapter(runopts) @@ -249,18 +243,21 @@ func Reinstall(browser Browser, verbose bool) error { } func reinstall(runopts *playwright.RunOptions) error { - l().Debugf("reinstalling browser: %s", runopts.Browsers[0]) + lg := slog.With("browser", runopts.Browsers[0]) + + lg.Debug("reinstalling browser") ad, err := pwcompat.NewAdapter(runopts) if err != nil { return err } - l().Debugf("removing %s", ad.DriverDirectory) + lg = lg.With("driver_directory", ad.DriverDirectory) + lg.Debug("removing driver directory") if err := os.RemoveAll(ad.DriverDirectory); err != nil { return err } // attempt to reinstall - l().Debugf("reinstalling %s", ad.DriverDirectory) + lg.Debug("reinstalling") if err := installFn(runopts); err != nil { // we did everything we could, but it still failed. return err diff --git a/auth/rod.go b/auth/rod.go index a49d240c..8c1137fd 100644 --- a/auth/rod.go +++ b/auth/rod.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "log/slog" "os" "time" @@ -12,7 +13,6 @@ import ( "github.com/rusq/slackdump/v3/auth/auth_ui" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) // RODHeadlessTimeout is the default timeout for the headless login flow. @@ -116,12 +116,12 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) { } defer cl.Close() - lg := logger.FromContext(ctx) + lg := slog.Default() t := time.Now() var sp simpleProvider switch resp.Type { case auth_ui.LInteractive, auth_ui.LUserBrowser: - lg.Printf("ℹ️ Initialising browser, once the browser appears, login as usual") + lg.InfoContext(ctx, "ℹ️ Initialising browser, once the browser appears, login as usual") var err error sp.Token, sp.Cookie, err = cl.Manual(ctx) if err != nil { @@ -135,7 +135,7 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) { case auth_ui.LCancel: return r, ErrCancelled } - lg.Printf("✅ authenticated (time taken: %s)", time.Since(t)) + lg.InfoContext(ctx, "✅ authenticated", "time_taken", time.Since(t).String()) return RodAuth{ simpleProvider: sp, diff --git a/channels.go b/channels.go index 8ba3ec6b..e7d01bcc 100644 --- a/channels.go +++ b/channels.go @@ -79,14 +79,13 @@ func (s *Session) getChannels(ctx context.Context, chanTypes []string, cb func(t } total += len(chans) - s.log.Printf("channels request #%5d, fetched: %4d, total: %8d (speed: %6.2f/sec, avg: %6.2f/sec)\n", - i, len(chans), total, - float64(len(chans))/float64(time.Since(reqStart).Seconds()), - float64(total)/float64(time.Since(fetchStart).Seconds()), + s.log.InfoContext(ctx, "channels", "request", i, "fetched", len(chans), "total", total, + "speed", float64(len(chans))/time.Since(reqStart).Seconds(), + "avg", float64(total)/time.Since(fetchStart).Seconds(), ) if nextcur == "" { - s.log.Printf("channels fetch complete, total: %d channels", total) + s.log.InfoContext(ctx, "channels fetch complete", "total", total) break } diff --git a/channels_test.go b/channels_test.go index 00311ba0..4e092845 100644 --- a/channels_test.go +++ b/channels_test.go @@ -3,6 +3,7 @@ package slackdump import ( "context" "errors" + "log/slog" "reflect" "testing" @@ -11,7 +12,6 @@ import ( "go.uber.org/mock/gomock" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -80,7 +80,7 @@ func TestSession_getChannels(t *testing.T) { sd := &Session{ client: mc, cfg: tt.fields.config, - log: logger.Silent, + log: slog.Default(), } if tt.expectFn != nil { diff --git a/cmd/slackdump/internal/archive/archive.go b/cmd/slackdump/internal/archive/archive.go index e65d572d..0dcdad91 100644 --- a/cmd/slackdump/internal/archive/archive.go +++ b/cmd/slackdump/internal/archive/archive.go @@ -4,6 +4,7 @@ import ( "context" _ "embed" "errors" + "log/slog" "time" "github.com/rusq/fsadapter" @@ -14,7 +15,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk/control" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/stream" ) @@ -63,7 +63,7 @@ func RunArchive(ctx context.Context, cmd *base.Command, args []string) error { base.SetExitStatus(base.SInitializationError) return err } - lg := logger.FromContext(ctx) + lg := cfg.Log stream := sess.Stream( stream.OptLatest(time.Time(cfg.Latest)), stream.OptOldest(time.Time(cfg.Oldest)), @@ -84,14 +84,14 @@ func RunArchive(ctx context.Context, cmd *base.Command, args []string) error { base.SetExitStatus(base.SApplicationError) return err } - lg.Printf("Recorded workspace data to %s", cd.Name()) + lg.Info("Recorded workspace data", "filename", cd.Name()) return nil } -func resultLogger(lg logger.Interface) func(sr stream.Result) error { +func resultLogger(lg *slog.Logger) func(sr stream.Result) error { return func(sr stream.Result) error { - lg.Printf("%s", sr) + lg.Info("stream", "result", sr.String()) return nil } } diff --git a/cmd/slackdump/internal/archive/search.go b/cmd/slackdump/internal/archive/search.go index da7247e7..9dd22068 100644 --- a/cmd/slackdump/internal/archive/search.go +++ b/cmd/slackdump/internal/archive/search.go @@ -3,6 +3,7 @@ package archive import ( "context" "errors" + "log/slog" "strings" "github.com/rusq/fsadapter" @@ -12,7 +13,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/control" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/stream" ) @@ -134,7 +134,7 @@ func initController(ctx context.Context, args []string) (*control.Controller, fu } defer cd.Close() - lg := logger.FromContext(ctx) + lg := slog.Default() dl, stop := fileproc.NewDownloader( ctx, cfg.DownloadFiles, diff --git a/cmd/slackdump/internal/cfg/cache.go b/cmd/slackdump/internal/cfg/cache.go index 3191a5b3..c7b964d7 100644 --- a/cmd/slackdump/internal/cfg/cache.go +++ b/cmd/slackdump/internal/cfg/cache.go @@ -1,10 +1,9 @@ package cfg import ( + "log/slog" "os" "path/filepath" - - "github.com/rusq/dlog" ) const ( @@ -15,7 +14,7 @@ const ( func ucd(ucdFn func() (string, error)) string { ucd, err := ucdFn() if err != nil { - dlog.Debug(err) + slog.Debug("ucd", "error", err) return "." } return filepath.Join(ucd, cacheDirName) diff --git a/cmd/slackdump/internal/cfg/cfg.go b/cmd/slackdump/internal/cfg/cfg.go index 8abb5913..d98f3073 100644 --- a/cmd/slackdump/internal/cfg/cfg.go +++ b/cmd/slackdump/internal/cfg/cfg.go @@ -4,6 +4,7 @@ package cfg import ( "flag" "fmt" + "log/slog" "os" "time" @@ -12,7 +13,6 @@ import ( "github.com/rusq/slackdump/v3/auth" "github.com/rusq/slackdump/v3/auth/browser" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" ) const ( @@ -22,6 +22,7 @@ const ( var ( TraceFile string LogFile string + JsonHandler bool Verbose bool AccessibleMode = (os.Getenv("ACCESSIBLE") != "" && os.Getenv("ACCESSIBLE") != "0") @@ -55,8 +56,7 @@ var ( UserCacheRetention time.Duration NoUserCache bool - Log logger.Interface - + Log *slog.Logger = slog.Default() // LoadSecrets is a flag that indicates whether to load secrets from the // environment variables. LoadSecrets bool @@ -104,6 +104,7 @@ const ( func SetBaseFlags(fs *flag.FlagSet, mask FlagMask) { fs.StringVar(&TraceFile, "trace", os.Getenv("TRACE_FILE"), "trace `filename`") fs.StringVar(&LogFile, "log", os.Getenv("LOG_FILE"), "log `file`, if not specified, messages are printed to STDERR") + fs.BoolVar(&JsonHandler, "log-json", osenv.Value("JSON_LOG", false), "log in JSON format") fs.BoolVar(&Verbose, "v", osenv.Value("DEBUG", false), "verbose messages") if mask&OmitAuthFlags == 0 { diff --git a/cmd/slackdump/internal/convertcmd/convert.go b/cmd/slackdump/internal/convertcmd/convert.go index 90874a19..12d782fd 100644 --- a/cmd/slackdump/internal/convertcmd/convert.go +++ b/cmd/slackdump/internal/convertcmd/convert.go @@ -11,7 +11,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" "github.com/rusq/slackdump/v3/internal/convert" - "github.com/rusq/slackdump/v3/logger" ) var CmdConvert = &base.Command{ @@ -60,8 +59,8 @@ func runConvert(ctx context.Context, cmd *base.Command, args []string) error { return errors.New("unsupported conversion type") } - lg := logger.FromContext(ctx) - lg.Printf("converting (%s) %q to (%s) %q", params.inputfmt, args[0], params.outputfmt, cfg.Output) + lg := cfg.Log + lg.InfoContext(ctx, "converting", "input_format", params.inputfmt, "source", args[0], "output_format", params.outputfmt, "output", cfg.Output) cflg := convertflags{ withFiles: cfg.DownloadFiles, @@ -73,7 +72,7 @@ func runConvert(ctx context.Context, cmd *base.Command, args []string) error { return err } - lg.Printf("completed in %s", time.Since(start)) + lg.InfoContext(ctx, "completed", "took", time.Since(start)) return nil } @@ -123,7 +122,7 @@ func chunk2export(ctx context.Context, src, trg string, cflg convertflags) error fsa, convert.WithIncludeFiles(cflg.withFiles), convert.WithTrgFileLoc(sttFn), - convert.WithLogger(logger.FromContext(ctx)), + convert.WithLogger(cfg.Log), ) if err := cvt.Convert(ctx); err != nil { return err diff --git a/cmd/slackdump/internal/diag/edge.go b/cmd/slackdump/internal/diag/edge.go index c538138a..3c4c13bd 100644 --- a/cmd/slackdump/internal/diag/edge.go +++ b/cmd/slackdump/internal/diag/edge.go @@ -6,9 +6,9 @@ import ( "os" "github.com/rusq/slackdump/v3/auth" + "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/edge" - "github.com/rusq/slackdump/v3/logger" ) var CmdEdge = &base.Command{ @@ -33,7 +33,7 @@ func init() { } func runEdge(ctx context.Context, cmd *base.Command, args []string) error { - lg := logger.FromContext(ctx) + lg := cfg.Log prov, err := auth.FromContext(ctx) if err != nil { @@ -47,9 +47,9 @@ func runEdge(ctx context.Context, cmd *base.Command, args []string) error { return err } defer cl.Close() - lg.Print("connected") + lg.Info("connected") - lg.Printf("*** Search for Channels test ***") + lg.Info("*** Search for Channels test ***") channels, err := cl.SearchChannels(ctx, "") if err != nil { return err @@ -115,7 +115,7 @@ func runEdge(ctx context.Context, cmd *base.Command, args []string) error { // } // } - lg.Print("OK") + lg.Info("OK") return nil } diff --git a/cmd/slackdump/internal/diag/eztest.go b/cmd/slackdump/internal/diag/eztest.go index 02d1567e..6ea68c83 100644 --- a/cmd/slackdump/internal/diag/eztest.go +++ b/cmd/slackdump/internal/diag/eztest.go @@ -11,8 +11,8 @@ import ( "github.com/playwright-community/playwright-go" "github.com/rusq/slackdump/v3/auth" + "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" - "github.com/rusq/slackdump/v3/logger" ) var CmdEzTest = &base.Command{ @@ -65,7 +65,7 @@ func init() { } func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error { - lg := logger.FromContext(ctx) + lg := cfg.Log if err := cmd.Flag.Parse(args); err != nil { base.SetExitStatus(base.SInvalidParameters) @@ -95,9 +95,9 @@ func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error return err } if res.Err == nil { - lg.Println("OK") + lg.Info("OK") } else { - lg.Println("ERROR") + lg.Error("ERROR", "error", *res.Err) base.SetExitStatus(base.SApplicationError) return errors.New(*res.Err) } diff --git a/cmd/slackdump/internal/diag/record.go b/cmd/slackdump/internal/diag/record.go index 58bcc49f..af5d05d1 100644 --- a/cmd/slackdump/internal/diag/record.go +++ b/cmd/slackdump/internal/diag/record.go @@ -78,7 +78,8 @@ func runRecord(ctx context.Context, _ *base.Command, args []string) error { rec := chunk.NewRecorder(w) for _, ch := range args { - cfg.Log.Printf("streaming channel %q", ch) + lg := cfg.Log.With("channel_id", ch) + lg.InfoContext(ctx, "streaming") if err := sess.Stream().SyncConversations(ctx, rec, ch); err != nil { if err2 := rec.Close(); err2 != nil { base.SetExitStatus(base.SApplicationError) diff --git a/cmd/slackdump/internal/diag/search.go b/cmd/slackdump/internal/diag/search.go index d5318fc8..d7db564b 100644 --- a/cmd/slackdump/internal/diag/search.go +++ b/cmd/slackdump/internal/diag/search.go @@ -16,7 +16,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/dirproc" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" "golang.org/x/time/rate" ) @@ -78,7 +77,7 @@ func runSearch(ctx context.Context, cmd *base.Command, args []string) error { query := args[0] lim := rate.NewLimiter(rate.Every(3*time.Second), 5) - lg := logger.FromContext(ctx) + lg := cfg.Log var p = slack.SearchParameters{ Sort: "timestamp", SortDirection: "desc", @@ -101,10 +100,10 @@ func runSearch(ctx context.Context, cmd *base.Command, args []string) error { enc.Encode(sm.Matches) if sm.NextCursor == "" { - lg.Print("no more messages") + lg.Info("no more messages") break } - lg.Printf("cursor %s", sm.NextCursor) + lg.Info("paginating", "cursor", sm.NextCursor) p.Cursor = sm.NextCursor if err := lim.Wait(ctx); err != nil { diff --git a/cmd/slackdump/internal/diag/uninstall.go b/cmd/slackdump/internal/diag/uninstall.go index daf28d11..a711c92e 100644 --- a/cmd/slackdump/internal/diag/uninstall.go +++ b/cmd/slackdump/internal/diag/uninstall.go @@ -15,7 +15,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/dumpui" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/updaters" - "github.com/rusq/slackdump/v3/logger" ) var CmdUninstall = &base.Command{ @@ -82,12 +81,12 @@ func removeFunc(dry bool) func(string) error { func uninstallPlaywright(ctx context.Context, si info.PwInfo, dry bool) error { removeFn := removeFunc(dry) - lg := logger.FromContext(ctx) - lg.Printf("Deleting %s", si.Path) + lg := cfg.Log + lg.InfoContext(ctx, "Deleting", "path", si.Path) if err := removeFn(si.Path); err != nil { return fmt.Errorf("failed to remove the playwright library: %w", err) } - lg.Printf("Deleting browsers in %s", si.BrowsersPath) + lg.InfoContext(ctx, "Deleting browsers", "browsers_path", si.BrowsersPath) if err := removeFn(si.BrowsersPath); err != nil { return fmt.Errorf("failed to remove the playwright browsers: %w", err) @@ -96,7 +95,7 @@ func uninstallPlaywright(ctx context.Context, si info.PwInfo, dry bool) error { if len(dir) == 0 { return errors.New("unable to reliably determine playwright path") } - lg.Printf("Deleting all playwright versions from: %s", dir) + lg.InfoContext(ctx, "Deleting all playwright versions", "dir", dir) if err := removeFn(dir); err != nil { return fmt.Errorf("failed to remove the playwright versions: %w", err) } @@ -104,20 +103,20 @@ func uninstallPlaywright(ctx context.Context, si info.PwInfo, dry bool) error { return nil } -func uninstallRod(_ context.Context, si info.RodInfo, dry bool) error { +func uninstallRod(ctx context.Context, si info.RodInfo, dry bool) error { removeFn := removeFunc(dry) if si.Path == "" { return errors.New("unable to determine rod browser path") } lg := cfg.Log - lg.Printf("Deleting incognito Browser...") + lg.InfoContext(ctx, "Deleting incognito Browser...") if !dry { _ = slackauth.RemoveBrowser() // just to make sure. } else { - lg.Printf("Would remove incognito browser") + lg.InfoContext(ctx, "Would remove incognito browser") } - lg.Printf("Deleting %s...", si.Path) + lg.InfoContext(ctx, "Deleting...", "path", si.Path) if err := removeFn(si.Path); err != nil { return fmt.Errorf("failed to remove the rod browser: %w", err) } diff --git a/cmd/slackdump/internal/dump/dump.go b/cmd/slackdump/internal/dump/dump.go index e030920f..3b0cc73c 100644 --- a/cmd/slackdump/internal/dump/dump.go +++ b/cmd/slackdump/internal/dump/dump.go @@ -25,7 +25,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" "github.com/rusq/slackdump/v3/internal/nametmpl" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/stream" "github.com/rusq/slackdump/v3/types" ) @@ -77,7 +76,7 @@ func RunDump(ctx context.Context, _ *base.Command, args []string) error { return ErrNothingToDo } - lg := logger.FromContext(ctx) + lg := cfg.Log // initialize the list of entities to dump. list, err := structures.NewEntityList(args) @@ -106,7 +105,7 @@ func RunDump(ctx context.Context, _ *base.Command, args []string) error { } defer func() { if err := fsa.Close(); err != nil { - lg.Printf("warning: failed to close the filesystem: %v", err) + lg.WarnContext(ctx, "warning: failed to close the filesystem", "error", err) } }() @@ -136,7 +135,7 @@ func RunDump(ctx context.Context, _ *base.Command, args []string) error { base.SetExitStatus(base.SApplicationError) return err } - lg.Printf("dumped %d conversations in %s", len(p.list.Include), time.Since(start)) + lg.InfoContext(ctx, "conversation dump finished", "count", len(p.list.Include), "took", time.Since(start)) return nil } @@ -181,8 +180,8 @@ func dump(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, p dump return fmt.Errorf("failed to create temp directory: %w", err) } - lg := logger.FromContext(ctx) - lg.Debugf("using directory: %s", dir) + lg := cfg.Log + lg.Debug("using directory", "dir", dir) // files subprocessor var sdl fileproc.Downloader @@ -226,7 +225,7 @@ func dump(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, p dump } defer func() { if err := proc.Close(); err != nil { - lg.Printf("failed to close conversation processor: %v", err) + lg.WarnContext(ctx, "failed to close conversation processor", "error", err) } }() @@ -239,7 +238,7 @@ func dump(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, p dump } if sr.IsLast { //TODO: this is unbeautiful. - lg.Printf("%s dumped", sr) + lg.InfoContext(ctx, "dumped", "sr", sr.String()) } return nil }), @@ -247,7 +246,7 @@ func dump(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, p dump return fmt.Errorf("failed to dump conversations: %w", err) } - lg.Debugln("stream complete, waiting for all goroutines to finish") + lg.DebugContext(ctx, "stream complete, waiting for all goroutines to finish") if err := coord.Wait(); err != nil { return err } diff --git a/cmd/slackdump/internal/emoji/emojidl/emoji.go b/cmd/slackdump/internal/emoji/emojidl/emoji.go index e025fdbe..7b6bd55b 100644 --- a/cmd/slackdump/internal/emoji/emojidl/emoji.go +++ b/cmd/slackdump/internal/emoji/emojidl/emoji.go @@ -27,7 +27,7 @@ import ( "sync" "github.com/rusq/fsadapter" - "github.com/rusq/slackdump/v3/logger" + "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" ) const ( @@ -63,7 +63,7 @@ func DlFS(ctx context.Context, sess emojidumper, fsa fsadapter.FS, failFast bool // fetch downloads the emojis and saves them to the fsa. It spawns numWorker // goroutines for getting the files. It will call fetchFn for each emoji. func fetch(ctx context.Context, fsa fsadapter.FS, emojis map[string]string, failFast bool) error { - lg := logger.FromContext(ctx) + lg := cfg.Log.With("in", "fetch", "dir", emojiDir, "numWorkers", numWorkers, "failFast", failFast) var ( emojiC = make(chan emoji) @@ -105,6 +105,7 @@ func fetch(ctx context.Context, fsa fsadapter.FS, emojis map[string]string, fail total = len(emojis) count = 0 ) + lg = lg.With("total", total) for res := range resultC { if res.err != nil { if errors.Is(res.err, context.Canceled) { @@ -113,10 +114,10 @@ func fetch(ctx context.Context, fsa fsadapter.FS, emojis map[string]string, fail if failFast { return fmt.Errorf("failed: %q: %w", res.name, res.err) } - lg.Printf("failed: %q: %s", res.name, res.err) + lg.WarnContext(ctx, "failed", "name", res.name, "error", res.err) } count++ - lg.Printf("downloaded % 5d/%d %q", count, total, res.name) + lg.InfoContext(ctx, "downloaded", "count", count, "name", res.name) } return nil diff --git a/cmd/slackdump/internal/export/export.go b/cmd/slackdump/internal/export/export.go index 250dd609..fd264bc3 100644 --- a/cmd/slackdump/internal/export/export.go +++ b/cmd/slackdump/internal/export/export.go @@ -7,7 +7,6 @@ import ( "strings" "time" - "github.com/rusq/dlog" "github.com/rusq/fsadapter" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap" @@ -15,7 +14,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) var CmdExport = &base.Command{ @@ -84,8 +82,9 @@ func runExport(ctx context.Context, cmd *base.Command, args []string) error { base.SetExitStatus(base.SUserError) return err } + lg := cfg.Log defer func() { - dlog.Debugln("closing the fsadapter") + lg.DebugContext(ctx, "closing the fsadapter") fsa.Close() }() @@ -99,7 +98,6 @@ func runExport(ctx context.Context, cmd *base.Command, args []string) error { return fmt.Errorf("export failed: %w", err) } - lg := logger.FromContext(ctx) - lg.Printf("export completed in %s", time.Since(start).Truncate(time.Second).String()) + lg.InfoContext(ctx, "export completed", "took", time.Since(start).String()) return nil } diff --git a/cmd/slackdump/internal/export/v2.go b/cmd/slackdump/internal/export/v2.go index a427c764..9fc2a9d9 100644 --- a/cmd/slackdump/internal/export/v2.go +++ b/cmd/slackdump/internal/export/v2.go @@ -6,16 +6,16 @@ import ( "github.com/rusq/fsadapter" "github.com/rusq/slackdump/v3" + "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" "github.com/rusq/slackdump/v3/export" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) func exportV2(ctx context.Context, sess *slackdump.Session, fs fsadapter.FS, list *structures.EntityList, flags exportFlags) error { config := export.Config{ Oldest: time.Time(flags.Oldest), Latest: time.Time(flags.Latest), - Logger: logger.FromContext(ctx), + Logger: cfg.Log, List: list, Type: export.ExportType(flags.ExportStorageType), MemberOnly: flags.MemberOnly, diff --git a/cmd/slackdump/internal/export/v3.go b/cmd/slackdump/internal/export/v3.go index 3078980c..5ec00af4 100644 --- a/cmd/slackdump/internal/export/v3.go +++ b/cmd/slackdump/internal/export/v3.go @@ -2,6 +2,7 @@ package export import ( "context" + "log/slog" "os" "github.com/rusq/fsadapter" @@ -16,7 +17,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk/transform" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/stream" ) @@ -24,20 +24,20 @@ import ( // exportV3 runs the export v3. func exportV3(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, list *structures.EntityList, params exportFlags) error { - lg := logger.FromContext(ctx) + lg := cfg.Log tmpdir, err := os.MkdirTemp("", "slackdump-*") if err != nil { return err } - lg.Printf("using %s as the temporary directory", tmpdir) + lg.InfoContext(ctx, "temporary directory in use", "tmpdir", tmpdir) chunkdir, err := chunk.OpenDir(tmpdir) if err != nil { return err } defer chunkdir.Close() - if !lg.IsDebug() { + if !lg.Enabled(ctx, slog.LevelDebug) { defer func() { _ = chunkdir.RemoveAll() }() } updFn := func() func(_ *slack.Channel, m *slack.Message) error { @@ -61,7 +61,7 @@ func exportV3(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, li -1, progressbar.OptionClearOnFinish(), progressbar.OptionSpinnerType(8)), - lg.IsDebug(), + lg.Enabled(ctx, slog.LevelDebug), ) _ = pb.RenderBlank() @@ -69,7 +69,7 @@ func exportV3(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, li stream.OptOldest(params.Oldest), stream.OptLatest(params.Latest), stream.OptResultFn(func(sr stream.Result) error { - lg.Debugf("conversations: %s", sr.String()) + lg.DebugContext(ctx, "conversations", "sr", sr.String()) pb.Describe(sr.String()) _ = pb.Add(1) return nil @@ -88,7 +88,7 @@ func exportV3(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, li control.WithTransformer(tf), ) - lg.Print("running export...") + lg.InfoContext(ctx, "running export...") if err := ctr.Run(ctx, list); err != nil { _ = pb.Finish() return err @@ -104,8 +104,8 @@ func exportV3(ctx context.Context, sess *slackdump.Session, fsa fsadapter.FS, li } pb.Describe("OK") lg.Debug("index written") - lg.Println("conversations export finished") - lg.Debugf("chunk files in: %s", tmpdir) + lg.InfoContext(ctx, "conversations export finished") + lg.DebugContext(ctx, "chunk files retained", "dir", tmpdir) return nil } diff --git a/cmd/slackdump/internal/export/v3_test.go b/cmd/slackdump/internal/export/v3_test.go index c64adc17..9dfdbe88 100644 --- a/cmd/slackdump/internal/export/v3_test.go +++ b/cmd/slackdump/internal/export/v3_test.go @@ -2,13 +2,10 @@ package export import ( "context" - "log" "net/http" - "os" "path/filepath" "testing" - "github.com/rusq/dlog" "github.com/rusq/fsadapter" "github.com/rusq/slack" "github.com/rusq/slackdump/v3" @@ -17,7 +14,6 @@ import ( "github.com/rusq/slackdump/v3/internal/fixtures" "github.com/rusq/slackdump/v3/internal/network" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) const ( @@ -35,7 +31,6 @@ func Test_exportV3(t *testing.T) { // defer srv.Close() // cl := slack.New("", slack.OptionAPIURL(srv.URL())) - // lg := dlog.New(os.Stderr, "test ", log.LstdFlags, true) // ctx := logger.NewContext(context.Background(), lg) // prov := &chunktest.TestAuth{ // FakeToken: "xoxp-1234567890-1234567890-1234567890-1234567890", @@ -67,12 +62,11 @@ func Test_exportV3(t *testing.T) { defer srv.Close() cl := slack.New("", slack.OptionAPIURL(srv.URL())) - lg := dlog.New(os.Stderr, "test ", log.LstdFlags, true) - ctx := logger.NewContext(context.Background(), lg) prov := &chunktest.TestAuth{ FakeToken: "xoxp-1234567890-1234567890-1234567890-1234567890", WantHTTPClient: http.DefaultClient, } + ctx := context.Background() sess, err := slackdump.New(ctx, prov, slackdump.WithSlackClient(cl), slackdump.WithLimits(network.NoLimits)) if err != nil { t.Fatal(err) diff --git a/cmd/slackdump/internal/format/format.go b/cmd/slackdump/internal/format/format.go index 025bceee..0c3a82c8 100644 --- a/cmd/slackdump/internal/format/format.go +++ b/cmd/slackdump/internal/format/format.go @@ -8,11 +8,11 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "runtime/trace" - "github.com/rusq/dlog" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap" @@ -20,7 +20,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/cache" "github.com/rusq/slackdump/v3/internal/format" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -111,13 +110,13 @@ type idextractor interface { func convert(ctx context.Context, w io.Writer, cvt format.Formatter, rs io.ReadSeeker) error { ctx, task := trace.NewTask(ctx, "convert") defer task.End() - lg := logger.FromContext(ctx) + lg := cfg.Log dump, err := detectAndRead(rs) if err != nil { return err } - lg.Printf("Detected dump type: %q", dump.filetype) + lg.InfoContext(ctx, "Successfully detected file type", "type", dump.filetype) if dump.filetype == dtUsers { // special case. Users do not need to have users fetched from slack etc, @@ -267,7 +266,7 @@ func searchCache(ctx context.Context, cacheDir string, ids []string) ([]slack.Us } return err1 } - dlog.Printf("matching file: %s", path) + slog.InfoContext(ctx, "matching file", "path", path) return filepath.SkipDir }) if err != nil { diff --git a/cmd/slackdump/internal/list/channels.go b/cmd/slackdump/internal/list/channels.go index 7bebcc0e..d10257c1 100644 --- a/cmd/slackdump/internal/list/channels.go +++ b/cmd/slackdump/internal/list/channels.go @@ -3,7 +3,6 @@ package list import ( "context" "fmt" - "log" "runtime/trace" "time" @@ -13,7 +12,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/cache" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -116,11 +114,10 @@ func (l *channels) Retrieve(ctx context.Context, sess *slackdump.Session, m *cac go func() { defer close(usersc) if l.opts.resolveUsers { - - lg.Println("getting users to resolve DM names") + lg.InfoContext(ctx, "getting users to resolve DM names") u, err := fetchUsers(ctx, sess, m, cfg.NoUserCache, teamID) if err != nil { - log.Printf("error getting users to resolve DM names (ignored): %s", err) + lg.ErrorContext(ctx, "error getting users to resolve DM names", "error", err) return } usersc <- u @@ -142,7 +139,7 @@ func (l *channels) Retrieve(ctx context.Context, sess *slackdump.Session, m *cac l.channels = cc l.users = <-usersc if err := m.CacheChannels(teamID, cc); err != nil { - logger.FromContext(ctx).Printf("warning: failed to cache channels (ignored): %s", err) + lg.WarnContext(ctx, "failed to cache channels (ignored)", "error", err) } return nil } diff --git a/cmd/slackdump/internal/list/common.go b/cmd/slackdump/internal/list/common.go index f9e259ca..eafa7a44 100644 --- a/cmd/slackdump/internal/list/common.go +++ b/cmd/slackdump/internal/list/common.go @@ -13,7 +13,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/cache" "github.com/rusq/slackdump/v3/internal/format" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -134,7 +133,7 @@ func saveData(ctx context.Context, data any, filename string, typ format.Type, u if err := fmtPrint(ctx, f, data, typ, users); err != nil { return err } - logger.FromContext(ctx).Printf("Data saved to: %q\n", filename) + cfg.Log.InfoContext(ctx, "Data saved", "filename", filename) return nil } diff --git a/cmd/slackdump/internal/list/users.go b/cmd/slackdump/internal/list/users.go index 6e3ab60e..d72b1082 100644 --- a/cmd/slackdump/internal/list/users.go +++ b/cmd/slackdump/internal/list/users.go @@ -14,7 +14,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/internal/cache" "github.com/rusq/slackdump/v3/internal/osext" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -93,7 +92,7 @@ type userCacher interface { } func fetchUsers(ctx context.Context, ug userGetter, m userCacher, skipCache bool, teamID string) ([]slack.User, error) { - lg := logger.FromContext(ctx) + lg := cfg.Log.With("team_id", teamID, "cache", !skipCache) if !skipCache { // attempt to load from cache @@ -107,7 +106,7 @@ func fetchUsers(ctx context.Context, ug userGetter, m userCacher, skipCache bool // some funky error return nil, err } - lg.Println("user cache expired or empty, caching users") + lg.InfoContext(ctx, "user cache expired or empty, caching users") } // getting users from API users, err := ug.GetUsers(ctx) @@ -117,7 +116,7 @@ func fetchUsers(ctx context.Context, ug userGetter, m userCacher, skipCache bool // saving users to cache, will ignore any errors, but notify the user. if err := m.CacheUsers(teamID, users); err != nil { - lg.Printf("warning: failed saving user cache (ignored): %s", err) + lg.WarnContext(ctx, "failed saving user cache (ignored)", "error", err) } return users, nil diff --git a/cmd/slackdump/internal/view/view.go b/cmd/slackdump/internal/view/view.go index 616144a9..1b27a9a7 100644 --- a/cmd/slackdump/internal/view/view.go +++ b/cmd/slackdump/internal/view/view.go @@ -18,7 +18,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/viewer" "github.com/rusq/slackdump/v3/internal/viewer/source" - "github.com/rusq/slackdump/v3/logger" ) var CmdView = &base.Command{ @@ -63,17 +62,17 @@ func RunView(ctx context.Context, cmd *base.Command, args []string) error { v.Close() }() - lg := logger.FromContext(ctx) + lg := cfg.Log - lg.Printf("listening on %s", listenAddr) + lg.InfoContext(ctx, "listening on", "addr", listenAddr) go func() { if err := br.OpenURL(fmt.Sprintf("http://%s", listenAddr)); err != nil { - lg.Printf("unable to open browser: %s", err) + lg.WarnContext(ctx, "unable to open browser", "error", err) } }() if err := v.ListenAndServe(); err != nil { if errors.Is(err, http.ErrServerClosed) { - lg.Print("bye") + lg.InfoContext(ctx, "bye") return nil } base.SetExitStatus(base.SApplicationError) @@ -95,38 +94,38 @@ const ( ) func loadSource(ctx context.Context, src string) (viewer.Sourcer, error) { - lg := logger.FromContext(ctx) + lg := cfg.Log.With("source", src) fi, err := os.Stat(src) if err != nil { return nil, err } switch srcType(src, fi) { case sfChunk | sfDirectory: - lg.Debugf("loading chunk directory: %s", src) + lg.DebugContext(ctx, "loading chunk directory") dir, err := chunk.OpenDir(src) if err != nil { return nil, err } return source.NewChunkDir(dir), nil case sfExport | sfZIP: - lg.Debugf("loading export zip: %s", src) + lg.DebugContext(ctx, "loading export zip") f, err := zip.OpenReader(src) if err != nil { return nil, err } return source.NewExport(f, src) case sfExport | sfDirectory: - lg.Debugf("loading export directory: %s", src) + lg.DebugContext(ctx, "loading export directory") return source.NewExport(os.DirFS(src), src) case sfDump | sfZIP: - lg.Debugf("loading dump zip: %s", src) + lg.DebugContext(ctx, "loading dump zip") f, err := zip.OpenReader(src) if err != nil { return nil, err } return source.NewDump(f, src) case sfDump | sfDirectory: - lg.Debugf("loading dump directory: %s", src) + lg.DebugContext(ctx, "loading dump directory") return source.NewDump(os.DirFS(src), src) default: return nil, fmt.Errorf("unsupported source type: %s", src) diff --git a/cmd/slackdump/internal/workspace/import.go b/cmd/slackdump/internal/workspace/import.go index dcb26ef7..0d431437 100644 --- a/cmd/slackdump/internal/workspace/import.go +++ b/cmd/slackdump/internal/workspace/import.go @@ -59,7 +59,8 @@ func importFile(ctx context.Context, filename string) error { base.SetExitStatus(base.SCacheError) return err } - cfg.Log.Printf("Workspace %q added and selected. It is advised that you delete the file %q", wsp, filename) + cfg.Log.InfoContext(ctx, "Workspace added and selected", "workspace", wsp) + cfg.Log.InfoContext(ctx, "It is advised that you delete the file", "filename", filename) return nil } diff --git a/cmd/slackdump/internal/workspace/new.go b/cmd/slackdump/internal/workspace/new.go index 13521d91..a40b5ef0 100644 --- a/cmd/slackdump/internal/workspace/new.go +++ b/cmd/slackdump/internal/workspace/new.go @@ -79,7 +79,7 @@ func createWsp(ctx context.Context, m manager, wsp string, confirm bool) error { } } - lg.Debugln("requesting authentication...") + lg.DebugContext(ctx, "requesting authentication...") ad := cache.AuthData{ Token: cfg.SlackToken, Cookie: cfg.SlackCookie, @@ -89,21 +89,21 @@ func createWsp(ctx context.Context, m manager, wsp string, confirm bool) error { if err != nil { if errors.Is(err, auth.ErrCancelled) { base.SetExitStatus(base.SCancelled) - lg.Println(auth.ErrCancelled) + lg.WarnContext(ctx, auth.ErrCancelled.Error()) return ErrOpCancelled } base.SetExitStatus(base.SAuthError) return err } - lg.Debugf("selecting %q as current...", realname(wsp)) + lg.Debug("selecting as current...", "workspace", realname(wsp)) // select it if err := m.Select(realname(wsp)); err != nil { base.SetExitStatus(base.SApplicationError) return fmt.Errorf("failed to select the default workpace: %s", err) } fmt.Fprintf(os.Stdout, "Success: added workspace %q\n", realname(wsp)) - lg.Debugf("workspace %q, type %T", realname(wsp), prov) + lg.DebugContext(ctx, "workspace type", "workspace", realname(wsp), "type", fmt.Sprintf("%T", prov)) return nil } diff --git a/cmd/slackdump/internal/workspace/new_test.go b/cmd/slackdump/internal/workspace/new_test.go index 15667351..4638abe3 100644 --- a/cmd/slackdump/internal/workspace/new_test.go +++ b/cmd/slackdump/internal/workspace/new_test.go @@ -3,16 +3,16 @@ package workspace import ( "context" "errors" + "log/slog" "testing" "github.com/rusq/slackdump/v3/auth" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg" - "github.com/rusq/slackdump/v3/logger" "go.uber.org/mock/gomock" ) func init() { - cfg.Log = logger.Silent + cfg.Log = slog.Default() } func Test_createWsp(t *testing.T) { diff --git a/cmd/slackdump/internal/workspace/wiz_select.go b/cmd/slackdump/internal/workspace/wiz_select.go index c4dcb874..b9175f73 100644 --- a/cmd/slackdump/internal/workspace/wiz_select.go +++ b/cmd/slackdump/internal/workspace/wiz_select.go @@ -11,7 +11,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/workspace/workspaceui" "github.com/rusq/slackdump/v3/internal/cache" - "github.com/rusq/slackdump/v3/logger" ) // TODO: organise as a self-sufficient model with proper error handling. @@ -54,7 +53,7 @@ func wizSelect(ctx context.Context, cmd *base.Command, args []string) error { base.SetExitStatus(base.SWorkspaceError) return fmt.Errorf("error setting the current workspace: %s", err) } - logger.FromContext(ctx).Debugf("selected workspace: %s", newWsp) + cfg.Log.Debug("selected workspace", "workspace", newWsp) } return nil diff --git a/cmd/slackdump/main.go b/cmd/slackdump/main.go index 1c9d3650..bba4f57c 100644 --- a/cmd/slackdump/main.go +++ b/cmd/slackdump/main.go @@ -13,7 +13,6 @@ import ( "github.com/charmbracelet/huh" "github.com/joho/godotenv" - "github.com/rusq/dlog" "github.com/rusq/tracer" "golang.org/x/term" @@ -35,7 +34,6 @@ import ( "github.com/rusq/slackdump/v3/cmd/slackdump/internal/view" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/wizard" "github.com/rusq/slackdump/v3/cmd/slackdump/internal/workspace" - "github.com/rusq/slackdump/v3/logger" ) func init() { @@ -64,7 +62,7 @@ func init() { func main() { if isRoot() { - dlog.Fatal("slackdump: cowardly refusing to run as root") + log.Fatal("slackdump: cowardly refusing to run as root") } flag.Usage = base.Usage @@ -124,7 +122,7 @@ BigCmdLoop: } if err := invoke(cmd, args); err != nil { msg := fmt.Sprintf("Error %03[1]d (%[1]s): %[2]s", base.ExitStatus(), err) - logger.Default.Print(msg) + slog.Error(msg, "command", base.CmdName, "error", err, "code", base.ExitStatus(), "status", base.ExitStatus().String()) } base.Exit() return @@ -176,11 +174,10 @@ func invoke(cmd *base.Command, args []string) error { defer task.End() // initialise default logging. - if lg, err := initLog(cfg.LogFile, cfg.Verbose); err != nil { + if lg, err := initLog(cfg.LogFile, cfg.JsonHandler, cfg.Verbose); err != nil { return err } else { - lg.SetPrefix(cmd.Name() + ": ") - ctx = logger.NewContext(ctx, lg) + lg.With("command", cmd.Name()) cfg.Log = lg } @@ -227,7 +224,7 @@ func initTrace(filename string) error { return nil } - dlog.Printf("trace will be written to %q", filename) + slog.Info("trace will be written to", "filename", filename) trc := tracer.New(filename) if err := trc.Start(); err != nil { @@ -236,7 +233,7 @@ func initTrace(filename string) error { stop := func() { if err := trc.End(); err != nil { - dlog.Printf("failed to write the trace file: %s", err) + slog.Warn("failed to write the trace file", "filename", filename, "error", err) } } base.AtExit(stop) @@ -249,36 +246,39 @@ func initTrace(filename string) error { // an error, if any. The stop function must be called in the deferred call, it // will close the log file, if it is open. If the error is returned the stop // function is nil. -func initLog(filename string, verbose bool) (*dlog.Logger, error) { - lg := logger.Default +func initLog(filename string, jsonHandler bool, verbose bool) (*slog.Logger, error) { if verbose { slog.SetLogLoggerLevel(slog.LevelDebug) - lg.SetDebug(verbose) - lg.SetFlags(lg.Flags() | log.Lmicroseconds) } - - if filename == "" { - return lg, nil + var opts = &slog.HandlerOptions{ + Level: iftrue(verbose, slog.LevelDebug, slog.LevelInfo), } - - lg.Debugf("log messages will be written to: %q", filename) - lf, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) - if err != nil { - return lg, fmt.Errorf("failed to create the log file: %w", err) + if jsonHandler { + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, opts))) } - lg.SetOutput(lf) - sl := slog.New(slog.NewTextHandler(lf, &slog.HandlerOptions{ - Level: iftrue(verbose, slog.LevelDebug, slog.LevelInfo), - })) - slog.SetDefault(sl) + if filename != "" { + slog.Debug("log messages will be written to file", "filename", filename) + lf, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return slog.Default(), fmt.Errorf("failed to create the log file: %w", err) + } + log.SetOutput(lf) // redirect the standard log to the file just in case, panics will be logged there. - base.AtExit(func() { - if err := lf.Close(); err != nil { - dlog.Printf("failed to close the log file: %s", err) + var h slog.Handler = slog.NewTextHandler(lf, opts) + if jsonHandler { + h = slog.NewJSONHandler(lf, opts) } - }) - return lg, nil + sl := slog.New(h) + slog.SetDefault(sl) + base.AtExit(func() { + if err := lf.Close(); err != nil { + slog.Error("failed to close the log file", "error", err) + } + }) + } + + return slog.Default(), nil } func iftrue[T any](cond bool, t T, f T) T { diff --git a/downloader/deprecated.go b/downloader/deprecated.go index 9fd5e6fa..d00dcea1 100644 --- a/downloader/deprecated.go +++ b/downloader/deprecated.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "log/slog" "path" "path/filepath" @@ -14,7 +15,6 @@ import ( "golang.org/x/time/rate" "github.com/rusq/fsadapter" - "github.com/rusq/slackdump/v3/logger" ) const ( @@ -72,7 +72,7 @@ func WorkersV1(n int) OptionV1 { // LoggerV1 allows to use an external log library, that satisfies the // logger.Interface. -func LoggerV1(l logger.Interface) OptionV1 { +func LoggerV1(l *slog.Logger) OptionV1 { return func(c *ClientV1) { WithLogger(l)(c.v2) } diff --git a/downloader/deprecated_test.go b/downloader/deprecated_test.go index a61ec32b..9f41d6d2 100644 --- a/downloader/deprecated_test.go +++ b/downloader/deprecated_test.go @@ -2,7 +2,7 @@ package downloader import ( "context" - "log" + "log/slog" "os" "path" "path/filepath" @@ -17,11 +17,9 @@ import ( gomock "go.uber.org/mock/gomock" "golang.org/x/time/rate" - "github.com/rusq/dlog" "github.com/rusq/fsadapter" "github.com/rusq/slackdump/v3/internal/fixtures" "github.com/rusq/slackdump/v3/internal/mocks/mock_downloader" - "github.com/rusq/slackdump/v3/logger" ) var ( @@ -264,7 +262,7 @@ func TestSession_newFileDownloader(t *testing.T) { limiter: tl, retries: 3, workers: 4, - lg: dlog.New(os.Stderr, "unit ", log.LstdFlags+log.Lshortfile, true), + lg: slog.Default(), }, nameFn: Filename, } @@ -301,7 +299,7 @@ func TestSession_worker(t *testing.T) { limiter: tl, retries: defRetries, workers: defNumWorkers, - lg: dlog.New(os.Stderr, "unit ", log.LstdFlags+log.Lshortfile, true), + lg: slog.Default(), }, nameFn: Filename, } @@ -374,7 +372,7 @@ func TestClient_startWorkers(t *testing.T) { fsa: fsadapter.NewDirectory(t.TempDir()), limiter: rate.NewLimiter(5000, 1), workers: defNumWorkers, - lg: logger.Default, + lg: slog.Default(), }, nameFn: Filename, } @@ -445,7 +443,7 @@ func clientWithMock(t *testing.T, dir string) *ClientV1 { fsa: fsadapter.NewDirectory(dir), limiter: rate.NewLimiter(5000, 1), workers: defNumWorkers, - lg: logger.Default, + lg: slog.Default(), }, nameFn: Filename, } diff --git a/downloader/downloader.go b/downloader/downloader.go index 1d13af50..f0c144b6 100644 --- a/downloader/downloader.go +++ b/downloader/downloader.go @@ -5,6 +5,7 @@ import ( "fmt" "hash/crc64" "io" + "log/slog" "os" "path" "runtime/trace" @@ -12,7 +13,6 @@ import ( "github.com/rusq/fsadapter" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "golang.org/x/time/rate" ) @@ -21,7 +21,7 @@ type Client struct { sc Downloader limiter *rate.Limiter fsa fsadapter.FS - lg logger.Interface + lg *slog.Logger retries int workers int @@ -66,11 +66,11 @@ func Workers(n int) Option { } // Logger allows to use an external log library, that satisfies the -// logger.Interface. -func WithLogger(l logger.Interface) Option { +// *slog.Logger. +func WithLogger(l *slog.Logger) Option { return func(c *Client) { if l == nil { - l = logger.Default + l = slog.Default() } c.lg = l } @@ -86,7 +86,7 @@ func New(sc Downloader, fs fsadapter.FS, opts ...Option) *Client { sc: sc, fsa: fs, limiter: rate.NewLimiter(defLimit, 1), - lg: logger.Default, + lg: slog.Default(), chanBufSz: defFileBufSz, retries: defRetries, workers: defNumWorkers, @@ -133,7 +133,7 @@ func (c *Client) startWorkers(ctx context.Context, req <-chan Request) *sync.Wai go func(workerNum int) { c.worker(ctx, seen) wg.Done() - c.lg.Debugf("download worker %d terminated", workerNum) + c.lg.DebugContext(ctx, "download worker terminated", "worker", workerNum) }(i) } return &wg @@ -183,13 +183,14 @@ func (c *Client) worker(ctx context.Context, reqC <-chan Request) { if !moar { return } - c.lg.Debugf("saving %q to %s", path.Base(req.URL), req.Fullpath) + lg := c.lg.With("filename", path.Base(req.URL), "destination", req.Fullpath) + lg.DebugContext(ctx, "saving file") n, err := c.download(ctx, req.Fullpath, req.URL) if err != nil { - c.lg.Printf("error saving %q to %q: %s", path.Base(req.URL), req.Fullpath, err) + lg.ErrorContext(ctx, "error saving file", "error", err) break } - c.lg.Debugf("file %q saved to %s: %d bytes written", path.Base(req.URL), req.Fullpath, n) + lg.DebugContext(ctx, "file saved", "bytes_written", n) } } } @@ -215,7 +216,7 @@ func (c *Client) download(ctx context.Context, fullpath string, url string) (int if err := c.sc.GetFile(url, tf); err != nil { if _, err := tf.Seek(0, io.SeekStart); err != nil { - c.lg.Debugf("seek error: %s", err) + c.lg.ErrorContext(ctx, "seek", "error", err) } return fmt.Errorf("download to %q failed, [src=%s]: %w", fullpath, url, err) } @@ -254,9 +255,9 @@ func (c *Client) Stop() { } close(c.requests) - c.lg.Debugf("requests channel closed, waiting for all downloads to complete") + c.lg.Debug("requests channel closed, waiting for all downloads to complete") c.wg.Wait() - c.lg.Debugf("wait complete: no more files to download") + c.lg.Debug("wait complete: no more files to download") c.requests = nil c.wg = nil @@ -283,7 +284,7 @@ func (c *Client) AsyncDownloader(ctx context.Context, queueC <-chan Request) (<- defer close(done) for r := range queueC { if err := c.Download(r.Fullpath, r.URL); err != nil { - c.lg.Printf("error downloading %q: %s", r.URL, err) + c.lg.Error("download error", "url", r.URL, "error", err) } } c.Stop() diff --git a/downloader/downloader_test.go b/downloader/downloader_test.go index 636d492e..6aa922d0 100644 --- a/downloader/downloader_test.go +++ b/downloader/downloader_test.go @@ -1,12 +1,17 @@ package downloader import ( + "log/slog" "testing" "github.com/rusq/slackdump/v3/internal/fixtures" "github.com/stretchr/testify/assert" ) +func init() { + slog.SetLogLoggerLevel(slog.LevelDebug) +} + func Test_fltSeen(t *testing.T) { t.Run("ensure that we don't get dup files", func(t *testing.T) { source := []Request{ diff --git a/export/expfmt.go b/export/expfmt.go index d93f7a94..0346ed8f 100644 --- a/export/expfmt.go +++ b/export/expfmt.go @@ -1,18 +1,19 @@ package export import ( + "log/slog" + "github.com/rusq/slack" "github.com/rusq/fsadapter" "github.com/rusq/slackdump/v3/internal/structures/files/dl" - "github.com/rusq/slackdump/v3/logger" ) // newV2FileExporter returns the appropriate exporter for the ExportType. -func newV2FileExporter(t ExportType, fs fsadapter.FS, cl *slack.Client, l logger.Interface, token string) dl.Exporter { +func newV2FileExporter(t ExportType, fs fsadapter.FS, cl *slack.Client, l *slog.Logger, token string) dl.Exporter { switch t { default: - l.Printf("unknown export type %s, not downloading any files", t) + l.Warn("unknown export type, files won't be downloaded", "type", t) fallthrough case TNoDownload: return dl.NewFileUpdater(token) diff --git a/export/expfmt_test.go b/export/expfmt_test.go index 8d015d08..e2564a7d 100644 --- a/export/expfmt_test.go +++ b/export/expfmt_test.go @@ -2,11 +2,11 @@ package export import ( "fmt" + "log/slog" "testing" "github.com/rusq/fsadapter" "github.com/rusq/slack" - "github.com/rusq/slackdump/v3/logger" ) func Test_newFileExporter(t *testing.T) { @@ -14,7 +14,7 @@ func Test_newFileExporter(t *testing.T) { t ExportType fs fsadapter.FS cl *slack.Client - l logger.Interface + l *slog.Logger token string } tests := []struct { @@ -22,10 +22,10 @@ func Test_newFileExporter(t *testing.T) { args args wantT string }{ - {"unknown is nodownload", args{t: ExportType(255), l: logger.Silent, token: "abcd"}, "dl.Nothing"}, - {"no", args{t: TNoDownload, l: logger.Silent, token: "abcd"}, "dl.Nothing"}, - {"standard", args{t: TStandard, fs: fsadapter.NewDirectory("."), cl: &slack.Client{}, l: logger.Silent, token: "abcd"}, "*dl.Std"}, - {"mattermost", args{t: TMattermost, fs: fsadapter.NewDirectory("."), cl: &slack.Client{}, l: logger.Silent, token: "abcd"}, "*dl.Mattermost"}, + {"unknown is nodownload", args{t: ExportType(255), l: slog.Default(), token: "abcd"}, "dl.Nothing"}, + {"no", args{t: TNoDownload, l: slog.Default(), token: "abcd"}, "dl.Nothing"}, + {"standard", args{t: TStandard, fs: fsadapter.NewDirectory("."), cl: &slack.Client{}, l: slog.Default(), token: "abcd"}, "*dl.Std"}, + {"mattermost", args{t: TMattermost, fs: fsadapter.NewDirectory("."), cl: &slack.Client{}, l: slog.Default(), token: "abcd"}, "*dl.Mattermost"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/export/export.go b/export/export.go index 5898e2df..cf561363 100644 --- a/export/export.go +++ b/export/export.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "path/filepath" "runtime/trace" @@ -15,7 +16,6 @@ import ( "github.com/rusq/slackdump/v3" "github.com/rusq/slackdump/v3/internal/structures" "github.com/rusq/slackdump/v3/internal/structures/files/dl" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -23,7 +23,7 @@ import ( type Export struct { fs fsadapter.FS // target filesystem sd dumper // Session instance - lg logger.Interface + lg *slog.Logger dl dl.Exporter // options @@ -34,7 +34,7 @@ type Export struct { // provided fs. func New(sd *slackdump.Session, fs fsadapter.FS, cfg Config) *Export { if cfg.Logger == nil { - cfg.Logger = logger.Default + cfg.Logger = slog.Default() } se := &Export{ @@ -76,8 +76,10 @@ func (se *Export) messages(ctx context.Context, users types.Users) error { se.dl.Start(ctx) defer func() { se.td(ctx, "info", "waiting for downloads to finish") + se.lg.DebugContext(ctx, "waiting for downloads to finish") se.dl.Stop() se.td(ctx, "info", "dl stopped") + se.lg.DebugContext(ctx, "downloader stopped") }() } @@ -123,7 +125,7 @@ func (se *Export) exclusiveExport(ctx context.Context, uidx structures.UserIndex if err := se.sd.StreamChannels(ctx, slackdump.AllChanTypes, func(ch slack.Channel) error { if listIdx.IsExcluded(ch.ID) { trace.Logf(ctx, "info", "skipping %s", ch.ID) - se.lg.Printf("skipping: %s", ch.ID) + se.lg.InfoContext(ctx, "skipping (excluded)", "channel_id", ch.ID) return nil } if err := se.exportConversation(ctx, uidx, ch); err != nil { @@ -136,7 +138,7 @@ func (se *Export) exclusiveExport(ctx context.Context, uidx structures.UserIndex }); err != nil { return nil, fmt.Errorf("channels: error: %w", err) } - se.lg.Printf(" out of which exported: %d", len(chans)) + se.lg.InfoContext(ctx, " out of which exported: ", "n", len(chans)) return chans, nil } @@ -160,7 +162,7 @@ func (se *Export) inclusiveExport(ctx context.Context, uidx structures.UserIndex for _, entry := range list.Include { if elIdx.IsExcluded(entry) { se.td(ctx, "info", "skipping %s", entry) - se.lg.Printf("skipping: %s", entry) + se.lg.InfoContext(ctx, "skipping (excluded)", "entry", entry) continue } sl, err := structures.ParseLink(entry) @@ -257,6 +259,5 @@ func serialize(w io.Writer, data any) error { // td outputs the message to trace and logs a debug message. func (se *Export) td(ctx context.Context, category string, fmt string, a ...any) { - se.lg.Debugf(fmt, a...) trace.Logf(ctx, category, fmt, a...) } diff --git a/export/options.go b/export/options.go index fa647091..99c5aac9 100644 --- a/export/options.go +++ b/export/options.go @@ -1,17 +1,17 @@ package export import ( + "log/slog" "time" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) // Config allows to configure slack export options. type Config struct { Oldest time.Time Latest time.Time - Logger logger.Interface + Logger *slog.Logger List *structures.EntityList Type ExportType MemberOnly bool diff --git a/export/options_test.go b/export/options_test.go index a7996cb3..5c3c1fc7 100644 --- a/export/options_test.go +++ b/export/options_test.go @@ -1,18 +1,18 @@ package export import ( + "log/slog" "testing" "time" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) func TestOptions_IsFilesEnabled(t *testing.T) { type fields struct { Oldest time.Time Latest time.Time - Logger logger.Interface + Logger *slog.Logger List *structures.EntityList Type ExportType ExportToken string diff --git a/go.mod b/go.mod index 66521cbf..93866f11 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/playwright-community/playwright-go v0.4702.0 github.com/rusq/chttp v1.0.2 - github.com/rusq/dlog v1.4.0 github.com/rusq/encio v0.1.0 github.com/rusq/fsadapter v1.0.2 github.com/rusq/osenv/v2 v2.0.1 @@ -42,7 +41,6 @@ require ( golang.org/x/term v0.25.0 golang.org/x/text v0.19.0 golang.org/x/time v0.7.0 - gopkg.in/yaml.v3 v3.0.1 src.elv.sh v0.21.0 ) @@ -87,4 +85,5 @@ require ( golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 15df88e4..0d1d15df 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,6 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rusq/chttp v1.0.2 h1:bc8FTKE/l318Kie3sb2KrGi7Fu5tSDQY+JiXMsq4fO8= github.com/rusq/chttp v1.0.2/go.mod h1:bmuoQMUFs9fmigUmT7xbp8s0rHyzUrf7+78yLklr1so= -github.com/rusq/dlog v1.4.0 h1:64oHTSzHjzG6TXKvMbPKQzvqADCZRn6XgAWnp7ASr5k= -github.com/rusq/dlog v1.4.0/go.mod h1:kjZAEvBu7m3+mnJQKoIeLul1YB3kJq/6lZBdDTZmpzA= github.com/rusq/encio v0.1.0 h1:DauNaVtIf79kILExhMGIsE5svYwPnDSksdYP0oVVcr8= github.com/rusq/encio v0.1.0/go.mod h1:AP3lDpo/BkcHcOMNduBlZdd0sbwhruq6+NZtYm5Mxb0= github.com/rusq/fsadapter v1.0.2 h1:T+hG8nvA4WlM5oLRcUMEPEOdmDWEd9wmD2oNpztwz90= diff --git a/internal/cache/auth.go b/internal/cache/auth.go index 2245a815..4e55f6f8 100644 --- a/internal/cache/auth.go +++ b/internal/cache/auth.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "runtime" @@ -14,7 +15,6 @@ import ( "github.com/rusq/encio" "github.com/rusq/slackdump/v3/auth" - "github.com/rusq/slackdump/v3/logger" ) const ezLogin = "EZ-Login 3000" @@ -155,13 +155,14 @@ func initProvider(ctx context.Context, cacheDir string, filename string, workspa } // try to load the existing credentials, if saved earlier. - lg := logger.FromContext(ctx) + lg := slog.With("cache_dir", cacheDir, "filename", filename, "workspace", workspace) if creds == nil || creds.IsEmpty() { if prov, err := tryLoad(ctx, credsFile); err != nil { msg := fmt.Sprintf("failed to load saved credentials: %s", err) trace.Log(ctx, "warn", msg) + slog.DebugContext(ctx, msg) if auth.IsInvalidAuthErr(err) { - lg.Println("authentication details expired, relogin is necessary") + lg.InfoContext(ctx, "authentication details expired, relogin is necessary") } } else { msg := "loaded saved credentials" diff --git a/internal/chunk/control/control.go b/internal/chunk/control/control.go index e933101c..035a957d 100644 --- a/internal/chunk/control/control.go +++ b/internal/chunk/control/control.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime/trace" "sync" @@ -18,7 +19,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/dirproc" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/processor" ) @@ -36,7 +36,7 @@ type Controller struct { // it's not necessary for all use cases. filer processor.Filer // lg is the logger - lg logger.Interface + lg *slog.Logger // flags flags Flags } @@ -68,7 +68,7 @@ func WithTransformer(tf ExportTransformer) Option { } // WithLogger configures the controller with a logger. -func WithLogger(lg logger.Interface) Option { +func WithLogger(lg *slog.Logger) Option { return func(c *Controller) { if lg != nil { c.lg = lg @@ -83,7 +83,7 @@ func New(cd *chunk.Directory, s Streamer, opts ...Option) *Controller { s: s, filer: &noopFiler{}, tf: &noopTransformer{}, - lg: logger.Default, + lg: slog.Default(), } for _, opt := range opts { opt(c) @@ -111,10 +111,10 @@ func (e Error) Error() string { } func (c *Controller) Run(ctx context.Context, list *structures.EntityList) error { - ctx, task := trace.NewTask(logger.NewContext(ctx, c.lg), "Controller.Run") + ctx, task := trace.NewTask(ctx, "Controller.Run") defer task.End() - lg := c.lg + lg := c.lg.With("in", "controller.Run") var ( wg sync.WaitGroup @@ -136,7 +136,7 @@ func (c *Controller) Run(ctx context.Context, list *structures.EntityList) error go func() { defer wg.Done() defer close(linkC) - defer lg.Debug("channels done") + defer lg.DebugContext(ctx, "channels done") if err := generator(ctx, linkC, list); err != nil { errC <- Error{"channel generator", "generator", err} @@ -149,7 +149,7 @@ func (c *Controller) Run(ctx context.Context, list *structures.EntityList) error wg.Add(1) go func() { defer wg.Done() - defer lg.Debug("workspace info done") + defer lg.DebugContext(ctx, "workspace info done") if err := workspaceWorker(ctx, c.s, c.cd); err != nil { errC <- Error{"workspace", "worker", err} return @@ -244,7 +244,6 @@ func genChFromAPI(s Streamer, cd *chunk.Directory, memberOnly bool) linkFeederFu case <-ctx.Done(): return context.Cause(ctx) case links <- ch.ID: - } } return nil @@ -259,7 +258,7 @@ func genChFromAPI(s Streamer, cd *chunk.Directory, memberOnly bool) linkFeederFu if err := chanproc.Close(); err != nil { return fmt.Errorf("error closing channel processor: %w", err) } - logger.FromContext(ctx).Debug("channels done") + slog.DebugContext(ctx, "channels done") return nil } } diff --git a/internal/chunk/control/search.go b/internal/chunk/control/search.go index 186e0719..1f7c631f 100644 --- a/internal/chunk/control/search.go +++ b/internal/chunk/control/search.go @@ -19,7 +19,7 @@ func (s *Controller) SearchMessages(ctx context.Context, query string) error { if err := eg.Wait(); err != nil { return err } - s.lg.Printf("search for query %q completed in: %s", query, time.Since(start)) + s.lg.InfoContext(ctx, "search completed ", "query", query, "took", time.Since(start).String()) return nil } @@ -35,7 +35,7 @@ func (s *Controller) SearchFiles(ctx context.Context, query string) error { if err := eg.Wait(); err != nil { return err } - s.lg.Printf("search for query %q completed in: %s", query, time.Since(start)) + s.lg.InfoContext(ctx, "search completed ", "query", query, "took", time.Since(start).String()) return nil } @@ -54,6 +54,6 @@ func (s *Controller) SearchAll(ctx context.Context, query string) error { if err := eg.Wait(); err != nil { return err } - s.lg.Printf("search for query %q completed in: %s", query, time.Since(start)) + s.lg.InfoContext(ctx, "search completed ", "query", query, "took", time.Since(start).String()) return nil } diff --git a/internal/chunk/control/workers.go b/internal/chunk/control/workers.go index 42daea54..13cf408b 100644 --- a/internal/chunk/control/workers.go +++ b/internal/chunk/control/workers.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime/trace" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/dirproc" "github.com/rusq/slackdump/v3/internal/chunk/transform" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/processor" ) @@ -32,7 +32,7 @@ func userWorker(ctx context.Context, s Streamer, chunkdir *chunk.Directory, tf T if err := userproc.Close(); err != nil { return fmt.Errorf("error closing user processor: %w", err) } - logger.FromContext(ctx).Debug("users done") + slog.DebugContext(ctx, "users done") if len(users) == 0 { return fmt.Errorf("unable to proceed, no users found") } @@ -43,7 +43,7 @@ func userWorker(ctx context.Context, s Streamer, chunkdir *chunk.Directory, tf T } func conversationWorker(ctx context.Context, s Streamer, proc processor.Conversations, links <-chan string) error { - lg := logger.FromContext(ctx) + lg := slog.Default() if err := s.Conversations(ctx, proc, links); err != nil { if errors.Is(err, transform.ErrClosed) { return fmt.Errorf("upstream error: %w", err) @@ -55,7 +55,7 @@ func conversationWorker(ctx context.Context, s Streamer, proc processor.Conversa } func workspaceWorker(ctx context.Context, s Streamer, cd *chunk.Directory) error { - lg := logger.FromContext(ctx) + lg := slog.Default() lg.Debug("workspaceWorker started") wsproc, err := dirproc.NewWorkspace(cd) if err != nil { @@ -73,7 +73,7 @@ func searchMsgWorker(ctx context.Context, s Streamer, filer processor.Filer, cd ctx, task := trace.NewTask(ctx, "searchMsgWorker") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.Default() lg.Debug("searchMsgWorker started") search, err := dirproc.NewSearch(cd, filer) if err != nil { @@ -91,7 +91,7 @@ func searchFileWorker(ctx context.Context, s Streamer, filer processor.Filer, cd ctx, task := trace.NewTask(ctx, "searchMsgWorker") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.Default() lg.Debug("searchFileWorker started") search, err := dirproc.NewSearch(cd, filer) if err != nil { diff --git a/internal/chunk/dirproc/conversations.go b/internal/chunk/dirproc/conversations.go index 70cdc4fc..dfe0b948 100644 --- a/internal/chunk/dirproc/conversations.go +++ b/internal/chunk/dirproc/conversations.go @@ -4,11 +4,11 @@ import ( "context" "errors" "io" + "log/slog" "runtime/trace" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/processor" ) @@ -29,7 +29,7 @@ type Transformer interface { // Zero value is unusable. Use [NewConversation] to create a new instance. type Conversations struct { t tracker - lg logger.Interface + lg *slog.Logger // subproc is the files subprocessor, it is called by the Files method // in addition to recording the files in the chunk file (if recordFiles is @@ -73,7 +73,7 @@ type counter interface { type ConvOption func(*Conversations) // WithLogger sets the logger for the processor. -func WithLogger(lg logger.Interface) ConvOption { +func WithLogger(lg *slog.Logger) ConvOption { return func(cv *Conversations) { cv.lg = lg } @@ -105,7 +105,7 @@ func NewConversation(cd *chunk.Directory, filesSubproc processor.Filer, tf Trans c := &Conversations{ t: newFileTracker(cd), - lg: logger.Default, + lg: slog.Default(), subproc: filesSubproc, tf: tf, } @@ -129,6 +129,8 @@ func (cv *Conversations) Messages(ctx context.Context, channelID string, numThre ctx, task := trace.NewTask(ctx, "Messages") defer task.End() + lg := cv.lg.With("in", "Messages", "channel_id", channelID, "num_threads", numThreads, "is_last", isLast, "len_messages", len(mm)) + lg.Debug("started") cv.debugtrace(ctx, "%s: Messages: numThreads=%d, isLast=%t, len(mm)=%d", channelID, numThreads, isLast, len(mm)) id := chunk.ToFileID(channelID, "", false) @@ -137,7 +139,9 @@ func (cv *Conversations) Messages(ctx context.Context, channelID string, numThre return err } n := r.Add(numThreads) + cv.debugtrace(ctx, "%s: Messages: increased by %d to %d", channelID, numThreads, n) + lg.DebugContext(ctx, "count increased", "by", numThreads, "current", n) if err := r.Messages(ctx, channelID, numThreads, isLast, mm); err != nil { return err @@ -146,6 +150,7 @@ func (cv *Conversations) Messages(ctx context.Context, channelID string, numThre if isLast { n := r.Dec() cv.debugtrace(ctx, "%s: Messages: decreased by 1 to %d, finalising", channelID, n) + lg.DebugContext(ctx, "count decreased", "by", 1, "current", n) return cv.finalise(ctx, id) } return nil @@ -157,6 +162,8 @@ func (cv *Conversations) ThreadMessages(ctx context.Context, channelID string, p ctx, task := trace.NewTask(ctx, "ThreadMessages") defer task.End() + lg := cv.lg.With("in", "ThreadMessages", "channel_id", channelID, "parent_ts", parent.ThreadTimestamp, "is_last", isLast, "len(tm)", len(tm)) + lg.Debug("started") cv.debugtrace(ctx, "%s: ThreadMessages: parent=%s, isLast=%t, len(tm)=%d", channelID, parent.ThreadTimestamp, isLast, len(tm)) id := chunk.ToFileID(channelID, parent.ThreadTimestamp, threadOnly) @@ -169,6 +176,7 @@ func (cv *Conversations) ThreadMessages(ctx context.Context, channelID string, p } if isLast { n := r.Dec() + lg.DebugContext(ctx, "count decreased, finalising", "by", 1, "current", n) cv.debugtrace(ctx, "%s:%s: ThreadMessages: decreased by 1 to %d, finalising", id, parent.Timestamp, n) return cv.finalise(ctx, id) } @@ -177,10 +185,13 @@ func (cv *Conversations) ThreadMessages(ctx context.Context, channelID string, p // finalise closes the channel file if there are no more threads to process. func (cv *Conversations) finalise(ctx context.Context, id chunk.FileID) error { + lg := cv.lg.With("in", "finalise", "file_id", id) if tc := cv.t.RefCount(id); tc > 0 { + lg.DebugContext(ctx, "not finalising", "ref_count", tc) cv.debugtrace(ctx, "%s: finalise: not finalising, ref count = %d", id, tc) return nil } + lg.Debug("finalising", "ref_count", 0) cv.debugtrace(ctx, "%s: finalise: ref count = 0, finalising...", id) if err := cv.t.Unregister(id); err != nil { return err @@ -224,6 +235,5 @@ func (cv *Conversations) Close() error { } func (cv *Conversations) debugtrace(ctx context.Context, fmt string, args ...any) { - cv.lg.Debugf(fmt, args...) trace.Logf(ctx, "debug", fmt, args...) } diff --git a/internal/chunk/dirproc/conversations_test.go b/internal/chunk/dirproc/conversations_test.go index 8a589792..a9326efa 100644 --- a/internal/chunk/dirproc/conversations_test.go +++ b/internal/chunk/dirproc/conversations_test.go @@ -3,12 +3,12 @@ package dirproc import ( "context" "errors" + "log/slog" "testing" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/fixtures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/mocks/mock_processor" "github.com/rusq/slackdump/v3/processor" "go.uber.org/mock/gomock" @@ -152,7 +152,7 @@ func TestConversations_Messages(t *testing.T) { mh := NewMockdatahandler(ctrl) cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: tt.fields.subproc, recordFiles: tt.fields.recordFiles, tf: tt.fields.tf, @@ -305,7 +305,7 @@ func TestConversations_ThreadMessages(t *testing.T) { mh := NewMockdatahandler(ctrl) cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: tt.fields.subproc, recordFiles: tt.fields.recordFiles, tf: tt.fields.tf, @@ -400,7 +400,7 @@ func TestConversations_ChannelInfo(t *testing.T) { cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: tt.fields.subproc, recordFiles: tt.fields.recordFiles, tf: tt.fields.tf, @@ -504,7 +504,7 @@ func TestConversations_finalise(t *testing.T) { tt.expectFn(mt, mtf) cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: tt.fields.subproc, recordFiles: tt.fields.recordFiles, tf: mtf, @@ -635,7 +635,7 @@ func TestConversations_Files(t *testing.T) { tt.expectFn(mt, mfh, mf) cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: mf, recordFiles: tt.fields.recordFiles, tf: tt.fields.tf, @@ -732,7 +732,7 @@ func TestConversations_ChannelUsers(t *testing.T) { tt.expectFn(mt, mdh) cv := &Conversations{ t: mt, - lg: logger.Default, + lg: slog.Default(), subproc: tt.fields.subproc, recordFiles: tt.fields.recordFiles, tf: tt.fields.tf, diff --git a/internal/chunk/file.go b/internal/chunk/file.go index 067b4470..d581921b 100644 --- a/internal/chunk/file.go +++ b/internal/chunk/file.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log" + "log/slog" "path/filepath" "runtime/trace" "sort" @@ -16,11 +17,9 @@ import ( "github.com/rusq/slack" - "github.com/rusq/dlog" "github.com/rusq/slackdump/v3/internal/chunk/state" "github.com/rusq/slackdump/v3/internal/fasttime" "github.com/rusq/slackdump/v3/internal/osext" - "github.com/rusq/slackdump/v3/logger" ) var ( @@ -53,7 +52,7 @@ func (idx index) offsetsWithPrefix(prefix string) []int64 { var offsets []int64 for id, off := range idx { if len(id) == 0 { - dlog.Panicf("internal error: invalid id: %q", id) + log.Panicf("internal error: invalid id: %q", id) } if strings.HasPrefix(string(id), prefix) { offsets = append(offsets, off...) @@ -126,7 +125,7 @@ func indexChunks(dec decoder) (index, error) { idx[id] = append(idx[id], offset) } - logger.Default.Debugf("indexing chunks: %d: called from %v, took %s (%.2f/sec)", len(idx), osext.Caller(2), time.Since(start), float64(len(idx))/time.Since(start).Seconds()) + slog.Default().Debug("indexing chunks", "len(idx)", len(idx), "caller", osext.Caller(2), "took", time.Since(start).String(), "took", float64(len(idx))/time.Since(start).Seconds()) return idx, nil } @@ -136,7 +135,7 @@ func (f *File) ensure() { var err error f.idx, err = indexChunks(json.NewDecoder(f.rs)) if err != nil { - log.Panicf("%s: index error: %s", osext.Caller(1), err) + log.Panicf("internal error: %s: index error: %s", osext.Caller(1), err) } } } @@ -278,14 +277,14 @@ func (f *File) AllChannelInfos() ([]slack.Channel, error) { } for i := range chans { if chans[i].IsArchived { - logger.Default.Debugf("skipping archived channel %s", chans[i].ID) + slog.Default().Debug("skipping archived channel", "i", i, "id", chans[i].ID) continue } members, err := f.ChannelUsers(chans[i].ID) if err != nil { if errors.Is(err, ErrNotFound) { // ignoring missing channel users - logger.Default.Printf("no users for channel %s: %v (never mind, let's continue)", chans[i].ID, err) + slog.Default().Warn("no users", "channel_id", chans[i].ID, "error", err) continue } return nil, err diff --git a/internal/chunk/filemgr.go b/internal/chunk/filemgr.go index d0931df6..67a5bab6 100644 --- a/internal/chunk/filemgr.go +++ b/internal/chunk/filemgr.go @@ -7,10 +7,9 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "sync" - - "github.com/rusq/slackdump/v3/logger" ) // filemgr manages temporary files and handles for compressed files. @@ -28,7 +27,7 @@ func newFileMgr() (*filemgr, error) { if err != nil { return nil, err } - logger.Default.Debugf("created temporary directory: %s", tmpdir) + slog.Default().Debug("created temporary directory", "dir", tmpdir) return &filemgr{ tmpdir: tmpdir, once: new(sync.Once), @@ -50,7 +49,7 @@ func (dp *filemgr) Destroy() error { var errcount int for hash, f := range dp.handles { if err := f.Close(); err != nil { - logger.Default.Printf("error closing file: %v", err) + slog.Default().Error("error closing file", "err", err) errcount++ continue } diff --git a/internal/chunk/obfuscate/directory.go b/internal/chunk/obfuscate/directory.go index 3b7d3b56..dd06b8f1 100644 --- a/internal/chunk/obfuscate/directory.go +++ b/internal/chunk/obfuscate/directory.go @@ -4,6 +4,7 @@ import ( "compress/gzip" "context" "fmt" + "log/slog" "math/rand" "os" "path/filepath" @@ -11,7 +12,6 @@ import ( "sync" "time" - "github.com/rusq/dlog" "github.com/rusq/slackdump/v3/internal/chunk" ) @@ -26,7 +26,7 @@ func DoDir(ctx context.Context, src string, trg string, options ...Option) error } rand.New(rand.NewSource(opts.seed)) - lg := dlog.FromContext(ctx) + lg := slog.Default() files, err := os.ReadDir(src) if err != nil { return err @@ -41,9 +41,9 @@ func DoDir(ctx context.Context, src string, trg string, options ...Option) error continue } if !strings.HasSuffix(f.Name(), ".json.gz") { - lg.Printf("skipping %s", f.Name()) + lg.DebugContext(ctx, "skipping", "filename", f.Name()) } - lg.Debugf("processing %s", f.Name()) + lg.DebugContext(ctx, "processing %s", "filename", f.Name()) once.Do(func() { err = os.MkdirAll(trg, 0755) }) diff --git a/internal/chunk/obfuscate/obfuscate.go b/internal/chunk/obfuscate/obfuscate.go index 6c6c0f92..e5b7dde4 100644 --- a/internal/chunk/obfuscate/obfuscate.go +++ b/internal/chunk/obfuscate/obfuscate.go @@ -12,12 +12,12 @@ import ( "errors" "hash" "io" + "log" "math/rand" "runtime/trace" "strings" "time" - "github.com/rusq/dlog" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" ) @@ -114,7 +114,7 @@ func (o obfuscator) Chunk(c *chunk.Chunk) { case chunk.CWorkspaceInfo: o.WorkspaceInfo(c.WorkspaceInfo) default: - dlog.Panicf("unknown chunk type: %s", c.Type) + log.Panicf("unknown chunk type: %s", c.Type) } } diff --git a/internal/chunk/transform/coordinator.go b/internal/chunk/transform/coordinator.go index e8f9c9f4..534c5ae8 100644 --- a/internal/chunk/transform/coordinator.go +++ b/internal/chunk/transform/coordinator.go @@ -3,9 +3,9 @@ package transform import ( "context" "errors" + "log/slog" "github.com/rusq/slackdump/v3/internal/chunk" - "github.com/rusq/slackdump/v3/logger" ) type COption func(*Coordinator) @@ -42,12 +42,13 @@ func NewCoordinator(ctx context.Context, cvt Converter, opts ...COption) *Coordi } func (c *Coordinator) worker(ctx context.Context) { - lg := logger.FromContext(ctx) defer close(c.errC) + lg := slog.Default() + for id := range c.idC { if err := c.cvt.Convert(ctx, id); err != nil { - lg.Printf("error converting %q: %v", id, err) + lg.Error("worker: conversion failed", "id", id, "error", err) c.errC <- err } } diff --git a/internal/chunk/transform/export.go b/internal/chunk/transform/export.go index 93b5209e..f906fa79 100644 --- a/internal/chunk/transform/export.go +++ b/internal/chunk/transform/export.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "path/filepath" "runtime/trace" "sort" @@ -17,7 +18,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/fasttime" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -67,11 +67,11 @@ func (e *ExpConverter) Convert(ctx context.Context, id chunk.FileID) error { ctx, task := trace.NewTask(ctx, "transform") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.With("file_id", id) { userCnt := len(e.users) trace.Logf(ctx, "input", "len(users)=%d", userCnt) - lg.Debugf("transforming channel %s, user len=%d", id, userCnt) + lg.DebugContext(ctx, "transforming channel", "id", id, "user_count", userCnt) } // load the chunk file @@ -95,7 +95,7 @@ func (e *ExpConverter) Convert(ctx context.Context, id chunk.FileID) error { } func (e *ExpConverter) writeMessages(ctx context.Context, pl *chunk.File, ci *slack.Channel) error { - lg := logger.FromContext(ctx) + lg := slog.With("in", "writeMessages", "channel", ci.ID) uidx := types.Users(e.users).IndexByID() trgdir := ExportChanName(ci) @@ -127,7 +127,7 @@ func (e *ExpConverter) writeMessages(ctx context.Context, pl *chunk.File, ci *sl } else { // this shouldn't happen as we have the guard in the if // condition, but if it does (i.e. API changed), log it. - lg.Printf("not an error, possibly deleted thread: %q not found in chunk file", ci.ID+":"+m.ThreadTimestamp) + lg.Warn("not an error, possibly deleted thread not found in chunk file", "slack_link", ci.ID+":"+m.ThreadTimestamp) } } } diff --git a/internal/chunk/transform/export_coordinator.go b/internal/chunk/transform/export_coordinator.go index acc47d5a..c1b13990 100644 --- a/internal/chunk/transform/export_coordinator.go +++ b/internal/chunk/transform/export_coordinator.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "sync/atomic" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" - "github.com/rusq/slackdump/v3/logger" ) type UserConverter interface { @@ -40,7 +40,7 @@ type UserConverter interface { // channel that was completed. type ExportCoordinator struct { cvt UserConverter - lg logger.Interface + lg *slog.Logger closed atomic.Bool start chan struct{} @@ -79,7 +79,7 @@ func WithUsers(users []slack.User) ExpOption { func NewExportCoordinator(ctx context.Context, cvt UserConverter, tfopt ...ExpOption) *ExportCoordinator { t := &ExportCoordinator{ cvt: cvt, - lg: logger.FromContext(ctx), + lg: slog.Default(), start: make(chan struct{}), ids: make(chan chunk.FileID, bufferSz), err: make(chan error, 1), @@ -109,7 +109,7 @@ func (t *ExportCoordinator) StartWithUsers(ctx context.Context, users []slack.Us // WithUsers option. Otherwise, use [ExportCoordinator.StartWithUsers] method. // The function doesn't check if coordinator was already started or not. func (t *ExportCoordinator) Start(ctx context.Context) error { - t.lg.Debugln("transform: starting transform") + t.lg.DebugContext(ctx, "transform: starting transform") if !t.cvt.HasUsers() { return errors.New("internal error: users not initialised") } @@ -143,7 +143,7 @@ func (t *ExportCoordinator) Transform(ctx context.Context, id chunk.FileID) erro if t.closed.Load() { return ErrClosed } - t.lg.Debugln("transform: placing channel in the queue", id) + t.lg.Debug("transform: placing channel (file) in the queue", "id", id) t.ids <- id return nil } @@ -151,13 +151,15 @@ func (t *ExportCoordinator) Transform(ctx context.Context, id chunk.FileID) erro func (t *ExportCoordinator) worker(ctx context.Context) { defer close(t.err) - t.lg.Debugln("transform: worker waiting") + lg := t.lg.With("in", "ExportCoordinator.worker") + + lg.Debug("worker waiting") <-t.start - t.lg.Debugln("transform: worker started") + lg.Debug("worker started") for id := range t.ids { - t.lg.Debugf("transform: transforming channel %s", id) + lg.Debug("transforming channel", "channel_id", id) if err := t.cvt.Convert(ctx, chunk.FileID(id)); err != nil { - t.lg.Debugf("transform: error transforming channel %s: %s", id, err) + lg.Debug("transforming channel failure", "channel_id", id, "error", err) t.err <- err continue } @@ -172,11 +174,11 @@ func (t *ExportCoordinator) Close() (err error) { if t.closed.Load() { return nil } - t.lg.Debugln("transform: closing transform") + t.lg.Debug("transform: closing transform") t.closed.Store(true) close(t.ids) close(t.start) - t.lg.Debugln("transform: waiting for workers to finish") + t.lg.Debug("transform: waiting for workers to finish") return <-t.err } diff --git a/internal/chunk/transform/fileproc/fileproc.go b/internal/chunk/transform/fileproc/fileproc.go index f30678f2..306896f0 100644 --- a/internal/chunk/transform/fileproc/fileproc.go +++ b/internal/chunk/transform/fileproc/fileproc.go @@ -8,12 +8,12 @@ import ( "context" "errors" "fmt" + "log/slog" "github.com/rusq/fsadapter" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/downloader" "github.com/rusq/slackdump/v3/internal/structures/files" - "github.com/rusq/slackdump/v3/logger" ) // Downloader is the interface that wraps the Download method. @@ -117,7 +117,7 @@ func (NoopDownloader) Download(fullpath string, url string) error { // NewDownloader initializes the downloader and returns it, along with a // function that should be called to stop it. -func NewDownloader(ctx context.Context, gEnabled bool, cl downloader.Downloader, fsa fsadapter.FS, lg logger.Interface) (sdl Downloader, stop func()) { +func NewDownloader(ctx context.Context, gEnabled bool, cl downloader.Downloader, fsa fsadapter.FS, lg *slog.Logger) (sdl Downloader, stop func()) { if !gEnabled { return NoopDownloader{}, func() {} } else { diff --git a/internal/chunk/transform/standard.go b/internal/chunk/transform/standard.go index 1f295305..9a0a3b2c 100644 --- a/internal/chunk/transform/standard.go +++ b/internal/chunk/transform/standard.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "sort" "strings" @@ -14,7 +15,6 @@ import ( "github.com/rusq/slackdump/v3/internal/fasttime" "github.com/rusq/slackdump/v3/internal/nametmpl" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -38,7 +38,7 @@ func StdWithPipeline(f ...func(channelID string, threadTS string, mm []slack.Mes } } -func StdWithLogger(log logger.Interface) StdOption { +func StdWithLogger(log *slog.Logger) StdOption { return func(s *StdConverter) { s.lg = log } @@ -49,7 +49,7 @@ func NewStandard(fsa fsadapter.FS, cd *chunk.Directory, opts ...StdOption) (*Std std := &StdConverter{ cd: cd, fsa: fsa, - lg: logger.Default, + lg: slog.Default(), tmpl: nametmpl.NewDefault(), } for _, opt := range opts { @@ -78,7 +78,7 @@ type StdConverter struct { cd *chunk.Directory // working chunk directory fsa fsadapter.FS // output file system tmpl Templater // file name template - lg logger.Interface // logger + lg *slog.Logger // logger pipeline []pipelineFunc // pipeline filter functions } diff --git a/internal/convert/chunkexp.go b/internal/convert/chunkexp.go index 369fbd78..cf3a7eb6 100644 --- a/internal/convert/chunkexp.go +++ b/internal/convert/chunkexp.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "runtime/trace" @@ -15,7 +16,6 @@ import ( "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/chunk/transform" "github.com/rusq/slackdump/v3/internal/chunk/transform/fileproc" - "github.com/rusq/slackdump/v3/logger" ) const ( @@ -40,7 +40,7 @@ type ChunkToExport struct { srcFileLoc func(*slack.Channel, *slack.File) string trgFileLoc func(*slack.Channel, *slack.File) string - lg logger.Interface + lg *slog.Logger workers int // number of workers to use to convert channels @@ -76,7 +76,7 @@ func WithTrgFileLoc(fn func(*slack.Channel, *slack.File) string) C2EOption { } // WithLogger sets the logger. -func WithLogger(lg logger.Interface) C2EOption { +func WithLogger(lg *slog.Logger) C2EOption { return func(c *ChunkToExport) { if lg != nil { c.lg = lg @@ -91,7 +91,7 @@ func NewChunkToExport(src *chunk.Directory, trg fsadapter.FS, opt ...C2EOption) includeFiles: false, srcFileLoc: fileproc.MattermostFilepath, trgFileLoc: fileproc.MattermostFilepath, - lg: logger.Default, + lg: slog.Default(), request: make(chan copyrequest, 1), result: make(chan copyresult, 1), workers: defWorkers, @@ -140,8 +140,6 @@ func (c *ChunkToExport) Convert(ctx context.Context) error { ctx, task := trace.NewTask(ctx, "convert.ChunkToExport") defer task.End() - lg := logger.FromContext(ctx) - if err := c.Validate(); err != nil { return err } @@ -190,7 +188,8 @@ func (c *ChunkToExport) Convert(ctx context.Context) error { go func() { defer wg.Done() for ch := range chC { - lg.Debugf("processing channel %q", ch.ID) + lg := c.lg.With("channel", ch.ID) + lg.Debug("processing channel") if err := conv.Convert(ctx, chunk.ToFileID(ch.ID, "", false)); err != nil { errC <- fmt.Errorf("converter: failed to process %q: %w", ch.ID, err) return @@ -202,7 +201,7 @@ func (c *ChunkToExport) Convert(ctx context.Context) error { wg.Add(1) go func() { defer wg.Done() - lg.Debugf("writing index for %s", c.src.Name()) + c.lg.DebugContext(ctx, "writing index", "name", c.src.Name()) if err := conv.WriteIndex(); err != nil { errC <- err } @@ -268,7 +267,7 @@ func (c *ChunkToExport) fileCopy(ch *slack.Channel, msg *slack.Message) error { } for _, f := range msg.Files { if err := fileproc.IsValidWithReason(&f); err != nil { - c.lg.Printf("skipping file %q: %v", f.ID, err) + c.lg.Warn("skipping", "file", f.ID, "error", err) continue } @@ -281,7 +280,7 @@ func (c *ChunkToExport) fileCopy(ch *slack.Channel, msg *slack.Message) error { if _, err := os.Stat(srcpath); err != nil { return ©error{f.ID, err} } - c.lg.Debugf("copying %q to %q", srcpath, trgpath) + c.lg.Debug("copying", "srcpath", srcpath, "trgpath", trgpath) if err := copy2trg(c.trg, trgpath, srcpath); err != nil { return ©error{f.ID, err} } diff --git a/internal/convert/chunkexp_test.go b/internal/convert/chunkexp_test.go index b2b72cde..f9445f3e 100644 --- a/internal/convert/chunkexp_test.go +++ b/internal/convert/chunkexp_test.go @@ -2,24 +2,22 @@ package convert import ( "context" - "log" + "log/slog" "os" "path/filepath" "testing" - "github.com/rusq/dlog" "github.com/rusq/fsadapter" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/chunk" "github.com/rusq/slackdump/v3/internal/fixtures" - "github.com/rusq/slackdump/v3/logger" ) const ( testSrcDir = "../../tmp/ora600" // TODO: fix manual nature of this/obfuscate ) -var testLogger = dlog.New(os.Stderr, "unit ", log.Lshortfile|log.LstdFlags, true) +var testLogger = slog.Default() func TestChunkToExport_Validate(t *testing.T) { fixtures.SkipInCI(t) @@ -117,7 +115,8 @@ func TestChunkToExport_Convert(t *testing.T) { c := NewChunkToExport(cd, fsa, WithIncludeFiles(true)) - ctx := logger.NewContext(context.Background(), testLogger) + ctx := context.Background() + c.lg = testLogger if err := c.Convert(ctx); err != nil { t.Fatal(err) } diff --git a/internal/edge/edge.go b/internal/edge/edge.go index 265e234c..7e8c351d 100644 --- a/internal/edge/edge.go +++ b/internal/edge/edge.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "os" @@ -20,7 +21,6 @@ import ( "github.com/rusq/slack" "github.com/rusq/slackauth" "github.com/rusq/slackdump/v3/auth" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/tagops" ) @@ -212,7 +212,7 @@ func (cl *Client) PostForm(ctx context.Context, path string, form url.Values) (* func (cl *Client) record(b []byte) { if cl.tape != nil { if _, err := cl.tape.Write(b); err != nil { - logger.Default.Printf("error writing to tape: %s", err) + slog.Default().Error("error writing to tape", "error", err) } } } @@ -248,7 +248,7 @@ func (cl *Client) ParseResponse(req any, r *http.Response) error { // if it receives another rate limit error, it returns slack.RateLimitedError // to let the caller handle it. func do(ctx context.Context, cl *http.Client, req *http.Request) (*http.Response, error) { - lg := logger.FromContext(ctx) + lg := slog.Default() req.Header.Set("Accept-Language", "en-NZ,en-AU;q=0.9,en;q=0.8") req.Header.Set("User-Agent", slackauth.DefaultUserAgent) @@ -261,7 +261,7 @@ func do(ctx context.Context, cl *http.Client, req *http.Request) (*http.Response if err != nil { return nil, err } - lg.Debugf("got rate limited, waiting %s", wait) + lg.Debug("got rate limited, waiting", "delay", wait) time.Sleep(wait) resp, err = cl.Do(req) @@ -270,7 +270,7 @@ func do(ctx context.Context, cl *http.Client, req *http.Request) (*http.Response } // if we are still rate limited, then we are in trouble if resp.StatusCode == http.StatusTooManyRequests { - lg.Debug("edge.do: did my best, but still rate limited, giving up, not my problem") + lg.DebugContext(ctx, "edge.do: did my best, but still rate limited, giving up, not my problem") wait, err = parseRetryAfter(resp) if err != nil { return nil, err diff --git a/internal/edge/search.go b/internal/edge/search.go index ef986be6..4d944f74 100644 --- a/internal/edge/search.go +++ b/internal/edge/search.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "errors" + "log/slog" "runtime/trace" "github.com/google/uuid" "github.com/rusq/slack" - "github.com/rusq/slackdump/v3/logger" ) // search.* API @@ -87,9 +87,10 @@ const ( func (cl *Client) SearchChannels(ctx context.Context, query string) ([]slack.Channel, error) { ctx, task := trace.NewTask(ctx, "SearchChannels") defer task.End() + lg := slog.With(ctx, "in", "SearchChannels", "query", query) + trace.Logf(ctx, "params", "query=%q", query) - lg := logger.FromContext(ctx) clientReq, err := uuid.NewRandom() if err != nil { return nil, err @@ -151,12 +152,13 @@ func (cl *Client) SearchChannels(ctx context.Context, query string) ([]slack.Cha lg.Debug("no more channels") break } - lg.Debugf("next_cursor=%q", sr.Pagination.NextCursor) + lg.DebugContext(ctx, "pagination", "next_cursor", sr.Pagination.NextCursor) form.Cursor = sr.Pagination.NextCursor if err := lim.Wait(ctx); err != nil { return nil, err } } trace.Logf(ctx, "info", "channels found=%d", len(cc)) + lg.DebugContext(ctx, "channels", "count", len(cc)) return cc, nil } diff --git a/internal/network/network.go b/internal/network/network.go index d2c1f68a..9e0dadd5 100644 --- a/internal/network/network.go +++ b/internal/network/network.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log/slog" "net" "net/http" "runtime/trace" @@ -12,8 +13,6 @@ import ( "github.com/rusq/slack" "golang.org/x/time/rate" - - "github.com/rusq/slackdump/v3/logger" ) // defNumAttempts is the default number of retry attempts. @@ -63,6 +62,7 @@ func WithRetry(ctx context.Context, lim *rate.Limiter, maxAttempts int, fn func( if maxAttempts == 0 { maxAttempts = defNumAttempts } + lg := slog.With("maxAttempts", maxAttempts) var lastErr error for attempt := 0; attempt < maxAttempts; attempt++ { @@ -82,7 +82,7 @@ func WithRetry(ctx context.Context, lim *rate.Limiter, maxAttempts int, fn func( } lastErr = cbErr - tracelogf(ctx, "error", "WithRetry: %[1]s (%[1]T) after %[2]d attempts", cbErr, attempt+1) + lg.ErrorContext(ctx, "WithRetry", "error", cbErr, "attempt", attempt+1) var ( rle *slack.RateLimitedError sce slack.StatusCodeError @@ -90,6 +90,7 @@ func WithRetry(ctx context.Context, lim *rate.Limiter, maxAttempts int, fn func( ) switch { case errors.As(cbErr, &rle): + slog.InfoContext(ctx, "got rate limited, sleeping", "retry_after_sec", rle.RetryAfter, "error", cbErr) tracelogf(ctx, "info", "got rate limited, sleeping %s (%s)", rle.RetryAfter, cbErr) time.Sleep(rle.RetryAfter) continue @@ -97,6 +98,7 @@ func WithRetry(ctx context.Context, lim *rate.Limiter, maxAttempts int, fn func( if isRecoverable(sce.Code) { // possibly transient error delay := waitFn(attempt) + slog.WarnContext(ctx, "got server error, sleeping", "status_code", sce.Code, "error", cbErr, "delay", delay.String()) tracelogf(ctx, "info", "got server error %d, sleeping %s (%s)", sce.Code, delay, cbErr) time.Sleep(delay) continue @@ -105,6 +107,7 @@ func WithRetry(ctx context.Context, lim *rate.Limiter, maxAttempts int, fn func( if ne.Op == "read" || ne.Op == "write" { // possibly transient error delay := netWaitFn(attempt) + slog.WarnContext(ctx, "got network error, sleeping", "op", ne.Op, "error", cbErr, "delay", delay.String()) tracelogf(ctx, "info", "got network error %s on %q, sleeping %s", cbErr, ne.Op, delay) time.Sleep(delay) continue @@ -144,12 +147,10 @@ func expWait(attempt int) time.Duration { return delay } -func tracelogf(ctx context.Context, category string, fmt string, a ...any) { +func tracelogf(ctx context.Context, category string, format string, a ...any) { mu.RLock() defer mu.RUnlock() - lg := logger.FromContext(ctx) - trace.Logf(ctx, category, fmt, a...) - lg.Debugf(fmt, a...) + trace.Logf(ctx, category, format, a...) } // SetMaxAllowedWaitTime sets the maximum time to wait for a transient error. diff --git a/internal/structures/files/dl/base.go b/internal/structures/files/dl/base.go index 9c4d3437..f97651ac 100644 --- a/internal/structures/files/dl/base.go +++ b/internal/structures/files/dl/base.go @@ -2,8 +2,7 @@ package dl import ( "context" - - "github.com/rusq/slackdump/v3/logger" + "log/slog" ) const entFiles = "files" @@ -11,7 +10,7 @@ const entFiles = "files" type base struct { dl exportDownloader token string // token is the token that will be appended to each file URL. - l logger.Interface + l *slog.Logger } func (bd *base) Start(ctx context.Context) { diff --git a/internal/structures/files/dl/mattermost.go b/internal/structures/files/dl/mattermost.go index 984b8484..f593253d 100644 --- a/internal/structures/files/dl/mattermost.go +++ b/internal/structures/files/dl/mattermost.go @@ -4,6 +4,7 @@ package dl import ( "errors" + "log/slog" "path/filepath" "github.com/rusq/slack" @@ -12,7 +13,6 @@ import ( "github.com/rusq/slackdump/v3" "github.com/rusq/slackdump/v3/downloader" "github.com/rusq/slackdump/v3/internal/structures/files" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -23,7 +23,7 @@ type Mattermost struct { // NewMattermost returns the dl, that downloads the files into // the __uploads directory, so that it could be transformed into bulk import // by mmetl and imported into mattermost with mmctl import bulk. -func NewMattermost(fs fsadapter.FS, cl *slack.Client, l logger.Interface, token string) *Mattermost { +func NewMattermost(fs fsadapter.FS, cl *slack.Client, l *slog.Logger, token string) *Mattermost { return &Mattermost{ base: base{ l: l, diff --git a/internal/structures/files/dl/standard.go b/internal/structures/files/dl/standard.go index 637b4383..4c1b6024 100644 --- a/internal/structures/files/dl/standard.go +++ b/internal/structures/files/dl/standard.go @@ -4,6 +4,7 @@ package dl import ( "errors" + "log/slog" "path" "path/filepath" @@ -13,7 +14,6 @@ import ( "github.com/rusq/slackdump/v3" "github.com/rusq/slackdump/v3/downloader" "github.com/rusq/slackdump/v3/internal/structures/files" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -23,7 +23,7 @@ type Std struct { // NewStd returns standard dl, which downloads files into // "channel_id/attachments" directory. -func NewStd(fs fsadapter.FS, cl *slack.Client, l logger.Interface, token string) *Std { +func NewStd(fs fsadapter.FS, cl *slack.Client, l *slog.Logger, token string) *Std { return &Std{ base: base{ dl: downloader.NewV1(cl, fs, downloader.LoggerV1(l)), @@ -50,7 +50,7 @@ func (d *Std) ProcessFunc(channelName string) slackdump.ProcessFunc { if err != nil { return err } - d.l.Debugf("submitted for download: %s", file.Name) + d.l.Debug("submitted for download", "filename", file.Name) total++ if d.token != "" { if err := files.Update(msg, addr, files.UpdateTokenFn(d.token)); err != nil { diff --git a/internal/structures/url_parse.go b/internal/structures/url_parse.go index 30feca35..27ab8e50 100644 --- a/internal/structures/url_parse.go +++ b/internal/structures/url_parse.go @@ -4,6 +4,7 @@ package structures import ( "fmt" + "log/slog" "net/url" "regexp" "strings" @@ -32,6 +33,13 @@ func (u SlackLink) IsThread() bool { return u.ThreadTS != "" } +func (u SlackLink) LogValue() slog.Value { + if u.ThreadTS == "" { + return slog.GroupValue(slog.Group("channel", slog.String("id", u.Channel))) + } + return slog.GroupValue(slog.Group("thread", slog.String("channel_id", u.Channel), slog.String("thread_ts", u.ThreadTS))) +} + func (u SlackLink) IsValid() bool { return u.Channel != "" || (u.Channel != "" && u.ThreadTS != "") } diff --git a/internal/viewer/handlers.go b/internal/viewer/handlers.go index 8d52dc55..9916ec2b 100644 --- a/internal/viewer/handlers.go +++ b/internal/viewer/handlers.go @@ -16,7 +16,7 @@ import ( func (v *Viewer) indexHandler(w http.ResponseWriter, r *http.Request) { page := v.view() if err := v.tmpl.ExecuteTemplate(w, "index.html", page); err != nil { - v.lg.Printf("error: %v", err) + v.lg.ErrorContext(r.Context(), "indexHandler", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) } } @@ -53,26 +53,28 @@ func (v *Viewer) newFileHandler(fn func(w http.ResponseWriter, r *http.Request, } func (v *Viewer) channelHandler(w http.ResponseWriter, r *http.Request, id string) { + ctx := r.Context() + lg := v.lg.With("in", "channelHandler", "channel", id) mm, err := v.src.AllMessages(id) if err != nil { if errors.Is(err, fs.ErrNotExist) { http.NotFound(w, r) return } - v.lg.Printf("%s: error: %v", id, err) + lg.ErrorContext(ctx, "AllMessages", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } if len(mm) > 0 { first, err := fasttime.TS2int(mm[0].Timestamp) if err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "TS2int", "idx", 0, "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } last, err := fasttime.TS2int(mm[len(mm)-1].Timestamp) if err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "TS2int", "idx", -1, "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -81,11 +83,11 @@ func (v *Viewer) channelHandler(w http.ResponseWriter, r *http.Request, id strin } } - v.lg.Debugf("conversation: %s, got %d messages", id, len(mm)) + lg.DebugContext(ctx, "conversation", "id", id, "message_count", len(mm)) ci, err := v.src.ChannelInfo(id) if err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "src.ChannelInfo", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -99,7 +101,7 @@ func (v *Viewer) channelHandler(w http.ResponseWriter, r *http.Request, id strin template = "hx_conversation" } if err := v.tmpl.ExecuteTemplate(w, template, page); err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "ExecuteTemplate", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -115,18 +117,20 @@ func (v *Viewer) threadHandler(w http.ResponseWriter, r *http.Request, id string http.NotFound(w, r) return } + ctx := r.Context() + lg := v.lg.With("in", "threadHandler", "channel", id, "thread", ts) mm, err := v.src.AllThreadMessages(id, ts) if err != nil { - v.lg.Printf("%s: error: %v", id, err) + lg.ErrorContext(ctx, "AllThreadMessages", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - v.lg.Debugf("conversation: %s, thread: %s, got %d messages", id, ts, len(mm)) + lg.DebugContext(ctx, "Messages", "mm_count", len(mm)) ci, err := v.src.ChannelInfo(id) if err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "ChannelInfo", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -146,13 +150,13 @@ func (v *Viewer) threadHandler(w http.ResponseWriter, r *http.Request, id string // so we need to fetch them. msg, err := v.src.AllMessages(id) if err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "AllMessages", "error", err, "template", template) http.Error(w, err.Error(), http.StatusInternalServerError) } page.Messages = msg } if err := v.tmpl.ExecuteTemplate(w, template, page); err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "ExecuteTemplate", "error", err, "template", template) http.Error(w, err.Error(), http.StatusInternalServerError) } } @@ -161,11 +165,13 @@ func (v *Viewer) fileHandler(w http.ResponseWriter, r *http.Request) { var ( id = r.PathValue("id") filename = r.PathValue("filename") + ctx = r.Context() ) if id == "" || filename == "" { http.NotFound(w, r) return } + lg := v.lg.With("in", "fileHandler", "id", id, "filename", filename) fs := v.src.FS() path, err := v.src.File(id, filename) if err != nil { @@ -173,7 +179,7 @@ func (v *Viewer) fileHandler(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "File", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -187,6 +193,8 @@ func (v *Viewer) userHandler(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } + ctx := r.Context() + lg := v.lg.With("in", "userHandler", "user_id", uid) u, found := v.um[uid] if !found { http.NotFound(w, r) @@ -195,7 +203,7 @@ func (v *Viewer) userHandler(w http.ResponseWriter, r *http.Request) { spew.Dump(u) if err := v.tmpl.ExecuteTemplate(w, "hx_user", u); err != nil { - v.lg.Printf("error: %v", err) + lg.ErrorContext(ctx, "ExecuteTemplate", "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) } } diff --git a/internal/viewer/source/export.go b/internal/viewer/source/export.go index 4ff68c4c..c95ec0fc 100644 --- a/internal/viewer/source/export.go +++ b/internal/viewer/source/export.go @@ -3,12 +3,12 @@ package source import ( "fmt" "io/fs" + "log/slog" "path" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/export" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" ) // Export implements viewer.Sourcer for the zip file Slack export format. @@ -119,7 +119,7 @@ func (e *Export) walkChannelMessages(channelID string, fn func(m *slack.Message) } for i, m := range em { if m.Msg == nil { - logger.Default.Debugf("skipping an empty message in %s at index %d", pth, i) + slog.Default().Debug("skipping an empty message", "pth", pth, "index", i) continue } if err := fn(&slack.Message{Msg: *m.Msg}); err != nil { diff --git a/internal/viewer/template_test.go b/internal/viewer/template_test.go index ce04eea3..b7777c01 100644 --- a/internal/viewer/template_test.go +++ b/internal/viewer/template_test.go @@ -2,6 +2,7 @@ package viewer import ( "html/template" + "log/slog" "net/http" "testing" @@ -9,7 +10,6 @@ import ( "github.com/rusq/slackdump/v3/internal/fixtures" st "github.com/rusq/slackdump/v3/internal/structures" "github.com/rusq/slackdump/v3/internal/viewer/renderer" - "github.com/rusq/slackdump/v3/logger" ) func TestViewer_username(t *testing.T) { @@ -19,7 +19,7 @@ func TestViewer_username(t *testing.T) { rtr Sourcer tmpl *template.Template srv *http.Server - lg logger.Interface + lg *slog.Logger r renderer.Renderer } type args struct { @@ -35,6 +35,7 @@ func TestViewer_username(t *testing.T) { "bot message", fields{ um: st.UserIndex{}, + lg: slog.Default(), }, args{ m: fixtures.Load[*slack.Message](fixtures.AppMessageJSON), diff --git a/internal/viewer/viewer.go b/internal/viewer/viewer.go index 411fa388..d5000bdc 100644 --- a/internal/viewer/viewer.go +++ b/internal/viewer/viewer.go @@ -17,7 +17,6 @@ import ( st "github.com/rusq/slackdump/v3/internal/structures" "github.com/rusq/slackdump/v3/internal/viewer/renderer" "github.com/rusq/slackdump/v3/internal/viewer/source" - "github.com/rusq/slackdump/v3/logger" ) var debug = os.Getenv("DEBUG") != "" @@ -38,7 +37,7 @@ type Viewer struct { // handles srv *http.Server - lg logger.Interface + lg *slog.Logger r renderer.Renderer } @@ -101,7 +100,7 @@ func New(ctx context.Context, addr string, r Sourcer) (*Viewer, error) { src: r, ch: cc, um: um, - lg: logger.FromContext(ctx), + lg: slog.Default(), } // postinit initTemplates(v) @@ -143,7 +142,7 @@ func (v *Viewer) Close() error { } v.lg.Debug("server closed") if ee != nil { - v.lg.Printf("errors: %v", ee) + v.lg.Error("close", "errors", ee) } return ee } diff --git a/logger/logger.go b/logger/logger.go deleted file mode 100644 index f600c747..00000000 --- a/logger/logger.go +++ /dev/null @@ -1,60 +0,0 @@ -package logger - -import ( - "context" - "log" - "os" - - "github.com/rusq/dlog" -) - -// Interface is the interface for a logger. -type Interface interface { - Debug(...any) - Debugf(fmt string, a ...any) - Debugln(...any) - Print(...any) - Printf(fmt string, a ...any) - Println(...any) - IsDebug() bool -} - -// Default is the default logger. It logs to stderr and debug logging can be -// enabled by setting the DEBUG environment variable to 1. For example: -// -// DEBUG=1 slackdump -var Default = dlog.New(log.Default().Writer(), "", log.LstdFlags, os.Getenv("DEBUG") == "1") - -// Silent is a logger that does not log anything. -var Silent = silent{} - -// Silent is a logger that does not log anything. -type silent struct{} - -func (s silent) Debug(...any) {} -func (s silent) Debugf(fmt string, a ...any) {} -func (s silent) Debugln(...any) {} -func (s silent) Print(...any) {} -func (s silent) Printf(fmt string, a ...any) {} -func (s silent) Println(...any) {} -func (s silent) IsDebug() bool { return false } - -type logCtx uint8 - -const ( - logCtxKey logCtx = iota -) - -// NewContext returns a new context with the logger. -func NewContext(ctx context.Context, l Interface) context.Context { - return context.WithValue(ctx, logCtxKey, l) -} - -// FromContext returns the logger from the context. If no logger is found, -// the Default logger is returned. -func FromContext(ctx context.Context) Interface { - if l, ok := ctx.Value(logCtxKey).(Interface); ok { - return l - } - return Default -} diff --git a/logger/logger_test.go b/logger/logger_test.go deleted file mode 100644 index fc5f53c8..00000000 --- a/logger/logger_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package logger - -import "testing" - -func BenchmarkSlientPrintf(b *testing.B) { - var l = Silent - for i := 0; i < b.N; i++ { - l.Printf("hello world, %s, %d", "foo", i) - } - // This benchmark compares the performance of the Silent logger when - // using io.Discard, and when using a no-op function. - // io.Discard: BenchmarkSlientPrintf-16 93075956 12.92 ns/op 8 B/op 0 allocs/op - // no-op func: BenchmarkSlientPrintf-16 1000000000 0.2364 ns/op 0 B/op 0 allocs/op - // - // Oh, look! We have an WINNER. The no-op function wins, no surprises. -} diff --git a/messages.go b/messages.go index abb7624e..3e12dd05 100644 --- a/messages.go +++ b/messages.go @@ -141,14 +141,14 @@ func (s *Session) dumpChannel(ctx context.Context, channelID string, oldest, lat messages = append(messages, chunk...) - s.log.Printf("messages request #%5d, fetched: %4d (%s), total: %8d (speed: %6.2f/sec, avg: %6.2f/sec)\n", - i, len(resp.Messages), results, len(messages), - float64(len(resp.Messages))/float64(time.Since(reqStart).Seconds()), - float64(len(messages))/float64(time.Since(fetchStart).Seconds()), + s.log.InfoContext(ctx, "messages", "request", i, "fetched", len(resp.Messages), "total", len(messages), + "process results", results, + "speed", float64(len(resp.Messages))/time.Since(reqStart).Seconds(), + "avg", float64(len(messages))/time.Since(fetchStart).Seconds(), ) if !resp.HasMore { - s.log.Printf("messages fetch complete, total: %d", len(messages)) + s.log.InfoContext(ctx, "messages fetch complete", "total", len(messages)) break } diff --git a/messages_test.go b/messages_test.go index eb13e631..7071f7b5 100644 --- a/messages_test.go +++ b/messages_test.go @@ -3,6 +3,7 @@ package slackdump import ( "context" "errors" + "log/slog" "reflect" "testing" @@ -13,7 +14,6 @@ import ( "github.com/rusq/slackdump/v3/internal/fixtures" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -219,7 +219,7 @@ func TestSession_DumpMessages(t *testing.T) { sd := &Session{ client: mc, cfg: tt.fields.config, - log: logger.Silent, + log: slog.Default(), } got, err := sd.DumpAll(tt.args.ctx, tt.args.channelID) if (err != nil) != tt.wantErr { @@ -300,7 +300,7 @@ func TestSession_DumpAll(t *testing.T) { sd := &Session{ client: mc, cfg: tt.fields.config, - log: logger.Silent, + log: slog.Default(), } got, err := sd.DumpAll(tt.args.ctx, tt.args.slackURL) if (err != nil) != tt.wantErr { diff --git a/processor/discarder.go b/processor/discarder.go index 6fd1e306..8d62381c 100644 --- a/processor/discarder.go +++ b/processor/discarder.go @@ -2,10 +2,11 @@ package processor import ( "context" + "log/slog" "runtime" - "github.com/rusq/dlog" "github.com/rusq/slack" + "github.com/rusq/slackdump/v3/internal/structures" ) var _ Conversations = new(Printer) @@ -13,43 +14,45 @@ var _ Conversations = new(Printer) type Printer struct{} func (d *Printer) Messages(ctx context.Context, channelID string, numThreads int, isLast bool, messages []slack.Message) error { - dlog.Printf("Discarding %d messages", len(messages)) + slog.Info("Discarding messages", "n", len(messages)) for i := range messages { - dlog.Printf(" message: %s", messages[i].Timestamp) + slog.Info(" message", "ts", messages[i].Timestamp) } return nil } func (d *Printer) ThreadMessages(ctx context.Context, channelID string, parent slack.Message, threadOnly, isLast bool, replies []slack.Message) error { - dlog.Printf("Discarding %d replies to %s", len(replies), parent.Timestamp) + slog.Info("Discarding %d replies to %s", "n", len(replies), "parent_ts", parent.Timestamp) for i := range replies { - dlog.Printf(" reply: %s", replies[i].Timestamp) + slog.Info(" reply", "ts", replies[i].Timestamp) } return nil } func (d *Printer) Files(_ context.Context, ch *slack.Channel, parent slack.Message, files []slack.File) error { - dlog.Printf("Discarding %d files to %s (thread: %v)", len(files), parent.Timestamp, parent.ThreadTimestamp != "") + slog.Info("Discarding files", "n", len(files), "parent_ts", parent.Timestamp, "is_thread", parent.ThreadTimestamp != "") if parent.Timestamp == "" { runtime.Breakpoint() } for i := range files { - dlog.Printf(" file: %s", files[i].ID) + slog.Info(" file", "id", files[i].ID) } return nil } func (d *Printer) ChannelInfo(_ context.Context, ch *slack.Channel, threadID string) error { - dlog.Printf("Discarding channel info for %s (thread, if set %q)", ch.Name, threadID) + sl := structures.SlackLink{Channel: ch.ID, ThreadTS: threadID} + slog.Info("Discarding channel info", "channel_name", ch.Name, "slack_link", sl) return nil } -func (d *Printer) ChannelUsers(_ context.Context, ch string, threadTS string, u []string) error { - dlog.Printf("Discarding channel users for %s (len=%d)", ch, len(u)) +func (d *Printer) ChannelUsers(_ context.Context, ch string, threadID string, u []string) error { + sl := structures.SlackLink{Channel: ch, ThreadTS: threadID} + slog.Info("Discarding channel users", "slack_link", sl, "users_len", len(u)) return nil } func (d *Printer) Close() error { - dlog.Println("Discarder closing") + slog.Info("Discarder closing") return nil } diff --git a/slackdump.go b/slackdump.go index 8a18926a..f9197196 100644 --- a/slackdump.go +++ b/slackdump.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log" + "log/slog" "runtime/trace" "time" @@ -16,7 +17,6 @@ import ( "github.com/rusq/slackdump/v3/auth" "github.com/rusq/slackdump/v3/internal/edge" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/stream" ) @@ -26,10 +26,10 @@ import ( // Session stores basic session parameters. Zero value is not usable, must be // initialised with New. type Session struct { - client clienter // Slack client - uc *usercache // usercache contains the list of users. - fs fsadapter.FS // filesystem adapter - log logger.Interface // logger + client clienter // Slack client + uc *usercache // usercache contains the list of users. + fs fsadapter.FS // filesystem adapter + log *slog.Logger // logger wspInfo *WorkspaceInfo // workspace info @@ -103,7 +103,7 @@ func WithLimits(l network.Limits) Option { // given, the default logger is initialised with the filename specified in // Config.Logfile. If the Config.Logfile is empty, the default logger writes // to STDERR. -func WithLogger(l logger.Interface) Option { +func WithLogger(l *slog.Logger) Option { return func(s *Session) { if l != nil { s.log = l @@ -154,7 +154,7 @@ func NewNoValidate(ctx context.Context, prov auth.Provider, opts ...Option) (*Se cfg: defConfig, uc: new(usercache), - log: logger.Default, + log: slog.Default(), } for _, opt := range opts { opt(sd) diff --git a/slackdump_test.go b/slackdump_test.go index 904b812d..04b8526c 100644 --- a/slackdump_test.go +++ b/slackdump_test.go @@ -3,6 +3,7 @@ package slackdump import ( "context" "log" + "log/slog" "math" "net/http" "os" @@ -16,7 +17,6 @@ import ( "github.com/rusq/slackdump/v3/internal/edge" "github.com/rusq/slackdump/v3/internal/mocks/mock_auth" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" ) @@ -213,7 +213,7 @@ func TestSession_initClient(t *testing.T) { s := Session{ client: nil, - log: logger.Default, + log: slog.Default(), } err := s.initClient(context.Background(), mprov, false) assert.NoError(t, err, "unexpected error") @@ -232,7 +232,7 @@ func TestSession_initClient(t *testing.T) { s := Session{ client: nil, - log: logger.Default, + log: slog.Default(), } err := s.initClient(context.Background(), mprov, false) assert.NoError(t, err, "unexpected error") @@ -250,7 +250,7 @@ func TestSession_initClient(t *testing.T) { s := Session{ client: nil, - log: logger.Default, + log: slog.Default(), } err := s.initClient(context.Background(), mprov, true) assert.NoError(t, err, "unexpected error") diff --git a/stream/conversation.go b/stream/conversation.go index ceea35cd..c13af47c 100644 --- a/stream/conversation.go +++ b/stream/conversation.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "runtime/trace" "sync" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/network" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/processor" "golang.org/x/sync/errgroup" ) @@ -18,9 +18,9 @@ import ( // SyncConversations fetches the conversations from the link which can be a // channelID, channel URL, thread URL or a link in Slackdump format. func (cs *Stream) SyncConversations(ctx context.Context, proc processor.Conversations, link ...string) error { - lg := logger.FromContext(ctx) + lg := slog.With("links", link) return cs.ConversationsCB(ctx, proc, link, func(sr Result) error { - lg.Debugf("stream: finished processing: %s", sr) + lg.DebugContext(ctx, "stream: finished processing", "result", sr) return nil }) } @@ -29,7 +29,7 @@ func (cs *Stream) ConversationsCB(ctx context.Context, proc processor.Conversati ctx, task := trace.NewTask(ctx, "channelStream.Conversations") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.With("links", link) cs.resultFn = append(cs.resultFn, cb) linkC := make(chan string, 1) @@ -38,7 +38,7 @@ func (cs *Stream) ConversationsCB(ctx context.Context, proc processor.Conversati for _, l := range link { linkC <- l } - lg.Debugf("stream: sent %d links", len(link)) + lg.DebugContext(ctx, "stream: sent link count", "len", len(link)) }() if err := cs.Conversations(ctx, proc, linkC); err != nil { @@ -172,7 +172,7 @@ func (cs *Stream) channel(ctx context.Context, id string, callback func(mm []sla ctx, task := trace.NewTask(ctx, "channel") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.With("channel_id", id) cursor := "" for { @@ -207,7 +207,7 @@ func (cs *Stream) channel(ctx context.Context, id string, callback func(mm []sla } if !resp.HasMore { - lg.Debugf("server reported channel %s done", id) + lg.DebugContext(ctx, "server reported channel done") break } cursor = resp.ResponseMetaData.NextCursor @@ -225,8 +225,8 @@ func (cs *Stream) thread(ctx context.Context, sl *structures.SlackLink, callback return fmt.Errorf("not a thread: %s", sl) } - lg := logger.FromContext(ctx) - lg.Debugf("- getting: %s", sl) + lg := slog.With("slack_link", sl) + lg.DebugContext(ctx, "- getting") var cursor string for { @@ -273,7 +273,7 @@ func (cs *Stream) thread(ctx context.Context, sl *structures.SlackLink, callback // sends the thread request on threadC. It returns thread count in the mm and // error if any. func procChanMsg(ctx context.Context, proc processor.Conversations, threadC chan<- request, channel *slack.Channel, isLast bool, mm []slack.Message) (int, error) { - lg := logger.FromContext(ctx) + lg := slog.With("channel_id", channel.ID, "is_last", isLast, "msg_count", len(mm)) var trs = make([]request, 0, len(mm)) for i := range mm { @@ -283,7 +283,7 @@ func procChanMsg(ctx context.Context, proc processor.Conversations, threadC chan // start processing the channel and will have the initial reference // count, if it needs it. if mm[i].Msg.ThreadTimestamp != "" && mm[i].Msg.SubType != structures.SubTypeThreadBroadcast && mm[i].LatestReply != structures.LatestReplyNoReplies { - lg.Debugf("- message #%d/channel=%s,thread: id=%s, thread_ts=%s", i, channel.ID, mm[i].Timestamp, mm[i].Msg.ThreadTimestamp) + lg.DebugContext(ctx, "- message", "i", i, "thread", mm[i].Timestamp, "thread_ts", mm[i].Msg.ThreadTimestamp) trs = append(trs, request{ sl: &structures.SlackLink{ Channel: channel.ID, diff --git a/stream/search.go b/stream/search.go index 9d314037..7f1f28df 100644 --- a/stream/search.go +++ b/stream/search.go @@ -2,12 +2,12 @@ package stream import ( "context" + "log/slog" "runtime/trace" "sync" "github.com/rusq/slack" "github.com/rusq/slackdump/v3/internal/network" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/processor" "golang.org/x/sync/errgroup" ) @@ -79,7 +79,7 @@ func (cs *Stream) searchmsg(ctx context.Context, query string, fn func(sm []slac ctx, task := trace.NewTask(ctx, "searchMessages") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.With("query", query) var p = slack.SearchParameters{ Sort: "timestamp", @@ -102,7 +102,7 @@ func (cs *Stream) searchmsg(ctx context.Context, query string, fn func(sm []slac return err } if sm.NextCursor == "" { - lg.Debug("SearchMessages: no more messages") + lg.DebugContext(ctx, "SearchMessages: no more messages") break } p.Cursor = sm.NextCursor @@ -117,7 +117,7 @@ func (cs *Stream) SearchFiles(ctx context.Context, proc processor.FileSearcher, ctx, task := trace.NewTask(ctx, "SearchFiles") defer task.End() - lg := logger.FromContext(ctx) + lg := slog.With("query", query) var p = slack.SearchParameters{ Sort: "timestamp", @@ -143,7 +143,7 @@ func (cs *Stream) SearchFiles(ctx context.Context, proc processor.FileSearcher, return err } if sm.NextCursor == "" { - lg.Debug("SearchFiles: no more messages") + lg.DebugContext(ctx, "SearchFiles: no more messages") break } p.Cursor = sm.NextCursor diff --git a/thread.go b/thread.go index a7c88082..422061e2 100644 --- a/thread.go +++ b/thread.go @@ -145,15 +145,10 @@ func (s *Session) dumpThread( return nil, err } - s.log.Printf(" thread request #%5d, fetched: %4d, total: %8d, process results: %s (speed: %6.2f/sec, avg: %6.2f/sec)\n", - i+1, len(msgs), len(thread), - prs, - float64(len(msgs))/time.Since(reqStart).Seconds(), - float64(len(thread))/time.Since(fetchStart).Seconds(), - ) + s.log.InfoContext(ctx, " thread", "request", i+1, "fetched", len(msgs), "total", len(thread), "process results", prs, "speed", float64(len(msgs))/time.Since(reqStart).Seconds(), "avg", float64(len(thread))/time.Since(fetchStart).Seconds()) if !hasmore { - s.log.Printf(" thread fetch complete, total: %d", len(thread)) + s.log.InfoContext(ctx, " thread fetch complete", "total", len(thread)) break } cursor = nextCursor diff --git a/thread_test.go b/thread_test.go index d37aef6c..487afa9a 100644 --- a/thread_test.go +++ b/thread_test.go @@ -2,6 +2,7 @@ package slackdump import ( "context" + "log/slog" "testing" "time" @@ -14,7 +15,6 @@ import ( "github.com/rusq/slackdump/v3/internal/network" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -166,7 +166,7 @@ func TestSession_DumpThreadWithFiles(t *testing.T) { sd := &Session{ client: mc, cfg: tt.fields.config, - log: logger.Silent, + log: slog.Default(), } got, err := sd.dumpThreadAsConversation(tt.args.ctx, structures.SlackLink{Channel: tt.args.channelID, ThreadTS: tt.args.threadTS}, tt.args.oldest, tt.args.latest) if (err != nil) != tt.wantErr { @@ -384,7 +384,7 @@ func TestSession_dumpThread(t *testing.T) { sd := &Session{ client: mc, cfg: tt.fields.config, - log: logger.Silent, + log: slog.Default(), } got, err := sd.dumpThread(tt.args.ctx, tt.args.l, tt.args.channelID, tt.args.threadTS, tt.args.oldest, tt.args.latest) if (err != nil) != tt.wantErr { diff --git a/users_test.go b/users_test.go index 1d726005..2d993c54 100644 --- a/users_test.go +++ b/users_test.go @@ -2,6 +2,7 @@ package slackdump import ( "context" + "log/slog" "reflect" "testing" "time" @@ -14,7 +15,6 @@ import ( "github.com/rusq/slackdump/v3/internal/fixtures" "github.com/rusq/slackdump/v3/internal/network" "github.com/rusq/slackdump/v3/internal/structures" - "github.com/rusq/slackdump/v3/logger" "github.com/rusq/slackdump/v3/types" ) @@ -178,7 +178,7 @@ func TestSession_GetUsers(t *testing.T) { wspInfo: &slack.AuthTestResponse{TeamID: testSuffix}, cfg: tt.fields.config, uc: &tt.fields.usercache, - log: logger.Silent, + log: slog.Default(), } got, err := sd.GetUsers(tt.args.ctx) if (err != nil) != tt.wantErr {