carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// Package apperr is the pure half of the Errors tab (spec
2// 2026-09-07-errors-and-analytics-design.md §3.1–3.2): the wire record,
3// sanitisation, fingerprinting, the box-side coalescing sink, the chunk
4// read and the grouping fold. No I/O and no clock of its own — the same
5// split internal/applog keeps: internal/host ships, internal/edge and the
6// activator produce, internal/console reads.
7package apperr
8
9import (
10 "time"
11
12 "github.com/carlosframework/platform/internal/applog"
13)
14
15// Sources an error can come from (spec §3.1). log is never stored in the
16// errors/ family — it is derived on read from logs/ (§3.2).
17const (
18 SourceLog = "log"
19 SourceEdge = "edge"
20 SourceServer = "server"
21 SourceBrowser = "browser"
22)
23
24// Kinds the platform itself produces. A server or browser caller sets its
25// own kind (an exception class, typically).
26const (
27 KindHTTP = "http" // an edge-observed 5xx
28 KindLog = "log" // a log line at or above the configured level
29)
30
31// FPVersion stamps the fingerprint algorithm so it can change without
32// merging old groups into new ones.
33const FPVersion = 1
34
35// Record is one coalesced error on the wire — one JSONL line inside an
36// errors/ chunk. Additive shape (AGENTS.md rule): every field but TS,
37// Source, Name and FP is omitempty, unknown keys are ignored on read.
38// Count, First and Last are the coalescing state: one record stands for
39// Count occurrences of FP on Host under Release between First and Last;
40// the other fields are the first occurrence's sample.
41type Record struct {
42 TS time.Time `json:"ts"`
43 Box string `json:"box,omitempty"`
44 Host string `json:"host,omitempty"`
45 Source string `json:"source"`
46 Kind string `json:"kind,omitempty"`
47 Name string `json:"name"`
48 Level string `json:"level,omitempty"` // log source: warn | error
49 Path string `json:"path,omitempty"`
50 Status int `json:"status,omitempty"`
51 Cause string `json:"cause,omitempty"` // Availability: why the edge could not get an answer (availability.go)
52 Stream string `json:"stream,omitempty"` // log source only: the applog stream, derived on read and never stored
53 Release string `json:"release,omitempty"`
54 Stack string `json:"stack,omitempty"`
55 Fields map[string]string `json:"fields,omitempty"`
56 FP string `json:"fp"`
57 FPV int `json:"fpv,omitempty"`
58 Count int `json:"count,omitempty"`
59 First time.Time `json:"first,omitempty"`
60 Last time.Time `json:"last,omitempty"`
61 Seq uint64 `json:"seq,omitempty"`
62}
63
64// MaxStack bounds a stored stack (spec §3.1).
65const MaxStack = 8 << 10
66
67// FromLog derives the log-source record for one applog line (spec §3.2:
68// read-derived, never stored twice). Fingerprinted on the normalised
69// message alone — a log line has no frame.
70func FromLog(r applog.Record) Record {
71 name := Sanitize(r.Msg)
72 return Record{
73 TS: r.TS, Box: r.Box, Host: r.Host,
74 Source: SourceLog, Kind: KindLog, Name: name, Level: r.Level, Stream: r.Stream,
75 Fields: r.Fields,
76 FP: Fingerprint(SourceLog, KindLog, name, ""), FPV: FPVersion,
77 Count: 1, First: r.TS, Last: r.TS, Seq: r.Seq,
78 }
79}
80