Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add geolocation to trace events #724

Merged
merged 6 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
22 changes: 22 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,19 @@ func ClientIP(r *http.Request) string {
return r.RemoteAddr
}
}

func ClientGeoLocation(r *http.Request) GeoLocation {
var clientGeoLocation GeoLocation

if r.Header.Get("Cf-Ipcity") != "" {
clientGeoLocation.City = r.Header.Get("Cf-Ipcity")
}
if r.Header.Get("Cf-Region-Code") != "" {
clientGeoLocation.Region = r.Header.Get("Cf-Region-Code")
}
if r.Header.Get("Cf-Ipcountry") != "" {
clientGeoLocation.Country = r.Header.Get("Cf-Ipcountry")
}

return clientGeoLocation
}
mszekiel marked this conversation as resolved.
Show resolved Hide resolved
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))
})
}
10 changes: 9 additions & 1 deletion otelx/semconv/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ 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)))))

mszekiel marked this conversation as resolved.
Show resolved Hide resolved
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 {
var geoLocationAttributes []otelattr.KeyValue
mszekiel marked this conversation as resolved.
Show resolved Hide resolved

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
}
Loading