liminix

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }