This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
glTail is a Ruby program that tails log files (typically over SSH from remote servers) and visualizes incoming log events as bouncing colored blobs in an OpenGL window. The author's own README warns the code is messy and uses global variables — treat existing style as legacy and avoid large refactors.
- First-time setup:
bundle install && bin/setup—bin/setupbuilds the vendored chipmunk C extension; bundler does not auto-build extensions forpath:gems, so this step is mandatory and must be re-run if the chipmunk source changes. - Run:
bundle exec ruby bin/gl_tail <configfile>(defaults togl_tail.yamlin the cwd; always run viabundle execso the vendored chipmunk and the right opengl/glu/glut are loaded). - Generate a starter config:
bin/gl_tail --new myconfig.yaml(copiesdist/config.yaml). - List built-in parsers:
bin/gl_tail <any-config> --parsers. - List configuration options:
bin/gl_tail <any-config> --options. - Debug:
--debug/-d(general),--debug-ssh/-ds(SSH only),--quiet/-q. - Build gem:
rake build(Rakefile only loadsbundler/gem_tasks—build,install,release).
Note: most flags other than --new, --help, and --version require a config-file argument because bin/gl_tail validates the file's existence before dispatching. That's pre-existing behavior, not a bug.
There is no real test suite — test/test_gl_tail.rb is empty.
Runtime keys while the visualizer is running: f toggles target FPS, b cycles default blob type, space toggles bouncing, shift+f toggles fullscreen. SIGUSR2 toggles debug level.
The codebase was last touched against Ruby 2.1 (.versions.conf still says ruby-2.1.4); it now runs on Ruby 3.x+ but only after a small set of compatibility patches:
- chipmunk 5.3.4.5 is vendored under
vendor/chipmunk/and patched for Ruby 3.x's stricterrb_funcallargc check (rb_cpSpace.clines ~141 and ~449). The upstream gem on rubygems.org will not compile against Ruby 3+. Bundler picks the vendored copy via thegem 'chipmunk', path: 'vendor/chipmunk'line at the top ofGemfile. - opengl/glu/glut 0.10/8.x are split gems now (the old
openglgem bundled all three). All three need GCC 14+ warning-to-error demotions to build; that's wired into.bundle/configviabundle config set build.<gem> --with-cflags=...(see.bundle/config). If you bundle on a fresh checkout, runbin/setupafterbundle installto (re)build the vendored chipmunk too. - logger and resolv-replace were dropped from Ruby's default gems in 3.4/3.5; both are now declared in
gltail.gemspec. bin/gl_tailhadtrap('KILL')(illegal — SIGKILL is untrappable) and arequire <relative path>(broken since Ruby 1.9 but somehow tolerated). Both fixed.vendor/chipmunk/lib/chipmunk.rbpreviously referenced the long-removedConfig::CONFIGconstant; it now usesRbConfig::CONFIG.
System libraries required: GL, GLU, freeglut, plus a working display (X or XWayland). SSH key auth or in-config passwords are needed for remote sources.
Entry point bin/gl_tail parses CLI args, loads lib/gl_tail.rb (which requires every subsystem), then:
GlTail::Config.parse_yaml(file)—lib/gl_tail/config/yaml_parser.rbreads the YAML, building aConfig(lib/gl_tail/config.rb) of servers, parsers, groups, and screen settings via theConfigurablemixin (lib/gl_tail/config/configurable.rb).GlTail::Engine.new(config).start—lib/gl_tail/engine.rbis the main loop. It owns the GLUT window, a Chipmunk physicsspace, the FontStore/BlobStore caches, and orchestrates per-frame rendering of activities/blocks/items.
Data flow per server:
- Sources (
lib/gl_tail/sources/) produce log lines.base.rbis the abstract source;ssh.rbtails remote files via Net::SSH (optionally through a gateway);local.rbtails local files viafile/tail. Each source is associated with one or more parsers. - Parsers (
lib/gl_tail/parsers/*.rb) subclassParser(lib/gl_tail/parser.rb). The base class auto-registers subclasses by strippingParserfrom the class name and downcasing — soclass ApacheParser < Parserregisters as:apache. New-style parsers compose two collaborators (see "Parser pipeline" below); legacy parsers still override#parse(line)directly.lib/gl_tail.rbglobsparsers/*.rbso dropping a new file in that dir auto-loads it. - Activities / Blocks / Items / Elements (
activity.rb,block.rb,item.rb,element.rb) are the visualization primitives parsers emit. The Engine consumes them each frame, runs Chipmunk physics ($PHYSICS = true), and renders blobs/text viaBlobStoreandFontStore(the latter loadslib/gl_tail/font.bin). - Resolver (
resolver.rb) does async DNS lookups for IPs surfaced by parsers.
Originally each parser conflated regex/JSON parsing with the gltail-domain
logic of add_activity / add_event calls. Newer parsers split those:
- Adapter (
lib/gl_tail/adapter.rb,lib/gl_tail/adapters/*.rb):parse(line) { |record| ... }. Turns a raw line into one or more normalized record hashes. Implementations:Adapters::Fluentdwraps anyFluent::Plugin::*Parser(apache2, nginx, json, regexp, syslog, …);Adapters::CaddyJsonflattens Caddy v2's nested JSON into the canonical HTTP-access shape;Adapters::Regexis a simple named-capture regex for legacy formats fluentd doesn't cover. - Mapper (
lib/gl_tail/mapper.rb,lib/gl_tail/mappers/*.rb):emit(record). Turns a normalized record intoadd_activity/add_eventcalls.Mappers::HttpAccesscovers Apache, Nginx, IIS, and Caddy via a single mapper plus per-parser config flags (parsed user-agents, referrer normalization, content-type extension lists, which events to emit). The historical per-parser quirks are intentionally preserved as flags so byte-compat with the old behavior is auditable viatest/golden/.
A new-style parser is a 5-line shell:
class ApacheParser < Parser
use_adapter [:fluentd, :apache2]
use_mapper [:http_access, { parse_useragent: true, users_check_rate: 8, ... }]
endThe base Parser#parse pairs adapter.parse(line) { |record| mapper.emit(record) }. Every shipped parser has been converted; legacy parsers that need to live alongside (custom in-tree subclasses) can still override #parse directly — the base class falls back to the override when no use_adapter/use_mapper is declared.
Each lib/gl_tail/parsers/<name>.rb defines all three collaborators inline (a parser-specific Adapter under GlTail::Adapters::<Name>, a Mapper under GlTail::Mappers::<Name>, and the Parser shell), so per-format logic stays colocated. Shared mappers (just HttpAccess today, covering Apache/Nginx/IIS/Caddy) live in lib/gl_tail/mappers/. The HTTP cluster uses fluentd's stock parsers; everything else uses bespoke per-parser adapters because their log shapes are unique.
Adding a new HTTP-access log format: write an Adapter that yields the canonical record shape (host, method, path, code, size, referer, agent), and a Parser file wiring it to Mappers::HttpAccess with the appropriate flags.
pfsensehad two latent bugs that left it unusable on Ruby 3.0+: asourechosttypo (NameError on every match) and a call to the long-removedDate.day_fraction_to_time. Both are fixed in the newParsers::PFSense, and the 5-minute clog-replay time filter is now opt-in (recent_only: true). The old parser produced zero output on modern Ruby; the new one actually works.
The legacy NginxParser regex captured status before the request string
([date] STATUS "request"), which only matches a custom log_format
somebody had configured upstream. The new Parsers::Nginx uses fluentd's
stock NginxParser, which expects the standard combined format
([date] "request" status). If a user had been pointing gltail at a server
running the old custom format they will now see zero activities — a config
break, not a code break, but worth knowing.
test/golden_check.rb is a tiny golden-record harness: given test/samples/<parser>.txt, it runs lines through Parser.parse against a stub source, captures the sequence of add_activity / add_event calls, and snapshots them as JSON in test/golden/<parser>.json. Use it before any parser refactor:
bundle exec ruby test/golden_check.rb capture <parser> # record golden
bundle exec ruby test/golden_check.rb verify <parser> # diff against golden
bundle exec ruby test/golden_check.rb show <parser> # print captured callsAll 19 shipped parsers have committed goldens (test/samples/<name>.txt + test/golden/<name>.json); the harness gates every parser conversion. The harness sets $VRB = $DBG = 0 so legacy parsers' printf(...) if $VRB > 0 branches don't NoMethodError out of test scope.
config.yaml (top-level) is a working example. dist/config.yaml is the template copied by --new. Both define config: (screen/window/physics), servers: (host + sources + parser), and groups: (visual grouping/coloring).