fixes
This commit is contained in:
1 parent
c8cc0fb28a
commit
e790c02d59
16 files changed
+659
-15
No files matched your search
@@ -3,8 +3,10 @@
|
||||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -86,6 +88,8 @@ type Issue struct {
|
||||
// Status is the triage state: open, resolved or ignored.
|
||||
Status string `json:"status"`
|
||||
ResolvedAt int64 `json:"resolvedAt,omitempty"`
|
||||
// RegressedAt is when a resolved issue last fired again and was reopened.
|
||||
RegressedAt int64 `json:"regressedAt,omitempty"`
|
||||
// NormalizedTitle is the deterministic, redacted form used for exact grouping.
|
||||
NormalizedTitle string `json:"normalizedTitle,omitempty"`
|
||||
// PatternID groups related exact issues without merging their operational IDs.
|
||||
@@ -117,10 +121,14 @@ type Pattern struct {
|
||||
// Tracker incrementally fingerprints and deduplicates errors as they're
|
||||
// recorded. It is safe for concurrent use.
|
||||
type Tracker struct {
|
||||
mu sync.Mutex
|
||||
issues map[string]*Issue
|
||||
feedback map[string]string
|
||||
sink OccurrenceSink
|
||||
mu sync.Mutex
|
||||
issues map[string]*Issue
|
||||
// triage holds persisted resolved/ignored decisions by issue id, applied
|
||||
// when an issue is (re)created after a restart.
|
||||
triage map[string]TriageState
|
||||
statusStore StatusStore
|
||||
feedback map[string]string
|
||||
sink OccurrenceSink
|
||||
}
|
||||
|
||||
// OccurrenceSink receives every recorded occurrence (already normalized) so
|
||||
@@ -161,7 +169,43 @@ type ListPage struct {
|
||||
|
||||
// NewTracker creates an empty Tracker.
|
||||
func NewTracker() *Tracker {
|
||||
return &Tracker{issues: make(map[string]*Issue), feedback: make(map[string]string)}
|
||||
return &Tracker{issues: make(map[string]*Issue), feedback: make(map[string]string), triage: make(map[string]TriageState)}
|
||||
}
|
||||
|
||||
// SetStatusStore loads persisted triage decisions and saves future ones.
|
||||
func (t *Tracker) SetStatusStore(ctx context.Context, store StatusStore) error {
|
||||
states, err := store.Load(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.statusStore = store
|
||||
for id, st := range states {
|
||||
t.triage[id] = st
|
||||
if issue, ok := t.issues[id]; ok {
|
||||
issue.Status, issue.ResolvedAt = st.Status, st.ResolvedAt
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// persistLocked records a decision. The write happens off the lock.
|
||||
func (t *Tracker) persistLocked(id string, st TriageState) {
|
||||
if st.Status == StatusOpen {
|
||||
delete(t.triage, id)
|
||||
} else {
|
||||
t.triage[id] = st
|
||||
}
|
||||
if t.statusStore == nil {
|
||||
return
|
||||
}
|
||||
store := t.statusStore
|
||||
go func() {
|
||||
if err := store.Save(context.Background(), id, st); err != nil {
|
||||
log.Printf("failed to persist issue status: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Record adds an occurrence of an error, creating a new issue or merging into
|
||||
@@ -196,6 +240,9 @@ func (t *Tracker) record(kind, service, title string, sample Sample) (string, Oc
|
||||
Status: StatusOpen,
|
||||
NormalizedTitle: NormalizeTitle(title),
|
||||
}
|
||||
if st, ok := t.triage[id]; ok {
|
||||
issue.Status, issue.ResolvedAt = st.Status, st.ResolvedAt
|
||||
}
|
||||
issue.PatternSignature = patternSignature(kind, service, title, sample)
|
||||
issue.PatternID = Fingerprint("pattern", issue.PatternSignature)
|
||||
t.issues[id] = issue
|
||||
@@ -214,6 +261,8 @@ func (t *Tracker) record(kind, service, title string, sample Sample) (string, Oc
|
||||
if issue.Status == StatusResolved {
|
||||
issue.Status = StatusOpen
|
||||
issue.ResolvedAt = 0
|
||||
issue.RegressedAt = sample.Timestamp
|
||||
t.persistLocked(id, TriageState{Status: StatusOpen})
|
||||
}
|
||||
issue.Samples = append(issue.Samples, sample)
|
||||
if len(issue.Samples) > maxSamples {
|
||||
@@ -462,6 +511,8 @@ func (t *Tracker) SetStatus(id, status string) (Issue, bool) {
|
||||
} else {
|
||||
issue.ResolvedAt = time.Now().UnixNano()
|
||||
}
|
||||
issue.RegressedAt = 0
|
||||
t.persistLocked(id, TriageState{Status: status, ResolvedAt: issue.ResolvedAt})
|
||||
return cloneIssue(issue), true
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user