Add structured logfmt logger for queries, malformed/refused, startup
Jaime Fournier <jaimef@linbsd.org>
d4e305e259e8b6df2cfdc467a5b8a39a56cec5a8
diff --git a/Makefile b/Makefile
index 697ad6e..0a73e64 100644
--- a/Makefile
+++ b/Makefile
@@ -86,3 +86,5 @@ static-clean:
clean:
find lib -name '*.so' -delete 2>/dev/null || true
find lib -name '*.wpo' -delete 2>/dev/null || true
+ find lib \( -name '*.tarm64osx' -o -name '*.ta6le' \
+ -o -name '*.ta6fb' -o -name '*.tarm64le' \) -delete 2>/dev/null || true
diff --git a/build.ss b/build.ss
index 19af422..67a1a72 100644
--- a/build.ss
+++ b/build.ss
@@ -10,5 +10,6 @@
(jerboa-dns lookup)
(jerboa-dns wasm-dns)
(jerboa-dns wasm-cdb)
+ (jerboa-dns log)
(jerboa-dns server)
(jerboa-dns main))
diff --git a/lib/jerboa-dns/log.sls b/lib/jerboa-dns/log.sls
new file mode 100644
index 0000000..5ba5156
--- /dev/null
+++ b/lib/jerboa-dns/log.sls
@@ -0,0 +1,178 @@
+#!chezscheme
+;;; (jerboa-dns log) — structured logfmt logger for jdns.
+;;;
+;;; All events are written to stderr as a single line in logfmt:
+;;;
+;;; ts=2026-05-17T03:25:09.624Z lvl=info evt=query src=192.0.2.1:53122 \
+;;; qid=0x4f3a qname=example.com qtype=A qclass=IN rcode=NOERROR \
+;;; resp_bytes=64 lat_us=312
+;;;
+;;; Values containing space, =, or " are double-quoted and escaped.
+;;;
+;;; Verbosity: per-query info events are gated by *log-queries* (default
+;;; #f, since a busy authoritative server emits thousands of these per
+;;; second). Warn-level events (malformed/refused/servfail) are always
+;;; emitted regardless.
+
+(library (jerboa-dns log)
+ (export
+ log-startup
+ log-query
+ log-malformed
+ log-refused
+ log-servfail
+ log-error
+ log-info
+ set-log-queries!
+ log-queries-on?
+ qtype-name
+ rcode-name
+ bytes-prefix-hex)
+
+ (import (chezscheme))
+
+ ;; ── Verbosity toggle ───────────────────────────────────────────────────
+ (define *log-queries* #f)
+ (define (log-queries-on?) *log-queries*)
+ (define (set-log-queries! flag) (set! *log-queries* (and flag #t)))
+
+ ;; ── Timestamp ──────────────────────────────────────────────────────────
+ ;; ISO-8601 UTC with millisecond precision. Uses Chez (current-date 0)
+ ;; which returns a date record with the requested UTC offset.
+ (define (format-ts!)
+ (let* ([d (current-date 0)]
+ [ms (quotient (date-nanosecond d) 1000000)])
+ (fprintf (current-error-port)
+ "~4,'0d-~2,'0d-~2,'0dT~2,'0d:~2,'0d:~2,'0d.~3,'0dZ"
+ (date-year d) (date-month d) (date-day d)
+ (date-hour d) (date-minute d) (date-second d) ms)))
+
+ ;; ── logfmt value quoting ───────────────────────────────────────────────
+ (define (needs-quoting? s)
+ (let loop ([i 0])
+ (cond
+ [(= i (string-length s)) #f]
+ [(let ([c (string-ref s i)])
+ (or (char=? c #\space) (char=? c #\=) (char=? c #\")
+ (char<? c #\space)))
+ #t]
+ [else (loop (+ i 1))])))
+
+ (define (write-quoted! port s)
+ (write-char #\" port)
+ (let loop ([i 0])
+ (cond
+ [(= i (string-length s)) (write-char #\" port)]
+ [else
+ (let ([c (string-ref s i)])
+ (cond
+ [(char=? c #\") (write-char #\\ port) (write-char #\" port)]
+ [(char=? c #\\) (write-char #\\ port) (write-char #\\ port)]
+ [(char=? c #\newline) (write-char #\\ port) (write-char #\n port)]
+ [(char=? c #\tab) (write-char #\\ port) (write-char #\t port)]
+ [else (write-char c port)])
+ (loop (+ i 1)))])))
+
+ (define (write-value! port v)
+ (let ([s (cond
+ [(string? v) v]
+ [(symbol? v) (symbol->string v)]
+ [(number? v) (number->string v)]
+ [(boolean? v) (if v "true" "false")]
+ [(not v) "-"]
+ [else (format "~a" v)])])
+ (cond
+ [(zero? (string-length s)) (display "\"\"" port)]
+ [(needs-quoting? s) (write-quoted! port s)]
+ [else (display s port)])))
+
+ ;; ── Core emitter ───────────────────────────────────────────────────────
+ ;; kvs is a flat list: (key val key val ...). Keys are symbols, values
+ ;; can be any displayable atom. Missing values (#f) print as `-`.
+ (define (emit! level event kvs)
+ (let ([p (current-error-port)])
+ (display "ts=" p) (format-ts!)
+ (display " lvl=" p) (display level p)
+ (display " evt=" p) (display event p)
+ (let loop ([rest kvs])
+ (cond
+ [(null? rest) (void)]
+ [(null? (cdr rest))
+ (error 'emit! "odd number of key/value args" event kvs)]
+ [else
+ (write-char #\space p)
+ (display (car rest) p)
+ (write-char #\= p)
+ (write-value! p (cadr rest))
+ (loop (cddr rest))]))
+ (newline p)
+ (flush-output-port p)))
+
+ ;; ── Public helpers ─────────────────────────────────────────────────────
+ ;; Each takes positional args (kept short for callers) and produces one
+ ;; event line. Use log-info / log-error for ad-hoc events without a
+ ;; dedicated helper.
+
+ (define (log-startup . kvs) (emit! 'info 'startup kvs))
+ (define (log-info evt . kvs) (emit! 'info evt kvs))
+ (define (log-error evt . kvs) (emit! 'error evt kvs))
+
+ (define (log-query src qid qname qtype qclass rcode resp-bytes lat-us)
+ (when *log-queries*
+ (emit! 'info 'query
+ (list 'src src 'qid qid 'qname qname 'qtype qtype 'qclass qclass
+ 'rcode rcode 'resp_bytes resp-bytes 'lat_us lat-us))))
+
+ (define (log-malformed src bytes first16 reason)
+ (emit! 'warn 'malformed
+ (list 'src src 'bytes bytes 'first16 first16 'reason reason)))
+
+ (define (log-refused src qid qname qtype reason)
+ (emit! 'warn 'refused
+ (list 'src src 'qid qid 'qname qname 'qtype qtype 'reason reason)))
+
+ (define (log-servfail src qid qname qtype reason)
+ (emit! 'warn 'servfail
+ (list 'src src 'qid qid 'qname qname 'qtype qtype 'reason reason)))
+
+ ;; ── Shared lookup tables ──────────────────────────────────────────────
+ ;; Mapping qtype codes to symbolic names (covers everything jdns-data
+ ;; can emit plus the few client-side query types that may show up).
+ (define (qtype-name n)
+ (case n
+ [(1) "A"] [(2) "NS"] [(5) "CNAME"]
+ [(6) "SOA"] [(12) "PTR"] [(15) "MX"]
+ [(16) "TXT"] [(28) "AAAA"] [(33) "SRV"]
+ [(35) "NAPTR"] [(43) "DS"] [(46) "RRSIG"]
+ [(47) "NSEC"] [(48) "DNSKEY"][(50) "NSEC3"]
+ [(52) "TLSA"] [(99) "SPF"] [(252) "AXFR"]
+ [(253) "MAILB"] [(255) "ANY"]
+ [else (number->string n)]))
+
+ ;; DNS RCODEs (RFC 1035 §4.1.1) — low 4 bits of header byte 3.
+ (define (rcode-name code)
+ (case (bitwise-and code #x0f)
+ [(0) "NOERROR"] [(1) "FORMERR"]
+ [(2) "SERVFAIL"] [(3) "NXDOMAIN"]
+ [(4) "NOTIMP"] [(5) "REFUSED"]
+ [else (number->string code)]))
+
+ ;; First N bytes of a bytevector as a continuous hex string — useful for
+ ;; including in malformed-packet logs so an operator can decode by hand.
+ (define (bytes-prefix-hex bv n)
+ (let* ([m (min n (bytevector-length bv))]
+ [out (make-string (* m 2))])
+ (do ([i 0 (+ i 1)]) ((= i m))
+ (let* ([b (bytevector-u8-ref bv i)]
+ [hi (quotient b 16)]
+ [lo (remainder b 16)])
+ (string-set! out (* i 2) (hex-digit hi))
+ (string-set! out (+ (* i 2) 1) (hex-digit lo))))
+ out))
+
+ (define (hex-digit n)
+ (cond
+ [(< n 10) (integer->char (+ (char->integer #\0) n))]
+ [else (integer->char (+ (char->integer #\a) (- n 10)))]))
+
+ ) ;; end library
diff --git a/lib/jerboa-dns/main.sls b/lib/jerboa-dns/main.sls
index b204fdb..d25ba80 100644
--- a/lib/jerboa-dns/main.sls
+++ b/lib/jerboa-dns/main.sls
@@ -7,10 +7,17 @@
(import
(chezscheme)
(jerboa-dns server)
+ (jerboa-dns log)
(jerboa-dns zone-compiler))
;; ========== Server Entry Point ==========
+ (define (truthy-env? s)
+ (and s
+ (let ([s (string-downcase s)])
+ (not (or (string=? s "") (string=? s "0")
+ (string=? s "false") (string=? s "no"))))))
+
(define (run-jdns! args)
;; Configuration from environment variables (djbdns convention)
;; or command-line args: [ip] [port] [root-dir] [uid] [gid]
@@ -22,6 +29,9 @@
[gid (and (getenv "GID") (string->number (getenv "GID")))]
[data-file (or (getenv "DATA") "data.cdb")])
+ (when (truthy-env? (getenv "JDNS_LOG_QUERIES"))
+ (set-log-queries! #t))
+
;; Override from args if provided
(let process-args ([rest args]
[ip ip] [port port] [root-dir root-dir]
@@ -45,10 +55,13 @@
uid (string->number (cadr rest)) data-file)]
[(string=? (car rest) "--data")
(process-args (cddr rest) ip port root-dir uid gid (cadr rest))]
+ [(string=? (car rest) "--log-queries")
+ (set-log-queries! #t)
+ (process-args (cdr rest) ip port root-dir uid gid data-file)]
[else
(display (format "jdns: unknown option ~a\n" (car rest))
(current-error-port))
- (display "Usage: jdns [--ip IP] [--port PORT] [--root DIR] [--uid UID] [--gid GID] [--data FILE]\n"
+ (display "Usage: jdns [--ip IP] [--port PORT] [--root DIR] [--uid UID] [--gid GID] [--data FILE] [--log-queries]\n"
(current-error-port))
(exit 1)]))))
diff --git a/lib/jerboa-dns/server.sls b/lib/jerboa-dns/server.sls
index 60d8d63..fb59af7 100644
--- a/lib/jerboa-dns/server.sls
+++ b/lib/jerboa-dns/server.sls
@@ -36,6 +36,7 @@
(jerboa-dns response)
(jerboa-dns lookup)
(jerboa-dns wasm-dns)
+ (jerboa-dns log)
;; The wasm-cdb wrapper exposes the same predicate/flag names as
;; wasm-dns; we only need its reader API here, so import that subset.
(only (jerboa-dns wasm-cdb)
@@ -201,9 +202,7 @@
(let ([rc (c-cap-rights-limit fd rights)])
(foreign-free rights)
(when (< rc 0)
- (display (format "jdns: warning: cap_rights_limit failed for fd ~a\n" fd)
- (current-error-port))
- (flush-output-port (current-error-port))))))
+ (log-info 'cap_rights_limit_failed 'fd fd)))))
(define (apply-capsicum-fd-restrictions!)
;; Restrict stdio fds using Capsicum cap_rights_limit.
@@ -229,9 +228,7 @@
(bitwise-ior CAP_WRITE CAP_FSTAT CAP_SEEK)
CAP_EVENT)
- (display "jdns: capsicum fd restrictions applied\n"
- (current-error-port))
- (flush-output-port (current-error-port)))
+ (log-info 'capsicum_applied))
;; ========== Query Processing ==========
;; One response-state reused across queries (like djbdns global).
@@ -261,64 +258,106 @@
[(_) (values #f #f #f #f)]
[(id qname qtype qclass) (values id qname qtype qclass)])))
+ (define (client-src-str client-addr)
+ (string-append
+ (sockaddr-in-addr-str client-addr)
+ ":"
+ (number->string (sockaddr-in-port client-addr))))
+
+ (define (now-monotonic-ns)
+ (let ([t (current-time 'time-monotonic)])
+ (+ (* (time-second t) 1000000000) (time-nanosecond t))))
+
+ (define (response-rcode rs)
+ (bitwise-and (bytevector-u8-ref (response-buffer rs) 3) #x0f))
+
+ (define (condition-reason e)
+ (cond
+ [(message-condition? e) (condition-message e)]
+ [else "unknown"]))
+
(define (process-query! rs sock pkt-buf pkt-len client-addr cdb)
;; Parse query, look up in CDB, send response.
;; cdb is the shared reader opened once at startup (see run-server!).
- (let ([pkt (make-bytevector pkt-len)])
+ (let ([pkt (make-bytevector pkt-len)]
+ [t0 (now-monotonic-ns)]
+ [src (client-src-str client-addr)])
;; Copy from foreign memory to bytevector
(do ([i 0 (+ i 1)]) ((= i pkt-len))
(bytevector-u8-set! pkt i (foreign-ref 'unsigned-8 pkt-buf i)))
(guard (e [#t
- ;; On any error, send SERVFAIL and log
- (guard (e2 [#t (void)])
- (display (format "jdns: query error: ~a\n"
- (if (message-condition? e)
- (condition-message e)
- "unknown"))
- (current-error-port))
- (flush-output-port (current-error-port)))
- (let ([err-rs (new-response-state)])
- (when (>= pkt-len 12)
- (response-query! err-rs (make-bytevector 1 0) DNS-T-A DNS-C-IN)
- (response-id! err-rs (uint16-get pkt 0))
- (response-servfail! err-rs)
- (send-response! sock err-rs client-addr)))])
+ ;; On any error, log SERVFAIL and try to send one over the wire
+ (let ([qid (and (>= pkt-len 2) (uint16-get pkt 0))])
+ (guard (e2 [#t (void)])
+ (log-servfail src
+ (or qid 0)
+ "-" "-"
+ (condition-reason e)))
+ (guard (e3 [#t (void)])
+ (when (>= pkt-len 12)
+ (let ([err-rs (new-response-state)])
+ (response-query! err-rs (make-bytevector 1 0) DNS-T-A DNS-C-IN)
+ (response-id! err-rs qid)
+ (response-servfail! err-rs)
+ (send-response! sock err-rs client-addr)))))])
;; Parse query (via wasm sandbox if enabled and available)
(let-values ([(id qname qtype qclass) (parse-query-dispatch pkt pkt-len)])
- (when id
- ;; Only handle class IN
- (when (or (= qclass DNS-C-IN) (= qclass DNS-C-ANY))
- ;; Reject AXFR
- (unless (= qtype DNS-T-AXFR)
- ;; Initialize response
- (response-query! rs qname qtype qclass)
- (response-id! rs id)
-
- ;; Look up against the shared CDB reader. The reader is
- ;; opened once in run-server! and reused across queries —
- ;; the sandbox wrapper resets its bump arena and refills
- ;; fuel on every cdb_query so the wasm instance stays
- ;; clean between calls.
- (let ([client-ip (make-bytevector 4 0)])
- ;; Extract from sockaddr (sin_addr at offset 4)
- (do ([i 0 (+ i 1)]) ((= i 4))
- (bytevector-u8-set! client-ip i
- (foreign-ref 'unsigned-8 (+ client-addr 4) i)))
- (dns-respond rs cdb qname qtype client-ip))
-
- ;; Truncate if > 512 bytes
- (when (> (response-length rs) MAX-PACKET)
- (response-tc! rs))
-
- ;; Log query
- (log-query qname qtype
- (sockaddr-in-addr-str client-addr)
- (response-length rs))
-
- ;; Send response
- (send-response! sock rs client-addr))))))))
+ (cond
+ [(not id)
+ ;; Malformed or unparseable — silent on the wire (djbdns
+ ;; anti-amplification convention), but loud in the logs.
+ (log-malformed src pkt-len
+ (bytes-prefix-hex pkt 16)
+ (if (< pkt-len 12) "header_too_short" "parse_failed"))]
+
+ [(not (or (= qclass DNS-C-IN) (= qclass DNS-C-ANY)))
+ ;; Non-IN class — silent drop, logged at warn level.
+ (log-refused src id
+ (dns-domain-to-dot qname 0)
+ (qtype-name qtype)
+ "non_in_class")]
+
+ [(= qtype DNS-T-AXFR)
+ ;; AXFR refused — silent drop, logged at warn level.
+ (log-refused src id
+ (dns-domain-to-dot qname 0)
+ (qtype-name qtype)
+ "axfr_disabled")]
+
+ [else
+ ;; Initialize response
+ (response-query! rs qname qtype qclass)
+ (response-id! rs id)
+
+ ;; Look up against the shared CDB reader. The reader is
+ ;; opened once in run-server! and reused across queries —
+ ;; the sandbox wrapper resets its bump arena and refills
+ ;; fuel on every cdb_query so the wasm instance stays
+ ;; clean between calls.
+ (let ([client-ip (make-bytevector 4 0)])
+ ;; Extract from sockaddr (sin_addr at offset 4)
+ (do ([i 0 (+ i 1)]) ((= i 4))
+ (bytevector-u8-set! client-ip i
+ (foreign-ref 'unsigned-8 (+ client-addr 4) i)))
+ (dns-respond rs cdb qname qtype client-ip))
+
+ ;; Truncate if > 512 bytes
+ (when (> (response-length rs) MAX-PACKET)
+ (response-tc! rs))
+
+ ;; Send response
+ (send-response! sock rs client-addr)
+
+ ;; Log query (gated by *log-queries*; see (jerboa-dns log)).
+ (log-query src id
+ (dns-domain-to-dot qname 0)
+ (qtype-name qtype)
+ (if (= qclass DNS-C-ANY) "ANY" "IN")
+ (rcode-name (response-rcode rs))
+ (response-length rs)
+ (quotient (- (now-monotonic-ns) t0) 1000))])))))
(define (send-response! sock rs client-addr)
(let* ([len (response-length rs)]
@@ -330,18 +369,6 @@
(c-sendto sock foreign-buf len 0 client-addr SOCKADDR_IN_SIZE)
(foreign-free foreign-buf)))
- (define (log-query qname qtype client-addr-str response-len)
- (let ([name (dns-domain-to-dot qname 0)]
- [type-name (case qtype
- [(1) "A"] [(2) "NS"] [(5) "CNAME"]
- [(6) "SOA"] [(12) "PTR"] [(15) "MX"]
- [(16) "TXT"] [(28) "AAAA"] [(33) "SRV"]
- [(255) "ANY"] [else (number->string qtype)])])
- (display (format "query ~a ~a from ~a (~a bytes)\n"
- name type-name client-addr-str response-len)
- (current-error-port))
- (flush-output-port (current-error-port))))
-
;; ========== Main Server Loop ==========
(define (run-server! config)
@@ -370,9 +397,7 @@
(error 'run-server! "cannot bind" ip port))
(foreign-free sa))
- (display (format "jdns: listening on ~a:~a\n" ip port)
- (current-error-port))
- (flush-output-port (current-error-port))
+ (log-startup 'ip ip 'port port)
;; 4. chroot + chdir to data directory
;; chroot restricts filesystem access to this directory tree.
@@ -382,16 +407,12 @@
(if chroot-ok
(begin
(c-chdir "/")
- (display (format "jdns: chroot to ~a\n" root-dir)
- (current-error-port))
- (flush-output-port (current-error-port)))
+ (log-info 'chroot 'dir root-dir))
(begin
;; chroot failed (not root?) — fall back to chdir
(when (= (c-chdir root-dir) -1)
(error 'run-server! "cannot chdir" root-dir))
- (display (format "jdns: chdir to ~a (chroot unavailable)\n" root-dir)
- (current-error-port))
- (flush-output-port (current-error-port))))))
+ (log-info 'chdir 'dir root-dir 'reason "chroot_unavailable")))))
;; 5. Drop privileges (gid before uid, as setuid may remove
;; the ability to call setgid)
@@ -403,10 +424,7 @@
(error 'run-server! "cannot setuid" uid)))
(when (or uid gid)
- (display (format "jdns: dropped privileges (uid=~a gid=~a)\n"
- (or uid "unchanged") (or gid "unchanged"))
- (current-error-port))
- (flush-output-port (current-error-port)))
+ (log-info 'privdrop 'uid (or uid "unchanged") 'gid (or gid "unchanged")))
;; 6. Apply OS-level sandbox (defense in depth)
(cond
@@ -426,12 +444,10 @@
;; linear memory) and ~15% under no-sandbox load (__open syscall
;; on the CDB file) — see bench/results/summary.md.
(let ([cdb (open-sandboxed-cdb data-file)])
- (display (format "jdns: opened CDB ~a (~a path)\n"
- data-file
- (if (sandboxed-cdb-reader-using-sandbox? cdb)
+ (log-info 'cdb_opened
+ 'file data-file
+ 'parser (if (sandboxed-cdb-reader-using-sandbox? cdb)
"sandboxed" "in-process"))
- (current-error-port))
- (flush-output-port (current-error-port))
;; 8. Main loop
(let ([recv-buf (foreign-alloc 65536)]
@@ -448,12 +464,8 @@
(when (> n 0)
;; Process query
(guard (e [#t
- (display (format "jdns: error processing query: ~a\n"
- (if (message-condition? e)
- (condition-message e)
- "unknown"))
- (current-error-port))
- (flush-output-port (current-error-port))])
+ (log-error 'recv_loop_error
+ 'reason (condition-reason e))])
(process-query! rs sock recv-buf n client-addr cdb))))
(loop)))))))
@@ -465,9 +477,7 @@
;; Uses Landlock ABI if available (Linux 5.13+).
;; Graceful fallback if not available.
(guard (e [#t
- (display "jdns: landlock not available — running without filesystem sandbox\n"
- (current-error-port))
- (flush-output-port (current-error-port))])
+ (log-info 'landlock_unavailable 'reason "no_filesystem_sandbox")])
;; Try to load the Jerboa Landlock module dynamically
(let ([ll-available? (guard (e [#t #f])
(eval '(begin
@@ -481,8 +491,6 @@
(landlock-add-read-only! rs ".")
(landlock-install! rs)))
(interaction-environment))
- (display "jdns: landlock filesystem sandbox active\n"
- (current-error-port))
- (flush-output-port (current-error-port))))))
+ (log-info 'landlock_active)))))
) ;; end library
diff --git a/lib/jerboa-dns/wasm-cdb.sls b/lib/jerboa-dns/wasm-cdb.sls
index 16ec731..39311a4 100644
--- a/lib/jerboa-dns/wasm-cdb.sls
+++ b/lib/jerboa-dns/wasm-cdb.sls
@@ -47,7 +47,8 @@
(import
(chezscheme)
(std wasm sandbox)
- (jerboa-dns cdb))
+ (jerboa-dns cdb)
+ (jerboa-dns log))
;; Tag bytes — must match wasm/cdb/src/lib.rs.
(define TAG-REC #x02)
@@ -127,12 +128,9 @@
(define (warn-fallback-once! reason)
(unless *warned-fallback?*
(set! *warned-fallback?* #t)
- (let ([p (current-error-port)])
- (display "jdns: WARNING — wasm CDB parser unavailable (" p)
- (display reason p)
- (display "); falling back to in-process cdb-find-all. " p)
- (display "Set JDNS_REQUIRE_WASM_CDB=1 to error instead.\n" p)
- (flush-output-port p))))
+ (log-info 'wasm_cdb_fallback
+ 'reason reason
+ 'hint "set JDNS_REQUIRE_WASM_CDB=1 to error instead")))
;; ========== Reader record ==========
;; Carries both the wasm instance (when sandboxed) and the underlying
diff --git a/lib/jerboa-dns/wasm-dns.sls b/lib/jerboa-dns/wasm-dns.sls
index 19a5dbc..305cc7e 100644
--- a/lib/jerboa-dns/wasm-dns.sls
+++ b/lib/jerboa-dns/wasm-dns.sls
@@ -32,7 +32,8 @@
(import
(chezscheme)
(std wasm sandbox)
- (jerboa-dns protocol))
+ (jerboa-dns protocol)
+ (jerboa-dns log))
;; Tag bytes — must match wasm/src/lib.rs.
(define TAG-QUERY #x01)
@@ -122,12 +123,9 @@
(define (warn-fallback-once! reason)
(unless *warned-fallback?*
(set! *warned-fallback?* #t)
- (let ([p (current-error-port)])
- (display "jdns: WARNING — wasm DNS parser unavailable (" p)
- (display reason p)
- (display "); falling back to in-process parse-query. " p)
- (display "Set JDNS_REQUIRE_WASM_PARSER=1 to error instead.\n" p)
- (flush-output-port p))))
+ (log-info 'wasm_dns_fallback
+ 'reason reason
+ 'hint "set JDNS_REQUIRE_WASM_PARSER=1 to error instead")))
;; ---------- Output decoder ----------