Skip to content
This repository has been archived by the owner on Oct 11, 2024. It is now read-only.

Commit

Permalink
Write logs to disk by default (#2082)
Browse files Browse the repository at this point in the history
## Description

This changes the behavior of logs and writes it to disk instead of `stdout` by default. It also adds a new flag `--log-file` to specify the filename for the log file.

## Does this PR need a docs update or release note?

- [x] ✅ Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [ ] ⛔ No 

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🧹 Tech Debt/Cleanup

## Issue(s)

<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->
* fixes #2076

## Test Plan

<!-- How will this be tested prior to merging.-->
- [x] 💪 Manual
- [ ] ⚡ Unit test
- [ ] 💚 E2E
  • Loading branch information
meain authored Jan 13, 2023
1 parent f777aa2 commit 01d8d08
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- The selectors Reduce() process will only include details that match the DiscreteOwner, if one is specified.
- New selector constructors will automatically set the DiscreteOwner if given a single-item slice.
- Write logs to disk by default ([#2082](https://github.com/alcionai/corso/pull/2082))

### Fixed

Expand Down
6 changes: 4 additions & 2 deletions src/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func BuildCommandTree(cmd *cobra.Command) {
cmd.Flags().BoolP("version", "v", false, "current version info")
cmd.PersistentPostRunE = config.InitFunc()
config.AddConfigFlags(cmd)
logger.AddLogLevelFlag(cmd)
logger.AddLoggingFlags(cmd)
observe.AddProgressBarFlags(cmd)
print.AddOutputFlag(cmd)
options.AddGlobalOperationFlags(cmd)
Expand All @@ -91,7 +91,9 @@ func Handle() {

BuildCommandTree(corsoCmd)

ctx, log := logger.Seed(ctx, logger.PreloadLogLevel())
loglevel, logfile := logger.PreloadLoggingFlags()
ctx, log := logger.Seed(ctx, loglevel, logfile)

defer func() {
_ = log.Sync() // flush all logs in the buffer
}()
Expand Down
82 changes: 63 additions & 19 deletions src/pkg/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ package logger
import (
"context"
"os"
"path/filepath"
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// Default location for writing logs, initialized in platform specific files
var userLogsDir string

var (
logCore *zapcore.Core
loggerton *zap.SugaredLogger

// logging level flag
llFlag = "info"

// logging file flags
lfFlag = ""

DebugAPI bool
readableOutput bool
)
Expand All @@ -34,17 +42,26 @@ const (
const (
debugAPIFN = "debug-api-calls"
logLevelFN = "log-level"
logFileFN = "log-file"
readableLogsFN = "readable-logs"
)

// adds the persistent flag --log-level to the provided command.
// defaults to "info".
// Returns the default location for writing logs
func defaultLogLocation() string {
return filepath.Join(userLogsDir, "corso", "logs", time.Now().UTC().Format("2006-01-02T15-04-05Z")+".log")
}

// adds the persistent flag --log-level and --log-file to the provided command.
// defaults to "info" and the default log location.
// This is a hack for help displays. Due to seeding the context, we also
// need to parse the log level before we execute the command.
func AddLogLevelFlag(cmd *cobra.Command) {
func AddLoggingFlags(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.StringVar(&llFlag, logLevelFN, "info", "set the log level to debug|info|warn|error")

// The default provided here is only for help info
fs.StringVar(&lfFlag, logFileFN, "corso-<timestamp>.log", "location for writing logs, use '-' for stdout")

fs.Bool(debugAPIFN, false, "add non-2xx request/response errors to logging")

fs.Bool(
Expand All @@ -54,34 +71,58 @@ func AddLogLevelFlag(cmd *cobra.Command) {
fs.MarkHidden(readableLogsFN)
}

// Due to races between the lazy evaluation of flags in cobra and the need to init logging
// behavior in a ctx, log-level gets pre-processed manually here using pflags. The canonical
// AddLogLevelFlag() ensures the flag is displayed as part of the help/usage output.
func PreloadLogLevel() string {
// Due to races between the lazy evaluation of flags in cobra and the
// need to init logging behavior in a ctx, log-level and log-file gets
// pre-processed manually here using pflags. The canonical
// AddLogLevelFlag() and AddLogFileFlag() ensures the flags are
// displayed as part of the help/usage output.
func PreloadLoggingFlags() (string, string) {
dlf := defaultLogLocation()
fs := pflag.NewFlagSet("seed-logger", pflag.ContinueOnError)
fs.ParseErrorsWhitelist.UnknownFlags = true
fs.String(logLevelFN, "info", "set the log level to debug|info|warn|error")
fs.String(logFileFN, dlf, "location for writing logs")
fs.BoolVar(&DebugAPI, debugAPIFN, false, "add non-2xx request/response errors to logging")
fs.BoolVar(&readableOutput, readableLogsFN, false, "minimizes log output: removes the file and date, colors the level")
// prevents overriding the corso/cobra help processor
fs.BoolP("help", "h", false, "")

// parse the os args list to find the log level flag
if err := fs.Parse(os.Args[1:]); err != nil {
return "info"
return "info", dlf
}

// retrieve the user's preferred log level
// automatically defaults to "info"
levelString, err := fs.GetString(logLevelFN)
if err != nil {
return "info"
return "info", dlf
}

// retrieve the user's preferred log file location
// automatically defaults to default log location
logfile, err := fs.GetString(logFileFN)
if err != nil {
return "info", dlf
}

if logfile == "-" {
logfile = "stdout"
}

if logfile != "stdout" && logfile != "stderr" {
logdir := filepath.Dir(logfile)

err := os.MkdirAll(logdir, 0o755)
if err != nil {
return "info", "stderr"
}
}

return levelString
return levelString, logfile
}

func genLogger(level logLevel) (*zapcore.Core, *zap.SugaredLogger) {
func genLogger(level logLevel, logfile string) (*zapcore.Core, *zap.SugaredLogger) {
// when testing, ensure debug logging matches the test.v setting
for _, arg := range os.Args {
if arg == `--test.v=true` {
Expand Down Expand Up @@ -136,20 +177,23 @@ func genLogger(level logLevel) (*zapcore.Core, *zap.SugaredLogger) {
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
}

cfg.OutputPaths = []string{logfile}
lgr, err = cfg.Build(opts...)
} else {
lgr, err = zap.NewProduction()
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{logfile}
lgr, err = cfg.Build()
}

// fall back to the core config if the default creation fails
if err != nil {
lgr = zap.New(*logCore)
lgr = zap.New(core)
}

return &core, lgr.Sugar()
}

func singleton(level logLevel) *zap.SugaredLogger {
func singleton(level logLevel, logfile string) *zap.SugaredLogger {
if loggerton != nil {
return loggerton
}
Expand All @@ -161,7 +205,7 @@ func singleton(level logLevel) *zap.SugaredLogger {
return loggerton
}

logCore, loggerton = genLogger(level)
logCore, loggerton = genLogger(level, logfile)

return loggerton
}
Expand All @@ -178,12 +222,12 @@ const ctxKey loggingKey = "corsoLogger"
// It also parses the command line for flag values prior to executing
// cobra. This early parsing is necessary since logging depends on
// a seeded context prior to cobra evaluating flags.
func Seed(ctx context.Context, lvl string) (context.Context, *zap.SugaredLogger) {
func Seed(ctx context.Context, lvl, logfile string) (context.Context, *zap.SugaredLogger) {
if len(lvl) == 0 {
lvl = "info"
}

zsl := singleton(levelOf(lvl))
zsl := singleton(levelOf(lvl), logfile)

return Set(ctx, zsl), zsl
}
Expand All @@ -192,7 +236,7 @@ func Seed(ctx context.Context, lvl string) (context.Context, *zap.SugaredLogger)
func SeedLevel(ctx context.Context, level logLevel) (context.Context, *zap.SugaredLogger) {
l := ctx.Value(ctxKey)
if l == nil {
zsl := singleton(level)
zsl := singleton(level, defaultLogLocation())
return Set(ctx, zsl), zsl
}

Expand All @@ -212,7 +256,7 @@ func Set(ctx context.Context, logger *zap.SugaredLogger) context.Context {
func Ctx(ctx context.Context) *zap.SugaredLogger {
l := ctx.Value(ctxKey)
if l == nil {
return singleton(levelOf(llFlag))
return singleton(levelOf(llFlag), defaultLogLocation())
}

return l.(*zap.SugaredLogger)
Expand Down
10 changes: 10 additions & 0 deletions src/pkg/logger/logpath_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package logger

import (
"os"
"path/filepath"
)

func init() {
userLogsDir = filepath.Join(os.Getenv("HOME"), "Library", "Logs")
}
9 changes: 9 additions & 0 deletions src/pkg/logger/logpath_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package logger

import (
"os"
)

func init() {
userLogsDir = os.Getenv("LOCALAPPDATA")
}
17 changes: 17 additions & 0 deletions src/pkg/logger/logpath_xdg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !windows && !darwin
// +build !windows,!darwin

package logger

import (
"os"
"path/filepath"
)

func init() {
if os.Getenv("XDG_CACHE_HOME") != "" {
userLogsDir = os.Getenv("XDG_CACHE_HOME")
} else {
userLogsDir = filepath.Join(os.Getenv("HOME"), ".cache")
}
}
14 changes: 14 additions & 0 deletions website/docs/setup/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,17 @@ directory within the container.
</TabItem>
</Tabs>
## Log Files
The location of log files varies by operating system:
* On Linux - `~/.cache/corso/logs/<timestamp>.log`
* On macOS - `~/Library/Logs/corso/logs/<timestamp>.log`
* On Windows - `%LocalAppData%\corso/logs/<timestamp>.log`
Log file location can be overridden by setting the `--log-file` flag.
:::info
You can use `stdout` or `stderr` as the `--log-file` location to redirect the logs to "stdout" and "stderr" respectively.
:::
3 changes: 2 additions & 1 deletion website/docs/support/bugs-and-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ You can learn more about the Corso roadmap and how to interpret it [here](https:

If you run into a bug or have feature requests, please file a [GitHub issue](https://github.com/alcionai/corso/issues/)
and attach the `bug` or `enhancement` label to the issue. When filing bugs, please run Corso with `--log-level debug`
and add the logs to the bug report.
and add the logs to the bug report. You can find more information about where logs are stored in the
[log files](../../setup/configuration/#log-files) section in setup docs.
4 changes: 3 additions & 1 deletion website/styles/Vocab/Base/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ Gitlab
cyberattack
Atlassian
SLAs
runbooks
runbooks
stdout
stderr

0 comments on commit 01d8d08

Please sign in to comment.