store.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package configissues
  2. import (
  3. "sync"
  4. log "github.com/sirupsen/logrus"
  5. )
  6. var (
  7. storeMu sync.RWMutex
  8. issues []Issue
  9. stickyMu sync.Mutex
  10. sticky []Issue
  11. )
  12. // BeginConfigLoad clears sticky issues captured during config decode/include load.
  13. func BeginConfigLoad() {
  14. stickyMu.Lock()
  15. defer stickyMu.Unlock()
  16. sticky = nil
  17. }
  18. // ReportSticky records an issue that cannot be re-derived after config decode
  19. // (for example unset environment variables that were already expanded).
  20. func ReportSticky(issue Issue) {
  21. stickyMu.Lock()
  22. defer stickyMu.Unlock()
  23. sticky = append(sticky, issue)
  24. }
  25. func copySticky() []Issue {
  26. stickyMu.Lock()
  27. defer stickyMu.Unlock()
  28. out := make([]Issue, len(sticky))
  29. copy(out, sticky)
  30. return out
  31. }
  32. // CopySticky returns a copy of sticky load-time issues.
  33. func CopySticky() []Issue {
  34. return copySticky()
  35. }
  36. // Replace sets the current issue list and logs only newly appeared issues.
  37. func Replace(next []Issue) {
  38. storeMu.Lock()
  39. defer storeMu.Unlock()
  40. previous := make(map[string]struct{}, len(issues))
  41. for _, issue := range issues {
  42. previous[issueFingerprint(issue)] = struct{}{}
  43. }
  44. issues = make([]Issue, len(next))
  45. copy(issues, next)
  46. for _, issue := range issues {
  47. if _, exists := previous[issueFingerprint(issue)]; exists {
  48. continue
  49. }
  50. logNewIssue(issue)
  51. }
  52. }
  53. func issueFingerprint(issue Issue) string {
  54. return issue.Severity + "\x00" +
  55. issue.Code + "\x00" +
  56. issue.ActionID + "\x00" +
  57. issue.ActionTitle + "\x00" +
  58. issue.ArgumentName + "\x00" +
  59. issue.Source + "\x00" +
  60. issue.ConfigFile + "\x00" +
  61. issue.Message
  62. }
  63. func logNewIssue(issue Issue) {
  64. fields := log.Fields{
  65. "code": issue.Code,
  66. "actionTitle": issue.ActionTitle,
  67. "argumentName": issue.ArgumentName,
  68. "configFile": issue.ConfigFile,
  69. "source": issue.Source,
  70. }
  71. if issue.Severity == SeverityError {
  72. log.WithFields(fields).Error(issue.Message)
  73. return
  74. }
  75. log.WithFields(fields).Warn(issue.Message)
  76. }
  77. // Report adds a live issue if it is not already present and logs it when new.
  78. // Use this for runtime discoveries that happen outside Rebuild (for example
  79. // filesystem watcher setup failures).
  80. func Report(issue Issue) {
  81. storeMu.Lock()
  82. defer storeMu.Unlock()
  83. issueFingerprintValue := issueFingerprint(issue)
  84. for _, existing := range issues {
  85. if issueFingerprint(existing) == issueFingerprintValue {
  86. return
  87. }
  88. }
  89. issues = append(issues, issue)
  90. logNewIssue(issue)
  91. }
  92. // List returns a copy of the current issues.
  93. func List() []Issue {
  94. storeMu.RLock()
  95. defer storeMu.RUnlock()
  96. out := make([]Issue, len(issues))
  97. copy(out, issues)
  98. return out
  99. }
  100. // Count returns the number of current issues.
  101. func Count() int {
  102. storeMu.RLock()
  103. defer storeMu.RUnlock()
  104. return len(issues)
  105. }