From 32263f00bc069e90f52ed87f73a9adde4aeac157 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Wed, 17 Dec 2025 16:21:41 +0100 Subject: [PATCH] Introduce a vector sidecar process --- config/puma_plugins/vector.rb | 94 +++++++++++++++++++++++++++++++++++ config/udp_logger.rb | 40 +++++++++++++++ config/vector.toml | 32 ++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 config/puma_plugins/vector.rb create mode 100644 config/udp_logger.rb create mode 100644 config/vector.toml diff --git a/config/puma_plugins/vector.rb b/config/puma_plugins/vector.rb new file mode 100644 index 0000000..c089e1d --- /dev/null +++ b/config/puma_plugins/vector.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "puma/plugin" +require "rbconfig" +require "timeout" +require "shellwords" + +Puma::Plugin.create do + def start(launcher) + @launcher = launcher + @events = launcher.events + @options = launcher.config.options + @vector_pid = nil + + setup_paths + start_vector + register_hooks + end + + private + + def setup_paths + @app_root = @options[:directory] || Dir.pwd + @vector_bin = env_or_option("ONLYLOGS_VECTOR_BIN", :onlylogs_vector_bin, "vector") + @vector_config = env_or_option( + "ONLYLOGS_VECTOR_CONFIG", + :onlylogs_vector_config, + File.expand_path("../vector.toml", __dir__) + ) + @vector_args = env_or_option("ONLYLOGS_VECTOR_ARGS", :onlylogs_vector_args, "") + @dsn = env_or_option("ONLYLOGS_DSN", :onlylogs_dsn, "https://onlylogs.io/drain/testmac") + end + + def register_hooks + events = @launcher.events + events.register(:on_restart) { restart_vector } + at_exit { stop_vector } + end + + def env_or_option(env_key, option_key, default) + ENV.fetch(env_key, @options.fetch(option_key, default)) + end + + def start_vector + stop_vector if @vector_pid + + unless File.exist?(@vector_config) + warn "Vector config not found at #{@vector_config}; skipping start" + return + end + + args = [ @vector_bin, "--config", @vector_config ] + args += Shellwords.split(@vector_args.to_s) unless @vector_args.to_s.empty? + + info "Starting Vector sidecar (config: #{@vector_config}, dsn: #{@dsn})" + env = { "ONLYLOGS_DSN" => @dsn } + @vector_pid = Process.spawn(env, *args, chdir: @app_root, pgroup: true) + rescue Errno::ENOENT => e + error "Unable to start Vector sidecar: #{e.message}" + end + + def restart_vector + info "Restarting Vector sidecar" + start_vector + end + + def stop_vector + return unless @vector_pid + + info "Stopping Vector sidecar" + pgid = Process.getpgid(@vector_pid) + Process.kill("TERM", -pgid) + Timeout.timeout(5) { Process.wait(@vector_pid) } + rescue Errno::ESRCH, Errno::ECHILD + # already stopped + rescue Timeout::Error + warn "Vector sidecar did not stop in time, killing" + Process.kill("KILL", -pgid) rescue nil + ensure + @vector_pid = nil + end + + def info(message) + @events.log("[VectorSidecar] #{message}") + end + + def warn(message) + @events.log("[VectorSidecar][WARN] #{message}") + end + + def error(message) + @events.error("[VectorSidecar][ERROR] #{message}") + end +end diff --git a/config/udp_logger.rb b/config/udp_logger.rb new file mode 100644 index 0000000..f6248f7 --- /dev/null +++ b/config/udp_logger.rb @@ -0,0 +1,40 @@ +# udp_logger.rb +require "logger" +require "socket" + +class UdpLogger < Logger + def initialize(host: "127.0.0.1", port: 6000, local_fallback: $stdout) + # Use a normal Logger underneath so we still see logs locally + super(local_fallback) + + @udp_host = host + @udp_port = port + @socket = UDPSocket.new + end + + # Override Logger#add, which all the level methods delegate to + def add(severity, message = nil, progname = nil, &block) + # Same semantics as Logger: + if message.nil? + if block_given? + message = block.call + else + message = progname + progname = nil + end + end + + # Send plain text over UDP to Vector + begin + payload = message.to_s + @socket.send(payload, 0, @udp_host, @udp_port) + rescue => e + # Swallow UDP errors so logging never crashes the app + warn "UDP logger error: #{e.class}: #{e.message}" + end + + # Also log locally (stdout / file) via normal Logger behavior + super(severity, message, progname, &block) + end +end + diff --git a/config/vector.toml b/config/vector.toml new file mode 100644 index 0000000..e659e1a --- /dev/null +++ b/config/vector.toml @@ -0,0 +1,32 @@ +# Where Vector keeps internal state (buffers, etc.) +data_dir = "/usr/local/var/lib/vector" + +# --- 1) SOURCE: UDP socket listening on localhost:6000 --- + +[sources.udp_logs] +type = "socket" +mode = "udp" # UDP mode +address = "127.0.0.1:6000" + +# --- 2) SINK: console (for debugging, optional but very handy) --- + +[sinks.console] +type = "console" +inputs = ["udp_logs"] +encoding.codec = "json" +target = "stdout" + +# --- 3) SINK: Onlylogs HTTP drain --- + +[sinks.onlylogs] +type = "http" +inputs = ["udp_logs"] # consume events from udp_logs +method = "post" +uri = "${ONLYLOGS_DSN}" + +encoding.codec = "text" + +#[sinks.onlylogs.request] +# Adjust headers if Onlylogs expects something specific +#headers.Content-Type = "application/json" +