client/web: add metric logging logic to the web client (#10434)

Add metric logging logic for the web client frontend. This is an initial
pass of adding the base logic, plus a single point where it is used for
validation that the logging is working correctly. More metric logging
calls will follow in subsquent PRs.

Updates https://github.com/tailscale/tailscale/issues/10261

Signed-off-by: Mario Minardi <mario@tailscale.com>
pull/10469/head
Mario Minardi 7 months ago committed by GitHub
parent 9c4b73d77d
commit 6b083a8ddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -77,3 +77,30 @@ export function setSynoToken(token?: string) {
export function setUnraidCsrfToken(token?: string) {
unraidCsrfToken = token
}
// incrementMetric hits the client metrics local API endpoint to
// increment the given counter metric by one.
export function incrementMetric(metricName: MetricName) {
const postData : MetricsPOSTData[] = [{
Name: metricName,
Type: "counter",
Value: 1
}]
apiFetch("/local/v0/upload-client-metrics", "POST", postData)
.catch((error) => {
console.error(error)
})
}
type MetricsPOSTData = {
Name: MetricName
Type: MetricType
Value: number
}
type MetricType = "counter" | "gauge"
export type MetricName =
| "web_client_advertise_exitnode_enable"
| "web_client_advertise_exitnode_disable"

@ -6,6 +6,7 @@ import { apiFetch, setUnraidCsrfToken } from "src/api"
import { ExitNode, noExitNode, runAsExitNode } from "src/hooks/exit-nodes"
import { VersionInfo } from "src/hooks/self-update"
import { assertNever } from "src/util"
import { incrementMetric, MetricName } from "src/api"
export type NodeData = {
Profile: UserProfile
@ -173,16 +174,25 @@ export default function useNodeData() {
setIsPosting(false)
refreshData() // refresh data after POST finishes
}
const updateMetrics = () => {
// only update metrics if values have changed
if (data?.AdvertisingExitNode !== d.AdvertiseExitNode) {
incrementMetric(d.AdvertiseExitNode ? "web_client_advertise_exitnode_enable" : "web_client_advertise_exitnode_disable")
}
}
return apiFetch("/routes", "POST", d)
.then(onComplete)
.then(() => {
updateMetrics()
onComplete()
})
.catch((err) => {
onComplete()
alert("Failed to update routes")
throw err
})
},
[setIsPosting, refreshData]
[setIsPosting, refreshData, data?.AdvertisingExitNode]
)
useEffect(

@ -759,12 +759,6 @@ func (s *Server) servePostRoutes(w http.ResponseWriter, r *http.Request) {
return
}
oldPrefs, err := s.lc.GetPrefs(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Calculate routes.
routesStr := strings.Join(data.AdvertiseRoutes, ",")
routes, err := netutil.CalcAdvertiseRoutes(routesStr, data.AdvertiseExitNode)
@ -797,15 +791,6 @@ func (s *Server) servePostRoutes(w http.ResponseWriter, r *http.Request) {
return
}
// Report metrics.
if data.AdvertiseExitNode != hasExitNodeRoute(oldPrefs.AdvertiseRoutes) {
if data.AdvertiseExitNode {
s.lc.IncrementCounter(r.Context(), "web_client_advertise_exitnode_enable", 1)
} else {
s.lc.IncrementCounter(r.Context(), "web_client_advertise_exitnode_disable", 1)
}
}
w.WriteHeader(http.StatusOK)
}
@ -979,6 +964,7 @@ var localapiAllowlist = []string{
"/v0/update/check",
"/v0/update/install",
"/v0/update/progress",
"/v0/upload-client-metrics",
}
// csrfKey returns a key that can be used for CSRF protection.

Loading…
Cancel
Save