Subject: [PATCH v2] services: nginx: Make log formats configurable.

From: Leo Nikkilä <[email-masked]>
Date: Mon, 26 Feb 2024 03:16:29 +0200

Oops, accidentally erased one of the hunks in the patch. Here's v2.

* gnu/services/web.scm (<nginx-log-format-configuration>): New record.
(<nginx-configuration>)[log-format, log-formats]: New fields.
(assert-valid-log-format-escape): New procedure.
(emit-nginx-log-format-config): New procedure.
(default-nginx-config): Make log formats configurable.
* doc/guix.texi (Web Services): Document it.
---
 doc/guix.texi        | 40 +++++++++++++++++++++++++++++++++++++---
 gnu/services/web.scm | 41 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 671cdab6f8..71a00740bc 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -31557,10 +31557,19 @@ started.
 @item @code{log-directory} (default: @code{"/var/log/nginx"})
 The directory to which NGinx will write log files.
 
+@item @code{log-format} (default: @code{'combined}) (type: symbol)
+Logging format for the access log. Nginx defaults to the httpd-like
+format named @code{'combined}.  Other formats can be defined using
+@code{log-formats}.
+
+@item @code{log-formats} (default: @code{'()})
+A list of additional @dfn{log formats} to define, the elements should be
+of type @code{<nginx-log-format-configuration>}.
+
 @item @code{log-level} (default: @code{'error}) (type: symbol)
-Logging level, which can be any of the following values: @code{'debug},
-@code{'info}, @code{'notice}, @code{'warn}, @code{'error}, @code{'crit},
-@code{'alert}, or @code{'emerg}.
+Logging level for the error log, which can be any of the following
+values: @code{'debug}, @code{'info}, @code{'notice}, @code{'warn},
+@code{'error}, @code{'crit}, @code{'alert}, or @code{'emerg}.
 
 @item @code{run-directory} (default: @code{"/var/run/nginx"})
 The directory in which NGinx will create a pid file, and write temporary
@@ -31684,6 +31693,31 @@ valued G-expression.
 @end table
 @end deftp
 
+@deftp {Data Type} nginx-log-format-configuration
+Data type representing the configuration of a custom Nginx log format.
+This type has the following parameters:
+
+@table @asis
+@item @code{name} (type: symbol)
+The name of the log format as a symbol.
+
+@item @code{escape} (default: @code{'default}) (type: symbol)
+The mode used to escape values of variables in the format.  The
+supported values are:
+
+@itemize
+@item @code{'default}, to escape all characters outside of the ASCII
+      printable range.
+@item @code{'json}, to escape all characters invalid in JSON strings.
+@item @code{'none}, to disable escaping.
+@end itemize
+
+@item @code{format} (type: string)
+The format as accepted by the @code{log_format} directive.
+
+@end table
+@end deftp
+
 @anchor{nginx-server-configuration}
 @deftp {Data Type} nginx-server-configuration
 Data type representing the configuration of an nginx server block.
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index 406117c457..5f895bab6d 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -17,6 +17,7 @@
 ;;; Copyright © 2022 Simen Endsjø <[email-masked]>
 ;;; Copyright © 2023 Bruno Victal <[email-masked]>
 ;;; Copyright © 2023 Miguel Ángel Moreno <[email-masked]>
+;;; Copyright © 2024 Leo Nikkilä <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -103,6 +104,8 @@ (define-module (gnu services web)
             nginx-configuration-nginx
             nginx-configuration-shepherd-requirement
             nginx-configuration-log-directory
+            nginx-configuration-log-format
+            nginx-configuration-log-formats
             nginx-configuration-log-level
             nginx-configuration-run-directory
             nginx-configuration-server-blocks
@@ -114,6 +117,12 @@ (define-module (gnu services web)
             nginx-configuration-extra-content
             nginx-configuration-file
 
+            nginx-log-format-configuration
+            nginx-log-format-configuration?
+            nginx-log-format-configuration-name
+            nginx-log-format-configuration-escape
+            nginx-log-format-configuration-format
+
             nginx-server-configuration
             nginx-server-configuration?
             nginx-server-configuration-listen
@@ -522,6 +531,23 @@ (define httpd-service-type
                   (httpd-configuration))
                 (description "Run the Apache httpd Web server.")))
 
+(define-record-type* <nginx-log-format-configuration>
+  nginx-log-format-configuration make-nginx-log-format-configuration
+  nginx-log-format-configuration?
+  (name                nginx-log-format-configuration-name)
+  (escape              nginx-log-format-configuration-escape
+                       (sanitize assert-valid-log-format-escape)
+                       (default 'default))
+  (format              nginx-log-format-configuration-format))
+
+(define (assert-valid-log-format-escape escape)
+  "Ensure @var{escape} is one of @code{'default}, @code{'json}, or
+@code{'none}."
+  (unless (memq escape '(default json none))
+    (raise
+     (formatted-message (G_ "unknown log format escape '~a'~%") escape)))
+  escape)
+
 (define-record-type* <nginx-server-configuration>
   nginx-server-configuration make-nginx-server-configuration
   nginx-server-configuration?
@@ -580,6 +606,10 @@ (define-record-type* <nginx-configuration>
   (log-level     nginx-configuration-log-level
                  (sanitize assert-valid-log-level)
                  (default 'error))
+  (log-format    nginx-configuration-log-format     ;symbol
+                 (default 'combined))
+  (log-formats   nginx-configuration-log-formats    ;list of <nginx-log-format-configuration>
+                 (default '()))
   (run-directory nginx-configuration-run-directory  ;string
                  (default "/var/run/nginx"))
   (server-blocks nginx-configuration-server-blocks
@@ -635,6 +665,12 @@ (define emit-global-directive
     ((key . value)
      (format #f "~a ~a;~%" key value))))
 
+(define emit-nginx-log-format-config
+  (match-lambda
+    (($ <nginx-log-format-configuration> name escape format)
+     (list "    log_format " (symbol->string name) " escape="
+           (symbol->string escape) " " format ";\n"))))
+
 (define emit-nginx-location-config
   (match-lambda
     (($ <nginx-location-configuration> uri body)
@@ -718,7 +754,7 @@ (define (default-nginx-config config)
   (match-record config
                 <nginx-configuration>
                 (nginx log-directory run-directory
-                 log-level
+                 log-level log-format log-formats
                  server-blocks upstream-blocks
                  server-names-hash-bucket-size
                  server-names-hash-bucket-max-size
@@ -740,7 +776,8 @@ (define (default-nginx-config config)
            "    fastcgi_temp_path " run-directory "/fastcgi_temp;\n"
            "    uwsgi_temp_path " run-directory "/uwsgi_temp;\n"
            "    scgi_temp_path " run-directory "/scgi_temp;\n"
-           "    access_log " log-directory "/access.log;\n"
+           (map emit-nginx-log-format-config log-formats)
+           "    access_log " log-directory "/access.log " (symbol->string log-format) ";\n"
            "    include " nginx "/share/nginx/conf/mime.types;\n"
            (if lua-package-path
                #~(format #f "    lua_package_path ~s;~%"
-- 
2.41.0