1

I have the following code:


func handleMessage(msg Message, out chan<- string, w withdrawalSocketListener) {
    tracer := tracing.GetTracer()
    ctx, span := tracer.Start(context.Background(), "receive-withdrawal-msg-tcp-socket")
    defer func() {
        span.End()
    }()

    wr := &model.WithdrawalRequest{}

    _, unmarshallSpan := tracer.Start(ctx, "unmarshalling") // I should also End() this span after the if statement, but this if case with a return messes it up
    if err := json.Unmarshal(msg.GetBody(), wr); err != nil {
        log.Info().Err(err).Msg("failed to parse tcp inbound body")
        out <- "NACK"
        return
    }

    err := w.withdrawalService.Withdraw(wr)
    if err != nil {
        log.Info().Err(err).Msg("failed to process withdraw request")
        out <- "NACK"
        return
    }

    out <- "ACK"
}

As you can see with the unmarshallSpan, I want to end it after the if statement, but I do not know how to stop it correctly after the if check that comes right after it. I also want to start another child trace just before the withdrawalService.Withdraw function, so the other child of unmarshalling should stop there. I also do need the return statements which makes it a bit harder. So my question is, how to close the child span unmarshallSpan 'correctly'?

BrianM
  • 951
  • 2
  • 12
  • 29
  • Use like `defer unmarshallSpan.End()` right after the `unmarshallSpan` start , As this will ensure the span is ended properly. Also see https://stackoverflow.com/questions/24720097/golang-defer-behavior, https://go.dev/tour/flowcontrol/12 – PRATHEESH PC Jun 12 '23 at 02:54
  • But I want it to stop before the `w.withdrawalService.Withdraw(wr)` occurs, with your suggestion it will run until the end of the function which is not something I want to achieve. I also want to create a span eventually for the Withdraw function in order to see how it all flows – BrianM Jun 12 '23 at 14:57

1 Answers1

0

The simple answer here is to just call End twice. I would also recommend setting the span to represent the error:

_, unmarshallSpan := tracer.Start(ctx, "unmarshalling")
if err := json.Unmarshal(msg.GetBody(), wr); err != nil {
    log.Info().Err(err).Msg("failed to parse tcp inbound body")
    out <- "NACK"
    unmarshallSpan.RecordError(err)
    unmarshallSpan.SetStatus(codes.Error, "failed to parse tcp inbound body")
    unmarshallSpan.End()
    return
}
unmarshallSpan.End()

However, outside of the simplest of cases, it likely make sense to create a helper function to make this repeatable (so you can also do the same for w.withdrawalService.Withdraw(wr)). For example:

func withSpan(ctx context.Context, name string, f func() error) error {
    _, s := tracing.GetTracer().Start(ctx, name)
    defer s.End()
    err := f()
    if err != nil {
        s.RecordError(err)
        s.SetStatus(codes.Error, "")
    }
    return err
}

Your example would then use withSpan like the following:

err := withSpan(ctx, "unmarshalling", func() error {
    return json.Unmarshal(msg.GetBody(), wr)
})
if err != nil {
    log.Info().Err(err).Msg("failed to parse tcp inbound body")
    out <- "NACK"
    return
}
MrAlias
  • 1,316
  • 15
  • 26