log tailer: the rotated mail.log read until postlogd lets it go
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
postlogd opens maillog_file once per process (Postfix 3.7.11,
src/postlogd/postlogd.c, pre_jail_init()) and leaves it only when it
exits: at "postfix reload" master closes the service's status pipe
(src/master/master_service.c, master_restart_service() ->
master_status_cleanup()) and dgram_server_abort() exits on reading its end
(src/master/dgram_server.c). logrotate renames and creates, then runs the
reload (build/logrotate-mail.conf postrotate; logrotate 3.21.0,
logrotate.c, rotateLogSet(), after rotateSingleLog()), so lines go to the
renamed file in between, and the tailer, already on the new one, never
read them (assumptions A-33). follow() now keeps the renamed file open as a
rotatedTail and reads it on each tick for rotatedGrace (a minute, far
longer than the gap); its places are never saved as the resume point, so a
restart still reads the rotated file through readRotated. A truncated file
is not kept.
The RFC 3339 stamp goes (A-12): it was written for a maillog_file_format
said to arrive in Postfix 3.9; there is none in 3.7.11 nor in the 3.12
snapshot of 2026-09-25 (proto/postconf.proto; msg_logger.c,
msg_logger_print() keeps strftime("%b %d %H:%M:%S ")). Every fixture now
carries the stamp Postfix writes; logtail's TestMain fixes time.Local to
UTC, since the stamp is local time and this PC is UTC+3.
Tests: TestFollowReadsTheRotatedFileAfterTheSwitch (Linux: a line appended
to the renamed file after the switch reaches the store). Stand
(2026-10-14): go vet and go test ./... pass on the server (23 packages);
make e2e exit 0, delivery_log_survives_rotation included. The tested tree
was this commit plus the previous one; the CHANGELOG entry was written
after the run, and a comment's logrotate citation corrected
(rotateLogSet runs postrotate, not rotateSingleLog).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -153,9 +153,9 @@ func TestDeliveryPageMarksAQueuedMessageAsStillWaiting(t *testing.T) {
|
||||
func TestDeliveryPageShowsThisMessagesLogLines(t *testing.T) {
|
||||
h, row := serverWithDelivery(t)
|
||||
h.cfg.MailLogPath = writeMailLog(t,
|
||||
"2026-08-03T05:15:52.219218+00:00 host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com[203.0.113.4]",
|
||||
"2026-08-03T05:15:52.300000+00:00 host postfix/qmgr[10]: 99999999: from=<other@example.ru>, size=500, nrcpt=1 (queue active)",
|
||||
"2026-08-03T05:16:03.884210+00:00 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, dsn=2.0.0, status=sent (250 OK)",
|
||||
"Aug 03 05:15:52 host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com[203.0.113.4]",
|
||||
"Aug 03 05:15:52 host postfix/qmgr[10]: 99999999: from=<other@example.ru>, size=500, nrcpt=1 (queue active)",
|
||||
"Aug 03 05:16:03 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, dsn=2.0.0, status=sent (250 OK)",
|
||||
)
|
||||
|
||||
out := getBody(t, h.HandleDelivery, "/outbound/log/"+itoa(row.ID))
|
||||
@@ -163,8 +163,8 @@ func TestDeliveryPageShowsThisMessagesLogLines(t *testing.T) {
|
||||
`<pre class="sp-log">`,
|
||||
// The stamp is split off from the line, without the microseconds and the
|
||||
// offset that make it the widest thing on it.
|
||||
`<span class="sp-d">2026-08-03 05:15:52</span> host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com`,
|
||||
`<span class="sp-d">2026-08-03 05:16:03</span> host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>`,
|
||||
`<span class="sp-d">Aug 03 05:15:52</span> host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com`,
|
||||
`<span class="sp-d">Aug 03 05:16:03</span> host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>`,
|
||||
"status=sent (250 OK)",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
@@ -196,20 +196,20 @@ func TestDeliveryPageKeepsAnUnstampedLogLineWhole(t *testing.T) {
|
||||
func TestDeliveryPageShowsStoredLogLinesAfterRotation(t *testing.T) {
|
||||
h, row := serverWithDelivery(t)
|
||||
h.cfg.RetryPolicy = postfix.DefaultRetryPolicy()
|
||||
h.cfg.MailLogPath = writeMailLog(t, "2026-08-04T00:00:01.000000+00:00 host postfix/qmgr[10]: 99999999: removed")
|
||||
h.cfg.MailLogPath = writeMailLog(t, "Aug 04 00:00:01 host postfix/qmgr[10]: 99999999: removed")
|
||||
if _, err := h.store.InsertSendLogLines([]store.SendLogLine{
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.AddDate(0, 0, -40), Line: "2026-06-24T05:00:00.000000+00:00 host postfix/qmgr[10]: 4A1B2C3D: an earlier message under the same id"},
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.Add(-time.Second), Line: "2026-08-03T05:15:52.219218+00:00 host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com[203.0.113.4]"},
|
||||
{QueueID: "99999999", At: row.CreatedAt, Line: "2026-08-03T05:15:52.300000+00:00 host postfix/qmgr[10]: 99999999: from=<other@example.ru>, size=500, nrcpt=1 (queue active)"},
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.Add(11 * time.Second), Line: "2026-08-03T05:16:03.884210+00:00 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, dsn=2.0.0, status=sent (250 OK)"},
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.AddDate(0, 0, -40), Line: "Jun 24 05:00:00 host postfix/qmgr[10]: 4A1B2C3D: an earlier message under the same id"},
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.Add(-time.Second), Line: "Aug 03 05:15:52 host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com[203.0.113.4]"},
|
||||
{QueueID: "99999999", At: row.CreatedAt, Line: "Aug 03 05:15:52 host postfix/qmgr[10]: 99999999: from=<other@example.ru>, size=500, nrcpt=1 (queue active)"},
|
||||
{QueueID: row.QueueID, At: row.CreatedAt.Add(11 * time.Second), Line: "Aug 03 05:16:03 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, dsn=2.0.0, status=sent (250 OK)"},
|
||||
}, false); err != nil {
|
||||
t.Fatalf("InsertSendLogLines: %v", err)
|
||||
}
|
||||
|
||||
out := getBody(t, h.HandleDelivery, "/outbound/log/"+itoa(row.ID))
|
||||
for _, want := range []string{
|
||||
`<span class="sp-d">2026-08-03 05:15:52</span> host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com`,
|
||||
`<span class="sp-d">2026-08-03 05:16:03</span> host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>`,
|
||||
`<span class="sp-d">Aug 03 05:15:52</span> host postfix/smtpd[20]: 4A1B2C3D: client=mail.example.com`,
|
||||
`<span class="sp-d">Aug 03 05:16:03</span> host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>`,
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("delivery log is missing %q:\n%s", want, out)
|
||||
|
||||
@@ -159,7 +159,7 @@ func TestMessagePageDrawsTheOutcomeAtItsLevel(t *testing.T) {
|
||||
} {
|
||||
h, row := serverWithDelivery(t)
|
||||
h.cfg.MailLogPath = writeMailLog(t,
|
||||
"2026-08-03T05:16:03.884210+00:00 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, status="+tc.status+" (reply)")
|
||||
"Aug 03 05:16:03 host postfix/smtp[26]: 4A1B2C3D: to=<public@example.ru>, status="+tc.status+" (reply)")
|
||||
if _, err := h.store.UpdateStatus(row.QueueID, row.To, tc.status); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func TestARefusedMessageShowsPostfixsLine(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if subject == "with its line" {
|
||||
line := time.Now().UTC().Format("2006-01-02T15:04:05Z") + " mail postfix/smtpd[2131]: NOQUEUE: milter-reject: MAIL from unknown[198.51.100.7]: 451 4.7.1 Service unavailable - try again later; from=<alerts@example.org> proto=ESMTP helo=<app>"
|
||||
line := time.Now().Format("Jan 02 15:04:05") + " mail postfix/smtpd[2131]: NOQUEUE: milter-reject: MAIL from unknown[198.51.100.7]: 451 4.7.1 Service unavailable - try again later; from=<alerts@example.org> proto=ESMTP helo=<app>"
|
||||
if ok, err := st.AttachRefusalLine("198.51.100.7", "alerts", "alerts@example.org", time.Now(), line); err != nil || !ok {
|
||||
t.Fatalf("attach = %v, %v", ok, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user