Skip to content

Commit 5d1e2da

Browse files
Ignore some known errors and convert some errors to metric
ref DEV-3102
2 parents 80ecc49 + 3026202 commit 5d1e2da

12 files changed

Lines changed: 161 additions & 45 deletions

File tree

.vettedpositions

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@
5959
/pkg/auth/handler/webapp/auth_entry_point_middleware.go:31:31: requestcontext
6060
/pkg/auth/handler/webapp/auth_entry_point_middleware.go:32:35: requestcontext
6161
/pkg/auth/handler/webapp/authflow_change_password.go:96:26: requestcontext
62-
/pkg/auth/handler/webapp/authflow_controller.go:1012:30: requestcontext
63-
/pkg/auth/handler/webapp/authflow_controller.go:1017:24: requestcontext
64-
/pkg/auth/handler/webapp/authflow_controller.go:1025:19: requestcontext
65-
/pkg/auth/handler/webapp/authflow_controller.go:1033:18: requestcontext
62+
/pkg/auth/handler/webapp/authflow_controller.go:1020:30: requestcontext
63+
/pkg/auth/handler/webapp/authflow_controller.go:1025:24: requestcontext
64+
/pkg/auth/handler/webapp/authflow_controller.go:1033:19: requestcontext
65+
/pkg/auth/handler/webapp/authflow_controller.go:1041:18: requestcontext
6666
/pkg/auth/handler/webapp/authflow_create_password.go:132:26: requestcontext
6767
/pkg/auth/handler/webapp/authflow_enter_oob_otp.go:156:26: requestcontext
6868
/pkg/auth/handler/webapp/authflow_enter_password.go:139:26: requestcontext

pkg/auth/handler/saml/login_finish.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (h *LoginFinishHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
3838

3939
authInfoID, ok := h.AuthenticationInfoResolver.GetAuthenticationInfoID(r)
4040
if !ok {
41-
logger.Warn(ctx, "authentication info id is missing")
41+
logger.WithSkipLogging().Warn(ctx, "authentication info id is missing")
4242
// Maybe the user visited the page directly, tell him not to do so.
4343
http.Error(rw, "invoking this endpoint directly is not supported", http.StatusBadRequest)
4444
return

pkg/auth/handler/webapp/authflow_controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,11 @@ func (c *AuthflowController) makeSessionOptionsFromSAML(ctx context.Context, sam
950950
func (c *AuthflowController) deriveFlowNameFromOAuthSession(ctx context.Context, oauthSessionID string, flowType authflow.FlowType) (string, error) {
951951
entry, err := c.OAuthSessions.Get(ctx, oauthSessionID)
952952
if err != nil {
953-
return "", err
953+
if errors.Is(err, oauthsession.ErrNotFound) {
954+
return "", errors.Join(webapp.ErrInvalidSession, err)
955+
} else {
956+
return "", err
957+
}
954958
}
955959

956960
req := entry.T.AuthorizationRequest
@@ -968,7 +972,11 @@ func (c *AuthflowController) deriveFlowNameFromOAuthSession(ctx context.Context,
968972
func (c *AuthflowController) deriveFlowNameFromSAMLSession(ctx context.Context, samlSessionID string, flowType authflow.FlowType) (string, error) {
969973
samlSession, err := c.SAMLSessions.Get(ctx, samlSessionID)
970974
if err != nil {
971-
return "", err
975+
if errors.Is(err, samlsession.ErrNotFound) {
976+
return "", errors.Join(webapp.ErrInvalidSession, err)
977+
} else {
978+
return "", err
979+
}
972980
}
973981

974982
specifiedFlowGroup := "" // SAML cannot specify flow group in request

pkg/lib/config/configsource/database.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,16 @@ func (d *Database) invalidateAppByDomain(ctx context.Context, domain string) {
337337
})
338338
if err != nil {
339339
logger := DatabaseLogger.GetLogger(ctx)
340-
logger.WithError(err).Error(ctx, "failed to invalidate app cache by domain",
341-
slog.String("domain", domain),
342-
)
340+
if errors.Is(err, ErrAppNotFound) {
341+
// If the domain was deleted
342+
logger.Info(ctx, "failed to invalidate app cache because domain not found",
343+
slog.String("domain", domain),
344+
)
345+
} else {
346+
logger.WithError(err).Error(ctx, "failed to invalidate app cache by domain",
347+
slog.String("domain", domain),
348+
)
349+
}
343350
}
344351
}
345352

pkg/lib/oauthrelyingparty/oauthrelyingpartyutil/error.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/authgear/oauthrelyingparty/pkg/api/oauthrelyingparty"
55

66
"github.com/authgear/authgear-server/pkg/api/apierrors"
7+
"github.com/authgear/authgear-server/pkg/util/slogutil"
78
)
89

910
var InvalidConfiguration = apierrors.InternalError.WithReason("InvalidConfiguration")
@@ -17,3 +18,19 @@ func NewOAuthError(errResp *oauthrelyingparty.ErrorResponse) error {
1718
"error_uri": errResp.ErrorURI,
1819
})
1920
}
21+
22+
type OAuthRelyingPartyInternalError struct {
23+
err error
24+
IsLoggingSkippable bool
25+
}
26+
27+
var _ error = (*OAuthRelyingPartyInternalError)(nil)
28+
var _ slogutil.LoggingSkippable = (*OAuthRelyingPartyInternalError)(nil)
29+
30+
func (e *OAuthRelyingPartyInternalError) Error() string {
31+
return e.err.Error()
32+
}
33+
34+
func (e *OAuthRelyingPartyInternalError) SkipLogging() bool {
35+
return e.IsLoggingSkippable
36+
}

pkg/lib/oauthrelyingparty/oauthrelyingpartyutil/oidc.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ func FetchOIDCDiscoveryDocument(ctx context.Context, client *http.Client, endpoi
5050
}
5151

5252
if resp.StatusCode != http.StatusOK {
53-
return nil, fmt.Errorf("failed to fetch OIDC discovery document: unexpected status code: %d", resp.StatusCode)
53+
return nil, &OAuthRelyingPartyInternalError{
54+
err: fmt.Errorf("failed to fetch OIDC discovery document: unexpected status code: %d", resp.StatusCode),
55+
IsLoggingSkippable: true,
56+
}
5457
}
5558

5659
var document OIDCDiscoveryDocument

pkg/portal/service/app.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,14 @@ func (s *AppService) Create(ctx context.Context, userID string, id string) (*mod
406406
}
407407
err = s.AppConfigs.Create(ctx, createAppOpts)
408408
if err != nil {
409-
// TODO(portal): cleanup orphaned resources created from failed app creation
410-
logger.WithError(err).Error(ctx, "failed to create app", slog.String("app_id", id))
411-
return err
409+
if errors.Is(err, ErrDuplicatedAppID) {
410+
logger.Info(ctx, "failed to create duplicated app", slog.String("app_id", id))
411+
return err
412+
} else {
413+
// TODO(portal): cleanup orphaned resources created from failed app creation
414+
logger.WithError(err).Error(ctx, "failed to create app", slog.String("app_id", id))
415+
return err
416+
}
412417
}
413418

414419
err = s.DefaultDomains.CreateAllDefaultDomains(ctx, id)

pkg/util/slogutil/metric.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,39 @@ import (
55
"database/sql"
66
"database/sql/driver"
77
"errors"
8+
"net"
89
"os"
910
"syscall"
1011

1112
"github.com/lib/pq"
13+
"go.opentelemetry.io/otel/attribute"
14+
"go.opentelemetry.io/otel/metric"
15+
"golang.org/x/sys/unix"
16+
17+
"github.com/authgear/authgear-server/pkg/util/otelutil"
1218
)
1319

20+
// MetricOptionAttributeKeyValue is a MetricOption that adds an attribute.
21+
type MetricOptionAttributeKeyValue struct {
22+
attribute.KeyValue
23+
}
24+
25+
// ToOtelMetricOption implements otelutil.MetricOption.
26+
func (o MetricOptionAttributeKeyValue) ToOtelMetricOption() metric.MeasurementOption {
27+
return metric.WithAttributes(o.KeyValue)
28+
}
29+
1430
// MetricErrorName is a symbolic name for some errors
1531
type MetricErrorName string
1632

1733
const (
1834
MetricErrorNameContextCanceled MetricErrorName = "context.canceled"
1935
MetricErrorNameContextDeadlineExceeded MetricErrorName = "context.deadline_exceeded"
2036
MetricErrorNameOSErrDeadlineExceeded MetricErrorName = "os.err_deadline_exceeded"
21-
MetricErrorNameSyscallECONNRESET MetricErrorName = "syscall.ECONNRESET"
2237
MetricErrorNamePQ57014 MetricErrorName = "pq.57014"
2338
MetricErrorNameSQLTxDone MetricErrorName = "sql.tx_done"
2439
MetricErrorNameSQLDriverBadConn MetricErrorName = "sql.driver.bad_conn"
40+
MetricErrorNameNetOpError MetricErrorName = "net.op_error"
2541
)
2642

2743
func GetMetricErrorName(err error) (MetricErrorName, bool) {
@@ -51,13 +67,40 @@ func GetMetricErrorName(err error) (MetricErrorName, bool) {
5167
return MetricErrorNameSQLDriverBadConn, true
5268
case errors.Is(err, os.ErrDeadlineExceeded):
5369
return MetricErrorNameOSErrDeadlineExceeded, true
54-
// There are ECONNRESET, ECONNREFUSED, and ECONNABORTED.
55-
// ECONNREFUSED may indicate a configuration problem that should be logged.
56-
// ECONNRESET is about connection disconnected unexpectedly.
57-
// We did not see ECONNABORTED so keep logging it.
58-
case errors.Is(err, syscall.ECONNRESET):
59-
return MetricErrorNameSyscallECONNRESET, true
70+
case isNetOpError(err):
71+
// We used to identify syscall.ECONNRESET separately.
72+
// But I checked the log and found that syscall.ECONNRESET
73+
// was actually wrapped inside a *net.OpError.
74+
// Now that we track *net.OpError as metric,
75+
// there is no point in handling syscall.ECONNRESET specifically.
76+
return MetricErrorNameNetOpError, true
6077
}
6178

6279
return "", false
6380
}
81+
82+
func isNetOpError(err error) bool {
83+
var netOpError *net.OpError
84+
return errors.As(err, &netOpError)
85+
}
86+
87+
func MetricOptionsForError(err error) []otelutil.MetricOption {
88+
var opts []otelutil.MetricOption
89+
if errorName, ok := GetMetricErrorName(err); ok {
90+
opts = append(opts, MetricOptionAttributeKeyValue{attribute.Key("error_name").String(string(errorName))})
91+
}
92+
93+
var netOpError *net.OpError
94+
if errors.As(err, &netOpError) {
95+
opts = append(opts, MetricOptionAttributeKeyValue{attribute.Key("net_op_error.op").String(netOpError.Op)})
96+
opts = append(opts, MetricOptionAttributeKeyValue{attribute.Key("net_op_error.net").String(netOpError.Net)})
97+
98+
var syscallErrno syscall.Errno
99+
if errors.As(netOpError.Err, &syscallErrno) {
100+
symbolicName := unix.ErrnoName(syscallErrno)
101+
opts = append(opts, MetricOptionAttributeKeyValue{attribute.Key("net_op_error.syscall_errno").String(symbolicName)})
102+
}
103+
}
104+
105+
return opts
106+
}

pkg/util/slogutil/metric_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"database/sql/driver"
77
"errors"
88
"fmt"
9+
"net"
910
"os"
1011
"syscall"
1112
"testing"
@@ -68,11 +69,11 @@ func TestGetMetricErrorName(t *testing.T) {
6869
So(name, ShouldEqual, MetricErrorNameOSErrDeadlineExceeded)
6970
})
7071

71-
Convey("should return syscall.ECONNRESET for syscall.ECONNRESET", func() {
72+
Convey("should return false for syscall.ECONNRESET", func() {
7273
err := syscall.ECONNRESET
7374
name, ok := GetMetricErrorName(err)
74-
So(ok, ShouldBeTrue)
75-
So(name, ShouldEqual, MetricErrorNameSyscallECONNRESET)
75+
So(ok, ShouldBeFalse)
76+
So(name, ShouldEqual, MetricErrorName(""))
7677
})
7778

7879
Convey("should return false for other syscall errors", func() {
@@ -96,6 +97,17 @@ func TestGetMetricErrorName(t *testing.T) {
9697
So(name, ShouldEqual, MetricErrorNameSQLDriverBadConn)
9798
})
9899

100+
Convey("should return net.op_error for *net.OpError", func() {
101+
err := &net.OpError{
102+
Op: "write",
103+
Net: "tcp",
104+
Err: syscall.ECONNRESET,
105+
}
106+
name, ok := GetMetricErrorName(err)
107+
So(ok, ShouldBeTrue)
108+
So(name, ShouldEqual, MetricErrorNameNetOpError)
109+
})
110+
99111
Convey("should return false for unrecognized errors", func() {
100112
err := errors.New("some other error")
101113
name, ok := GetMetricErrorName(err)

pkg/util/slogutil/otel_metric_handler.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66

77
"github.com/jba/slog/withsupport"
88
"go.opentelemetry.io/otel"
9-
"go.opentelemetry.io/otel/attribute"
109
"go.opentelemetry.io/otel/metric"
1110

1211
"github.com/authgear/authgear-server/pkg/util/otelutil"
@@ -24,26 +23,14 @@ var CounterErrorCount = otelutil.MustInt64Counter(
2423
metric.WithUnit("{error}"),
2524
)
2625

27-
type metricOptionAttributeKeyValue struct {
28-
attribute.KeyValue
29-
}
30-
31-
func (o metricOptionAttributeKeyValue) ToOtelMetricOption() metric.MeasurementOption {
32-
return metric.WithAttributes(o.KeyValue)
33-
}
34-
35-
func WithErrorName(errorName MetricErrorName) otelutil.MetricOption {
36-
return metricOptionAttributeKeyValue{attribute.Key("error_name").String(string(errorName))}
37-
}
38-
39-
type OtelMetricHandlerTrackFuncType func(ctx context.Context, errorName MetricErrorName)
26+
type OtelMetricHandlerTrackFuncType func(ctx context.Context, errorName MetricErrorName, err error)
4027

4128
// OtelMetricHandlerTrackFunc is the real implementation.
42-
var OtelMetricHandlerTrackFunc OtelMetricHandlerTrackFuncType = func(ctx context.Context, errorName MetricErrorName) {
29+
var OtelMetricHandlerTrackFunc OtelMetricHandlerTrackFuncType = func(ctx context.Context, errorName MetricErrorName, err error) {
4330
otelutil.IntCounterAddOne(
4431
ctx,
4532
CounterErrorCount,
46-
WithErrorName(errorName),
33+
MetricOptionsForError(err)...,
4734
)
4835
}
4936

@@ -76,7 +63,7 @@ func (h *OtelMetricHandler) Handle(ctx context.Context, record slog.Record) erro
7663
if err, ok := attr.Value.Any().(error); ok {
7764
errorName, ok := GetMetricErrorName(err)
7865
if ok {
79-
h.trackFunc(ctx, errorName)
66+
h.trackFunc(ctx, errorName, err)
8067
}
8168
}
8269
}
@@ -87,7 +74,7 @@ func (h *OtelMetricHandler) Handle(ctx context.Context, record slog.Record) erro
8774
if err, ok := attr.Value.Any().(error); ok {
8875
errorName, ok := GetMetricErrorName(err)
8976
if ok {
90-
h.trackFunc(ctx, errorName)
77+
h.trackFunc(ctx, errorName, err)
9178
}
9279
}
9380
}

0 commit comments

Comments
 (0)