Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hashicorp/go-hclog
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.5.0
Choose a base ref
...
head repository: hashicorp/go-hclog
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.6.0
Choose a head ref
  • 9 commits
  • 5 files changed
  • 5 contributors

Commits on Apr 3, 2023

  1. Copy the full SHA
    3b9926b View commit details

Commits on Apr 10, 2023

  1. Merge pull request #128 from hashicorp/tsccr-auto-pinning/trusted/202…

    …3-04-03
    
    SEC-090: Automated trusted workflow pinning (2023-04-03)
    evanphx authored Apr 10, 2023
    Copy the full SHA
    f7ed9e4 View commit details

Commits on Nov 10, 2023

  1. Copy the full SHA
    04ed887 View commit details
  2. Copy the full SHA
    3d49354 View commit details

Commits on Nov 14, 2023

  1. Update intlogger.go

    Co-authored-by: Paul Banks <[email protected]>
    evanphx and banks authored Nov 14, 2023
    Copy the full SHA
    4911d46 View commit details

Commits on Nov 27, 2023

  1. Updated go doc to make it clear InferLevelsWithTimestamp relies on In…

    …ferLevels being true
    Peter Wilson committed Nov 27, 2023
    Copy the full SHA
    370a023 View commit details
  2. Tweak README

    Peter Wilson committed Nov 27, 2023
    Copy the full SHA
    748cdbc View commit details
  3. Merge pull request #135 from hashicorp/peteski22/docs/standard-logger…

    …-options-infer-levels-with-timestamp
    
    Docs: InferLevelsWithTimestamp relies on InferLevels being true
    evanphx authored Nov 27, 2023
    Copy the full SHA
    3d50de2 View commit details

Commits on Dec 4, 2023

  1. Merge pull request #134 from hashicorp/f-better-levels

    Implement the ability to more logically share level hierarchies
    evanphx authored Dec 4, 2023
    Copy the full SHA
    852f2c3 View commit details
Showing with 183 additions and 16 deletions.
  1. +2 −2 .github/workflows/test.yml
  2. +3 −2 README.md
  3. +77 −12 intlogger.go
  4. +19 −0 logger.go
  5. +82 −0 logger_test.go
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -15,10 +15,10 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
go-version: ${{ matrix.go-version }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Test
run: go test -v ./...
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -140,9 +140,10 @@ log.Printf("[DEBUG] %d", 42)
... [DEBUG] my-app: 42
```

Notice that if `appLogger` is initialized with the `INFO` log level _and_ you
Notice that if `appLogger` is initialized with the `INFO` log level, _and_ you
specify `InferLevels: true`, you will not see any output here. You must change
`appLogger` to `DEBUG` to see output. See the docs for more information.

If the log lines start with a timestamp you can use the
`InferLevelsWithTimestamp` option to try and ignore them.
`InferLevelsWithTimestamp` option to try and ignore them. Please note that in order
for `InferLevelsWithTimestamp` to be relevant, `InferLevels` must be set to `true`.
89 changes: 77 additions & 12 deletions intlogger.go
Original file line number Diff line number Diff line change
@@ -79,6 +79,16 @@ type intLogger struct {
writer *writer
level *int32

// The value of curEpoch the last time we performed the level sync process
ownEpoch uint64

// Shared amongst all the loggers created in this hierachy, used to determine
// if the level sync process should be run by comparing it with ownEpoch
curEpoch *uint64

// The logger this one was created from. Only set when syncParentLevel is set
parent *intLogger

headerColor ColorOption
fieldColor ColorOption

@@ -88,6 +98,7 @@ type intLogger struct {

// create subloggers with their own level setting
independentLevels bool
syncParentLevel bool

subloggerHook func(sub Logger) Logger
}
@@ -129,9 +140,9 @@ func newLogger(opts *LoggerOptions) *intLogger {
}

var (
primaryColor ColorOption = ColorOff
headerColor ColorOption = ColorOff
fieldColor ColorOption = ColorOff
primaryColor = ColorOff
headerColor = ColorOff
fieldColor = ColorOff
)
switch {
case opts.ColorHeaderOnly:
@@ -152,8 +163,10 @@ func newLogger(opts *LoggerOptions) *intLogger {
mutex: mutex,
writer: newWriter(output, primaryColor),
level: new(int32),
curEpoch: new(uint64),
exclude: opts.Exclude,
independentLevels: opts.IndependentLevels,
syncParentLevel: opts.SyncParentLevel,
headerColor: headerColor,
fieldColor: fieldColor,
subloggerHook: opts.SubloggerHook,
@@ -194,7 +207,7 @@ const offsetIntLogger = 3
// Log a message and a set of key/value pairs if the given level is at
// or more severe that the threshold configured in the Logger.
func (l *intLogger) log(name string, level Level, msg string, args ...interface{}) {
if level < Level(atomic.LoadInt32(l.level)) {
if level < l.GetLevel() {
return
}

@@ -597,7 +610,7 @@ func (l *intLogger) logJSON(t time.Time, name string, level Level, msg string, a
vals := l.jsonMapEntry(t, name, level, msg)
args = append(l.implied, args...)

if args != nil && len(args) > 0 {
if len(args) > 0 {
if len(args)%2 != 0 {
cs, ok := args[len(args)-1].(CapturedStacktrace)
if ok {
@@ -718,27 +731,27 @@ func (l *intLogger) Error(msg string, args ...interface{}) {

// Indicate that the logger would emit TRACE level logs
func (l *intLogger) IsTrace() bool {
return Level(atomic.LoadInt32(l.level)) == Trace
return l.GetLevel() == Trace
}

// Indicate that the logger would emit DEBUG level logs
func (l *intLogger) IsDebug() bool {
return Level(atomic.LoadInt32(l.level)) <= Debug
return l.GetLevel() <= Debug
}

// Indicate that the logger would emit INFO level logs
func (l *intLogger) IsInfo() bool {
return Level(atomic.LoadInt32(l.level)) <= Info
return l.GetLevel() <= Info
}

// Indicate that the logger would emit WARN level logs
func (l *intLogger) IsWarn() bool {
return Level(atomic.LoadInt32(l.level)) <= Warn
return l.GetLevel() <= Warn
}

// Indicate that the logger would emit ERROR level logs
func (l *intLogger) IsError() bool {
return Level(atomic.LoadInt32(l.level)) <= Error
return l.GetLevel() <= Error
}

const MissingKey = "EXTRA_VALUE_AT_END"
@@ -854,12 +867,62 @@ func (l *intLogger) resetOutput(opts *LoggerOptions) error {
// Update the logging level on-the-fly. This will affect all subloggers as
// well.
func (l *intLogger) SetLevel(level Level) {
atomic.StoreInt32(l.level, int32(level))
if !l.syncParentLevel {
atomic.StoreInt32(l.level, int32(level))
return
}

nsl := new(int32)
*nsl = int32(level)

l.level = nsl

l.ownEpoch = atomic.AddUint64(l.curEpoch, 1)
}

func (l *intLogger) searchLevelPtr() *int32 {
p := l.parent

ptr := l.level

max := l.ownEpoch

for p != nil {
if p.ownEpoch > max {
max = p.ownEpoch
ptr = p.level
}

p = p.parent
}

return ptr
}

// Returns the current level
func (l *intLogger) GetLevel() Level {
return Level(atomic.LoadInt32(l.level))
// We perform the loads immediately to keep the CPU pipeline busy, which
// effectively makes the second load cost nothing. Once loaded into registers
// the comparison returns the already loaded value. The comparison is almost
// always true, so the branch predictor should hit consistently with it.
var (
curEpoch = atomic.LoadUint64(l.curEpoch)
level = Level(atomic.LoadInt32(l.level))
own = l.ownEpoch
)

if curEpoch == own {
return level
}

// Perform the level sync process. We'll avoid doing this next time by seeing the
// epoch as current.

ptr := l.searchLevelPtr()
l.level = ptr
l.ownEpoch = curEpoch

return Level(atomic.LoadInt32(ptr))
}

// Create a *log.Logger that will send it's data through this Logger. This
@@ -912,6 +975,8 @@ func (l *intLogger) copy() *intLogger {
if l.independentLevels {
sl.level = new(int32)
*sl.level = *l.level
} else if l.syncParentLevel {
sl.parent = l
}

return &sl
19 changes: 19 additions & 0 deletions logger.go
Original file line number Diff line number Diff line change
@@ -233,6 +233,7 @@ type StandardLoggerOptions struct {
// [DEBUG] and strip it off before reapplying it.
// The timestamp detection may result in false positives and incomplete
// string outputs.
// InferLevelsWithTimestamp is only relevant if InferLevels is true.
InferLevelsWithTimestamp bool

// ForceLevel is used to force all output from the standard logger to be at
@@ -303,6 +304,24 @@ type LoggerOptions struct {
// will not affect the parent or sibling loggers.
IndependentLevels bool

// When set, changing the level of a logger effects only it's direct sub-loggers
// rather than all sub-loggers. For example:
// a := logger.Named("a")
// a.SetLevel(Error)
// b := a.Named("b")
// c := a.Named("c")
// b.GetLevel() => Error
// c.GetLevel() => Error
// b.SetLevel(Info)
// a.GetLevel() => Error
// b.GetLevel() => Info
// c.GetLevel() => Error
// a.SetLevel(Warn)
// a.GetLevel() => Warn
// b.GetLevel() => Warn
// c.GetLevel() => Warn
SyncParentLevel bool

// SubloggerHook registers a function that is called when a sublogger via
// Named, With, or ResetNamed is created. If defined, the function is passed
// the newly created Logger and the returned Logger is returned from the
82 changes: 82 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
@@ -586,6 +586,88 @@ func TestLogger(t *testing.T) {
t.Fatal("output from disabled logger:", str)
}
})

t.Run("sub-loggers levels don't bubble upward", func(t *testing.T) {
var buf bytes.Buffer

logger := New(&LoggerOptions{
Name: "root",
Output: &buf,
SyncParentLevel: true,
})

another := logger.Named("sublogger")
another.SetLevel(Error)

logger.Info("this is test")
str := buf.String()
dataIdx := strings.IndexByte(str, ' ')
rest := str[dataIdx+1:]
assert.Equal(t, "[INFO] root: this is test\n", rest)

buf.Reset()

a := logger.Named("a")
b := a.Named("b")
c := a.Named("c")

a.SetLevel(Error)

b.Info("this is a test")

require.Empty(t, buf.String())

b.SetLevel(Info)

assert.Equal(t, Error, a.GetLevel())

a.SetLevel(Error)

assert.Equal(t, Error, b.GetLevel())

assert.Equal(t, Error, c.GetLevel())

// Make sure that setting a sibling logger doesn't confuse
// when b had previously had it's own level.
c.SetLevel(Info)

assert.Equal(t, Error, b.GetLevel())
})

t.Run("level sync example", func(t *testing.T) {
var buf bytes.Buffer

logger := New(&LoggerOptions{
Name: "root",
Output: &buf,
SyncParentLevel: true,
})

s := assert.New(t)

a := logger.Named("a")
a.SetLevel(Error)
b := a.Named("b")
c := a.Named("c")
s.Equal(Error, b.GetLevel())
s.Equal(Error, c.GetLevel())

b.SetLevel(Info)
s.Equal(Error, a.GetLevel())
s.Equal(Info, b.GetLevel())
s.Equal(Error, c.GetLevel())

a.SetLevel(Warn)
s.Equal(Warn, a.GetLevel())
s.Equal(Warn, b.GetLevel())
s.Equal(Warn, c.GetLevel())

logger.SetLevel(Trace)
s.Equal(Trace, logger.GetLevel())
s.Equal(Trace, a.GetLevel())
s.Equal(Trace, b.GetLevel())
s.Equal(Trace, c.GetLevel())
})
}

func TestLogger_leveledWriter(t *testing.T) {