guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[shepherd] 05/05: Add log rotation service.


From: Ludovic Courtès
Subject: [shepherd] 05/05: Add log rotation service.
Date: Sat, 18 May 2024 16:59:04 -0400 (EDT)

civodul pushed a commit to branch devel
in repository shepherd.

commit 0484726801c2b5c1b7deecb9a96054c2c510ac71
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Sat May 18 11:41:53 2024 +0200

    Add log rotation service.
    
    * modules/shepherd/service/log-rotation.scm,
    tests/services/log-rotation-internal.scm,
    tests/services/log-rotation.sh: New files.
    * Makefile.am (dist_servicesub_DATA): Add the module.
    (TESTS): Add the tests.
    * configure.ac: Add ‘--with-gzip’ and ‘--with-zstd’.  Substitute ‘GZIP’
    and ‘ZSTD’.
    * modules/shepherd/system.scm.in (%gzip-program, %zstd-program): New
    variables.
    * po/POTFILES.in: Add log-rotation.scm.
    * .guix/modules/shepherd-package.scm (shepherd)[arguments]: Pass
    ‘--with-gzip’ and ‘--with-zstd’.
    [inputs]: Add GZIP and ZSTD.
---
 .guix/modules/shepherd-package.scm        |  13 +-
 Makefile.am                               |   5 +-
 configure.ac                              |  21 +++
 doc/shepherd.texi                         |  79 +++++++++++
 modules/shepherd/service/log-rotation.scm | 211 ++++++++++++++++++++++++++++++
 modules/shepherd/system.scm.in            |  12 +-
 po/POTFILES.in                            |   1 +
 tests/services/log-rotation-internal.scm  |  74 +++++++++++
 tests/services/log-rotation.sh            | 100 ++++++++++++++
 9 files changed, 512 insertions(+), 4 deletions(-)

diff --git a/.guix/modules/shepherd-package.scm 
b/.guix/modules/shepherd-package.scm
index 57b77ca..86f5f36 100644
--- a/.guix/modules/shepherd-package.scm
+++ b/.guix/modules/shepherd-package.scm
@@ -44,6 +44,7 @@
   #:use-module (guix modules)
   #:use-module (gnu packages)
   #:use-module (gnu packages autotools)
+  #:use-module (gnu packages compression)
   #:use-module (gnu packages gettext)
   #:use-module (gnu packages guile)
   #:use-module (gnu packages guile-xyz)
@@ -86,7 +87,15 @@
     (source source-checkout)
     (build-system gnu-build-system)
     (arguments
-     (list #:configure-flags #~'("--localstatedir=/var")
+     (list #:configure-flags
+           #~(list "--localstatedir=/var"
+                   (string-append "--with-gzip="
+                                  #$(this-package-input "gzip")
+                                  "/bin/gzip")
+                   (string-append "--with-zstd="
+                                  #$(this-package-input "zstd")
+                                  "/bin/zstd"))
+
            #:modules '((guix build gnu-build-system)
                        ((guix build guile-build-system)
                         #:select (target-guile-effective-version))
@@ -127,7 +136,7 @@
      (append (map specification->package development-packages)
              (list pkg-config guile-3.0-latest
                    guile-fibers-1.3)))            ;for cross-compilation
-    (inputs (list guile-3.0-latest guile-fibers-1.3))
+    (inputs (list guile-3.0-latest guile-fibers-1.3 gzip zstd))
     (synopsis "System service manager")
     (description
      "The GNU Shepherd is a daemon-managing daemon, meaning that it supervises
diff --git a/Makefile.am b/Makefile.am
index 19cbf7a..17c79c0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -49,6 +49,7 @@ nodist_shepherdsub_DATA =                     \
   modules/shepherd/config.scm                  \
   modules/shepherd/system.scm
 dist_servicesub_DATA =                         \
+  modules/shepherd/service/log-rotation.scm    \
   modules/shepherd/service/monitoring.scm      \
   modules/shepherd/service/repl.scm            \
   modules/shepherd/service/timer.scm
@@ -282,7 +283,9 @@ TESTS =                                             \
   tests/services/monitoring.sh                 \
   tests/services/repl.sh                       \
   tests/services/timer.sh                      \
-  tests/services/timer-events.scm
+  tests/services/timer-events.scm              \
+  tests/services/log-rotation.sh               \
+  tests/services/log-rotation-internal.scm
 
 TEST_EXTENSIONS = .sh .scm
 EXTRA_DIST += $(TESTS)
diff --git a/configure.ac b/configure.ac
index 4c9a6ae..c992e92 100644
--- a/configure.ac
+++ b/configure.ac
@@ -21,6 +21,27 @@ AC_CANONICAL_HOST
 AC_PROG_MKDIR_P
 AC_PROG_SED
 
+dnl Compression programs.
+AC_ARG_WITH([gzip],
+  AS_HELP_STRING([--with-gzip=PROGRAM],
+    [gzip program to use for log file compression]),
+  [GZIP="$withval"],
+  [GZIP="$(type -P gzip || echo gzip)"])
+
+AC_MSG_CHECKING([for gzip])
+AC_MSG_RESULT([$GZIP])
+AC_SUBST([GZIP])
+
+AC_ARG_WITH([zstd],
+  AS_HELP_STRING([--with-zstd=PROGRAM],
+    [zstd program to use for log file compression]),
+  [ZSTD="$withval"],
+  [ZSTD="$(type -P zstd || echo zstd)"])
+
+AC_MSG_CHECKING([for zstd])
+AC_MSG_RESULT([$ZSTD])
+AC_SUBST([ZSTD])
+
 dnl The 'timeout' program, introduced in GNU Coreutils 7.0 (2008).
 AC_PATH_PROG([TIMEOUT], [timeout], [not-found])
 AM_CONDITIONAL([HAVE_TIMEOUT], [test "x$TIMEOUT" != "xnot-found"])
diff --git a/doc/shepherd.texi b/doc/shepherd.texi
index d7268c4..fa6c88f 100644
--- a/doc/shepherd.texi
+++ b/doc/shepherd.texi
@@ -1136,6 +1136,11 @@ When @var{log-file} is true, it names the file to which 
the service's
 standard output and standard error are redirected.  @var{log-file} is
 created if it does not exist, otherwise it is appended to.
 
+@quotation Note
+@xref{Log Rotation Service}, for a service to rotate log files specified
+via the @code{#:log-file} parameter.
+@end quotation
+
 Guile's @code{setrlimit} procedure is applied on the entries in
 @var{resource-limits}.  For example, a valid value would be:
 
@@ -1920,10 +1925,84 @@ The Shepherd comes with a collection of services that 
let you control it
 or otherwise extend its functionality.  This chapter documents them.
 
 @menu
+* Log Rotation Service::        Tidying up log files.
 * Monitoring Service::          Monitoring shepherd resource usage.
 * REPL Service::                Interacting with a running shepherd.
 @end menu
 
+@node Log Rotation Service
+@section Log Rotation Service
+
+@cindex log rotation
+@cindex rotating log files of services
+All these services produce many log files; they're useful, for sure, but
+wouldn't it be nice to archive them or even delete them periodically?
+The @dfn{log rotation} service does exactly that.  Once you've enabled
+it, it periodically @dfn{rotates} the log files of services---those
+specified via the @code{#:log-file} argument of service constructors
+(@pxref{Service De- and Constructors}).
+
+By ``rotating'' we mean this: if a service produces
+@file{/var/log/my-service.log}, then rotating it consists in
+periodically renaming it and compressing it to obtain, say,
+@file{/var/log/my-service.log.1.gz}---after renaming the @emph{previous}
+@file{my-service.1.log.gz} to @file{my-service.log.2.gz}, and so
+on@footnote{This is comparable to what the venerable logrotate tool
+would do.}.  Files older than some configured threshold are deleted
+instead of being renamed.  The process is race-free: if the service is
+running, not a single line that it logs is lost during rotation.
+
+To enable the log rotation service, you can add the following lines to
+your configuration file (@pxref{Service Examples}):
+
+@lisp
+(use-modules (shepherd service log-rotation))
+
+(register-services
+  ;; Create a service that rotates log files once a week.
+  (list (log-rotation-service)))
+
+;; Start it.
+(start-service (lookup-service 'log-rotation))
+@end lisp
+
+This creates a @code{log-rotation} service, which is in fact a timed
+service (@pxref{Timers}).  By default it rotates logs once a week and
+you can see past and upcoming runs in the usual way:
+
+@example
+herd status log-rotation
+@end example
+
+You can also trigger it explicitly at any time, like so:
+
+@example
+herd trigger log-rotation
+@end example
+
+The default settings should be good for most use cases, but you can
+change them by passing the @code{log-rotation-service} procedure a
+number of arguments---see the reference documentation below.
+
+@deffn {Procedure} log-rotation-service [@var{event}] @
+          [#:provision '(log-rotation)] [#:requirement '()] @
+          [#:compression (%default-log-compression)] @
+          [#:expiry (%default-log-expiry)] @
+          [#:rotation-size-threshold (%default-rotation-size-threshold)]
+Return a timed service that rotates service logs on every occurrence of
+@var{event}, a calendar event.
+
+Compress log files according to @var{method}, which can be one of
+@code{'gzip}, @code{'zstd}, @code{'none}, or a one-argument procedure that
+is passed the file name.  Log files smaller than @var{rotation-size-threshold}
+are not rotated; copies older than @var{expiry} seconds are deleted.
+
+Last, @var{provision} and @var{requirement} are lists of symbols specifying
+what the service provides and requires, respectively.  Specifying
+@var{requirement} is useful to ensure, for example, that log rotation runs
+only if the service that mounts the file system that hosts log files is up.
+@end deffn
+
 @node Monitoring Service
 @section Monitoring Service
 
diff --git a/modules/shepherd/service/log-rotation.scm 
b/modules/shepherd/service/log-rotation.scm
new file mode 100644
index 0000000..bbb5ff9
--- /dev/null
+++ b/modules/shepherd/service/log-rotation.scm
@@ -0,0 +1,211 @@
+;; log-rotation.scm -- Rotating service log files.
+;; Copyright (C) 2024 Ludovic Courtès <ludo@gnu.org>
+;;
+;; This file is part of the GNU Shepherd.
+;;
+;; The GNU Shepherd is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or (at
+;; your option) any later version.
+;;
+;; The GNU Shepherd is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with the GNU Shepherd.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (shepherd service log-rotation)
+  #:use-module (shepherd support)
+  #:use-module (shepherd logger)
+  #:autoload   (shepherd service timer) (calendar-event
+                                         make-timer-constructor
+                                         make-timer-destructor
+                                         timer-trigger-action)
+  #:autoload   (shepherd service) (service
+                                   service-logger
+                                   for-each-service)
+  #:autoload   (shepherd system) (%gzip-program %zstd-program)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-71)
+  #:autoload   (ice-9 ftw) (scandir)
+  #:use-module (ice-9 match)
+  #:export (rotate-past-logs
+
+            %default-rotation-size-threshold
+            %default-log-expiry
+            %default-log-compression
+            log-rotation-service))
+
+(define %compression-extensions
+  ;; Compressed file extensions.
+  '("gz" "bz2" "lz" "zst"))
+
+(define (next-rotated-file-name reference file)
+  "Return the file name @var{file} should be renamed to and its rotation
+number (an integer).  Return #f and #f if @var{file} is not rotated from the
+@var{reference} file."
+  (define (next-file base n compression)
+    (let ((base (string-join base ".")))
+      (if (and (string=? base (basename reference))
+               (>= n 0)
+               (or (not compression)
+                   (member compression %compression-extensions)))
+          (values (string-append (dirname file) "/" base
+                                 "." (number->string (+ 1 n))
+                                 (if compression
+                                     (string-append "." compression)
+                                     ""))
+                  n)
+          (values #f #f))))
+
+  (match (string-split (basename file) #\.)
+    ((base ... (= string->number (? integer? n)) compression)
+     (next-file base n compression))
+    ((base ... (= string->number (? integer? n)))
+     (next-file base n #f))
+    (_
+     (values #f #f))))
+
+(define* (rotate-past-logs log-file
+                           #:optional (rotate rename-file))
+  "Rotate previously-rotated archives of @var{log-file}--e.g.,
+@code{\"/var/log/ntpd.log\"}--by calling @var{rotate} with the old and new
+file names."
+  (define directory
+    (dirname log-file))
+
+  (define (file->rotation file)
+    (let* ((file (in-vicinity directory file))
+           (next level (next-rotated-file-name log-file file)))
+      (and next level
+           (list level file next))))
+
+  (let* ((candidates (scandir directory
+                              (let ((base (basename log-file)))
+                                (lambda (file)
+                                  (and (not (string=? base file))
+                                       (string-prefix? base file))))))
+         (logs (filter-map file->rotation candidates)))
+    ;; Rotate logs starting from the highest level.
+    (for-each (match-lambda
+                ((level file next)
+                 (rotate file next)))
+              (sort logs
+                    (match-lambda*
+                      (((level1 . _) (level2 . _))
+                       (< level2 level1)))))))
+
+(define %default-rotation-size-threshold
+  ;; Default size in bytes below which log files are not considered for
+  ;; rotation.
+  (make-parameter 8192))
+
+(define %default-log-compression
+  ;; Default log file compression method.
+  (make-parameter 'gzip))
+
+(define (compress-file method file)
+  "Compress @var{file} in place according to @var{method}, which can be either
+a symbol denoting a supported compression method, or a procedure that gets
+called with @var{file}."
+  ;; Note: Delegate compression to a separate process rather than use an
+  ;; in-process compression library to reduce risks of memory corruption or
+  ;; leaks and to reduce the attack surface.
+  (match method
+    ('gzip (system* %gzip-program "-9" file))
+    ('zstd (system* %zstd-program "-9" file))
+    ('none #f)
+    ((? procedure? proc) (proc file))
+    (_  #f)))
+
+(define* (rotate-logs logger #:optional (rotate rename-file)
+                      #:key
+                      (compression (%default-log-compression))
+                      (rotation-size-threshold
+                       (%default-rotation-size-threshold)))
+  "Rotate the log file associated with @var{logger}, if any, along with any
+previously-archived log files.  Compress the log file of @var{logger}
+according to @var{method}.  Call @var{rotate} with the old a new file name for
+each rotation.  If the size of the log file is below
+@var{rotation-size-threshold}, do not rotate it."
+  (match (logger-file logger)
+    (#f #f)
+    (file
+     (if (> (stat:size (stat file)) rotation-size-threshold)
+         (begin
+           ;; First rotate past logs, to free "FILE.1".
+           (rotate-past-logs file rotate)
+           ;; Then rename FILE to "FILE.1".
+           (let* ((next (string-append file ".1"))
+                  (rotated? (rotate-log-file logger next)))
+             (when rotated?
+               (compress-file compression next)
+               (local-output (l10n "Rotated '~a'.") file))))
+         (local-output
+          (l10n "Not rotating '~a', which is below the ~a B threshold.")
+          file rotation-size-threshold)))))
+
+(define* (rotate-service-logs #:optional (rotate rename-file)
+                              #:key (rotation-size-threshold
+                                     (%default-rotation-size-threshold)))
+  "Rotate the log files of all the currently running services."
+  (for-each-service (lambda (service)
+                      (match (service-logger service)
+                        (#f #f)
+                        (logger (rotate-logs logger rotate
+                                             #:rotation-size-threshold
+                                             rotation-size-threshold))))))
+
+(define %default-calendar-event
+  ;; Default calendar event when log rotation is triggered.
+  (calendar-event #:minutes '(0)
+                  #:hours '(22)
+                  #:days-of-week '(sunday)))
+
+(define %default-log-expiry
+  ;; Default duration in seconds after which log files are deleted.
+  (make-parameter (* 3 30 24 3600)))
+
+(define* (log-rotation-service #:optional
+                               (event %default-calendar-event)
+                               #:key
+                               (provision '(log-rotation))
+                               (requirement '())
+                               (compression (%default-log-compression))
+                               (expiry (%default-log-expiry))
+                               (rotation-size-threshold
+                                (%default-rotation-size-threshold)))
+  "Return a timed service that rotates service logs on every occurrence of
+@var{event}, a calendar event.
+
+Compress log files according to @var{method}, which can be one of
+@code{'gzip}, @code{'zstd}, @code{'none}, or a one-argument procedure that
+is passed the file name.  Log files smaller than @var{rotation-size-threshold}
+are not rotated; copies older than @var{expiry} seconds are deleted.
+
+Last, @var{provision} and @var{requirement} are lists of symbols specifying
+what the service provides and requires, respectively.  Specifying
+@var{requirement} is useful to ensure, for example, that log rotation runs
+only if the service that mounts the file system that hosts log files is up."
+  (define (rotate old new)
+    (let ((stat (stat old))
+          (now (current-time)))
+      (if (<= (- now (stat:mtime stat)) expiry)
+          (rename-file old new)
+          (begin
+            (local-output (l10n "Deleting old log file '~a'.") old)
+            (delete-file old)))))
+
+  (define action
+    (lambda ()
+      (rotate-service-logs rotate
+                           #:rotation-size-threshold
+                           rotation-size-threshold)))
+
+  (service provision
+           #:start (make-timer-constructor event action)
+           #:stop (make-timer-destructor)
+           #:actions (list timer-trigger-action)))
diff --git a/modules/shepherd/system.scm.in b/modules/shepherd/system.scm.in
index ec8f032..4c83175 100644
--- a/modules/shepherd/system.scm.in
+++ b/modules/shepherd/system.scm.in
@@ -1,5 +1,5 @@
 ;; system.scm -- Low-level operating system interface.
-;; Copyright (C) 2013-2014, 2016, 2018, 2020, 2022-2023 Ludovic Courtès 
<ludo@gnu.org>
+;; Copyright (C) 2013-2014, 2016, 2018, 2020, 2022-2024 Ludovic Courtès 
<ludo@gnu.org>
 ;; Copyright (C) 2018 Carlo Zancanaro <carlo@zancanaro.id.au>
 ;; Copyright (C) 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
 ;;
@@ -42,6 +42,8 @@
             unblock-signals
             set-blocked-signals
             with-blocked-signals
+            %gzip-program
+            %zstd-program
             without-automatic-finalization))
 
 ;; The <sys/reboot.h> constants.
@@ -298,6 +300,14 @@ current thread."
   (call-with-blocked-signals signals (lambda () exp ...)))
 
 
+;;;
+;;; External programs.
+;;;
+
+(define %gzip-program "@GZIP@")
+(define %zstd-program "@ZSTD@")
+
+
 ;;;
 ;;; Guile shenanigans.
 ;;;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f53e6b0..9878406 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@ modules/shepherd/scripts/herd.scm
 modules/shepherd/scripts/reboot.scm
 modules/shepherd/support.scm
 modules/shepherd/service.scm
+modules/shepherd/service/log-rotation.scm
 modules/shepherd/service/monitoring.scm
 modules/shepherd/service/repl.scm
 modules/shepherd/service/timer.scm
diff --git a/tests/services/log-rotation-internal.scm 
b/tests/services/log-rotation-internal.scm
new file mode 100644
index 0000000..1679dc7
--- /dev/null
+++ b/tests/services/log-rotation-internal.scm
@@ -0,0 +1,74 @@
+;; GNU Shepherd --- Test the log rotation service.
+;; Copyright © 2024 Ludovic Courtès <ludo@gnu.org>
+;;
+;; This file is part of the GNU Shepherd.
+;;
+;; The GNU Shepherd is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or (at
+;; your option) any later version.
+;;
+;; The GNU Shepherd is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with the GNU Shepherd.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (test-log-rotation-internal)
+  #:use-module (shepherd service log-rotation)
+  #:use-module (srfi srfi-64)
+  #:use-module (ice-9 ftw)
+  #:use-module (ice-9 match))
+
+(define (call-with-temporary-directory proc)
+  (let ((directory (mkdtemp "/tmp/shepherd-logs-XXXXXX")))
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        (proc directory))
+      (lambda ()
+        (for-each (lambda (file)
+                    (delete-file (in-vicinity directory file)))
+                  (scandir directory
+                           (lambda (file)
+                             (not (member file '("." ".."))))))))))
+
+(define (create-file file)
+  (close-port (open-file file "w0")))
+
+
+(test-begin "log-rotation-internal")
+
+;; Ensure that the right files are rotated in the right order.
+(test-equal "rotate-past-logs"
+  '(("foo.log.3.gz" -> "foo.log.4.gz")
+    ("foo.log.2" -> "foo.log.3")
+    ("foo.log.1" -> "foo.log.2")
+    ("bar.log.10.zst" -> "bar.log.11.zst")
+    ("bar.log.1.zst" -> "bar.log.2.zst"))
+  (call-with-temporary-directory
+   (lambda (directory)
+     (let ((result '()))
+       (define (record-rotation old new)
+         (set! result (cons (list old '-> new) result)))
+
+       (for-each (lambda (base)
+                   (create-file (in-vicinity directory base)))
+                 '("foo.log"
+                   "foo.log.1" "foo.log.2" "foo.log.3.gz"
+                   "bar.log" "bar.log.1.zst" "bar.log.10.zst"))
+
+       (rotate-past-logs (in-vicinity directory "foo.log")
+                         record-rotation)
+       (rotate-past-logs (in-vicinity directory "bar.log")
+                         record-rotation)
+       (map (match-lambda
+              ((old '-> new)
+               (and (string=? (dirname old) directory)
+                    (string=? (dirname new) directory)
+                    (list (basename old) '-> (basename new)))))
+            (reverse result))))))
+
+(test-end "log-rotation-internal")
diff --git a/tests/services/log-rotation.sh b/tests/services/log-rotation.sh
new file mode 100644
index 0000000..10115f3
--- /dev/null
+++ b/tests/services/log-rotation.sh
@@ -0,0 +1,100 @@
+# GNU Shepherd --- Test log rotation.
+# Copyright © 2024 Ludovic Courtès <ludo@gnu.org>
+#
+# This file is part of the GNU Shepherd.
+#
+# The GNU Shepherd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or (at
+# your option) any later version.
+#
+# The GNU Shepherd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the GNU Shepherd.  If not, see <http://www.gnu.org/licenses/>.
+
+shepherd --version
+herd --version
+
+socket="t-socket-$$"
+conf="t-conf-$$"
+log="t-log-$$"
+pid="t-pid-$$"
+service_log1="$PWD/t-service-log1-$$"
+service_log2="$PWD/t-service-log2-$$"
+
+herd="herd -s $socket"
+
+trap "cat $log || true; rm -f $socket $conf $log $service_log1* $service_log2*;
+      test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT
+
+cat > "$conf" <<EOF
+(use-modules (shepherd service log-rotation))
+
+(define command
+  '("$SHELL" "-c" "while true; do echo logging things; sleep 0.2; done"))
+
+(%default-rotation-size-threshold 0)
+
+(define services
+  (list (service '(one)
+                #:start (make-forkexec-constructor
+                          command
+                          #:log-file "$service_log1")
+                #:stop (make-kill-destructor))
+        (service '(two)
+                #:start (make-forkexec-constructor
+                          '("sleep" "600")
+                          #:log-file "$service_log2")
+                #:stop (make-kill-destructor))
+        (log-rotation-service #:rotation-size-threshold 0)))
+
+(register-services services)
+EOF
+
+rm -f "$pid"
+shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" &
+
+# Wait till it's ready.
+while ! test -f "$pid" ; do sleep 0.3 ; done
+
+$herd start one
+$herd start two
+$herd start log-rotation
+
+sleep 0.5
+
+test -f "$service_log1"
+test -f "$service_log2"
+
+# First rotation.
+$herd trigger log-rotation
+
+until grep "Rotated " "$log"; do sleep 0.5; done
+
+test -f "$service_log1"
+test -f "$service_log1.1.gz"
+test -f "$service_log2.1.gz" && false
+test -f "$service_log2"
+
+# Second rotation.
+$herd trigger log-rotation
+
+until test -f "$service_log1.2.gz"; do sleep 0.5; done
+until test -f "$service_log1.1.gz"; do sleep 0.5; done
+test -f "$service_log1"
+test -f "$service_log2.1.gz" && false
+
+# Third rotation, with deletion of old log file.
+touch -d "2017-10-01" "$service_log1.2.gz"
+$herd trigger log-rotation
+
+until grep "Deleting .*$service_log1.2.gz" "$log"; do sleep 0.2; done
+until test -f "$service_log1.2.gz"; do sleep 0.2; done
+until test -f "$service_log1.1.gz"; do sleep 0.2; done
+test -f "$service_log1.3.gz" && false
+
+$herd stop log-rotation



reply via email to

[Prev in Thread] Current Thread [Next in Thread]