logger

package
v0.0.0-...-df423d5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2024 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Example (Logger_clues_standards)

ExampleLoggerCluesStandards reviews code standards around using the Clues package while logging.

package main

import (
	"context"

	"github.com/alcionai/clues"

	"github.com/alcionai/corso/src/internal/m365/resource"
	"github.com/alcionai/corso/src/pkg/logger"
	"github.com/alcionai/corso/src/pkg/path"
	"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
)

const itemID = "item_id"

var (
	err         error
	itemPath, _ = path.Build("tid", "own", path.ExchangeService, path.ContactsCategory, false, "foo")
)

func main() {
	ctx := clues.Add(context.Background(), "foo", "bar")
	log := logger.Ctx(ctx)

	// 1. Clues Ctx values are always added in .Ctx(); you don't
	// need to add them directly.
	//
	// preferred
	ctx = clues.Add(ctx, "item_id", itemID)
	logger.Ctx(ctx).Info("getting item")
	//
	// avoid
	ctx = clues.Add(ctx, "item_id", itemID)
	logger.Ctx(ctx).With(clues.In(ctx).Slice()...).Info("getting item")

	// 2. The last func to handle a context must add the clues to the error.
	//
	// preferred
	err := clues.WrapWC(ctx, err, "reason")
	// this dereference added for linter happiness
	_ = err

	// 3. Always extract structured data from errors.
	//
	// preferred
	log.With("error", err).Errorw("getting item", clues.InErr(err).Slice()...)
	//
	// avoid
	log.Errorw("getting item", "err", err)
	//
	// you can use the logger helper CtxErr() for the same results.
	// This helps to ensure all error values get packed into the logs
	// in the expected format.
	logger.CtxErr(ctx, err).Error("getting item")

	// 3. Protect pii in logs.
	// When it comes to protecting sensitive information, we only want
	// to hand loggers (and, by extension, clues errors) using one of
	// three approaches to securing values.
	//
	// First: plain, unhidden data.  This can only be logged if we are
	// absolutely assured that this data does not expose sensitive
	// information for a user.  Eg: internal ids and enums are fine to
	// log in plain text.  Everything else must be considered wisely.
	//
	// Second: manually concealed values.  Strings containing sensitive
	// info, and structs from external pacakges containing sensitive info,
	// can be logged by manually wrapping them with a clues.Hide() call.
	// Ex: clues.Hide(userName).  This will hash the value according to
	// the user's hash algorithm configuration.
	//
	// Third: managed string concealers.  Certain values have common
	// format and content, but appear commonly in the code as strings.
	// Examples include URLs and kopia repository paths.  These values
	// may have a concealer built specifically for them to maximize the
	// data we can view when debugging, instead of hashing the complete
	// string.  See graph/middleware.go LoggableURL{} and path/elements.go
	// LoggableDir{}.
	//
	// Fourth: structs that comply with clues.Concealer.  The Concealer
	// interface requires a struct to comply with Conceal() (for cases
	// where the struct is handed to a clues aggregator directly), and
	// fmt's Format(state, verb), where the assumption is the standard
	// format writer will be replaced with a Conceal() call (for cases
	// where the struct is handed to some non-compliant formatter/printer).
	//
	// preferred
	log.With(
		// internal type, safe to log plainly
		"resource_type", resource.Users,
		// string containing sensitive info, wrap with Hide()
		"user_name", clues.Hide("your_user_name@microsoft.example"),
		// string partially concealed by a managed concealer.
		"request_url", graph.LoggableURL("https://corsobackup.io"),
		// a concealer-compliant struct, safe to add plainly
		"storage_path", itemPath)
}
Output:

Example (Logger_standards)

ExampleLoggerStandards reviews code standards around logging in Corso.

package main

import (
	"context"

	"github.com/alcionai/corso/src/pkg/logger"
	"github.com/alcionai/corso/src/pkg/path"
)

const itemID = "item_id"

var (
	err         error
	itemPath, _ = path.Build("tid", "own", path.ExchangeService, path.ContactsCategory, false, "foo")
)

func main() {
	log := logger.Ctx(context.Background())

	// 1. Keep messages short. When possible, messages should state the current action.
	// Lowercase text, no ending punctuation.
	// This ensures logs are easy to scan, and simple to grok.
	//
	// preferred
	log.Info("getting item")
	// avoid
	log.Info("Getting one item from the service so that we can send it through the item feed.")

	// 2. Avoid statements like "unable to...", "failed to..", or "error when...".
	// Error level logs automatically imply a failure to do the action.
	//
	// preferred
	log.With("err", err).Error("connecting to repo")
	// avoid
	log.With("err", err).Error("unable to connect to repo")

	// 3. Do not fmt values into the message.  Use With() or -w() to add structured data.
	// By keeping dynamic data in a structured format, we maximize log readability,
	// and make logs very easy to search or filter in bulk, and very easy to control pii.
	//
	// preferred
	log.With("err", err).Error("getting item")
	log.Errorw("getting item", "err", err)
	// avoid
	log.Errorf("getting item %s: %v", itemID, err)

	// 4. Give data keys reasonable namespaces.  Use snake_case.
	// Overly generic keys can collide unexpectedly.
	//
	// preferred
	log.With("item_id", itemID).Info("getting item")
	// avoid
	log.With("id", itemID).Error("getting item")

	// 4. Avoid Warn-level logging.  Prefer Info or Error.
	// Minimize confusion/contention about what level a log
	// "should be".  Error during a failure, Info (or Debug)
	// otherwise.
	//
	// preferred
	log.With("err", err).Error("getting item")
	// avoid
	log.With("err", err).Warn("getting item")

	// 5. Avoid Panic/Fatal-level logging.  Prefer Error.
	// Panic and Fatal logging can crash the application without
	// flushing buffered logs and finishing out other telemetry.
	//
	// preferred
	log.With("err", err).Error("connecting to repo")
	// avoid
	log.With("err", err).Panic("connecting to repo")
}
Output:

Example (Seed)

ExampleSeed showcases seeding a logger into the context.

package main

import (
	"context"

	"github.com/alcionai/corso/src/pkg/logger"
)

func main() {
	// Before logging, a logger instance first needs to get seeded into
	// the context.  Seeding only needs to be done once.  For example
	// Corso's CLI layer seeds the logger in the cli initialization.
	ctx := context.Background()

	ls := logger.Settings{
		File:        logger.Stderr,
		Level:       logger.LLInfo,
		PIIHandling: logger.PIIPlainText,
	}

	ctx, log := logger.Seed(ctx, ls)

	// SDK consumers who configure their own zap logger can Set their logger
	// into the context directly, instead of Seeding a new one.
	ctx = logger.Set(ctx, log)

	// logs should always be flushed before exiting whichever func
	// seeded the logger.
	defer func() {
		_ = log.Sync() // flush all logs in the buffer
	}()

	// downstream, the logger will retrieve its configuration from
	// the context.
	func(ctx context.Context) {
		log := logger.Ctx(ctx)
		log.Info("hello, world!")
	}(ctx)
}
Output:

Index

Examples

Constants

View Source
const (
	LLDebug    logLevel = "debug"
	LLInfo     logLevel = "info"
	LLWarn     logLevel = "warn"
	LLError    logLevel = "error"
	LLDisabled logLevel = "disabled"
)
View Source
const (
	// use for cli/terminal
	LFText logFormat = "text"
	// use for cloud logging
	LFJSON logFormat = "json"
)
View Source
const (
	PIIHash      piiAlg = "hash"
	PIIMask      piiAlg = "mask"
	PIIPlainText piiAlg = "plaintext"
)
View Source
const (
	DebugAPIFN          = "debug-api-calls"
	LogFileFN           = "log-file"
	LogFormatFN         = "log-format"
	LogLevelFN          = "log-level"
	ReadableLogsFN      = "readable-logs"
	MaskSensitiveDataFN = "mask-sensitive-data"
)

flag names

View Source
const (
	Stderr = "stderr"
	Stdout = "stdout"
)

Variables

View Source
var (
	DebugAPIFV bool

	LogFormatFV         string
	LogLevelFV          string
	ReadableLogsFV      bool
	MaskSensitiveDataFV bool

	ResolvedLogFile string // logFileFV after processing

)

flag values

Functions

func AddLoggingFlags

func AddLoggingFlags(cmd *cobra.Command)

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 Ctx

func Ctx(ctx context.Context) *zap.SugaredLogger

Ctx retrieves the logger embedded in the context.

func CtxErr

func CtxErr(ctx context.Context, err error) *zap.SugaredLogger

CtxErr retrieves the logger embedded in the context and packs all of the structured data in the error inside it.

func CtxErrStack

func CtxErrStack(ctx context.Context, err error, skip int) *zap.SugaredLogger

CtxErrStack retrieves the logger embedded in the context and packs all of the structured data in the error inside it. If skip is non-zero, it skips the stack calls starting from the first. Skip always adds +1 to account for this wrapper.

func CtxOrSeed

func CtxOrSeed(ctx context.Context, set Settings) (context.Context, *zap.SugaredLogger)

CtxOrSeed attempts to retrieve the logger from the ctx. If not found, it generates a logger with the given settings and adds it to the context.

func CtxStack

func CtxStack(ctx context.Context, skip int) *zap.SugaredLogger

CtxStack retrieves the logger embedded in the context, and adds the stacktrace to the log info. If skip is non-zero, it skips the stack calls starting from the first. Skip always adds +1 to account for this wrapper.

func Flush

func Flush(ctx context.Context)

Flush writes out all buffered logs.

func ForceDebugLogLevel

func ForceDebugLogLevel() option

ForceDebugLogLevel reduces all logs emitted in the wrapper to debug level, independent of their original log level. Useful for silencing noisy dependency packages without losing the info altogether.

func GetLogFile

func GetLogFile(logFileFlagVal string) string

GetLogFile parses the log file. Uses the provided value, if populated, then falls back to the env var, and then defaults to stderr.

func Seed

Seed generates a logger within the context for later retrieval. 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 Set

func Set(ctx context.Context, logger *zap.SugaredLogger) context.Context

Set allows users to embed their own zap.SugaredLogger within the context.

func SetWithSettings

func SetWithSettings(
	ctx context.Context,
	logger *zap.SugaredLogger,
	set Settings,
) context.Context

SetWithSettings allows users to embed their own zap.SugaredLogger within the context and with the given logger settings.

func Wrap

func Wrap(zsl *zap.SugaredLogger, opts ...option) *wrapper

Wrap returns the sugaredLogger with an extended api used for dependency package interface compliance.

func WrapCtx

func WrapCtx(ctx context.Context, opts ...option) *wrapper

Wrap returns the logger in the package with an extended api used for dependency package interface compliance.

Types

type Settings

type Settings struct {
	File        string    // what file to log to (alt: stderr, stdout)
	Format      logFormat // whether to format as text (console) or json (cloud)
	Level       logLevel  // what level to log at
	PIIHandling piiAlg    // how to obscure pii
	LogStorage  bool      // Whether kopia logs should be added to the corso log.
}

Settings records the user's preferred logging settings.

func PreloadLoggingFlags

func PreloadLoggingFlags(args []string) Settings

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 (Settings) EnsureDefaults

func (s Settings) EnsureDefaults() Settings

EnsureDefaults sets any non-populated settings to their default value. exported for testing without circular dependencies.

type Writer

type Writer struct {
	Ctx context.Context
}

Writer is a wrapper that turns the logger embedded in the given ctx into an io.Writer. All logs are currently info-level.

func (Writer) Write

func (w Writer) Write(p []byte) (int, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL