Skip to content

Commit

Permalink
feat: add geolocation to trace events (#724)
Browse files Browse the repository at this point in the history
Co-authored-by: Miłosz Szekiel <[email protected]>
  • Loading branch information
alnr and mszekiel authored Sep 14, 2023
1 parent fbf9d35 commit 78da4d7
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 6 deletions.
14 changes: 14 additions & 0 deletions httpx/client_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import (
"strings"
)

type GeoLocation struct {
City string
Region string
Country string
}

func GetClientIPAddressesWithoutInternalIPs(ipAddresses []string) (string, error) {
var res string

Expand Down Expand Up @@ -36,3 +42,11 @@ func ClientIP(r *http.Request) string {
return r.RemoteAddr
}
}

func ClientGeoLocation(r *http.Request) *GeoLocation {
return &GeoLocation{
City: r.Header.Get("Cf-Ipcity"),
Region: r.Header.Get("Cf-Region-Code"),
Country: r.Header.Get("Cf-Ipcountry"),
}
}
32 changes: 32 additions & 0 deletions httpx/client_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,35 @@ func TestClientIP(t *testing.T) {
assert.Equal(t, "1.0.0.4", ClientIP(req))
})
}

func TestClientGeoLocation(t *testing.T) {
req := http.Request{
Header: http.Header{},
}
req.Header.Add("cf-ipcity", "Berlin")
req.Header.Add("cf-ipcountry", "Germany")
req.Header.Add("cf-region-code", "BE")

t.Run("cf-ipcity", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "Berlin", ClientGeoLocation(req).City)
})

t.Run("cf-ipcountry", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "Germany", ClientGeoLocation(req).Country)
})

t.Run("cf-region-code", func(t *testing.T) {
req := req.Clone(context.Background())
assert.Equal(t, "BE", ClientGeoLocation(req).Region)
})

t.Run("empty", func(t *testing.T) {
req := req.Clone(context.Background())
req.Header.Del("cf-ipcity")
req.Header.Del("cf-ipcountry")
req.Header.Del("cf-region-code")
assert.Equal(t, GeoLocation{}, *ClientGeoLocation(req))
})
}
9 changes: 8 additions & 1 deletion otelx/semconv/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ func AttributesFromContext(ctx context.Context) []attribute.KeyValue {
}

func Middleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
next(rw, r.WithContext(ContextWithAttributes(r.Context(), AttrClientIP(httpx.ClientIP(r)))))
ctx := ContextWithAttributes(r.Context(),
append(
AttrGeoLocation(*httpx.ClientGeoLocation(r)),
AttrClientIP(httpx.ClientIP(r)),
)...,
)

next(rw, r.WithContext(ctx))
}

func reverse[S ~[]E, E any](s S) {
Expand Down
14 changes: 12 additions & 2 deletions otelx/semconv/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"

"github.com/ory/x/httpx"
)

func TestAttributesFromContext(t *testing.T) {
Expand All @@ -21,11 +23,19 @@ func TestAttributesFromContext(t *testing.T) {
assert.Len(t, AttributesFromContext(ctx), 1)

uid1, uid2 := uuid.Must(uuid.NewV4()), uuid.Must(uuid.NewV4())
ctx = ContextWithAttributes(ctx, AttrIdentityID(uid1), AttrClientIP("127.0.0.1"), AttrIdentityID(uid2))
location := httpx.GeoLocation{
City: "Berlin",
Country: "Germany",
Region: "BE",
}
ctx = ContextWithAttributes(ctx, append(AttrGeoLocation(location), AttrIdentityID(uid1), AttrClientIP("127.0.0.1"), AttrIdentityID(uid2))...)
attrs := AttributesFromContext(ctx)
assert.Len(t, attrs, 3, "should deduplicate")
assert.Len(t, attrs, 6, "should deduplicate")
assert.Equal(t, []attribute.KeyValue{
attribute.String(AttributeKeyNID.String(), nid.String()),
attribute.String(AttributeKeyGeoLocationCity.String(), "Berlin"),
attribute.String(AttributeKeyGeoLocationCountry.String(), "Germany"),
attribute.String(AttributeKeyGeoLocationRegion.String(), "BE"),
attribute.String(AttributeKeyClientIP.String(), "127.0.0.1"),
attribute.String(AttributeKeyIdentityID.String(), uid2.String()),
}, attrs, "last duplicate attribute wins")
Expand Down
27 changes: 24 additions & 3 deletions otelx/semconv/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package semconv
import (
"github.com/gofrs/uuid"
otelattr "go.opentelemetry.io/otel/attribute"

"github.com/ory/x/httpx"
)

type Event string
Expand All @@ -22,9 +24,12 @@ func (a AttributeKey) String() string {
}

const (
AttributeKeyIdentityID AttributeKey = "IdentityID"
AttributeKeyNID AttributeKey = "ProjectID"
AttributeKeyClientIP AttributeKey = "ClientIP"
AttributeKeyIdentityID AttributeKey = "IdentityID"
AttributeKeyNID AttributeKey = "ProjectID"
AttributeKeyClientIP AttributeKey = "ClientIP"
AttributeKeyGeoLocationCity AttributeKey = "GeoLocationCity"
AttributeKeyGeoLocationRegion AttributeKey = "GeoLocationRegion"
AttributeKeyGeoLocationCountry AttributeKey = "GeoLocationCountry"
)

func AttrIdentityID(val uuid.UUID) otelattr.KeyValue {
Expand All @@ -38,3 +43,19 @@ func AttrNID(val uuid.UUID) otelattr.KeyValue {
func AttrClientIP(val string) otelattr.KeyValue {
return otelattr.String(AttributeKeyClientIP.String(), val)
}

func AttrGeoLocation(val httpx.GeoLocation) []otelattr.KeyValue {
geoLocationAttributes := make([]otelattr.KeyValue, 0, 3)

if val.City != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCity.String(), val.City))
}
if val.Country != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationCountry.String(), val.Country))
}
if val.Region != "" {
geoLocationAttributes = append(geoLocationAttributes, otelattr.String(AttributeKeyGeoLocationRegion.String(), val.Region))
}

return geoLocationAttributes
}

0 comments on commit 78da4d7

Please sign in to comment.