Skip to content

Commit 3edb868

Browse files
authored
chore: move utilities to subpackage (#3)
* move utilities to subpackage * move user visibility
1 parent a061784 commit 3edb868

File tree

6 files changed

+172
-158
lines changed

6 files changed

+172
-158
lines changed

middleware.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212
"time"
1313

14+
"github.com/hasura/gotel/otelutils"
1415
"go.opentelemetry.io/otel"
1516
"go.opentelemetry.io/otel/attribute"
1617
"go.opentelemetry.io/otel/codes"
@@ -107,7 +108,7 @@ func (tm *tracingMiddleware) ServeHTTP( //nolint:gocognit,cyclop,funlen,maintidx
107108
urlScheme = "http"
108109
}
109110

110-
hostName, port, _ := SplitHostPort(r.Host)
111+
hostName, port, _ := otelutils.SplitHostPort(r.Host)
111112

112113
switch {
113114
case port > 0:
@@ -169,7 +170,7 @@ func (tm *tracingMiddleware) ServeHTTP( //nolint:gocognit,cyclop,funlen,maintidx
169170
semconv.UserAgentOriginal(r.UserAgent()),
170171
)
171172

172-
peer, peerPort, _ := SplitHostPort(r.RemoteAddr)
173+
peer, peerPort, _ := otelutils.SplitHostPort(r.RemoteAddr)
173174

174175
if peer != "" {
175176
span.SetAttributes(semconv.NetworkPeerAddress(peer))
@@ -180,7 +181,9 @@ func (tm *tracingMiddleware) ServeHTTP( //nolint:gocognit,cyclop,funlen,maintidx
180181
}
181182

182183
requestBodySize := r.ContentLength
183-
requestLogHeaders := NewTelemetryHeaders(r.Header, tm.Options.AllowedRequestHeaders...)
184+
requestLogHeaders := otelutils.NewTelemetryHeaders(
185+
r.Header,
186+
tm.Options.AllowedRequestHeaders...)
184187
requestLogData := map[string]any{
185188
"url": r.URL.String(),
186189
"method": r.Method,
@@ -189,7 +192,7 @@ func (tm *tracingMiddleware) ServeHTTP( //nolint:gocognit,cyclop,funlen,maintidx
189192
"size": requestBodySize,
190193
}
191194

192-
SetSpanHeaderAttributes(span, "http.request.header", requestLogHeaders)
195+
otelutils.SetSpanHeaderAttributes(span, "http.request.header", requestLogHeaders)
193196

194197
var (
195198
ww WrapResponseWriter
@@ -314,12 +317,14 @@ func (tm *tracingMiddleware) ServeHTTP( //nolint:gocognit,cyclop,funlen,maintidx
314317
tm.Next.ServeHTTP(ww, rr)
315318

316319
statusCode := ww.Status()
317-
responseLogHeaders := NewTelemetryHeaders(ww.Header(), tm.Options.AllowedResponseHeaders...)
320+
responseLogHeaders := otelutils.NewTelemetryHeaders(
321+
ww.Header(),
322+
tm.Options.AllowedResponseHeaders...)
318323
responseLogData["size"] = ww.BytesWritten()
319324
responseLogData["headers"] = responseLogHeaders
320325

321326
span.SetAttributes(semconv.HTTPResponseBodySize(ww.BytesWritten()))
322-
SetSpanHeaderAttributes(span, "http.response.header", responseLogHeaders)
327+
otelutils.SetSpanHeaderAttributes(span, "http.response.header", responseLogHeaders)
323328

324329
// skip printing very large responses.
325330
if responseReader != nil && ww.BytesWritten() < 100*1024 {

otelutils/utils.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Package otelutils contain reusable utilities for OpenTelemetry attributes.
2+
package otelutils
3+
4+
import (
5+
"errors"
6+
"fmt"
7+
"net"
8+
"net/http"
9+
"regexp"
10+
"slices"
11+
"strconv"
12+
"strings"
13+
14+
"go.opentelemetry.io/otel/attribute"
15+
"go.opentelemetry.io/otel/trace"
16+
)
17+
18+
// UserVisibilityAttribute is the attribute to display on the Trace view.
19+
var UserVisibilityAttribute = attribute.String("internal.visibility", "user")
20+
21+
var sensitiveHeaderRegex = regexp.MustCompile(`auth|key|secret|token|password`)
22+
23+
var excludedSpanHeaderAttributes = map[string]bool{
24+
"baggage": true,
25+
"traceparent": true,
26+
"traceresponse": true,
27+
"tracestate": true,
28+
"x-b3-sampled": true,
29+
"x-b3-spanid": true,
30+
"x-b3-traceid": true,
31+
"x-b3-parentspanid": true,
32+
"x-b3-flags": true,
33+
"b3": true,
34+
}
35+
36+
var errInvalidHostPort = errors.New("invalid host port")
37+
38+
// SetSpanHeaderAttributes sets header attributes to the otel span.
39+
func SetSpanHeaderAttributes(
40+
span trace.Span,
41+
prefix string,
42+
headers http.Header,
43+
allowedHeaders ...string,
44+
) {
45+
allowedHeadersLength := len(allowedHeaders)
46+
47+
for key, values := range headers {
48+
lowerKey := strings.ToLower(key)
49+
50+
if (allowedHeadersLength == 0 && !excludedSpanHeaderAttributes[lowerKey]) ||
51+
(allowedHeadersLength > 0 && slices.Contains(allowedHeaders, lowerKey)) {
52+
span.SetAttributes(
53+
attribute.StringSlice(fmt.Sprintf("%s.%s", prefix, lowerKey), values),
54+
)
55+
}
56+
}
57+
}
58+
59+
// NewTelemetryHeaders creates a new header map with sensitive values masked.
60+
func NewTelemetryHeaders(httpHeaders http.Header, allowedHeaders ...string) http.Header {
61+
result := http.Header{}
62+
63+
if len(allowedHeaders) > 0 {
64+
for _, key := range allowedHeaders {
65+
value := httpHeaders.Get(key)
66+
67+
if value == "" {
68+
continue
69+
}
70+
71+
if IsSensitiveHeader(key) {
72+
result.Set(strings.ToLower(key), MaskString(value))
73+
} else {
74+
result.Set(strings.ToLower(key), value)
75+
}
76+
}
77+
78+
return result
79+
}
80+
81+
for key, headers := range httpHeaders {
82+
if len(headers) == 0 {
83+
continue
84+
}
85+
86+
values := headers
87+
if IsSensitiveHeader(key) {
88+
values = make([]string, len(headers))
89+
for i, header := range headers {
90+
values[i] = MaskString(header)
91+
}
92+
}
93+
94+
result[key] = values
95+
}
96+
97+
return result
98+
}
99+
100+
// IsSensitiveHeader checks if the header name is sensitive.
101+
func IsSensitiveHeader(name string) bool {
102+
return sensitiveHeaderRegex.MatchString(strings.ToLower(name))
103+
}
104+
105+
// MaskString masks the string value for security.
106+
func MaskString(input string) string {
107+
inputLength := len(input)
108+
109+
switch {
110+
case inputLength <= 6:
111+
return strings.Repeat("*", inputLength)
112+
case inputLength < 12:
113+
return input[0:1] + strings.Repeat("*", inputLength-1)
114+
default:
115+
return input[0:2] + strings.Repeat("*", 8) + fmt.Sprintf("(%d)", inputLength)
116+
}
117+
}
118+
119+
// SplitHostPort splits a network address hostport of the form "host",
120+
// "host%zone", "[host]", "[host%zone]", "host:port", "host%zone:port",
121+
// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and
122+
// port.
123+
//
124+
// An empty host is returned if it is not provided or unparsable. A negative
125+
// port is returned if it is not provided or unparsable.
126+
func SplitHostPort(hostport string) (string, int, error) {
127+
port := -1
128+
129+
if strings.HasPrefix(hostport, "[") {
130+
addrEnd := strings.LastIndex(hostport, "]")
131+
if addrEnd < 0 {
132+
// Invalid hostport.
133+
return "", port, errInvalidHostPort
134+
}
135+
136+
if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 {
137+
host := hostport[1:addrEnd]
138+
139+
return host, port, nil
140+
}
141+
} else {
142+
if i := strings.LastIndex(hostport, ":"); i < 0 {
143+
return hostport, port, nil
144+
}
145+
}
146+
147+
host, pStr, err := net.SplitHostPort(hostport)
148+
if err != nil {
149+
return host, port, err
150+
}
151+
152+
p, err := strconv.ParseUint(pStr, 10, 16)
153+
if err != nil {
154+
return "", port, err
155+
}
156+
157+
return host, int(p), err
158+
}

utils_test.go renamed to otelutils/utils_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package gotel
1+
package otelutils
22

33
import (
44
"net/http"

provider.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"github.com/prometheus/client_golang/prometheus/collectors"
1616
"go.opentelemetry.io/contrib/propagators/b3"
1717
"go.opentelemetry.io/otel"
18-
"go.opentelemetry.io/otel/attribute"
1918
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
2019
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
2120
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
@@ -79,9 +78,6 @@ const (
7978
OTELLogsExporterOTLP OTELLogsExporterType = "otlp"
8079
)
8180

82-
// UserVisibilityAttribute is the attribute to display on the Trace view.
83-
var UserVisibilityAttribute = attribute.String("internal.visibility", "user")
84-
8581
var (
8682
errInvalidOTLPCompressionType = errors.New(
8783
"invalid OTLP compression type, accept none, gzip only",

tracer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gotel
33
import (
44
"context"
55

6+
"github.com/hasura/gotel/otelutils"
67
"go.opentelemetry.io/otel"
78
traceapi "go.opentelemetry.io/otel/trace"
89
)
@@ -31,7 +32,7 @@ func (t *Tracer) Start(
3132
return t.Tracer.Start( //nolint:spancheck
3233
ctx,
3334
spanName,
34-
append(opts, traceapi.WithAttributes(UserVisibilityAttribute))...)
35+
append(opts, traceapi.WithAttributes(otelutils.UserVisibilityAttribute))...)
3536
}
3637

3738
// StartInternal creates a span and a context.Context containing the newly-created span.

0 commit comments

Comments
 (0)