feat: add analytics subcommand#784
Conversation
| func resolveAnalyticsProjectID() (string, error) { | ||
| if project != nil && project.ProjectId != "" { | ||
| return project.ProjectId, nil | ||
| } | ||
|
|
||
| if project == nil { | ||
| return "", fmt.Errorf("%s; %s", analyticsProjectIDRequirement, analyticsProjectSelectHint) | ||
| } | ||
|
|
||
| projectName := project.Name | ||
| if strings.TrimSpace(projectName) == "" { | ||
| projectName = "<selected>" | ||
| } | ||
|
|
||
| return "", fmt.Errorf( | ||
| "selected project [%s] is missing project_id; %s. %s", | ||
| projectName, | ||
| analyticsProjectIDRequirement, | ||
| analyticsProjectSelectHint, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Urf, I would love a better solution than this...it basically mandates the user either remove and re-auth the legacy project, or go and edit ~/.livekit/cli-config.yaml to manually add it. We've only started storing the project ID in the last ~5 months.
Let me see if there's an easy way to maybe install a startup hook that will backfill old projects with their ID.
There was a problem hiding this comment.
Yeah this is a bummer. I only noticed this since I fell into that older case, but it wasn't a big deal for me to re-auth (or just manually add it back in).
I couldn't find any endpoint/way of fetching the ID, otherwise I'd be happy to take a stab at a backfill. Let me know what you find.
|
This looks great so far! I will take a closer look and play around with it this evening. To answer your question, no -- analytics service is not implemented in LiveKit OSS. But neither are the |
|
Hey @rektdeckard is there anything I can help with for this PR? |
| if printCurl { | ||
| fmt.Printf("curl -H \"Authorization: Bearer %s\" \"%s\"\n", token, reqURL.String()) | ||
| } | ||
|
|
||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| req.Header = authutil.NewHeaderWithToken(token) | ||
| req.Header.Set("Content-Type", "application/json") |
There was a problem hiding this comment.
Minor consistency nit (non-blocking): the --curl output here is hand-rolled as a single line, while every other command prints curl via interceptors.NewCurlPrinter, which uses a multi-line format (sorted headers, Content-Type last, \ continuations). That interceptor is Twirp-only so it can't be reused for this REST call, but we can mirror its format with a small helper — and build the curl line from the actual req.Header so it stays in sync with what's sent (including the auth header, same as the interceptor does).
Replace this block with the request construction first, then print from the built request:
| if printCurl { | |
| fmt.Printf("curl -H \"Authorization: Bearer %s\" \"%s\"\n", token, reqURL.String()) | |
| } | |
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header = authutil.NewHeaderWithToken(token) | |
| req.Header.Set("Content-Type", "application/json") | |
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| req.Header = authutil.NewHeaderWithToken(token) | |
| req.Header.Set("Content-Type", "application/json") | |
| if printCurl { | |
| printAnalyticsCurl(os.Stdout, req) | |
| } |
And add this helper (plus "os" and "sort" to the imports):
// printAnalyticsCurl writes an equivalent curl command for an analytics REST
// request, matching the multi-line format used by interceptors.NewCurlPrinter
// for Twirp calls (sorted headers, Content-Type last, line continuations).
func printAnalyticsCurl(w io.Writer, req *http.Request) {
var buf strings.Builder
buf.WriteString("curl ")
buf.WriteString("\\\n\t")
hkeys := make([]string, 0, len(req.Header))
for k := range req.Header {
hkeys = append(hkeys, k)
}
sort.Strings(hkeys)
for _, h := range hkeys {
if h == "Content-Type" {
continue
}
for _, v := range req.Header[h] {
fmt.Fprintf(&buf, "-H '%s: %s' ", h, v)
buf.WriteString("\\\n\t")
}
}
buf.WriteString("-H 'Content-Type: application/json' ")
buf.WriteString("\\\n\t")
buf.WriteString(req.URL.String())
buf.WriteString("\n")
_, _ = io.WriteString(w, buf.String())
}
Adds a new
lk analyticscommand group to query LiveKit Cloud Analytics sessions.lk analytics listandlk analytics get SESSION_ID.--json/-joutput for both commands.--limit,--page,--start,--endwith validation.--project/ default project) and requires storedproject_id(no--project-idoverride).--curlsupport for analytics REST requests (prints equivalent curl command).go test ./cmd/lk.Mostly generated with Codex with manual code review.
Biggest thing I assumed was this is tightly coupled to LiveKit Cloud. Does this endpoint work for self hosted instances? I think the way this is built works for custom server URLs, but maybe some of the messages need to be tweaked