From af6e41db7a340305c03645d9a8f9eb1401487c38 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 6 Oct 2025 22:24:33 +0100 Subject: [PATCH] logshippers: add victorialogsend script it's only very lightly tested but it seems to work. the _stream may be quite wrong, or perhaps that's a local admin decision anyway --- pkgs/logshippers/default.nix | 27 +++++---- pkgs/logshippers/victorialogsend.fnl | 83 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 pkgs/logshippers/victorialogsend.fnl diff --git a/pkgs/logshippers/default.nix b/pkgs/logshippers/default.nix index 6638f72..59cce01 100644 --- a/pkgs/logshippers/default.nix +++ b/pkgs/logshippers/default.nix @@ -11,6 +11,14 @@ }: let name = "logshippers"; + luafy = name : source : + writeFennel name { + packages = [ anoia lualinux fennel ]; + macros = [ anoia.dev ]; + mainFunction = "run"; + } source; + incz = luafy name ./incz.fnl; + victorialogsend = luafy name ./victorialogsend.fnl; in stdenv.mkDerivation { inherit name; @@ -21,22 +29,13 @@ stdenv.mkDerivation { buildPhase = '' fennelrepl --test ./incz.fnl - cp -p ${ - writeFennel name { - packages = [ - anoia - lualinux - fennel - ]; - macros = [ - anoia.dev - ]; - mainFunction = "run"; - } ./incz.fnl - } ${name} + fennelrepl --test ./victorialogsend.fnl + cp -p ${incz} incz + cp -p ${victorialogsend} victorialogsend ''; installPhase = '' - install -D ${name} $out/bin/${name} + install -D incz $out/bin/incz + install -D victorialogsend $out/bin/victorialogsend ''; } diff --git a/pkgs/logshippers/victorialogsend.fnl b/pkgs/logshippers/victorialogsend.fnl new file mode 100644 index 0000000..ed19b73 --- /dev/null +++ b/pkgs/logshippers/victorialogsend.fnl @@ -0,0 +1,83 @@ +(local { : base64 : assoc } (require :anoia)) +(local tai64 (require :anoia.tai64)) +(local ll (require :lualinux)) +(import-macros { : expect= : define-tests } :anoia.assert) + +(local crlf "\r\n") + +(fn chunk [str] + (let [len (# str)] + (string.format "%x%s%s%s" len crlf str crlf))) + +(fn parse-url [str] + ;; this is a very poor parser as it won't recognise + ;; credentials in the authority and it lumps query-string/fragment + ;; into the path + (let [(scheme host path) + (string.match str "(.-)://(.-)(/.+)")] + { : scheme : host : path })) + +(define-tests + (expect= (parse-url "https://www.example.com/stairway/to/heaven") + { :scheme "https" + :host "www.example.com" + :path "/stairway/to/heaven" + })) + +(fn parse-args [args] + (case args + ["--basic-auth" auth & rest] + (assoc (parse-args rest) :auth auth) + + [url] { :url (parse-url url) } + _ (error "invalid args"))) + + +(fn http-header [host path auth] + (let [b64 (base64 :url) + authstr + (if auth + (string.format "Authorization: basic %s\n" (b64:encode auth)) + "")] + (string.format + "POST %s HTTP/1.1\r +Host: %s\ +%sTransfer-Encoding: chunked\r +\r +" + path host authstr))) + +(fn format-timestamp-rfc3339 [timestamp prec] + (let [(sec nano) (-> timestamp tai64.from-timestamp tai64.to-utc) + subsec (string.sub (string.format "%09d" nano) 1 prec)] + (.. (os.date "!%FT%T" sec) + "." subsec + "Z"))) + +(define-tests + (expect= (format-timestamp-rfc3339 "@4000000068e2f0d3257dc09b" 9) + "2025-10-05T22:26:54.628998299Z")) + + + +(fn process-line [line] + (let [(timestamp hostname service msg) (string.match line "(@%x+) (%g+) (%g+) (.+)$")] + (-> + (string.format + "{%q:%q,%q:%q,%q:%q,%q:%q}\n" + :_time (format-timestamp-rfc3339 timestamp) + :service service + :_msg msg + :host hostname) + chunk))) + +(fn run [] + (let [{ : auth : url } (parse-args arg)] + (ll.write 1 (http-header url.host url.path auth)) + (while (case (io.stdin:read "l") + line (ll.write 1 (process-line line)))) + (ll.write 1 (chunk ""))) + (io.stderr:write (ll.read 0))) + + +{ : run }