check / check (push) Waiting to run
Process log lines carry instance, as request lines do; the instance name is read before the other settings, so the line saying a setting is invalid carries it too. Every metric, Go's and the process's included, carries the label instance, set once on the registry. README.md says so, and that Prometheus keeps it as exported_instance unless the scrape sets honor_labels. Tests that read metrics expect the label. One test helper replaces the two alert tests' loops that wait for the metrics. Judgement call: the label is named instance, as in the log lines and alerts, although Prometheus gives each target a label of that name. Model: opus-5-5
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
package requestlog_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
)
|
|
|
|
func TestWriteWritesOneJSONLineMarkedRequest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var out bytes.Buffer
|
|
|
|
err := requestlog.Write(&out, &requestlog.Line{
|
|
Time: requestlog.FormatTime(time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)),
|
|
ClientIP: "203.0.113.9",
|
|
Status: 200,
|
|
Action: requestlog.ActionForward,
|
|
DurationTotal: requestlog.Milliseconds(1500 * time.Microsecond),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
|
|
text := out.String()
|
|
if strings.Count(text, "\n") != 1 || !strings.HasSuffix(text, "\n") {
|
|
t.Fatalf("wrote %q, want one line", text)
|
|
}
|
|
|
|
var fields map[string]any
|
|
|
|
err = json.Unmarshal(out.Bytes(), &fields)
|
|
if err != nil {
|
|
t.Fatalf("decode %q: %v", text, err)
|
|
}
|
|
|
|
want := map[string]any{
|
|
"type": "request", "time": "2026-10-03T12:00:00.000Z",
|
|
"client_ip": "203.0.113.9", "status": 200.0, "action": "forward",
|
|
"duration_total": 1.5,
|
|
}
|
|
for name, value := range want {
|
|
if fields[name] != value {
|
|
t.Errorf("%s is %v, want %v", name, fields[name], value)
|
|
}
|
|
}
|
|
|
|
unset := []string{
|
|
"forwarded_for", "content_type", "content_length", "request_headers",
|
|
"has_authorization", "has_cookie", "websocket", "response_content_type",
|
|
"upstream_status", "cache_control", "location", "aborted", "counts",
|
|
"limit_hit", "offence", "ban_expires", "duration_checks",
|
|
"duration_upstream_connect", "duration_upstream_first_byte",
|
|
"duration_upstream_total",
|
|
}
|
|
for _, name := range unset {
|
|
_, present := fields[name]
|
|
if present {
|
|
t.Errorf("%s is there with no value to give", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var out bytes.Buffer
|
|
|
|
requestlog.NewProcessLogger(&out, "fsn1app1/gitea").Info("starting", "version", "v1")
|
|
|
|
var fields map[string]any
|
|
|
|
err := json.Unmarshal(out.Bytes(), &fields)
|
|
if err != nil {
|
|
t.Fatalf("decode %q: %v", out.String(), err)
|
|
}
|
|
|
|
if fields["type"] != "process" || fields["instance"] != "fsn1app1/gitea" ||
|
|
fields["msg"] != "starting" || fields["level"] != "INFO" ||
|
|
fields["version"] != "v1" {
|
|
t.Errorf("process line %v", fields)
|
|
}
|
|
|
|
timeText, _ := fields["time"].(string)
|
|
|
|
logged, err := time.Parse(time.RFC3339, timeText)
|
|
if err != nil || !strings.HasSuffix(timeText, "Z") ||
|
|
len(timeText) != len("2006-01-02T15:04:05.000Z") ||
|
|
time.Since(logged) > time.Minute {
|
|
t.Errorf("process line time %q, want now in UTC with milliseconds", timeText)
|
|
}
|
|
}
|