Skip to content

Commit 51018b3

Browse files
feat(notifications): enhance default-legacy template with fields and debug logging (#200)
- Fixed missing container/image info in notifications by updating default-legacy template to use logrus fields - Revised template to past tense ("Stopped", "Removed") and removed signal for consistency and brevity - Added debug logging in shoutrrr.go for message generation and sending - Updated Notifications.md to document new template and behavior - Adjusted test to remove trailing newline expectation
1 parent c305398 commit 51018b3

File tree

9 files changed

+89
-13
lines changed

9 files changed

+89
-13
lines changed

cmd/root.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,19 @@ func runUpdatesWithNotifications(filter types.Filter) *metrics.Metric {
809809
logrus.WithError(err).Error("Update execution failed")
810810
}
811811

812+
// Debug report
813+
updatedNames := make([]string, 0, len(result.Updated()))
814+
for _, r := range result.Updated() {
815+
updatedNames = append(updatedNames, r.Name())
816+
}
817+
818+
logrus.WithFields(logrus.Fields{
819+
"scanned": len(result.Scanned()),
820+
"updated": len(result.Updated()),
821+
"failed": len(result.Failed()),
822+
"updated_names": updatedNames,
823+
}).Debug("Report before notification")
824+
812825
// Send the batched notification with update results (successes, failures, etc.).
813826
notifier.SendNotification(result)
814827

docs/notifications.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ system, [logrus](http://github.com/sirupsen/logrus).
1616

1717
## Settings
1818

19-
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info`, `debug` or `trace`.
19+
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level for notifications. Defaults to `info`. In legacy mode (`--notification-report=false`), only `info`-level logs trigger notifications, ensuring a focused step-by-step update summary. Possible values: `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`.
2020
- `--notifications-hostname` (env. `WATCHTOWER_NOTIFICATIONS_HOSTNAME`): Custom hostname specified in subject/title. Useful to override the operating system hostname.
2121
- `--notifications-delay` (env. `WATCHTOWER_NOTIFICATIONS_DELAY`): Delay before sending notifications expressed in seconds.
2222
- Watchtower will post a notification every time it is started. This behavior [can be changed](https://nicholas-fedor.github.io/watchtower/arguments/#without_sending_a_startup_message) with an argument.
@@ -47,17 +47,47 @@ Simple templates are used unless the `notification-report` flag is specified:
4747

4848
## Simple templates
4949

50-
The default value if not set is `{{range .}}{{.Message}}{{println}}{{end}}`. The example below uses a template that also
51-
outputs timestamp and log level.
50+
The default template for simple notifications (used when `WATCHTOWER_NOTIFICATION_REPORT` is not set) is:
51+
52+
```go
53+
{{- range $i, $e := . -}}
54+
{{- if $i}}{{- println -}}{{- end -}}
55+
{{- $msg := $e.Message -}}
56+
{{- if eq $msg "Found new image" -}}
57+
Found new image: {{$e.Data.image}} ({{with $e.Data.new_id}}{{.}}{{else}}unknown{{end}})
58+
{{- else if eq $msg "Stopping container" -}}
59+
Stopped stale container: {{$e.Data.container}} ({{with $e.Data.id}}{{.}}{{else}}unknown{{end}})
60+
{{- else if eq $msg "Started new container" -}}
61+
Started new container: {{$e.Data.container}} ({{with $e.Data.new_id}}{{.}}{{else}}unknown{{end}})
62+
{{- else if eq $msg "Removing image" -}}
63+
Removed stale image: {{with $e.Data.image_id}}{{.}}{{else}}unknown{{end}}
64+
{{- else if $e.Data -}}
65+
{{$msg}} | {{range $k, $v := $e.Data -}}{{$k}}={{$v}} {{- end}}
66+
{{- else -}}
67+
{{$msg}}
68+
{{- end -}}
69+
{{- end -}}
70+
```
71+
72+
This template processes `info`-level log entries as they occur, formatting key update events in past tense with container and image details from `logrus` fields. It sends each event immediately in legacy mode, mimicking a step-by-step log.
73+
74+
Example output for a single container update with `WATCHTOWER_CLEANUP` enabled:
75+
76+
```text
77+
Found new image: /app:latest (bb7ba9626731)
78+
Stopped stale container: /app (4a2a8f7298a2)
79+
Started new container: /app (f52721881bed)
80+
Removed stale image: 78612560eb20
81+
```
82+
83+
!!! note "Field Handling"
84+
If expected fields (e.g., `new_id`, `id`, `image_id`) are missing, the template uses "unknown" as a fallback to ensure readable output (e.g., `Stopped stale container: /app (unknown)`).
5285

5386
!!! tip "Custom date format"
54-
If you want to adjust the date/time format it must show how the
55-
[reference time](https://golang.org/pkg/time/#pkg-constants) (_Mon Jan 2 15:04:05 MST 2006_) would be displayed in your
56-
custom format.
57-
i.e., The day of the year has to be 1, the month has to be 2 (february), the hour 3 (or 15 for 24h time) etc.
87+
To include timestamps, modify the template with .Time.Format, e.g., {{.Time.Format "2006-01-02 15:04:05"}} {{$msg}}. The reference time format is Mon Jan 2 15:04:05 MST 2006, so adjust accordingly (e.g., day as 1, month as 2, hour as 3 or 15).
5888

5989
!!! note "Skipping notifications"
60-
To skip sending notifications that do not contain any information, you can wrap your template with `{{if .}}` and `{{end}}`.
90+
To skip sending notifications that do not contain any information, you can wrap your template with `{{if .}}` and `{{end}}`. The default template does not skip empty messages in legacy mode, as it processes logs as they occur.
6191

6292
Example:
6393

internal/actions/update.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ func cleanupImages(client container.Client, imageIDs map[types.ImageID]bool) {
384384
if err := client.RemoveImageByID(imageID); err != nil {
385385
logrus.WithError(err).WithField("image_id", imageID).Warn("Failed to remove image")
386386
} else {
387+
// Log detailed removal message
387388
logrus.WithField("image_id", imageID).Debug("Removed image")
388389
}
389390
}

pkg/container/container_source.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func StopSourceContainer(
174174

175175
// Stop the container if it’s running.
176176
if sourceContainer.IsRunning() {
177+
// Log detailed stop message
177178
clog.WithField("signal", signal).Info("Stopping container")
178179

179180
if err := api.ContainerKill(ctx, string(sourceContainer.ID()), signal); err != nil {
@@ -370,9 +371,9 @@ func getLegacyNetworkConfig(
370371
}
371372
}
372373

373-
// Warn if MAC preservation is desired but not possible
374+
// Provide debug log message if MAC preservation is desired but not possible
374375
if len(networks) > 0 {
375-
clog.Warn("MAC addresses not preserved in legacy config")
376+
clog.Debug("MAC addresses not preserved in legacy config")
376377
}
377378

378379
return config

pkg/container/container_target.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func StartTargetContainer(
8787
return createdContainerID, fmt.Errorf("%w: %w", errStartContainerFailed, err)
8888
}
8989

90+
// Log detailed start message
9091
clog.WithField("new_id", createdContainerID.ShortID()).Info("Started new container")
9192

9293
return createdContainerID, nil

pkg/container/image.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ func (c imageClient) HasNewImage(
138138
return false, currentImageID, nil
139139
}
140140

141+
// Log full image name and ID
141142
clog.WithField("new_id", newImageID.ShortID()).Info("Found new image")
142143

143144
return true, newImageID, nil

pkg/notifications/common_templates.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
package notifications
22

33
var commonTemplates = map[string]string{
4-
`default-legacy`: "{{range .}}{{.Message}}{{println}}{{end}}",
4+
"default-legacy": `
5+
{{- range $i, $e := . -}}
6+
{{- if $i}}{{- println -}}{{- end -}}
7+
{{- $msg := $e.Message -}}
8+
{{- if eq $msg "Found new image" -}}
9+
Found new image: {{$e.Data.image}} ({{with $e.Data.new_id}}{{.}}{{else}}unknown{{end}})
10+
{{- else if eq $msg "Stopping container" -}}
11+
Stopped stale container: {{$e.Data.container}} ({{with $e.Data.id}}{{.}}{{else}}unknown{{end}})
12+
{{- else if eq $msg "Started new container" -}}
13+
Started new container: {{$e.Data.container}} ({{with $e.Data.new_id}}{{.}}{{else}}unknown{{end}})
14+
{{- else if eq $msg "Removing image" -}}
15+
Removed stale image: {{with $e.Data.image_id}}{{.}}{{else}}unknown{{end}}
16+
{{- else if $e.Data -}}
17+
{{$msg}} | {{range $k, $v := $e.Data -}}{{$k}}={{$v}} {{- end}}
18+
{{- else -}}
19+
{{$msg}}
20+
{{- end -}}
21+
{{- end -}}`,
522

623
`default`: `
724
{{- if .Report -}}

pkg/notifications/shoutrrr.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ func createNotifier(
182182
// - notifier: Notifier instance.
183183
func sendNotifications(notifier *shoutrrrTypeNotifier) {
184184
for msg := range notifier.messages {
185+
LocalLog.WithField("message", msg).Debug("Sending notification")
185186
time.Sleep(notifier.delay)
186187
errs := notifier.Router.Send(msg, notifier.params)
187188

@@ -223,7 +224,10 @@ func (n *shoutrrrTypeNotifier) buildMessage(data Data) (string, error) {
223224
return "", fmt.Errorf("failed to execute template: %w", err)
224225
}
225226

226-
return body.String(), nil
227+
msg := body.String()
228+
LocalLog.WithField("message", msg).Debug("Generated notification message")
229+
230+
return msg, nil
227231
}
228232

229233
// sendEntries sends batched entries and optional report.
@@ -234,6 +238,10 @@ func (n *shoutrrrTypeNotifier) buildMessage(data Data) (string, error) {
234238
func (n *shoutrrrTypeNotifier) sendEntries(entries []*logrus.Entry, report types.Report) {
235239
msg, err := n.buildMessage(Data{n.data, entries, report})
236240

241+
LocalLog.WithError(err).
242+
WithFields(logrus.Fields{"message": msg}).
243+
Debug("Preparing to send entries")
244+
237245
if msg == "" {
238246
// Log in go func in case we entered from Fire to avoid stalling
239247
go func() { // Avoid blocking if called from Fire.
@@ -244,8 +252,12 @@ func (n *shoutrrrTypeNotifier) sendEntries(entries []*logrus.Entry, report types
244252
}
245253
}()
246254

255+
LocalLog.Debug("Message empty, skipping send")
256+
247257
return
248258
}
259+
260+
LocalLog.Debug("Queuing notification message")
249261
n.messages <- msg
250262
}
251263

pkg/notifications/shoutrrr_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ updt1 (mock/updt1:latest): Updated
154154

155155
s, err := shoutrrr.buildMessage(Data{Entries: entries})
156156
gomega.Expect(err).NotTo(gomega.HaveOccurred())
157-
gomega.Expect(s).To(gomega.Equal("foo bar\n"))
157+
gomega.Expect(s).To(gomega.Equal("foo bar"))
158158
})
159159
})
160160
ginkgo.When("given a valid custom template", func() {

0 commit comments

Comments
 (0)