TestLogAuditToStdoutFormat is a contract test for the structured JSON fields emitted to stdout when audit log stdout is enabled. Operators run commands like `docker logs | grep '"log_type":"audit"'` — changes to these keys or their values are breaking for downstream log pipelines.
(t *testing.T)
| 20 | // like `docker logs <container> | grep '"log_type":"audit"'` — changes to |
| 21 | // these keys or their values are breaking for downstream log pipelines. |
| 22 | func TestLogAuditToStdoutFormat(t *testing.T) { |
| 23 | a := require.New(t) |
| 24 | |
| 25 | // Redirect slog default to a JSON handler writing into a buffer so the |
| 26 | // test can inspect the exact structured payload. |
| 27 | prev := slog.Default() |
| 28 | t.Cleanup(func() { slog.SetDefault(prev) }) |
| 29 | |
| 30 | var buf bytes.Buffer |
| 31 | slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{ |
| 32 | Level: slog.LevelInfo, |
| 33 | }))) |
| 34 | |
| 35 | p := &storepb.AuditLog{ |
| 36 | Parent: "workspaces/ws-abc", |
| 37 | Method: "/bytebase.v1.AuthService/Login", |
| 38 | Resource: "alice@example.com", |
| 39 | Severity: storepb.AuditLog_INFO, |
| 40 | User: "users/alice@example.com", |
| 41 | Request: `{"email":"alice@example.com","web":true}`, |
| 42 | Response: `{"user":{"name":"users/alice@example.com","email":"alice@example.com"}}`, |
| 43 | Status: &spb.Status{Code: 0, Message: ""}, |
| 44 | Latency: durationpb.New(123_000_000), // 123ms |
| 45 | RequestMetadata: &storepb.RequestMetadata{ |
| 46 | CallerIp: "10.0.1.50", |
| 47 | CallerSuppliedUserAgent: "TestAgent/1.0", |
| 48 | }, |
| 49 | } |
| 50 | |
| 51 | logAuditToStdout(context.Background(), p) |
| 52 | |
| 53 | // Every line the handler wrote is an independent JSON object. |
| 54 | lines := bytes.Split(bytes.TrimSpace(buf.Bytes()), []byte{'\n'}) |
| 55 | a.Len(lines, 1, "logAuditToStdout must emit exactly one JSON record") |
| 56 | |
| 57 | var got map[string]any |
| 58 | a.NoError(json.Unmarshal(lines[0], &got), |
| 59 | "stdout record must be valid JSON: %s", string(lines[0])) |
| 60 | |
| 61 | // Keys the customer explicitly greps for — these form our public contract. |
| 62 | // If any of these assertions need to change, it's a breaking change for |
| 63 | // everyone consuming audit stdout logs. |
| 64 | a.Equal("audit", got["log_type"], `log_type=="audit" is how operators filter audit lines from application logs`) |
| 65 | a.Equal("workspaces/ws-abc", got["parent"]) |
| 66 | a.Equal("/bytebase.v1.AuthService/Login", got["method"]) |
| 67 | a.Equal("alice@example.com", got["resource"]) |
| 68 | a.Equal("users/alice@example.com", got["user"]) |
| 69 | a.Equal("INFO", got["severity"]) |
| 70 | a.Equal("10.0.1.50", got["client_ip"]) |
| 71 | a.Equal("TestAgent/1.0", got["user_agent"]) |
| 72 | a.Equal(float64(123), got["latency_ms"], "latency must be exposed in milliseconds") |
| 73 | a.Equal(`{"email":"alice@example.com","web":true}`, got["request"], |
| 74 | "request payload must be emitted verbatim (already redacted upstream)") |
| 75 | a.Equal(`{"user":{"name":"users/alice@example.com","email":"alice@example.com"}}`, got["response"]) |
| 76 | |
| 77 | // Status code 0 (OK) should be reported explicitly so downstream pipelines |
| 78 | // can distinguish successful from failed calls. |
| 79 | a.Equal(float64(0), got["status_code"]) |
nothing calls this directly
no test coverage detected