-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
log/slog: JSONHandler should format everything like json.Marshal #59345
Comments
Copying my comment on the CL over here for discussion purposes. Here's an overview of possible representations of a duration and their strengths and weaknesses. There is no obviously right approach. All have flaws of some kind.
|
Change https://go.dev/cl/480735 mentions this issue: |
Another possibility is a JSON string using the ISO 8601 representation of durations. I don't much like it, but, as far as I know, it's the only standardized string representation of a duration, so perhaps worth considering. For the fractional number of seconds option, it would also be possible to use scientific notation: 1e-9 is a reasonably intuitive, concise representation of 1 nanosecond. |
Oh right, I forgot about ISO 8601's duration format. I personally have never seen that format used in practice. My observation is that there's a general trend of moving away from ISO 8601 since it is overly complicated and esoteric. In fact RFC 3339 exists to document a particular subset of ISO 8601 so that the internet can more readily agree upon how a timestamp is represented. |
If you take the output of time.Duration.String, uppercase it and prepend I've seen ISO 8601 durations used in some contexts, notably https://www.w3.org/TR/xmlschema-2/#duration It also looks like it's coming to JavaScript: https://tc39.es/proposal-temporal/docs/duration.html FWIW, I would choose a JSON string using the format of time.Duration.String. The Go standard library has (in time.Duration.String) chosen a string format for durations, and it ought to be consistent about that. I also think it's a tasteful, well-designed representation that is concise, understandable and unambiguous. Though it's not the same as ISO8601 durations, I think there's a good reason for all the ways in which it diverges, and it's not inherently Go-specific. |
I have also never seen ISO 8601 used in practice. A duration string has a basic set of expectations which I guess are pretty unambiguous, and pretty well represented by the behavior of the standard library's time.ParseDuration function. For example |
How is this going to be implemented? Is it just a special case for an Attr containing a Duration key or value? I assume we're not trying to handle durations nested inside other JSON objects? |
Good point. Yes, it's only a special case for In light of that inconsistency, should we consider matching |
This proposal has been added to the active column of the proposals project |
There is no advantage of continuing to replicate the current encoding/json behaviour when it's clearly the worst of all the choices described in #59345 (comment). It would be nice to start with a more readable format or there is risk of getting stuck with it. While on development side there is a fairly easy upgrade path to the potential json/v2 behaviour, there will be resistance to it because then we would have either two different log rapresentations depending on time and version used or having to go back and retroactivly modify the old logs to have a single consistent rapresentation. This second option would be a heavy burdon to most projects. |
Fundamentally, "slog" will be released before any hypothetical v2 "json". If v2 "json" happens, there must be a way that users can switch from whether "slog" is using v1 or v2 "json" semantics. While the representation of |
Why not just ship a json v2 handler when there is such a thing? Shouldn’t be too troublesome for folks to switch their handlers |
Would it be possible to "backport" the Duration format to jsonv1 (under an option) in time for it to be used by slog and have the same behaviour also for nested structs? |
See #59365 (comment). The tentative plan is for v1 and v2 to use the same implementation under the hood and for every divergence in behavior to be configurable through options. Thus, v1 and v2 are technically the same thing, but just that they start with different default options. You could make v1 more v2-like by specifying the appropriate options, or make v2 more v1-like with the appropriate set of options. The options would be shared between v1 and v2 and be composable with each other. |
I agree that the number of nanoseconds is the worst format in the list, but maybe there's something worse.
For the first three, |
One option I don’t think I’ve seen suggested: Nanoseconds with a “ns” suffix. This is simpler for non-Go parsers to process than the full For what it’s worth, the protobuf JSON encoding represents durations as fractional seconds with a “s” suffix. |
I find the use of a "ns" suffix kind of arbitrary. I do like the protobuf approach of using a JSON string with a "s" suffix. Both representations are compatible with |
I've become convinced (see above) that the current JSONHandler should behave just like the current encoding/json. That goes for NaNs and infinities too. json.Marshal fails on those, but slog won't fail; it will output a JSON string with the error. A simple ReplaceAttr function can restore the special behavior (and the inconsistency along with it). |
Code which calls slog.Info has no way of knowing which handler will ultimately render the log events; nothing guarantees a JSONHandler. In any case, arbitrary structs passed as log values guarantee no specific output regardless of handler, and are therefore generally an error. Switching from explicit key-val fields to the example |
Based on the discussion above, this proposal seems like a likely accept. |
One small change: we will retain special handling of |
Change https://go.dev/cl/486855 mentions this issue: |
No change in consensus, so accepted. 🎉 |
@jba does this not include the special casing for source info? |
@seankhliao, the implementation special-cases Source, that is true. But it behaves exactly as json.Marshal would, except the field names are lower-cased. https://go.dev/cl/494035 adds json tags to the fields of |
Remove the special-case handling of NaN and infinities from appendJSONValue, making JSONHandler behave almost exactly like a json.Encoder without HTML escaping. The only differences are: - Encoding errors are turned into strings, instead of causing the Handle method to fail. - Values of type `error` are displayed as strings by calling their `Error` method. Fixes golang#59345. Change-Id: Id06bd952bbfef596e864bd5fd3f9f4f178f738c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/486855 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
Remove the special-case handling of NaN and infinities from appendJSONValue, making JSONHandler behave almost exactly like a json.Encoder without HTML escaping. The only differences are: - Encoding errors are turned into strings, instead of causing the Handle method to fail. - Values of type `error` are displayed as strings by calling their `Error` method. Fixes golang#59345. Change-Id: Id06bd952bbfef596e864bd5fd3f9f4f178f738c9 Reviewed-on: https://go-review.googlesource.com/c/go/+/486855 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
Ah, I missed this discussion. I just wanted to add (but probably too late) that errors should by my experience be JSON formatted as |
[edit: special case for
error
values]This issue now proposes that
JSONHandler
should format values just like ajson.Encoder
withSetEscapeHTML(false)
.The only exceptions are:
error
are formatted as the string obtained from calling theError
method.json.Encoder.Encode
returns a non-nil error, that error is formatted as string, instead of being returned fromHandle
.Original top post:
JSONHandler should format
time.Duration
values withtime.Duration.String
.For the most part, JSONHandler conforms to encoding/json.Marshal, which formats a duration as an integer number of nanoseconds.
Using the output of time.Duration.String is better for two reasons:
No unit is associated with the integer: you have to know it's nanos. If you didn't already know it was generated by Go, you'd probably think it was seconds. If computer clocks ever get faster, we may achieve higher resolutions, but we won't be able to change the format (too many consumers would silently break) so it would always be an approximation. The string format's interpretation is clear and extensible.
The integer can overflow the maximum integer that JSON can represent exactly.
json.Marshal
will output that as a JSON number, which may cause JSON parsers to fail or return values that only approximate the original.This change will slow down JSONHandler for durations, but favoring readability and correctness over speed is the right choice in this case. One can always write a
ReplaceAttr
function to return an int64 and get the fast behavior.Original proposal: #56345.
The text was updated successfully, but these errors were encountered: