civodul pushed a commit to branch devel
in repository shepherd.

commit 1f5fdf772922a7edcb303fe0d39f4f95b07487c4
Author: Ludovic Courtès <l...@gnu.org>
AuthorDate: Fri Aug 9 23:06:50 2024 +0200

    system-log: Keep recent messages in a ring buffer.
    
    * modules/shepherd/logger.scm (log-line): Make ‘now’ an optional
    parameter.
    * modules/shepherd/service/system-log.scm (log-dispatcher):
    Add #:history-size.  Add ‘messages’ variable to ‘loop’; insert new
    elements into ‘messages’ when new messages are received.
    (spawn-log-dispatcher): Add #:history-size and pass it.
    (system-log-service): Add #:history-size and pass it.
    * tests/services/system-log.sh: Check the “Recent messages” output of
    ‘herd status system-log’.
    * doc/shepherd.texi (System Log Service): Document #:history-size.
---
 doc/shepherd.texi                       |  4 +++
 modules/shepherd/logger.scm             | 10 +++---
 modules/shepherd/service/system-log.scm | 55 ++++++++++++++++++++++-----------
 tests/services/system-log.sh            |  8 ++++-
 4 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/doc/shepherd.texi b/doc/shepherd.texi
index b45bb93..90c5593 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -2053,6 +2053,7 @@ host (see below).
          [#:requirement '()] @
          [#:kernel-log-file (kernel-log-file)] @
          [#:message-destination (default-message-destination-procedure)] @
+         [#:history-size (default-log-history-size)] @
          [#:max-silent-time (default-max-silent-time)]
 Return the system log service (@dfn{syslogd}) with the given
 @var{provision} and @var{requirement} (lists of symbols).  The service accepts
@@ -2063,6 +2064,9 @@ it also reads messages from @code{#:kernel-log-file}, 
which defaults to
 Log messages are passed to @var{message-destination}, a one-argument procedure
 that must return the list of files to write it to.  Write a mark to log files
 when no message has been logged for more than @var{max-silent-time} seconds.
+
+Keep up to @var{history-size} messages in memory for the purposes of allowing
+users to view recent messages without opening various files.
 @end deffn
 
 @deffn {Procedure} default-message-destination-procedure
diff --git a/modules/shepherd/logger.scm b/modules/shepherd/logger.scm
index c82a959..3d15d62 100644
--- a/modules/shepherd/logger.scm
+++ b/modules/shepherd/logger.scm
@@ -100,11 +100,11 @@ first message received."
                       (get-operation channel1)
                       (get-operation channel2))))
 
-(define (log-line line output)
-  "Write @var{line} to @var{output} and return its timestamp."
-  (let* ((now (current-time))
-         (prefix (strftime default-logfile-date-format
-                           (localtime now))))
+(define* (log-line line output #:optional (now (current-time)))
+  "Write @var{line} to @var{output} with @var{now} as its timestamp; return
+@var{now}."
+  (let ((prefix (strftime default-logfile-date-format
+                          (localtime now))))
     ;; Avoid (ice-9 format) to reduce heap allocations.
     (put-string output prefix)
     (put-string output line)
diff --git a/modules/shepherd/service/system-log.scm 
b/modules/shepherd/service/system-log.scm
index 6c5794e..89fad46 100644
--- a/modules/shepherd/service/system-log.scm
+++ b/modules/shepherd/service/system-log.scm
@@ -22,7 +22,8 @@
   #:use-module (shepherd service)
   #:use-module (shepherd support)
   #:autoload   (shepherd config) (%localstatedir)
-  #:autoload   (shepherd logger) (open-log-file
+  #:autoload   (shepherd logger) (default-log-history-size
+                                  open-log-file
                                   log-line
                                   rotate-and-reopen-log-file)
   #:autoload   (shepherd comm) (system-log-file)
@@ -251,7 +252,10 @@ and passing them to @var{dispatcher}."
                                (system-log-priority info))
                        "-- MARK --" #f)))
 
-(define* (log-dispatcher channel message-destination #:key max-silent-time)
+(define* (log-dispatcher channel message-destination
+                         #:key
+                         max-silent-time
+                         (history-size (default-log-history-size)))
   "Dispatch system log messages received on @var{channel} to log files.  Call
 @var{message-destination} for each system log message to determine the
 destination file(s)."
@@ -259,7 +263,8 @@ destination file(s)."
     (default-message-destination-procedure))
 
   (lambda ()
-    (let loop ((ports vlist-null))
+    (let loop ((ports vlist-null)
+               (messages (ring-buffer history-size)))
       (match (if max-silent-time
                  (get-message* channel max-silent-time 'timeout)
                  (get-message channel))
@@ -272,6 +277,7 @@ destination file(s)."
                            (l10n "Uncaught exception \
 in message destination procedure: "))
                           (default-message-destination message)))
+               (now (current-time))
                (line (system-log-message->string message)))
            (loop (fold (lambda (file ports)
                          (match (vhash-assoc file ports)
@@ -279,7 +285,7 @@ in message destination procedure: "))
                             (catch 'system-error
                               (lambda ()
                                 (let ((port (open-log-file file)))
-                                  (log-line line port)
+                                  (log-line line port now)
                                   (vhash-cons file port ports)))
                               (lambda args
                                 (local-output
@@ -287,26 +293,27 @@ in message destination procedure: "))
                                  file (strerror (system-error-errno args)))
                                 ports)))
                            ((_ . port)
-                            (log-line line port)
+                            (log-line line port now)
                             ports)))
                        ports
-                       files))))
+                       files)
+                 (ring-buffer-insert (cons now line) messages))))
         ('timeout
          ;; Write a mark to all the files indiscriminately.
          (vhash-fold (lambda (file port _)
                        (log-line %heartbeat-message port))
                      #t
                      ports)
-         (loop ports))
+         (loop ports messages))
         (('recent-messages reply)
-         (put-message reply '())                  ;TODO: implement it
-         (loop ports))
+         (put-message reply (ring-buffer->list messages))
+         (loop ports messages))
         (('files reply)
          (put-message reply (vhash-fold (lambda (file _ lst)
                                           (cons file lst))
                                         '()
                                         ports))
-         (loop ports))
+         (loop ports messages))
         (('rotate file rotated-file reply)
          (match (vhash-assoc file ports)
            (#f
@@ -314,14 +321,15 @@ in message destination procedure: "))
 system log file '~a'.")
                           file)
             (put-message reply #f)
-            (loop ports))
+            (loop ports messages))
            ((_ . port)
             (let ((port (rotate-and-reopen-log-file port file
                                                     rotated-file)))
               (put-message reply (port? port))
               (if (port? port)
-                  (loop (vhash-cons file port (vhash-delete file ports)))
-                  (loop ports))))))
+                  (loop (vhash-cons file port (vhash-delete file ports))
+                        messages)
+                  (loop ports messages))))))
         ('terminate
          (local-output (l10n "Closing ~a system log ports.")
                        (vlist-length ports))
@@ -331,12 +339,17 @@ system log file '~a'.")
                      #t
                      ports))))))
 
-(define* (spawn-log-dispatcher message-destination #:key max-silent-time)
+(define* (spawn-log-dispatcher message-destination
+                               #:key
+                               max-silent-time
+                               (history-size (default-log-history-size)))
   "Spawn the log dispatcher, responsible for writing system log messages to
-the file(s) returned by @var{message-destination} for each message."
+the file(s) returned by @var{message-destination} for each message.  Keep up
+to @var{history-size} messages in a ring buffer."
   (let ((channel (make-channel)))
     (spawn-fiber (log-dispatcher channel message-destination
-                                 #:max-silent-time max-silent-time))
+                                 #:max-silent-time max-silent-time
+                                 #:history-size history-size))
     channel))
 
 (define (default-message-destination-procedure)
@@ -404,6 +417,7 @@ default destination to log it to."
                                                    (kernel-log-file)))
                              (message-destination
                               (default-message-destination-procedure))
+                             (history-size (default-log-history-size))
                              (max-silent-time (default-max-silent-time)))
   "Return the system log service (@dfn{syslogd}) with the given
 @var{provision} and @var{requirement} (lists of symbols).  The service accepts
@@ -413,7 +427,10 @@ it also reads messages from @code{#:kernel-log-file}, 
which defaults to
 
 Log messages are passed to @var{message-destination}, a one-argument procedure
 that must return the list of files to write it to.  Write a mark to log files
-when no message has been logged for more than @var{max-silent-time} seconds."
+when no message has been logged for more than @var{max-silent-time} seconds.
+
+Keep up to @var{history-size} messages in memory for the purposes of allowing
+users to view recent messages without opening various files."
   (define this-system-log
     (service provision
              #:requirement requirement
@@ -428,7 +445,9 @@ when no message has been logged for more than 
@var{max-silent-time} seconds."
                                                 '())))
                              (dispatcher (spawn-log-dispatcher 
message-destination
                                                                
#:max-silent-time
-                                                               
max-silent-time)))
+                                                               max-silent-time
+                                                               #:history-size
+                                                               history-size)))
                          (register-service-logger this-system-log dispatcher)
                          (spawn-fiber
                           (lambda ()
diff --git a/tests/services/system-log.sh b/tests/services/system-log.sh
index 742bcea..3f2cd4f 100644
--- a/tests/services/system-log.sh
+++ b/tests/services/system-log.sh
@@ -111,7 +111,7 @@ cat > "$logger" <<EOF
       (address (make-socket-address AF_INET INADDR_LOOPBACK 9898)))
   (sendto sock
           (string->utf8
-           "<31>Aug  4 10:05:55 localhost mtp-probe: checking bus 1, device 
111: \"/sys/devices/pci0000:00/0000:00:14.0/usb1/1-8\"\n")
+           "<31>Aug  4 10:05:55 mtp-probe: checking bus 1, device 111: 
\"/sys/devices/pci0000:00/0000:00:14.0/usb1/1-8\"\n")
           address))
 EOF
 
@@ -171,6 +171,12 @@ do
     $herd status system-log | grep "Log files: .*$file"
 done
 
+# Check the "Recent messages" part of 'herd status'.
+$herd status system-log
+$herd status system-log | grep "sudo: pam_unix"
+$herd status system-log | grep "USB disconnect"
+$herd status system-log | grep "mtp-probe: "
+
 # Ensure logs can be rotated.
 $herd start log-rotation
 $herd trigger log-rotation

Reply via email to