victorialogsend.fnl (2954B)
1 (local { : base64 : assoc } (require :anoia)) 2 (local tai64 (require :anoia.tai64)) 3 (local ll (require :lualinux)) 4 (import-macros { : expect= : define-tests } :anoia.assert) 5 6 (local crlf "\r\n") 7 8 (fn chunk [str] 9 (let [len (# str)] 10 (string.format "%x%s%s%s" len crlf str crlf))) 11 12 (fn parse-url [str] 13 ;; this is a very poor parser as it won't recognise 14 ;; credentials in the authority and it lumps query-string/fragment 15 ;; into the path 16 (let [(scheme host path) 17 (string.match str "(.-)://(.-)(/.+)")] 18 { : scheme : host : path })) 19 20 (define-tests 21 (expect= (parse-url "https://www.example.com/stairway/to/heaven") 22 { :scheme "https" 23 :host "www.example.com" 24 :path "/stairway/to/heaven" 25 })) 26 27 (fn parse-args [args] 28 (case args 29 ["--basic-auth" auth & rest] 30 (assoc (parse-args rest) :auth auth) 31 32 [fifo url] { :fifo fifo :url (parse-url url) } 33 _ (error "invalid args"))) 34 35 36 (fn http-header [host path auth] 37 (let [b64 (base64 :url) 38 authstr 39 (if auth 40 (string.format "Authorization: basic %s\n" (b64:encode auth)) 41 "")] 42 (string.format 43 "POST %s HTTP/1.1\r 44 Host: %s\ 45 %sTransfer-Encoding: chunked\r 46 \r 47 " 48 path host authstr))) 49 50 (fn format-timestamp-rfc3339 [timestamp prec] 51 (let [(sec nano) (-> timestamp tai64.from-timestamp tai64.to-unix) 52 subsec (string.sub (string.format "%09d" nano) 1 prec)] 53 (.. (os.date "!%FT%T" sec) 54 "." subsec 55 "Z"))) 56 57 (define-tests 58 (expect= (format-timestamp-rfc3339 "@4000000068e2f0d3257dc09b" 9) 59 "2025-10-05T22:26:54.628998299Z") 60 (expect= (format-timestamp-rfc3339 "@4000000068e2f0d3257dc09b" 3) 61 "2025-10-05T22:26:54.628Z")) 62 63 (fn process-line [line] 64 (let [(timestamp hostname service msg) (string.match line "(@%x+) (%g+) (%g+) (.*)$")] 65 (-> 66 (if timestamp 67 (string.format 68 "{%q:%q,%q:%q,%q:%q,%q:%q}\n" 69 :_time (format-timestamp-rfc3339 timestamp 3) 70 :service service 71 :_msg msg 72 :host hostname) 73 (string.format 74 "{%q:%q,%q:%q,%q:%q,%q:%q}\n" 75 :_time (os.date "!%FT%TZ") 76 :service "ERROR" 77 :_msg (string.format "can't parse log %q" msg) 78 :host hostname)) 79 chunk) 80 )) 81 82 (fn writefd [fd body] 83 (case (ll.write fd body) 84 (bytes) (when (< bytes (# body)) (writefd fd (string.sub body bytes))) 85 (nil errno) 86 (error (string.format "write to fd %d failed: %s" fd (ll.strerror errno)))) 87 true) 88 89 (fn run [] 90 (let [{ : auth : url : fifo } (parse-args arg) 91 http-response-fd 6 92 http-req-fd 7] 93 (writefd http-req-fd (http-header url.host url.path auth)) 94 (with-open [logs (assert (io.open fifo :r))] 95 (while (case (logs:read "l") 96 line (writefd http-req-fd (process-line line))))) 97 (writefd http-req-fd (chunk "")) 98 (io.stderr:write (ll.read http-response-fd)))) 99 100 101 { : run }