diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 5ba6f11ff..688b76290 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -73,6 +73,7 @@ var handler = map[string]localAPIHandler{ "debug-portmap": (*Handler).serveDebugPortmap, "debug-peer-endpoint-changes": (*Handler).serveDebugPeerEndpointChanges, "debug-capture": (*Handler).serveDebugCapture, + "debug-log": (*Handler).serveDebugLog, "derpmap": (*Handler).serveDERPMap, "dev-set-state-store": (*Handler).serveDevSetStateStore, "set-push-device-token": (*Handler).serveSetPushDeviceToken, @@ -1820,6 +1821,47 @@ func (h *Handler) serveDebugCapture(w http.ResponseWriter, r *http.Request) { h.b.StreamDebugCapture(r.Context(), w) } +func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) { + if !h.PermitRead { + http.Error(w, "debug-log access denied", http.StatusForbidden) + return + } + if r.Method != httpm.POST { + http.Error(w, "only POST allowed", http.StatusMethodNotAllowed) + return + } + defer h.b.TryFlushLogs() // kick off upload after we're done logging + + type logRequestJSON struct { + Lines []string + Prefix string + } + + var logRequest logRequestJSON + if err := json.NewDecoder(r.Body).Decode(&logRequest); err != nil { + http.Error(w, "invalid JSON body", 400) + return + } + + prefix := logRequest.Prefix + if prefix == "" { + prefix = "debug-log" + } + logf := logger.WithPrefix(h.logf, prefix+": ") + + // We can write logs too fast for logtail to handle, even when + // opting-out of rate limits. Limit ourselves to at most one message + // per 20ms and a burst of 60 log lines, which should be fast enough to + // not block for too long but slow enough that we can upload all lines. + logf = logger.SlowLoggerWithClock(r.Context(), logf, 20*time.Millisecond, 60, time.Now) + + for _, line := range logRequest.Lines { + logf("%s", line) + } + + w.WriteHeader(http.StatusNoContent) +} + var ( metricInvalidRequests = clientmetric.NewCounter("localapi_invalid_requests")