Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion extrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,22 @@ func SpanNameFormatter(v Values) string {
return method
}

func spanStatus(code int) (codes.Code, string) {
// SpanStatus returns the span status code and error description based on the HTTP status code and error.
//
// Spec:
//
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, unless there was another error
// (e.g., network error receiving the response body; or 3xx codes with max redirects exceeded), in which case status
// MUST be set to Error.
//
// Reference:
// - [Span.Status](https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status)
// - [Recording errors on spans](https://opentelemetry.io/docs/specs/semconv/general/recording-errors/)
func SpanStatus(code int, err error) (codes.Code, string) {
if err != nil { // When the operation ends with an error, instrumentation: SHOULD set the span status code to Error
return codes.Error, err.Error()
}

if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
Expand Down
78 changes: 78 additions & 0 deletions extrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package echootel

import (
"context"
"errors"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)

func TestValues_ExtractRequest(t *testing.T) {
Expand Down Expand Up @@ -683,3 +685,79 @@ func TestSplitProto(t *testing.T) {
})
}
}

func TestSpanStatus(t *testing.T) {
var testCases = []struct {
name string
whenStatus int
whenError error
expectCode codes.Code
expectDesc string
}{
{
name: "error overrides status code",
whenStatus: 200,
whenError: errors.New("network error"),
expectCode: codes.Error,
expectDesc: "network error",
},
{
name: "invalid status code below range",
whenStatus: 99,
whenError: nil,
expectCode: codes.Error,
expectDesc: "Invalid HTTP status code 99",
},
{
name: "invalid status code above range",
whenStatus: 600,
whenError: nil,
expectCode: codes.Error,
expectDesc: "Invalid HTTP status code 600",
},
{
name: "server error 500",
whenStatus: 500,
whenError: nil,
expectCode: codes.Error,
expectDesc: "",
},
{
name: "server error 503",
whenStatus: 503,
whenError: nil,
expectCode: codes.Error,
expectDesc: "",
},
{
name: "informational status 100",
whenStatus: 100,
whenError: nil,
expectCode: codes.Unset,
expectDesc: "",
},
{
name: "success status 200",
whenStatus: 200,
whenError: nil,
expectCode: codes.Unset,
expectDesc: "",
},
{
name: "redirect status 302",
whenStatus: 302,
whenError: nil,
expectCode: codes.Unset,
expectDesc: "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
code, desc := SpanStatus(tc.whenStatus, tc.whenError)

assert.Equal(t, tc.expectCode, code)
assert.Equal(t, tc.expectDesc, desc)
})
}
}
10 changes: 3 additions & 7 deletions otel.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/labstack/echo/v5/middleware"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
Expand Down Expand Up @@ -213,30 +212,27 @@ func (config Config) ToMiddleware() (echo.MiddlewareFunc, error) {
}
}()

// serve the request to the next middleware
err := next(c)

if err != nil {
span.SetAttributes(semconv.ErrorType(err))
span.SetStatus(codes.Error, err.Error())
if config.OnNextError != nil {
config.OnNextError(c, err)
}
}

resp, status := echo.ResolveResponseStatus(c.Response(), err)
span.SetStatus(SpanStatus(status, err))

ev.HTTPResponseStatusCode = status
if resp != nil {
ev.HTTPResponseBodySize = resp.Size
}
span.SetStatus(spanStatus(status))

endAttributes := ev.SpanEndAttributes()
if config.SpanEndAttributes != nil {
endAttributes = config.SpanEndAttributes(c, &ev, endAttributes)
}
span.SetAttributes(endAttributes...)

// Record the server-side attributes.
iv := RecordValues{
RequestDuration: time.Since(requestStartTime),
ExtractedValues: ev,
Expand Down
Loading