summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwikiapiserver2026-06-25 21:35:50 +0200
committerwikiapiserver2026-06-25 21:35:50 +0200
commit795fb7facf403f4e3d452d2e08ba11f98e8ee997 (patch)
treecab7c8dc6b16ef0d5cbd9871edb1bbab9fd4161c
parent550d382014b5f3476caed167025d6c4be16d844d (diff)
downloadwikiapiserver-795fb7facf403f4e3d452d2e08ba11f98e8ee997.tar.gz
feat: log article API failures to database
- Created api_logs table (username, article_name, status_code, response_time_ms, error, request_url) - GetArticle logs failures (network errors and non-2xx responses) with timing, status code, and response body - Successful requests are not logged
-rw-r--r--api/handlers.go28
-rw-r--r--db/db.go24
2 files changed, 50 insertions, 2 deletions
diff --git a/api/handlers.go b/api/handlers.go
index c9c3031..04fcc23 100644
--- a/api/handlers.go
+++ b/api/handlers.go
@@ -227,16 +227,30 @@ func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
return
}
- base := "https://api.enterprise.wikimedia.com/v2/structured-contents/" + url.QueryEscape(article)
- req, err := http.NewRequestWithContext(ctx, "GET", base+"?limit=1&filters[project]=en.wikipedia.org", nil)
+ baseURL := "https://api.enterprise.wikimedia.com/v2/structured-contents/" + url.QueryEscape(article)
+ queryURL := baseURL + "?limit=1&filters[project]=en.wikipedia.org"
+
+ req, err := http.NewRequestWithContext(ctx, "GET", queryURL, nil)
if err != nil {
serverError(w, "could not build request")
return
}
req.Header.Set("Authorization", "Bearer "+acct.AccessToken)
+ start := time.Now()
resp, err := http.DefaultClient.Do(req)
+ elapsed := int(time.Since(start).Milliseconds())
+
if err != nil {
+ log.Printf("article request failed: user=%s article=%s error=%v (%dms)", username, article, err, elapsed)
+ h.db.LogApiCall(ctx, &db.ApiLogEntry{
+ Username: username,
+ ArticleName: article,
+ StatusCode: 0,
+ ResponseMs: elapsed,
+ Error: err.Error(),
+ RequestURL: queryURL,
+ })
serverError(w, "wikimedia api error")
return
}
@@ -244,6 +258,16 @@ func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
b, _ := io.ReadAll(resp.Body)
+ log.Printf("article request failed: user=%s article=%s status=%d body=%s (%dms)",
+ username, article, resp.StatusCode, string(b), elapsed)
+ h.db.LogApiCall(ctx, &db.ApiLogEntry{
+ Username: username,
+ ArticleName: article,
+ StatusCode: resp.StatusCode,
+ ResponseMs: elapsed,
+ Error: string(b),
+ RequestURL: queryURL,
+ })
http.Error(w, string(b), resp.StatusCode)
return
}
diff --git a/db/db.go b/db/db.go
index 228a655..7409047 100644
--- a/db/db.go
+++ b/db/db.go
@@ -325,3 +325,27 @@ func (d *DB) HealthCheck(ctx context.Context) error {
_, err := d.conn.ExecContext(ctx, "SELECT 1")
return err
}
+
+// ApiLogEntry represents an API call log.
+type ApiLogEntry struct {
+ Username string
+ ArticleName string
+ StatusCode int
+ ResponseMs int
+ Error string
+ RequestURL string
+}
+
+// LogApiCall stores an API call log in the database.
+func (d *DB) LogApiCall(ctx context.Context, entry *ApiLogEntry) error {
+ _, err := d.conn.ExecContext(ctx,
+ `INSERT INTO api_logs (username, article_name, status_code, response_time_ms, error, request_url)
+ VALUES (?, ?, ?, ?, ?, ?)`,
+ entry.Username, entry.ArticleName, entry.StatusCode,
+ entry.ResponseMs, entry.Error, entry.RequestURL,
+ )
+ if err != nil {
+ return fmt.Errorf("log api call: %w", err)
+ }
+ return nil
+}