ForgeOpsTracker.StrictPrivacy (forge_ops_tracker v0.19.0)

Copy Markdown View Source

Strict privacy (Configuration.t/0's strict_privacy) is for apps that can't let personal or health data leave their own servers: an event carries only structured facts (the error's type, its stack frames, release, environment, server, which route it happened in), never free text a person could have typed. The rules, and the vectors every client's tests check against, are in this repo's sdks/STRICT_PRIVACY.md; the server applies the same rules again on arrival (app/services/strict_privacy.rb) when the project's own Strict privacy setting is on.

Summary

Functions

An error event's payload (ForgeOpsTracker.EventBuilder.build/7's shape) reduced to what strict privacy sends: "message" is "" and "message_fingerprint" is added; breadcrumbs, endpoint, SQL statement and source context are dropped; a raw-path "transaction_name" keeps only its first segment (see transaction_name/1); context and tags keep only allowed_fields keys with plain values (left out when nothing is left); the user keeps only its id.

The first 32 hex characters of the SHA-256 of normalize_message/1's UTF-8 bytes: lets ForgeOps group errors whose messages differ only in ids, without the message being sent.

The message with emails, quoted values, hex object ids, UUIDs and standalone numbers replaced, in that order: an email becomes "<email>", a double or single quoted run becomes "<str>", "0x" plus 4 or more hex digits becomes "0x", a UUID becomes "<uuid>" and a run of digits becomes "<n>". A single quote, a UUID and a number count only where no ASCII word character touches them.

One finished span under strict privacy: its name per span_name/1, and its data reduced to "db.statement" and "db.system" (left out when neither is there).

A span's name under strict privacy: an outbound call keeps its method and host ("GET https://api.example.com/patients/42?x=1" is "GET api.example.com"), a request named by its path goes through strict_path/1 ("GET /patients/42/notes" is "GET /patients/:path"), so does a bare path ("/42/edit" is "/:id/:path"), and any other name ("OrdersController#create") is kept.

A request or page path under strict privacy, with no query or fragment. A route template ("/patients/:id/notes", "/orders/{id}") is kept as it is: its words are a developer's and its parameters are already placeholders. A raw path keeps only its first segment (":id" when it holds an ASCII digit or is longer than 40 characters), then "/:path" if there was more, since anything after it can name a person: "/patients/jane-doe/12" is "/patients/:path". No segments at all is "/".

A transaction name under strict privacy: a route or a controller action is kept, but a raw path keeps only its first segment, by the same rule as span_name/1. A blank name is returned as it is.

Functions

event(payload, allowed_fields)

@spec event(map(), [String.t()]) :: map()

An error event's payload (ForgeOpsTracker.EventBuilder.build/7's shape) reduced to what strict privacy sends: "message" is "" and "message_fingerprint" is added; breadcrumbs, endpoint, SQL statement and source context are dropped; a raw-path "transaction_name" keeps only its first segment (see transaction_name/1); context and tags keep only allowed_fields keys with plain values (left out when nothing is left); the user keeps only its id.

message_fingerprint(message)

@spec message_fingerprint(String.t() | nil) :: String.t()

The first 32 hex characters of the SHA-256 of normalize_message/1's UTF-8 bytes: lets ForgeOps group errors whose messages differ only in ids, without the message being sent.

normalize_message(message)

@spec normalize_message(String.t() | nil) :: String.t()

The message with emails, quoted values, hex object ids, UUIDs and standalone numbers replaced, in that order: an email becomes "<email>", a double or single quoted run becomes "<str>", "0x" plus 4 or more hex digits becomes "0x", a UUID becomes "<uuid>" and a run of digits becomes "<n>". A single quote, a UUID and a number count only where no ASCII word character touches them.

span(span)

@spec span(map()) :: map()

One finished span under strict privacy: its name per span_name/1, and its data reduced to "db.statement" and "db.system" (left out when neither is there).

span_name(name)

@spec span_name(String.t()) :: String.t()

A span's name under strict privacy: an outbound call keeps its method and host ("GET https://api.example.com/patients/42?x=1" is "GET api.example.com"), a request named by its path goes through strict_path/1 ("GET /patients/42/notes" is "GET /patients/:path"), so does a bare path ("/42/edit" is "/:id/:path"), and any other name ("OrdersController#create") is kept.

strict_path(path)

@spec strict_path(String.t() | nil) :: String.t()

A request or page path under strict privacy, with no query or fragment. A route template ("/patients/:id/notes", "/orders/{id}") is kept as it is: its words are a developer's and its parameters are already placeholders. A raw path keeps only its first segment (":id" when it holds an ASCII digit or is longer than 40 characters), then "/:path" if there was more, since anything after it can name a person: "/patients/jane-doe/12" is "/patients/:path". No segments at all is "/".

transaction_name(name)

@spec transaction_name(String.t() | nil) :: String.t() | nil

A transaction name under strict privacy: a route or a controller action is kept, but a raw path keeps only its first segment, by the same rule as span_name/1. A blank name is returned as it is.