Skip to content

Commit

Permalink
Send metadata as query string
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaharagon committed Apr 16, 2024
1 parent 8673ff2 commit 47fdd0f
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 4 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ umami [<matcher>] {
device_detection
trusted_ip_header <name>
report_all_resources
static_metadata {
<key> <value>
...
<key> <value>
}
}
```

- **event_endpoint** is the address of your Umami installation's send API endpoint
- **website_uuid** is the UUID of the website from your Umami dashboard
- **event_endpoint** (required) is the address of your Umami installation's send API endpoint
- **website_uuid** (required) is the UUID of the website from your Umami dashboard
- **allowed_extensions** is a list of extensions which indicate valid content.
- Make sure to include `""` in this list if you want to track URLs which don't end in a file extension, such as `/` or `/about-us`
- If unspecified, the default list of extensions is:
Expand All @@ -37,8 +42,10 @@ umami [<matcher>] {
- **device_detection** can be enabled to set the sent screen resolution based on `Sec-CH-UA-Mobile`/`Sec-CH-UA-Platform`, for some rudimentary device detection without cookies. If this and `cookie_resolution` are both enabled, a screen resolution set by the cookie will take precedence.
- **trusted_ip_header** is the name of an incoming HTTP request header which contains the visitor's true IP, which will then be sent to Umami via the `X-Forwarded-For`. This may be useful if your Caddy server is behind a reverse proxy.
- **report_all_resources** can be included to report **all** requests to Umami, overriding allowed_extensions. By default, only requests with certain extensions are reported. This may be especially useful when using this module with a matcher.
- **static_metadata** is information which will be added to the query strings delivered to Umami. Note that if there is a query string which matches a key used here in the original web request, the value set here will be [*appended*](https://pkg.go.dev/net/url#Values.Add) to that query string.
- For example, if you set `server node1` as the `<key> <value>` pair here, when a visitor accesses `/about-us` on your website, the path sent to Umami will be `/about-us?server=node1`. This allows you to do things like track which Caddy server sent the event data to Umami, and filter events in Umami based on that metadata (because Umami allows filtering by query string, but not by other things like event data).

### Full Example
### Example

You should specify the order of the `umami` directive in your global options, otherwise the `umami` block has to be defined inside a `route` block.

Expand Down
59 changes: 58 additions & 1 deletion umami.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ type Umami struct {
ReportAllResources bool `json:"report_all_resources,omitempty"`
// The header to use to get the client IP address from, behind a trusted reverse proxy.
TrustedIPHeader string `json:"trusted_ip_header,omitempty"`
// A map of cookie-based consent settings. Only the first entry is currently used.
// A map of cookie-based consent settings. Only the first value in the map is utilized currently.
CookieConsent []CookieConsent `json:"cookie_consent,omitempty"`
// The name of the cookie that stores the visitor's screen resolution.
CookieResolution string `json:"cookie_resolution,omitempty"`
// Enable rudimentary device detection based on Sec-CH-UA-Mobile and Sec-CH-UA-Platform headers.
DeviceDetection bool `json:"device_detection,omitempty"`
// Optional static metadata to include with each event via query string.
StaticMetadata []StaticMetadata `json:"static_metadata,omitempty"`

logger *zap.Logger
}

// Cookie-based consent settings.
type CookieConsent struct {
// The name of the cookie that stores the consent setting.
Name string `json:"name,omitempty"`
Expand All @@ -55,6 +58,14 @@ type CookieConsent struct {
Behavior string `json:"behavior,omitempty"`
}

// Optional static metadata to include with each event via query string.
type StaticMetadata struct {
// The key of the metadata.
Key string `json:"key,omitempty"`
// The value of the metadata.
Value string `json:"value,omitempty"`
}

// CaddyModule returns the Caddy module information.
func (Umami) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Expand Down Expand Up @@ -108,6 +119,13 @@ func (p Umami) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.
go func() {
// Get request query strings.
queryStrings := r.URL.Query()

// Add optional metadata to query strings.
for _, metadata := range p.StaticMetadata {
queryStrings.Add(metadata.Key, metadata.Value)
}

// Encode query strings.
queryString := queryStrings.Encode()
p.logger.Debug("Query Strings", zap.String("queryStrings", queryString))

Expand Down Expand Up @@ -354,6 +372,12 @@ func (p *Umami) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
case "device_detection":
p.DeviceDetection = true
case "static_metadata":
Metadata, err := ParseCaddyfileStaticMetadata(d)
if err != nil {
return err
}
p.StaticMetadata = Metadata

default:
return d.Errf("unknown option '%s'", d.Val())
Expand All @@ -369,6 +393,38 @@ func (p *Umami) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}

func ParseCaddyfileStaticMetadata(d *caddyfile.Dispenser) ([]StaticMetadata, error) {
var metadata []StaticMetadata

// Allow for options formatted as:
// static_metadata key value
if d.NextArg() {
key := d.Val()
if !d.NextArg() {
return nil, d.ArgErr()
}
value := d.Val()
metadata = append(metadata, StaticMetadata{Key: key, Value: value})
return metadata, nil
}

// Allow for options formatted as:
// static_metadata {
// key value
// key value
// ...
// }
for nesting := d.Nesting(); d.NextBlock(nesting); {
key := d.Val()
if !d.NextArg() {
return nil, d.ArgErr()
}
value := d.Val()
metadata = append(metadata, StaticMetadata{Key: key, Value: value})
}
return metadata, nil
}

// MarshalCaddyfile implements caddyfile.Marshaler.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var umami Umami
Expand Down Expand Up @@ -397,6 +453,7 @@ func (p *Umami) Validate() error {
p.logger.Debug("Cookie Consent: " + fmt.Sprint(p.CookieConsent))
p.logger.Debug("Cookie Resolution: " + p.CookieResolution)
p.logger.Debug("Device Detection: " + fmt.Sprint(p.DeviceDetection))
p.logger.Debug("Static Metadata: " + fmt.Sprint(p.StaticMetadata))
p.logger.Info("Umami middleware validated")
return nil
}
Expand Down

0 comments on commit 47fdd0f

Please sign in to comment.