reload.go 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package config
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "slices"
  7. )
  8. // ReloadResult describes how a reload went. Problems are logged where they happen, Log writes a one line summary.
  9. type ReloadResult struct {
  10. // Err is set when the new config could not be read or parsed, no callbacks ran
  11. Err error
  12. // Failed is true when a callback could not apply part of the new config
  13. Failed bool
  14. // RestartRequired is true once any reload has needed a restart, and stays true until the process restarts
  15. RestartRequired bool
  16. // RestartKeys are the keys this reload changed that only apply on a restart
  17. RestartKeys []string
  18. }
  19. // Summary returns a one line summary of the reload and the level to log it at.
  20. func (r ReloadResult) Summary() (slog.Level, string) {
  21. switch {
  22. case r.Err != nil && r.RestartRequired:
  23. return slog.LevelError, "Config reload failed, the new config did not load and a restart is still required"
  24. case r.Err != nil:
  25. return slog.LevelError, "Config reload failed, the new config did not load"
  26. case r.Failed && r.RestartRequired:
  27. return slog.LevelError, "Config reload completed with errors and requires a restart to fully apply, review the logs"
  28. case r.Failed:
  29. return slog.LevelError, "Config reload completed with errors, review the logs"
  30. case r.RestartRequired:
  31. return slog.LevelWarn, "Config reload was completed but requires a restart to fully apply"
  32. default:
  33. return slog.LevelInfo, "Config reload complete"
  34. }
  35. }
  36. // String is the summary with Err and RestartKeys included, for output other than the log.
  37. func (r ReloadResult) String() string {
  38. _, msg := r.Summary()
  39. if r.Err != nil {
  40. msg = fmt.Sprintf("%s: %v", msg, r.Err)
  41. }
  42. if len(r.RestartKeys) > 0 {
  43. msg = fmt.Sprintf("%s, keys %v", msg, r.RestartKeys)
  44. }
  45. return msg
  46. }
  47. // Log writes Summary to l.
  48. func (r ReloadResult) Log(l *slog.Logger) {
  49. level, msg := r.Summary()
  50. if len(r.RestartKeys) > 0 {
  51. l.Log(context.Background(), level, msg, "keys", r.RestartKeys)
  52. return
  53. }
  54. l.Log(context.Background(), level, msg)
  55. }
  56. // Reload is passed to each reload callback to report how applying the new config went.
  57. type Reload struct {
  58. c *C
  59. failed bool
  60. restart bool
  61. // Changed keys that only apply on a restart, from every callback
  62. keys []string
  63. }
  64. // Failed marks the reload as failed, the caller logs why. Safe to call on a nil Reload, which is what the initial load
  65. // passes.
  66. func (r *Reload) Failed() {
  67. if r != nil {
  68. r.failed = true
  69. }
  70. }
  71. // RestartRequired marks the reload as needing a restart, the caller logs why. Safe to call on a nil Reload.
  72. func (r *Reload) RestartRequired() {
  73. if r != nil {
  74. r.restart = true
  75. }
  76. }
  77. // RestartRequiredIfChanged marks the reload as needing a restart if any of keys changed, and adds them to RestartKeys.
  78. // Use it for keys that are only read at startup. Safe to call on a nil Reload.
  79. func (r *Reload) RestartRequiredIfChanged(keys ...string) {
  80. if r == nil {
  81. return
  82. }
  83. for _, k := range keys {
  84. if r.c.HasChanged(k) && !slices.Contains(r.keys, k) {
  85. r.keys = append(r.keys, k)
  86. r.restart = true
  87. }
  88. }
  89. }