Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up hooks and logger config #102

Merged
merged 4 commits into from
Jul 26, 2016
Merged

Clean up hooks and logger config #102

merged 4 commits into from
Jul 26, 2016

Conversation

akshayjshah
Copy link
Contributor

@akshayjshah akshayjshah commented Jul 25, 2016

This PR is a mish-mash of interwoven changes I made on a plane. Oops.

It accomplishes a few goals:

  1. It adds an Entry type, which packages together the data passed between loggers, hooks, and encoders. At the end of this series of diffs, we'll be able to move most of Logger.WriteEntry's logic onto the entry itself, which will remove the ugly type-cast inside jsonEncoder.
  2. It pulls implementation-agnostic logger configuration into a separate struct, which loggers can embed to reduce boilerplate. This also makes it easy for other logger implementations to work with zap's built-in options.
  3. It switches to representing times as floating-point seconds since epoch. Most aggregation and indexing systems can work with second timestamps, but very few of them can work with nanosecond timestamps, so this is a more sensible default.

Remaining tasks for this round of changes:

  1. Produce a similar implementation-agnostic configuration for encoders. This'll let us customize timestamp, level, and message handling, and it'll remove the type-cast from our encoders.
  2. Flatten the serialized logs.
  3. Lock file handles.
  4. Clean up docs and examples.

Akshay Shah added 2 commits July 25, 2016 12:22
Add a constructor so that users can also create no-op fields. I
considered calling this `Nop`, but `zap.Nop()` looks like it should
return a logger instead of a Field.
Reduce boilerplate by introducing an Entry struct and pulling common
logger elements into a Config struct (that can be shared between Logger
implementations). Along the way, start logging timestamps as
floating-point seconds since epoch.
// returns a modified message and an error.
type hook func(Level, string, KeyValue) (string, error)
// A hook is executed each time the logger writes a message. It can modify the
// message, but must not retain references to the message or any of its
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/message/entry (since the whole entry is mutable)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

final.bytes = append(final.bytes, `","ts":`...)
final.bytes = strconv.AppendInt(final.bytes, ts.UnixNano(), 10)
final.bytes = strconv.AppendFloat(final.bytes, timeToSeconds(e.Time), 'f', -1, 64)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test for writing out a future time (maybe +100 years) and ensure that it's written without exponents

@prashantv
Copy link
Collaborator

lgtm, just add a small test for the time formatting

@akshayjshah akshayjshah merged commit f9a34fd into master Jul 26, 2016
@akshayjshah akshayjshah deleted the ajs-msg branch July 26, 2016 17:37

var (
_timeNow = time.Now // for tests
_entryPool = sync.Pool{New: func() interface{} { return &Entry{} }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is pretty odd. ISTM that Entry is safe to copy, why bother using pointers at all? Just pass it around by value.

Copy link
Contributor Author

@akshayjshah akshayjshah Jul 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is safe to copy, but it's a little large - at some point pre-1.0, we should test to see which option is faster.

Amendment: it's possible that I'm misunderstanding your comment. In general, we need to pass entries to hooks as a pointer, since hooks should be able to modify the entry's message (see AddCaller as an example). However, you're right to point out that it may be faster to create the entry on the stack in Logger.log and pass a reference to the hooks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type Entry struct {
    Level   Level // int32
    Time    time.Time // int64, int32, pointer
    Message string // represented as reflect.StringHeader: uintptr, int
    enc     encoder // interface, two pointers
}

Assuming 64-bit, that gives us:

4 + 8 + 4 + 8 + 8 + 8 + 8 + 8 = 56 bytes

Hard to imagine that using a sync.Pool is faster?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding your amendment: some benchmarking is needed, since creating the entry on the stack and passing a reference to the hooks may still cause the Go compiler to promote the Entry to the heap. Such is the price of fast compilation :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants