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

Add a human-optimized text encoder #123

Merged
merged 5 commits into from
Sep 26, 2016
Merged

Add a human-optimized text encoder #123

merged 5 commits into from
Sep 26, 2016

Conversation

akshayjshah
Copy link
Contributor

Add a text encoder that's friendlier for human consumption. By default, the TextEncoder output looks like this:

[I] 1970-01-01T00:00:00Z Something happened. foo=bar baz=bing

The timestamp format is configurable via functional options, and the encoder includes support for unsigned ints (in advance of #116).

Resolves #59.

final.bytes = append(final.bytes, msg...)
}

// A TextOption is used to set options for a text encoder.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than add separate options for NoTime and TimeFormat etc, I would prefer to:

  • change the JSONOption interface method to be something like applyJSON and have the TextOption interface use applyText. This will allow a single formatter to apply to both.
  • Make TextFormatter implement TextOption` as well.
  • Now the same NoTime and TimeFormatter types can be used as both text options and JSON options

This reduces the interface and allows the user to use the same options for both encoders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I can do that. It makes the JSON options more complex, but it's eminently doable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To document an offline conversation we had quite a while ago:

Consolidating these is possible, but I think it'll make them appreciably more complex. For example, the JSON encoder supports an option that sets the key for the entry's message (e.g., {"msg": "foo"} or {"the_message": "foo"}). That's not a meaningful concept for the text encoder.

I don't mind whichever direction @sectioneight wants to go with this.

Choose a reason for hiding this comment

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

I'll choose the path of least resistance here :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Happy to figure out how best to clean up the API in a separate PR.

I'm leaning towards separating time formatters from knowing about keys, There can be separate options for formatting time, and for customizing the time key. That way the options that customize keys (like MessageKey and TimestampKey) only implement JSONOption but the time formatting options NoTime, EpochFormatter, RFC3339Formatter can apply to both JSON encoders and text encoders.

Choose a reason for hiding this comment

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

@prashantv can you open an issue describing the desired changes to the APIs so we make sure to follow up in the future? I'm one of those weird people that actually likes following up on loose ends

Copy link
Collaborator

Choose a reason for hiding this comment

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

Done,
#149

Choose a reason for hiding this comment

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

u r awsm

@ascandella
Copy link

This seems useful. Is this still being worked on? Would you like help shepherding it over the finish line?

@akshayjshah
Copy link
Contributor Author

@sectioneight Very much so - I'm out on paternity leave tomorrow, and I'm not sure when I'm going to get back to this.

@ascandella
Copy link

Cool, will do. @akshayjshah do you have any local/unpushed changes before I take over this branch?

@akshayjshah
Copy link
Contributor Author

Nope, this is it - all yours.

@ascandella ascandella force-pushed the ajs-text branch 2 times, most recently from fe7eb32 to 29876f2 Compare September 16, 2016 01:24
@ascandella
Copy link

Ok @prashantv -- I took a pass at implementing the state. I'm not sure if the semantics are correct yet -- is the AddMarshaler the only place we need to care?

@ascandella ascandella force-pushed the ajs-text branch 2 times, most recently from 38c3add to 00a304c Compare September 25, 2016 18:55
type textEncoder struct {
bytes []byte
timeFmt string
nested *atomic.Bool

Choose a reason for hiding this comment

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

I went with an atomic Bool here -- I'm pretty sure these encoders are supposed to be goroutine safe but I don't see any sync primitives on modifying e.g. enc.bytes in the various methods.

Is locking taken care of by the logging and should the encoder not have to worry about it?

Choose a reason for hiding this comment

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

I removed the atomics after I realized encoders are only used once. Not sure why GH is still showing this...

textLogger.Info("With Marshaler", zap.Marshaler("m", zap.LogMarshalerFunc(m)))

// Output:
// [I] With Marshaler m={loggable=yes number=1}

Choose a reason for hiding this comment

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

This sort of feels like it's less useful as an example but as a unit test. However, it doesn't look like the unit test framework is set up to do "end-to-end" (e.g. assert the actual output) and I wanted to show that I handles this case properly.

Is there a better place to put this?

Choose a reason for hiding this comment

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

Moved to text_logger_test.go

Copy link
Collaborator

Choose a reason for hiding this comment

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

Still see it here -- has it been removed from example_test.go?

Choose a reason for hiding this comment

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

$ grep Marshaler example_test.go
// no results

@ascandella ascandella force-pushed the ajs-text branch 2 times, most recently from 10a0c8f to f979bf7 Compare September 25, 2016 19:08
When logging to console, JSON isn't the most friendly output - it's nice
for machines but not optimal for humans. This PR adds a text encoder
that prioritizes human readability, along with a few time-handling
options for that encoder.

In advance of #116, this PR also includes support for unsigned ints.

func (enc *textEncoder) AddBool(key string, val bool) {
enc.addKey(key)
if val {
Copy link
Collaborator

@prashantv prashantv Sep 25, 2016

Choose a reason for hiding this comment

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

Can simplify to just use AppendBool here (same as #148)

Choose a reason for hiding this comment

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

Done

}

func (enc *textEncoder) AddFloat64(key string, val float64) {
enc.addKey(key)
Copy link
Collaborator

Choose a reason for hiding this comment

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

we can simplify to just use strconv.AppendFloat here. In the JSON encoder, the output for nan/infinity is quoted, so a normal would get: "num": 1 but nan would get "num": "+Inf"

For the text encoder, we don't need to quote the inf/nan, and AppendFloat already handles these correctly,
https://play.golang.org/p/W3FP95QIMc

Choose a reason for hiding this comment

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

Done

enc.firstNested = true
enc.bytes = append(enc.bytes, '{')
err := obj.MarshalLog(enc)
enc.bytes = append(enc.bytes, '}')
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should reset firstNested to false here. I think a failing test case for this would:

  • add an object where the MarhsalLog adds no fields
  • then add a normal field (like string)

So we'd want something like emptyObj={} something=val but we might get emptyObj={}something=val

Choose a reason for hiding this comment

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

Good catch! Fixed and added negative test

@ascandella
Copy link

Ok @prashantv, I think I've addressed all comments (and nice catch on the nested error!)

@ascandella
Copy link

Still getting used to the new github review flow. Are there more comments to be addressed?

Copy link
Collaborator

@prashantv prashantv left a comment

Choose a reason for hiding this comment

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

lgtm, I think some of the examples can be removed now that they're in text_logger_test.go

// [I] This is a text log.
}

func ExampleNew_textEncoderSingleCurlyBrace() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we remove this example now that the integration test covers it

Choose a reason for hiding this comment

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

Yes, it's removed. Ugh, I still don't understand the new GH UI

textLogger.Info("With Marshaler", zap.Marshaler("m", zap.LogMarshalerFunc(m)))

// Output:
// [I] With Marshaler m={loggable=yes number=1}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Still see it here -- has it been removed from example_test.go?

@ascandella
Copy link

@prashantv updated by moving out of example_test -- does your approval mean a self-merge is now appropriate? Or what's your team's process on GitHub?

@prashantv
Copy link
Collaborator

Yep, for contributors who are part of the organization or project, typically approval means self-merge. For external contributors, the reviewer tends to merge.

Since you are in the org, feel free to self-merge! Thanks for wrapping this up @sectioneight

@prashantv
Copy link
Collaborator

Forgot to mention, suggest "Squash and merge" -- and feel free to edit the title/body of the squashed commit, rather than leaving the default list of all commits.

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.

4 participants