Yuxuan Wang created THRIFT-5745:
-----------------------------------

             Summary: go: Implement slog.LogValuer for exceptions and/or structs
                 Key: THRIFT-5745
                 URL: https://issues.apache.org/jira/browse/THRIFT-5745
             Project: Thrift
          Issue Type: Task
          Components: Go - Compiler
            Reporter: Yuxuan Wang


To follow up on THRIFT-5744, implement 
[slog.LogValuer|https://pkg.go.dev/log/slog#LogValuer] on compiler generated 
exceptions, and maybe also structs.

The current problem is that when logging an exception, the Stringer 
implementation will be used, but that usually gives you quite unhelpful string, 
especially when optional fields are used.

For example we have [this exception 
defined|https://github.com/reddit/baseplate.py/blob/8c446337dab6a1692900decf9cd01f3f4e39d764/baseplate/thrift/baseplate.thrift#L197]:

{code:java}
exception Error {
    1: optional i32 code
    2: optional string message
    3: optional map<string, string> details
    4: optional bool retryable
}
{code}

When you create an instance of it:

{code:go}
return &baseplate.Error{
  Code: thrift.Int32Ptr(404),
  Message: thrift.Pointer("no such id"),
}
{code}

And then log that error, instead of getting something useful with the code 
(404) and message ("no such id") in it, you get something like this instead 
because this is how we implemented fmt.Stringer from the compiler:

{code:go}
Error({Code:0xc000426648 Message:0xc00041ca10 Details:map[] Retryable:<nil>})
{code}

The compiler generates implementation for json.Marshaler for all exceptions, 
which will be much more helpful. The marshaled json for such error would be:

{code:json}
{"code":400,"message":"no such id"}
{code}

They are also much more expensive, so probably not suitable to replace the 
current Stringer implementation, but when logging them, it's probably worth it 
to implement slog.LogValuer so we can log something more useful, probably like 
this:

{code:go}
func (p *Error) LogValue() slog.Value{
  var sb strings.Builder
  sb.WriteString("baseplate.Error")
  bytes, err := json.Marshal(p)
  if err != nil {
    // should not happen
    return slog.StringValue(fmt.Sprintf("baseplate.Error.LogValue: failed to 
marshal json: %v", err))
  }
  sb.Write(bytes)
  return slog.StringValue(sb.String())
}
{code}

Which will make the error logged show as:

{code:json}
baseplate.Error{"code":400,"message":"no such id"}
{code}

Some open questions:

# Is this also useful for structs?
# Do we want to make this optional (e.g. only generate LogValue function when a 
compiler flag is passed in)?



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to