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

