log.rb 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. # lib/log.rb
  2. # Minimal structured logger for the scraping pipeline.
  3. #
  4. # Usage:
  5. # require_relative "log"
  6. # Log.info "geocode", "geocoded DA0306/2025 -> 42 Main St, Hobart"
  7. # Log.warn "enrich", "lookup failed for da_foo DA123: connection refused"
  8. # Log.debug "migrate", "column address_std already exists — skipped"
  9. # Log.error "db", "prepare failed: unknown column 'foo'"
  10. #
  11. # Output format (no timestamp — Docker/systemd adds one):
  12. # INFO [geocode] geocoded DA0306/2025 -> 42 Main St, Hobart
  13. # WARN [enrich] lookup failed for da_foo DA123: connection refused
  14. #
  15. # Verbosity is controlled by the LOG_LEVEL environment variable:
  16. # LOG_LEVEL=debug — all messages
  17. # LOG_LEVEL=info — info, warn, error (default)
  18. # LOG_LEVEL=warn — warn and error only
  19. # LOG_LEVEL=error — errors only
  20. #
  21. # INFO and DEBUG go to $stdout; WARN and ERROR go to $stderr so that
  22. # docker compose logs shows them on the correct stream.
  23. module Log
  24. LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
  25. # Flush immediately — important in Docker where stdout may be block-buffered.
  26. $stdout.sync = true
  27. $stderr.sync = true
  28. def self.debug(component, msg) = emit(:debug, component, msg)
  29. def self.info(component, msg) = emit(:info, component, msg)
  30. def self.warn(component, msg) = emit(:warn, component, msg)
  31. def self.error(component, msg) = emit(:error, component, msg)
  32. # ---------------------------------------------------------------------------
  33. def self.min_level
  34. key = ENV.fetch("LOG_LEVEL", "info").strip.downcase.to_sym
  35. LEVELS.fetch(key, LEVELS[:info])
  36. end
  37. private_class_method :min_level
  38. def self.emit(level, component, msg)
  39. return if LEVELS.fetch(level) < min_level
  40. label = level.to_s.upcase.ljust(5)
  41. stream = (level == :debug || level == :info) ? $stdout : $stderr
  42. stream.puts "#{label} [#{component}] #{msg}"
  43. end
  44. private_class_method :emit
  45. end