summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-10-12 10:17:47 +0000
committerLaurent Bercot <ska@appnovation.com>2023-10-12 10:17:47 +0000
commit3b0a901275e63ca7a0d46a5f58bdc6d1f706ce06 (patch)
tree4cc508d5b23733117e58547c431abb069621ed65
parentd9492e8561fe5373b1428c6d7cff4f25e2796a55 (diff)
downloadtipidee-3b0a901275e63ca7a0d46a5f58bdc6d1f706ce06.tar.gz
tipidee-3b0a901275e63ca7a0d46a5f58bdc6d1f706ce06.tar.xz
tipidee-3b0a901275e63ca7a0d46a5f58bdc6d1f706ce06.zip
New logging system; not used yet in tipideed
Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--doc/tipidee.conf.html91
-rw-r--r--doc/tipideed.html41
-rw-r--r--package/deps.mak4
-rw-r--r--src/config/defaults.c159
-rw-r--r--src/config/lexparse.c55
-rw-r--r--src/include/tipidee/log.h4
-rw-r--r--src/libtipidee/tipidee_log_answer.c2
-rw-r--r--src/libtipidee/tipidee_log_resource.c4
8 files changed, 264 insertions, 96 deletions
diff --git a/doc/tipidee.conf.html b/doc/tipidee.conf.html
index 73620bc..57c5c29 100644
--- a/doc/tipidee.conf.html
+++ b/doc/tipidee.conf.html
@@ -322,6 +322,97 @@ only the <tt>index.html</tt> file will be looked up when a resource resolves
 to a directory. </li>
 </ul>
 
+<div id="log">
+<h3> The <tt>log</tt> directive </h3>
+</div>
+
+<p>
+ <tt>log</tt> is also a global directive, but is introduced by the
+keyword <tt>log</tt>, without prepending <tt>global</tt>. It allows
+the user to control what will appear in
+<a href="tipideed.html">tipideed</a>'s log output.
+</p>
+
+<p>
+ <code> log nothing </code> <br>
+ <code> log <em>keyword1</em> <em>keyword2</em> ... </code>
+</p>
+
+<ul>
+ <li> <a href="tipideed.html">tipideed</a> writes all its logs to stderr.
+It prints fatal error messages
+with the prefix "<tt>tipideed: pid <em>pid</em>: fatal: </tt>",
+and warning messages
+with the prefix "<tt>tipideed: pid <em>pid</em>: warning: </tt>".
+In normal operation, if everything goes well, you should never see
+any of these. </li>
+ <li> Depending on what the <tt>log</tt> directive says, it also prints
+informational messages related to what it's doing. These informational
+messages all have the prefix "<tt>tipideed: pid <em>pid</em>: info: </tt>". </li>
+ <li> If no <tt>log</tt> directive has been provided, the default is
+<tt>log request answer size</tt>. </li>
+</ul>
+
+<p>
+ Here are the informational log lines printed by <a href="tipideed.html">tipideed</a>,
+depending on the keywords in the <tt>log</tt> directive:
+</p>
+
+<dl>
+ <dt> <tt>nothing</tt> </dt> <dd> Don't log anything else than warning and error messages. This
+keyword cannot be given with other keywords. </dd>
+ <dt> <tt>start</tt> </dt> <dd> Log a <tt>start</tt> line when <a href="tipideed.html">tipideed</a> starts
+ and an <tt>exit <em>exitcode</em> line when it exits. </dd>
+ <dt> <tt>ip</tt> </dt> <dd> Add an <tt>ip <em>client_ip</em></tt> field to the <tt>start</tt> line.
+This is potentially PII, so make sure to stay compliant with your local laws if you activate it. 
+<em>client_ip</em> is read from the TCPREMOTEIP environment variable.
+This keyword has no effect when given without the <tt>start</tt> keyword. </dd>
+ <dt> <tt>hostname</tt> </dt> <dd> Add a <tt>host <em>client_hostname</em> </tt> field to the <tt>start</tt> line.
+This is potentially PII, so make sure to stay compliant with your local laws if you activate it. 
+<em>client_hostname</em> is read from the TCPREMOTEHOST environment variable if it exists, or made up from
+TCPREMOTEIP otherwise. Make sure to invoke
+<a href="//skarnet.org/software/s6-networking/s6-tcpserver-access.html">s6-tcpserver-access</a> before
+<a href="tipideed.html">tipideed</a> in order to get meaningful values for this field.
+This keyword has no effect when given without the <tt>start</tt> keyword. </dd>
+ <dt> <tt>host_as_prefix</tt> </dt> <dd> Prepend all <tt>request</tt>, <tt>resource</tt> and <tt>answer</tt>
+lines with a <tt>host <em>host</em><tt> field. This field will not be repeated in the <tt>request</tt>
+line, so it changes the order of the fields. <em>host</em> is the virtual host the request is addressed
+to. <tt>host_as_prefix</tt> is useful when you want to log entries for different virtual hosts to
+different locations. For instance, if you're using
+<a href="//skarnet.org/software/s6/s6-log.html">s6-log</a>, and want entries for <tt>example.com</tt> and
+<tt>example.org</tt> to be logged to different backends, you would use the <tt>host_as_prefix</tt> directive,
+and use <code>- +"^tipidee: pid [[:digit:]]*: info: host example\\.com " </code> to select <tt>example.com</tt>-related
+lines, and <code>- +"^tipidee: pid [[:digit:]]*: info: host example\\.org " </code>
+to select <tt>example.org</tt>-related lines. Note that warning and error messages would still need an additional
+backend, as well as <tt>start</tt> and <tt>exit</tt> lines if you add the <tt>start</tt> directive
+to your <tt>log</tt> configuration. </dd>
+ <dt> <tt>request</tt> </dt> <dd> Log a <tt>request</tt> line when <a href="tipideed.html">tipideed</a>
+receives a request from its client. The line looks like <tt>request <em>method</em> host <em>host</em> path "<em>path</em>"
+http <em>version</em></tt>. The path is decoded, but if there are non-printable characters in it, they are
+encoded as hexadecimal values <tt>\0xab</tt>. If the request line includes a query, a <tt>query <em>query</em></tt> field
+is added before the <tt>http</tt> field. </dd>
+ <dt> <tt>referrer</tt> </dt> <dd> Add a <tt>referrer "<em>referrer</em>" field to the <tt>request</tt> line, for
+requests that include a <tt>Referrer:</tt> header. <em>referrer</em> is quoted like <em>path</em>, to avoid
+malicious clients messing with log lines.
+This keyword has no effect when given without the <tt>request</tt> keyword. </dd>
+ <dt> <tt>user-agent</tt> </dt> <dd> Add a <tt>user-agent "<em>user-agent</em>" field to the <tt>request</tt> line, for
+requests that include a <tt>User-Agent:</tt> header. <em>user-agent</em> is quoted like <em>path</em>, to avoid
+malicious clients messing with log lines.
+This keyword has no effect when given without the <tt>request</tt> keyword. </dd>
+ <dt> <tt>resource</tt> </dt> <dd> Log a <tt>resource</tt> line when <a href="tipideed.html">tipideed</a>
+has found a resource corresponding to the URI and is willing to serve it. The line looks like
+<tt>resource docroot <em>docroot</em> file <em>file</em> type <em>type</em></tt>. <em>docroot</em> is
+the document root of the virtual host; <em>file</em> is the path to the served file; <em>type</em>
+is <tt>nph</tt> for an NPH script, <tt>cgi</tt> for a CGI script, or the Content-Type for a regular file. </dd>
+ <dt> <tt>answer</tt> </dt> <dd> Log an <tt>answer</tt> line when <a href="tipideed.html">tipideed</a>
+answers the currently processed request. The line looks like <tt>answer <em>status</em></tt>, where <em>status</em> is
+the 3-digit status code returned to the client. </dd>
+ <dt> <tt>size</tt> </dt> <dd> Add a <tt>size <em>size</em></tt> field to the <tt>answer</tt> line,
+containing the Content-Length of the answer.
+This keyword has no effect when given without the <tt>answer</tt> keyword. </dd>
+ <dt> <tt>debug</tt> </dt> <dd> Log debug information. You should not need this in regular use. </dd>
+</ul>
+
 <div id="content-type">
 <h3> The <tt>content-type</tt> directive </h3>
 </div>
diff --git a/doc/tipideed.html b/doc/tipideed.html
index 8f080bc..977317c 100644
--- a/doc/tipideed.html
+++ b/doc/tipideed.html
@@ -28,7 +28,7 @@ a web server package: it serves files over HTTP.
 </div>
 
 <pre>
-     tipideed [ -v <em>verbosity</em> ] [ -f <em>cdbfile</em> ] [ -d <em>basedir</em> ] [ -R ] [ -U ]
+     tipideed [ -f <em>cdbfile</em> ] [ -d <em>basedir</em> ] [ -R ] [ -U ]
 </pre>
 
 <ul>
@@ -201,11 +201,6 @@ cannot be used by CGI scripts in a portable way.
 </div>
 
 <dl>
- <dt> -v <em>verbosity</em> </dt>
- <dd> The level of log verbosity. This is the same as the <tt>global verbosity</tt>
-setting in the <a href="tipidee.conf.html#verbosity">configuration file</a>; an explicit
-command line option overrides any setting present in the configuration file.</dd>
-
  <dt> -f <em>file</em> </dt>
  <dd> Use <em>file</em> as the compiled configuration database, typically obtained
 by running <tt><a href="tipidee-config.html">tipidee-config</a> -o <em>file</em></tt>.
@@ -335,6 +330,40 @@ at all, your documents will most likely be accessible for HTTP/1.0 clients under
 <tt>@:80</tt> or <tt>@:443</tt>.
 </p>
 
+<div id="logging">
+<h2> Logging </h2>
+</div>
+
+<ul>
+ <li> tipideed uses stderr for all its logging. All its log lines are prefixed
+with "<tt>tipideed: pid <em>pid</em>: </tt>". </li>
+ <li> The log lines continue with "<tt>fatal: </tt>" for fatal error messages (meaning
+that tipideed exits right after writing the message), or "<tt>warning: </tt>" for
+warnings (meaning that tipideed continues operating after writing the message).
+In normal operation, you should not see any fatal or warning line. </li>
+ <li> In normal operation, tipidee can log informational lines, and the continuing
+prefix is "<tt>info: </tt>". It can potentially log:
+  <ul>
+   <li> One line when it starts (i.e. a client has connected) </li>
+   <li> Up to three lines for every request:
+    <ul>
+     <li> One when the request is received </li>
+     <li> One when a suitable resource is found </li>
+     <li> One when an answer is sent </li>
+    </ul> </li>
+   <li> One line when it exits normally </li>
+  </ul> </li>
+ <li> What informational lines to log is configured via the
+<a href="tipidee.conf.html#log"><tt>log</tt></a> directive in the
+<a href="tipidee.conf.html">configuration file</a>. By default, only
+a minimal request line and an answer line are printed. </li>
+ <li> The log format is designed to be readable by a human, but still
+easily processable by automation. For instance, the regular prefix structure
+makes it easy for <a href="//skarnet.org/software/s6/s6-log.html">s6-log</a>
+to select different lines to send them to various backends for archiving or
+processing. </li>
+</ul>
+
 <div id="details">
 <h2> Detailed operation </h2>
 </div>
diff --git a/package/deps.mak b/package/deps.mak
index 016ff9e..bc7bee1 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -10,8 +10,8 @@ src/include/tipidee/tipidee.h: src/include/tipidee/conf.h src/include/tipidee/co
 src/tipideed/tipideed-internal.h: src/include/tipidee/tipidee.h
 src/config/confnode.o src/config/confnode.lo: src/config/confnode.c src/config/tipidee-config-internal.h
 src/config/conftree.o src/config/conftree.lo: src/config/conftree.c src/config/tipidee-config-internal.h
-src/config/defaults.o src/config/defaults.lo: src/config/defaults.c src/config/tipidee-config-internal.h
-src/config/lexparse.o src/config/lexparse.lo: src/config/lexparse.c src/config/tipidee-config-internal.h src/include/tipidee/config.h
+src/config/defaults.o src/config/defaults.lo: src/config/defaults.c src/config/tipidee-config-internal.h src/include/tipidee/log.h
+src/config/lexparse.o src/config/lexparse.lo: src/config/lexparse.c src/config/tipidee-config-internal.h src/include/tipidee/config.h src/include/tipidee/log.h
 src/config/tipidee-config-preprocess.o src/config/tipidee-config-preprocess.lo: src/config/tipidee-config-preprocess.c
 src/config/tipidee-config.o src/config/tipidee-config.lo: src/config/tipidee-config.c src/config/tipidee-config-internal.h src/include/tipidee/config.h
 src/libtipidee/tipidee_conf_free.o src/libtipidee/tipidee_conf_free.lo: src/libtipidee/tipidee_conf_free.c src/include/tipidee/conf.h
diff --git a/src/config/defaults.c b/src/config/defaults.c
index 390bb81..1017d29 100644
--- a/src/config/defaults.c
+++ b/src/config/defaults.c
@@ -2,6 +2,9 @@
 
 #include <stddef.h>
 
+#include <skalibs/uint32.h>
+
+#include <tipidee/log.h>
 #include "tipidee-config-internal.h"
 
 struct defaults_s
@@ -11,88 +14,90 @@ struct defaults_s
   size_t vlen ;
 } ;
 
-#define RECB(k, v, n) { .key = k, .value = v, .vlen = n }
-#define REC(k, v) RECB(k, v, sizeof(v))
+#define REC(k, v, n) { .key = (k), .value = (v), .vlen = (n) }
+#define RECS(k, v) REC(k, v, sizeof(v))
+#define RECU32(k, v) REC(k, (char const *)(uint32_t)UINT32_BIG(v), 4)
 
 struct defaults_s const defaults[] =
 {
-  RECB("G:verbosity", "\0\0\0\001", 4),
-  RECB("G:read_timeout", "\0\0\0", 4),
-  RECB("G:write_timeout", "\0\0\0", 4),
-  RECB("G:cgi_timeout", "\0\0\0", 4),
-  RECB("G:max_request_body_length", "\0\0 ", 4),
-  RECB("G:max_cgi_body_length", "\0@\0", 4),
-  REC("G:index_file", "index.html"),
+  RECU32("G:verbosity", 1),
+  RECU32("G:read_timeout", 0),
+  RECU32("G:write_timeout", 0),
+  RECU32("G:cgi_timeout", 0),
+  RECU32("G:max_request_body_length", 8192),
+  RECU32("G:max_cgi_body_length", 4194304),
+  RECS("G:index_file", "index.html"),
+  RECU32("G:logv", TIPIDEE_LOG_DEFAULT),
 
-  REC("T:html", "text/html"),
-  REC("T:htm", "text/html"),
-  REC("T:txt", "text/plain"),
-  REC("T:h", "text/plain"),
-  REC("T:c", "text/plain"),
-  REC("T:cc", "text/plain"),
-  REC("T:cpp", "text/plain"),
-  REC("T:ass", "text/plain"),
-  REC("T:java", "text/plain"),
-  REC("T:mjs", "text/javascript"),
-  REC("T:css", "text/css"),
-  REC("T:csv", "text/csv"),
-  REC("T:sub", "text/vnd.dvb.subtitle"),
-  REC("T:doc", "application/msword"),
-  REC("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
-  REC("T:js", "application/javascript"),
-  REC("T:jpg", "image/jpeg"),
-  REC("T:jpeg", "image/jpeg"),
-  REC("T:gif", "image/gif"),
-  REC("T:png", "image/png"),
-  REC("T:bmp", "image/bmp"),
-  REC("T:svg", "image/svg+xml"),
-  REC("T:tif", "image/tiff"),
-  REC("T:tiff", "image/tiff"),
-  REC("T:ico", "image/vnd.microsoft.icon"),
-  REC("T:au", "audio/basic"),
-  REC("T:aac", "audio/aac"),
-  REC("T:wav", "audio/wav"),
-  REC("T:mid", "audio/midi"),
-  REC("T:midi", "audio/midi"),
-  REC("T:mp3", "audio/mpeg"),
-  REC("T:ogg", "audio/ogg"),
-  REC("T:oga", "audio/ogg"),
-  REC("T:ogv", "video/ogg"),
-  REC("T:avi", "video/x-msvideo"),
-  REC("T:wmv", "video/x-ms-wmv"),
-  REC("T:qt", "video/quicktime"),
-  REC("T:mov", "video/quicktime"),
-  REC("T:mpe", "video/mpeg"),
-  REC("T:mpeg", "video/mpeg"),
-  REC("T:mp4", "video/mp4"),
-  REC("T:mkv", "video/x-matroska"),
-  REC("T:otf", "font/otf"),
-  REC("T:ttf", "font/ttf"),
-  REC("T:epub", "application/epub+zip"),
-  REC("T:jar", "application/java-archive"),
-  REC("T:json", "application/json"),
-  REC("T:jsonld", "application/ld+json"),
-  REC("T:pdf", "application/pdf"),
-  REC("T:ppt", "application/vnd.ms-powerpoint"),
-  REC("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
-  REC("T:odp", "application/vnd.oasis.opendocument.presentation"),
-  REC("T:ods", "application/vnd.oasis.opendocument.spreadsheet"),
-  REC("T:odt", "application/vnd.oasis.opendocument.text"),
-  REC("T:oggx", "application/ogg"),
-  REC("T:rar", "application/vnd.rar"),
-  REC("T:rtf", "application/rtf"),
-  REC("T:sh", "application/x-sh"),
-  REC("T:srt", "application/x-subrip"),
-  REC("T:tar", "application/x-tar"),
-  REC("T:xhtml", "application/xhtml+xml"),
-  REC("T:xls", "application/vnd.ms-excel"),
-  REC("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
-  REC("T:xml", "application/xml"),
-  REC("T:xul", "application/vnd.mozilla.xul+xml"),
-  REC("T:zip", "application/zip"),
-  REC("T:7z", "application/x-7z-compressed"),
+  RECS("T:html", "text/html"),
+  RECS("T:htm", "text/html"),
+  RECS("T:txt", "text/plain"),
+  RECS("T:h", "text/plain"),
+  RECS("T:c", "text/plain"),
+  RECS("T:cc", "text/plain"),
+  RECS("T:cpp", "text/plain"),
+  RECS("T:ass", "text/plain"),
+  RECS("T:java", "text/plain"),
+  RECS("T:mjs", "text/javascript"),
+  RECS("T:css", "text/css"),
+  RECS("T:csv", "text/csv"),
+  RECS("T:sub", "text/vnd.dvb.subtitle"),
+  RECS("T:doc", "application/msword"),
+  RECS("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
+  RECS("T:js", "application/javascript"),
+  RECS("T:jpg", "image/jpeg"),
+  RECS("T:jpeg", "image/jpeg"),
+  RECS("T:gif", "image/gif"),
+  RECS("T:png", "image/png"),
+  RECS("T:bmp", "image/bmp"),
+  RECS("T:svg", "image/svg+xml"),
+  RECS("T:tif", "image/tiff"),
+  RECS("T:tiff", "image/tiff"),
+  RECS("T:ico", "image/vnd.microsoft.icon"),
+  RECS("T:au", "audio/basic"),
+  RECS("T:aac", "audio/aac"),
+  RECS("T:wav", "audio/wav"),
+  RECS("T:mid", "audio/midi"),
+  RECS("T:midi", "audio/midi"),
+  RECS("T:mp3", "audio/mpeg"),
+  RECS("T:ogg", "audio/ogg"),
+  RECS("T:oga", "audio/ogg"),
+  RECS("T:ogv", "video/ogg"),
+  RECS("T:avi", "video/x-msvideo"),
+  RECS("T:wmv", "video/x-ms-wmv"),
+  RECS("T:qt", "video/quicktime"),
+  RECS("T:mov", "video/quicktime"),
+  RECS("T:mpe", "video/mpeg"),
+  RECS("T:mpeg", "video/mpeg"),
+  RECS("T:mp4", "video/mp4"),
+  RECS("T:mkv", "video/x-matroska"),
+  RECS("T:otf", "font/otf"),
+  RECS("T:ttf", "font/ttf"),
+  RECS("T:epub", "application/epub+zip"),
+  RECS("T:jar", "application/java-archive"),
+  RECS("T:json", "application/json"),
+  RECS("T:jsonld", "application/ld+json"),
+  RECS("T:pdf", "application/pdf"),
+  RECS("T:ppt", "application/vnd.ms-powerpoint"),
+  RECS("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
+  RECS("T:odp", "application/vnd.oasis.opendocument.presentation"),
+  RECS("T:ods", "application/vnd.oasis.opendocument.spreadsheet"),
+  RECS("T:odt", "application/vnd.oasis.opendocument.text"),
+  RECS("T:oggx", "application/ogg"),
+  RECS("T:rar", "application/vnd.rar"),
+  RECS("T:rtf", "application/rtf"),
+  RECS("T:sh", "application/x-sh"),
+  RECS("T:srt", "application/x-subrip"),
+  RECS("T:tar", "application/x-tar"),
+  RECS("T:xhtml", "application/xhtml+xml"),
+  RECS("T:xls", "application/vnd.ms-excel"),
+  RECS("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
+  RECS("T:xml", "application/xml"),
+  RECS("T:xul", "application/vnd.mozilla.xul+xml"),
+  RECS("T:zip", "application/zip"),
+  RECS("T:7z", "application/x-7z-compressed"),
 
-  RECB(0, 0, 0)
+  REC(0, 0, 0)
 } ;
 
 void conf_defaults (void)
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
index d541888..b048031 100644
--- a/src/config/lexparse.c
+++ b/src/config/lexparse.c
@@ -12,10 +12,12 @@
 #include <skalibs/genalloc.h>
 #include <skalibs/skamisc.h>
 
+#include <tipidee/log.h>
 #include <tipidee/config.h>
 #include "tipidee-config-internal.h"
 
 #define dietoobig() strerr_diefu1sys(100, "read configuration")
+#define BSEARCH(type, key, array) bsearch(key, (array), sizeof(array)/sizeof(type), sizeof(type), (int (*)(void const *, void const *))&strcmp)
 
 typedef struct mdt_s mdt, *mdt_ref ;
 struct mdt_s
@@ -33,15 +35,17 @@ struct globalkey_s
   uint32_t type ;
 } ;
 
-static int globalkey_cmp (void const *a, void const *b)
+struct logkey_s
 {
-  return strcmp((char const *)a, ((struct globalkey_s const *)b)->name) ;
-}
+  char const *name ;
+  uint32_t value ;
+} ;
 
 enum token_e
 {
   T_BANG,
   T_GLOBAL,
+  T_LOG,
   T_CONTENTTYPE,
   T_DOMAIN,
   T_NPHPREFIX,
@@ -61,10 +65,6 @@ struct directive_s
   enum token_e token ;
 } ;
 
-static int directive_cmp (void const *a, void const *b)
-{
-  return strcmp((char const *)a, ((struct directive_s const *)b)->s) ;
-}
 
 static void check_unique (char const *key, mdt const *md)
 {
@@ -101,7 +101,7 @@ static inline void parse_global (char const *s, size_t const *word, size_t n, md
   struct globalkey_s const *gl ;
   if (n < 2)
     strerr_dief8x(1, "too ", "few", " arguments to directive ", "global", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
-  gl = bsearch(s + word[0], globalkeys, sizeof(globalkeys)/sizeof(struct globalkey_s), sizeof(struct globalkey_s), &globalkey_cmp) ;
+  gl = BSEARCH(struct globalkey_s, s + word[0], globalkeys) ;
   if (!gl) strerr_dief6x(1, "unrecognized global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
   switch (gl->type)
   {
@@ -130,6 +130,39 @@ static inline void parse_global (char const *s, size_t const *word, size_t n, md
   }
 }
 
+static inline void parse_log (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+  static struct logkey_s const logkeys[] =
+  {
+    { .name = "answer", .value = TIPIDEE_LOG_ANSWER },
+    { .name = "debug", .value = TIPIDEE_LOG_DEBUG },
+    { .name = "host_as_prefix", .value = TIPIDEE_LOG_HOSTASPREFIX },
+    { .name = "hostname", .value = TIPIDEE_LOG_CLIENTHOST },
+    { .name = "ip", .value = TIPIDEE_LOG_CLIENTIP },
+    { .name = "nothing", .value = 0 },
+    { .name = "referrer", .value = TIPIDEE_LOG_REFERRER },
+    { .name = "request", .value = TIPIDEE_LOG_REQUEST },
+    { .name = "resource", .value = TIPIDEE_LOG_RESOURCE },
+    { .name = "response_size", .value = TIPIDEE_LOG_SIZE },
+    { .name = "start", .value = TIPIDEE_LOG_START },
+    { .name = "user-agent", .value = TIPIDEE_LOG_UA }
+  } ;
+  uint32_t v = 0 ;
+  char pack[4] ;
+  if (!n)
+    strerr_dief8x(1, "too ", "few", " arguments to directive ", "log", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  for (size_t i = 0 ; i < n ; i++)
+  {
+    struct logkey_s const *l = BSEARCH(struct logkey_s, s + word[i], logkeys) ;
+    if (!l) strerr_dief6x(1, "unrecognized log setting ", s + word[i], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+    if (l->value) v |= l->value ;
+    else if (n == 1) v = 0 ;
+    else strerr_dief5x(1, "log nothing cannot be mixed with other log values", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+  }
+  uint32_pack_big(pack, v) ;
+  add_unique("G:logv", pack, 4, md) ;
+}
+
 static inline void parse_contenttype (char const *s, size_t const *word, size_t n, mdt const *md)
 {
   char const *ct ;
@@ -295,6 +328,7 @@ static inline void process_line (char const *s, size_t const *word, size_t n, st
     { .s = "domain", .token = T_DOMAIN },
     { .s = "file-type", .token = T_FILETYPE },
     { .s = "global", .token = T_GLOBAL },
+    { .s = "log", .token = T_LOG },
     { .s = "no-auth", .token = T_NOAUTH },
     { .s = "noncgi", .token = T_NONCGI },
     { .s = "nonnph", .token = T_NONNPH },
@@ -306,7 +340,7 @@ static inline void process_line (char const *s, size_t const *word, size_t n, st
   char const *word0 ;
   if (!n--) return ;
   word0 = s + *word++ ;
-  directive = bsearch(word0, directives, sizeof(directives)/sizeof(struct directive_s), sizeof(struct directive_s), &directive_cmp) ;
+  directive = BSEARCH(struct directive_s, word0, directives) ;
   if (!directive)
     strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
   switch (directive->token)
@@ -328,6 +362,9 @@ static inline void process_line (char const *s, size_t const *word, size_t n, st
     case T_GLOBAL :
       parse_global(s, word, n, md) ;
       break ;
+    case T_LOG :
+      parse_log(s, word, n, md) ;
+      break ;
     case T_CONTENTTYPE :
       parse_contenttype(s, word, n, md) ;
       break ;
diff --git a/src/include/tipidee/log.h b/src/include/tipidee/log.h
index 29d621f..98e6d0e 100644
--- a/src/include/tipidee/log.h
+++ b/src/include/tipidee/log.h
@@ -6,6 +6,7 @@
 #include <sys/types.h>
 #include <stdint.h>
 
+#include <skalibs/strerr.h>
 #include <skalibs/stralloc.h>
 
 #include <tipidee/rql.h>
@@ -20,6 +21,7 @@
 #define TIPIDEE_LOG_CLIENTIP 0x0200
 #define TIPIDEE_LOG_CLIENTHOST 0x0400
 #define TIPIDEE_LOG_HOSTASPREFIX 0x1000
+#define TIPIDEE_LOG_DEBUG 0x10000
 
 #define TIPIDEE_LOG_DEFAULT (TIPIDEE_LOG_REQUEST | TIPIDEE_LOG_ANSWER | TIPIDEE_LOG_SIZE)
 
@@ -39,4 +41,6 @@ extern void tipidee_log_request (uint32_t, tipidee_rql const *, char const *, ch
 extern void tipidee_log_resource (uint32_t, tipidee_rql const *, char const *, char const *, tipidee_resattr const *) ;
 extern void tipidee_log_answer (uint32_t, tipidee_rql const *, unsigned int, off_t) ;
 
+#define tipidee_log_debug(v, ...) do { if ((v) & TIPIDEE_LOG_DEBUG) strerr_warn(PROG, ": debug: ", __VA_ARGS__) ; } while (0)
+
 #endif
diff --git a/src/libtipidee/tipidee_log_answer.c b/src/libtipidee/tipidee_log_answer.c
index b6e4617..ae4e880 100644
--- a/src/libtipidee/tipidee_log_answer.c
+++ b/src/libtipidee/tipidee_log_answer.c
@@ -1,6 +1,6 @@
 /* ISC license. */
 
-#include <sys/types.h>
+#include <stddef.h>
 
 #include <skalibs/uint64.h>
 #include <skalibs/types.h>
diff --git a/src/libtipidee/tipidee_log_resource.c b/src/libtipidee/tipidee_log_resource.c
index 6387027..b730612 100644
--- a/src/libtipidee/tipidee_log_resource.c
+++ b/src/libtipidee/tipidee_log_resource.c
@@ -1,5 +1,7 @@
 /* ISC license. */
 
+#include <stddef.h>
+
 #include <skalibs/strerr.h>
 
 #include <tipidee/log.h>
@@ -14,7 +16,7 @@ void tipidee_log_resource (uint32_t v, tipidee_rql const *rql, char const *docro
     a[m++] = " host " ;
     a[m++] = rql->uri.host ;
   }
-  a[m++] = " docroot " ;
+  a[m++] = " resource docroot " ;
   a[m++] = docroot ;
   a[m++] = " file " ;
   a[m++] = file ;