feat: track and report per-database and total backup duration in status and notifications
Build and Push / build (godump, amd64, linux) (push) Successful in 15s
Build and Push / build (godump, amd64, linux) (push) Successful in 15s
This commit is contained in:
@@ -23,6 +23,7 @@ type DBStatus struct {
|
|||||||
LastBackupTime time.Time `json:"last_backup_time"`
|
LastBackupTime time.Time `json:"last_backup_time"`
|
||||||
LastBackupSize int64 `json:"last_backup_size"`
|
LastBackupSize int64 `json:"last_backup_size"`
|
||||||
LastBackupResult string `json:"last_backup_result"` // success, skipped, failed
|
LastBackupResult string `json:"last_backup_result"` // success, skipped, failed
|
||||||
|
LastBackupDuration time.Duration `json:"last_backup_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceStatus struct {
|
type InstanceStatus struct {
|
||||||
@@ -42,6 +43,7 @@ type DBStatusSnapshot struct {
|
|||||||
LastBackupTime time.Time `json:"last_backup_time"`
|
LastBackupTime time.Time `json:"last_backup_time"`
|
||||||
LastBackupSize int64 `json:"last_backup_size"`
|
LastBackupSize int64 `json:"last_backup_size"`
|
||||||
LastBackupResult string `json:"last_backup_result"`
|
LastBackupResult string `json:"last_backup_result"`
|
||||||
|
LastBackupDuration time.Duration `json:"last_backup_duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceSnapshot struct {
|
type InstanceSnapshot struct {
|
||||||
@@ -74,6 +76,7 @@ func (s *InstanceStatus) Snapshot() InstanceSnapshot {
|
|||||||
LastBackupTime: db.LastBackupTime,
|
LastBackupTime: db.LastBackupTime,
|
||||||
LastBackupSize: db.LastBackupSize,
|
LastBackupSize: db.LastBackupSize,
|
||||||
LastBackupResult: db.LastBackupResult,
|
LastBackupResult: db.LastBackupResult,
|
||||||
|
LastBackupDuration: db.LastBackupDuration,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +297,7 @@ func (m *Manager) RunInstance(name string) {
|
|||||||
inst.mu.Lock()
|
inst.mu.Lock()
|
||||||
dbStatus := inst.Databases[db]
|
dbStatus := inst.Databases[db]
|
||||||
dbStatus.LastBackupTime = time.Now()
|
dbStatus.LastBackupTime = time.Now()
|
||||||
|
dbStatus.LastBackupDuration = duration
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(name, "Failed backup for database %s: %v", db, err)
|
logger.Error(name, "Failed backup for database %s: %v", db, err)
|
||||||
dbStatus.LastBackupResult = "failed"
|
dbStatus.LastBackupResult = "failed"
|
||||||
@@ -330,14 +334,18 @@ func (m *Manager) RunInstance(name string) {
|
|||||||
OverallResult: inst.OverallResult,
|
OverallResult: inst.OverallResult,
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
}
|
}
|
||||||
|
var totalDuration time.Duration
|
||||||
for _, db := range dbs {
|
for _, db := range dbs {
|
||||||
dbStat := inst.Databases[db]
|
dbStat := inst.Databases[db]
|
||||||
payload.Databases = append(payload.Databases, notify.DBResult{
|
payload.Databases = append(payload.Databases, notify.DBResult{
|
||||||
Name: dbStat.Name,
|
Name: dbStat.Name,
|
||||||
Size: dbStat.LastBackupSize,
|
Size: dbStat.LastBackupSize,
|
||||||
Result: dbStat.LastBackupResult,
|
Result: dbStat.LastBackupResult,
|
||||||
|
Duration: dbStat.LastBackupDuration,
|
||||||
})
|
})
|
||||||
|
totalDuration += dbStat.LastBackupDuration
|
||||||
}
|
}
|
||||||
|
payload.TotalDuration = totalDuration
|
||||||
inst.mu.Unlock()
|
inst.mu.Unlock()
|
||||||
|
|
||||||
logger.Info(name, "Backup job completed. Result: %s", inst.OverallResult)
|
logger.Info(name, "Backup job completed. Result: %s", inst.OverallResult)
|
||||||
|
|||||||
+16
-2
@@ -17,12 +17,14 @@ type DBResult struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Result string `json:"result"`
|
Result string `json:"result"`
|
||||||
|
Duration time.Duration `json:"duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
InstanceName string `json:"instance_name"`
|
InstanceName string `json:"instance_name"`
|
||||||
OverallResult string `json:"overall_result"`
|
OverallResult string `json:"overall_result"`
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
|
TotalDuration time.Duration `json:"total_duration"`
|
||||||
Databases []DBResult `json:"databases"`
|
Databases []DBResult `json:"databases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +60,9 @@ func sendWebhook(cfg config.WebhookConfig, payload Payload) {
|
|||||||
message := fmt.Sprintf("**GoDump Backup Result**\nInstance: %s\nResult: %s\nTime: %s\n\nDatabases:\n",
|
message := fmt.Sprintf("**GoDump Backup Result**\nInstance: %s\nResult: %s\nTime: %s\n\nDatabases:\n",
|
||||||
payload.InstanceName, payload.OverallResult, payload.Time.Format("2006-01-02 15:04:05"))
|
payload.InstanceName, payload.OverallResult, payload.Time.Format("2006-01-02 15:04:05"))
|
||||||
for _, db := range payload.Databases {
|
for _, db := range payload.Databases {
|
||||||
message += fmt.Sprintf("- %s: %s (%s)\n", db.Name, db.Result, formatBytes(db.Size))
|
message += fmt.Sprintf("- %s: %s (%s, %s)\n", db.Name, db.Result, formatBytes(db.Size), formatDuration(db.Duration))
|
||||||
}
|
}
|
||||||
|
message += fmt.Sprintf("\n**Total Duration:** %s", formatDuration(payload.TotalDuration))
|
||||||
|
|
||||||
webhookPayload := map[string]interface{}{
|
webhookPayload := map[string]interface{}{
|
||||||
"content": message,
|
"content": message,
|
||||||
@@ -113,6 +116,13 @@ func formatBytes(b int64) string {
|
|||||||
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
if d < time.Second {
|
||||||
|
return d.Round(time.Millisecond).String()
|
||||||
|
}
|
||||||
|
return d.Round(time.Second).String()
|
||||||
|
}
|
||||||
|
|
||||||
const emailTmplStr = `
|
const emailTmplStr = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -139,6 +149,7 @@ const emailTmplStr = `
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Database</th>
|
<th>Database</th>
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
|
<th>Duration</th>
|
||||||
<th>Result</th>
|
<th>Result</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -147,18 +158,21 @@ const emailTmplStr = `
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{.Name}}</td>
|
<td>{{.Name}}</td>
|
||||||
<td>{{formatBytes .Size}}</td>
|
<td>{{formatBytes .Size}}</td>
|
||||||
|
<td>{{formatDuration .Duration}}</td>
|
||||||
<td class="{{if eq .Result "success"}}accent{{else}}error{{end}}">{{.Result}}</td>
|
<td class="{{if eq .Result "success"}}accent{{else}}error{{end}}">{{.Result}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px;">Total Duration: <strong>{{formatDuration .TotalDuration}}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
||||||
func sendEmail(cfg config.EmailConfig, payload Payload) {
|
func sendEmail(cfg config.EmailConfig, payload Payload) {
|
||||||
tmpl, err := template.New("email").Funcs(template.FuncMap{"formatBytes": formatBytes}).Parse(emailTmplStr)
|
tmpl, err := template.New("email").Funcs(template.FuncMap{"formatBytes": formatBytes, "formatDuration": formatDuration}).Parse(emailTmplStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(payload.InstanceName, "Failed to parse email template: %v", err)
|
logger.Error(payload.InstanceName, "Failed to parse email template: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user