@@ -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
1531type MetricErrorName string
1632
1733const (
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
2743func 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+ }
0 commit comments