Hi

please find three patches working towards the feature described in the
subject attached to this email.

Best regards

-- 
Tim Düsterhus
Senior Software Engineer
Tideways GmbH
Königswinterer Str. 116
53227 Bonn
https://tideways.com/imprint
Sitz der Gesellschaft: Bonn
Geschäftsführer: Benjamin Außenhofer (geb. Eberlei)
Registergericht: Amtsgericht Bonn, HRB 22127
From 1e024b4b50d48d69d22bde8eb345a1e5f1d6e845 Mon Sep 17 00:00:00 2001
From: Tim Duesterhus <[email protected]>
Date: Wed, 15 Jul 2026 17:10:36 +0200
Subject: [PATCH 2/3] CLEANUP: halog: Clean up naming for variables related to
 `-hdr` processing
To: [email protected]
Cc: [email protected]

This is in preparation of allowing to filter based on the values of a header
capture by having a common "capture" prefix for variables related to extracting
header captures.
---
 admin/halog/halog.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/admin/halog/halog.c b/admin/halog/halog.c
index 022ccc93d..eaa808c43 100644
--- a/admin/halog/halog.c
+++ b/admin/halog/halog.c
@@ -123,7 +123,7 @@ struct url_stat {
 
 #define FILT2_TIMESTAMP         0x01
 #define FILT2_PRESERVE_QUERY    0x02
-#define FILT2_EXTRACT_CAPTURE   0x04
+#define FILT2_CAPTURE_PRINT     0x04
 
 #define FILT_OUTPUT_FMT   (FILT_COUNT_ONLY| \
 			   FILT_COUNT_STATUS| \
@@ -742,7 +742,7 @@ int main(int argc, char **argv)
 	int filter_time_resp = 0;
 	int filt_http_status_low = 0, filt_http_status_high = 0;
 	unsigned int filt2_timestamp_low = 0, filt2_timestamp_high = 0;
-	unsigned int filt2_capture_block = 0, filt2_capture_field = 0;
+	unsigned int filt2_capture_print_block = 0, filt2_capture_print_field = 0;
 	int skip_fields = 1;
 
 	void (*line_filter)(const char *accept_field, const char *time_field, struct timer **tptr) = NULL;
@@ -885,7 +885,7 @@ int main(int argc, char **argv)
 			char *sep, *str;
 
 			if (argc < 2) die("missing option for -hdr (<block>:<field>)\n");
-			filter2 |= FILT2_EXTRACT_CAPTURE;
+			filter2 |= FILT2_CAPTURE_PRINT;
 
 			argc--; argv++;
 			str = *argv;
@@ -895,10 +895,10 @@ int main(int argc, char **argv)
 			else
 				*sep++ = 0;
 
-			filt2_capture_block = *str ? atol(str) : 1;
-			filt2_capture_field = *sep ? atol(sep) : 1;
+			filt2_capture_print_block = *str ? atol(str) : 1;
+			filt2_capture_print_field = *sep ? atol(sep) : 1;
 
-			if (filt2_capture_block < 1 || filt2_capture_field < 1)
+			if (filt2_capture_print_block < 1 || filt2_capture_print_field < 1)
 				die("block and field must be at least 1 for -hdr (<block>:<field>)\n");
 		}
 		else if (strcmp(argv[0], "-o") == 0) {
@@ -1131,8 +1131,8 @@ int main(int argc, char **argv)
 		if (line_filter) {
 			if (filter & FILT_COUNT_IP_COUNT)
 				filter_count_ip(source_field, accept_field, time_field, &t);
-			else if (filter2 & FILT2_EXTRACT_CAPTURE)
-				filter_print_capture(accept_field, time_field, filt2_capture_block, filt2_capture_field);
+			else if (filter2 & FILT2_CAPTURE_PRINT)
+				filter_print_capture(accept_field, time_field, filt2_capture_print_block, filt2_capture_print_field);
 			else
 				line_filter(accept_field, time_field, &t);
 		}
-- 
2.43.0

From 4ec32767b00e0caa9df39c98444edaec7594938a Mon Sep 17 00:00:00 2001
From: Tim Duesterhus <[email protected]>
Date: Wed, 15 Jul 2026 17:09:21 +0200
Subject: [PATCH 1/3] MINOR: halog: Add reusable function to extract the value
 of header captures
To: [email protected]
Cc: [email protected]

This is in preparation of allowing to filter based on the values of a header
capture. No functional change is expected.
---
 admin/halog/halog.c | 36 ++++++++++++++++++++++--------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/admin/halog/halog.c b/admin/halog/halog.c
index 52986849f..022ccc93d 100644
--- a/admin/halog/halog.c
+++ b/admin/halog/halog.c
@@ -27,6 +27,8 @@
 #include <import/ebistree.h>
 #include <import/ebsttree.h>
 
+#include <import/ist.h>
+
 #define SOURCE_FIELD 5
 #define ACCEPT_FIELD 6
 #define SERVER_FIELD 8
@@ -158,7 +160,8 @@ void filter_count_term_codes(const char *accept_field, const char *time_field, s
 void filter_count_status(const char *accept_field, const char *time_field, struct timer **tptr);
 void filter_graphs(const char *accept_field, const char *time_field, struct timer **tptr);
 void filter_output_line(const char *accept_field, const char *time_field, struct timer **tptr);
-void filter_extract_capture(const char *accept_field, const char *time_field, unsigned int, unsigned int);
+struct ist filter_extract_capture(const char *accept_field, const char *time_field, unsigned int, unsigned int);
+void filter_print_capture(const char *accept_field, const char *time_field, unsigned int, unsigned int);
 void filter_accept_holes(const char *accept_field, const char *time_field, struct timer **tptr);
 
 void usage(FILE *output, const char *msg)
@@ -1129,7 +1132,7 @@ int main(int argc, char **argv)
 			if (filter & FILT_COUNT_IP_COUNT)
 				filter_count_ip(source_field, accept_field, time_field, &t);
 			else if (filter2 & FILT2_EXTRACT_CAPTURE)
-				filter_extract_capture(accept_field, time_field, filt2_capture_block, filt2_capture_field);
+				filter_print_capture(accept_field, time_field, filt2_capture_block, filt2_capture_field);
 			else
 				line_filter(accept_field, time_field, &t);
 		}
@@ -1388,7 +1391,7 @@ void filter_output_line(const char *accept_field, const char *time_field, struct
 	lines_out++;
 }
 
-void filter_extract_capture(const char *accept_field, const char *time_field, unsigned int block, unsigned int field)
+struct ist filter_extract_capture(const char *accept_field, const char *time_field, unsigned int block, unsigned int field)
 {
 	const char *e, *f;
 
@@ -1408,15 +1411,12 @@ void filter_extract_capture(const char *accept_field, const char *time_field, un
 		}
 
 		if (unlikely(!*e)) {
-			truncated_line(linenum, line);
-			return;
+			return IST_NULL;
 		}
 
 		/* We reached the URL, no more captures will follow. */
 		if (*e != '{') {
-			puts("");
-			lines_out++;
-			return;
+			return ist("");
 		}
 
 		/* e points the the opening brace of the capture block. */
@@ -1431,14 +1431,11 @@ void filter_extract_capture(const char *accept_field, const char *time_field, un
 			e++;
 
 		if (unlikely(!*e)) {
-			truncated_line(linenum, line);
-			return;
+			return IST_NULL;
 		}
 
 		if (*e != '|') {
-			puts("");
-			lines_out++;
-			return;
+			return ist("");
 		}
 
 		/* e points to the pipe. */
@@ -1452,11 +1449,22 @@ void filter_extract_capture(const char *accept_field, const char *time_field, un
 		f++;
 
 	if (unlikely(!*f)) {
+		return IST_NULL;
+	}
+
+	return ist2(e, f - e);
+}
+
+void filter_print_capture(const char *accept_field, const char *time_field, unsigned int block, unsigned int field)
+{
+	struct ist capture = filter_extract_capture(accept_field, time_field, block, field);
+
+	if (unlikely(!isttest(capture))) {
 		truncated_line(linenum, line);
 		return;
 	}
 
-	fwrite(e, f - e, 1, stdout);
+	fwrite(istptr(capture), istlen(capture), 1, stdout);
 	putchar('\n');
 	lines_out++;
 }
-- 
2.43.0

From 859fb05e910944e7eb4a4483b049215b6ed41bfb Mon Sep 17 00:00:00 2001
From: Tim Duesterhus <[email protected]>
Date: Wed, 15 Jul 2026 17:14:00 +0200
Subject: [PATCH 3/3] MINOR: halog: Add support filtering on header capture
 values using -hdr-match
To: [email protected]
Cc: [email protected]

This patch extends the existing support for printing captured header fields
(`-hdr`) by a new filter (`-hdr-match`) that only processes lines where the
given capture has a specific value. It works together with all existing filters
and output formats.

The full syntax is `-hdr-match <block>:<field>=<value>`, where <block> and <field>
work just like `-hdr` and `<value>` is an exact string match:

Example:

    capture request  header a len 50
    capture request  header b len 50
    capture request  header c len 50
    capture response header d len 50
    capture response header e len 50
    capture response header f len 50

- `-hdr-match 1:1=foo` will filter for requests where `a` is equal to "foo".
- `-hdr-match "2:3=foo bar"` will filter for requests where `f` is equal to
  "foo bar".

The chosen syntax leaves future scope for allowing `<block>:<field>*<value>`
for substring matches and `<block>:<field>^<value>` for prefix matches without
introducing a breaking change.
---
 admin/halog/halog.c | 49 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 47 insertions(+), 2 deletions(-)

diff --git a/admin/halog/halog.c b/admin/halog/halog.c
index eaa808c43..3d45ed173 100644
--- a/admin/halog/halog.c
+++ b/admin/halog/halog.c
@@ -124,6 +124,7 @@ struct url_stat {
 #define FILT2_TIMESTAMP         0x01
 #define FILT2_PRESERVE_QUERY    0x02
 #define FILT2_CAPTURE_PRINT     0x04
+#define FILT2_CAPTURE_MATCH     0x08
 
 #define FILT_OUTPUT_FMT   (FILT_COUNT_ONLY| \
 			   FILT_COUNT_STATUS| \
@@ -173,6 +174,7 @@ void usage(FILE *output, const char *msg)
 		"  halog [input_filters]* [modifiers]* [output_format] < log\n"
 		"    inp = [-e|-E] [-H] [-Q|-QS] [-rt|-RT <time>] [-ad <delay>] [-ac <count>]\n"
 		"          [-hs|-HS [min][:[max]]] [-tcn|-TCN <termcode>] [-time [min][:[max]]]\n"
+		"          [-hdr-match <block>:<field>=<value>]\n"
 		"    mod = [-q] [-v] [-m <lines>] [-s <skipflds>] [-query]\n"
 		"    out = {-c|-u|-uc|-ue|-ua|-ut|-uao|-uto|-uba|-ubt|-hdr <block>:<field>|\n"
 		"           -cc|-gt|-pct|-st|-tc|-srv|-ic}\n"
@@ -191,7 +193,7 @@ void help()
 {
 	usage(stdout, NULL);
 	printf(
-	       "Input filters - several filters may be combined\n"
+	       "Input filters - several filters may be combined, any of them may be omitted\n"
 	       " -H                      only match lines containing HTTP logs (ignore TCP)\n"
 	       " -E                      only match lines without any error (no 5xx status)\n"
 	       " -e                      only match lines with errors (status 5xx or negative)\n"
@@ -202,7 +204,10 @@ void help()
 	       "                         within min..max. Any of them may be omitted. Exact\n"
 	       "                         code is checked for if no ':' is specified.\n"
 	       " -time <[min][:max]>     only match requests recorded between timestamps.\n"
-	       "                         Any of them may be omitted.\n"
+	       " -hdr-match <block>:<field>=<value>\n"
+	       "                         only match requests where the captured header at the\n"
+	       "                         given <block>:<field> is equal to <value>.\n"
+	       "\n"
 	       "Modifiers\n"
 	       " -v                      invert the input filtering condition\n"
 	       " -q                      don't report errors/warnings\n"
@@ -743,6 +748,8 @@ int main(int argc, char **argv)
 	int filt_http_status_low = 0, filt_http_status_high = 0;
 	unsigned int filt2_timestamp_low = 0, filt2_timestamp_high = 0;
 	unsigned int filt2_capture_print_block = 0, filt2_capture_print_field = 0;
+	unsigned int filt2_capture_match_block = 0, filt2_capture_match_field = 0;
+	struct ist filt2_capture_match_value = IST_NULL;
 	int skip_fields = 1;
 
 	void (*line_filter)(const char *accept_field, const char *time_field, struct timer **tptr) = NULL;
@@ -901,6 +908,33 @@ int main(int argc, char **argv)
 			if (filt2_capture_print_block < 1 || filt2_capture_print_field < 1)
 				die("block and field must be at least 1 for -hdr (<block>:<field>)\n");
 		}
+		else if (strcmp(argv[0], "-hdr-match") == 0) {
+			char *colon, *equals, *str;
+
+			if (argc < 2) die("missing option for -hdr-match (<block>:<field>=<value>)\n");
+			filter2 |= FILT2_CAPTURE_MATCH;
+
+			argc--; argv++;
+			str = *argv;
+			colon = strchr(str, ':');
+			if (!colon)
+				die("missing colon in -hdr-match (<block>:<field>=<value>)\n");
+			else
+				*colon++ = 0;
+			
+			equals = strchr(colon, '=');
+			if (!equals)
+				die("missing equals in -hdr-match (<block>:<field>=<value>)\n");
+			else
+				*equals++ = 0;
+
+			filt2_capture_match_block = *str ? atol(str) : 1;
+			filt2_capture_match_field = *colon ? atol(colon) : 1;
+			filt2_capture_match_value = ist(equals);
+
+			if (filt2_capture_match_block < 1 || filt2_capture_match_field < 1)
+				die("block and field must be at least 1 for -hdr-match (<block>:<field>=<value>)\n");
+		}
 		else if (strcmp(argv[0], "-o") == 0) {
 			if (output_file)
 				die("Fatal: output file name already specified.\n");
@@ -995,6 +1029,17 @@ int main(int argc, char **argv)
 			continue;
 		}
 
+		if (filter2 & FILT2_CAPTURE_MATCH) {
+			struct ist capture = filter_extract_capture(accept_field, time_field, filt2_capture_match_block, filt2_capture_match_field);
+
+			if (unlikely(!isttest(capture))) {
+				truncated_line(linenum, line);
+				continue;
+			}
+
+			test &= isteq(capture, filt2_capture_match_value);
+		}
+
 		if (filter2 & FILT2_TIMESTAMP) {
 			uval = convert_date_to_timestamp(accept_field);
 			test &= (uval>=filt2_timestamp_low && uval<=filt2_timestamp_high) ;
-- 
2.43.0

Reply via email to