Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 47 additions & 17 deletions exe/log_bench
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "optparse"
require_relative "../lib/log_bench"

def show_help
puts <<~HELP
LogBench - Rails Log Viewer

Usage:
log_bench [log_file]
log_bench [options] [log_file]
log_bench --help
log_bench --version

Options:
--logger_type=TYPE Logger type: lograge (default) or logstruct
-h, --help Show this help message
-v, --version Show version

Examples:
log_bench # View log/development.log
log_bench log/production.log # View specific log file
log_bench /path/to/app/log/test.log # View log from another app
log_bench # View log/development.log
log_bench log/production.log # View specific log file
log_bench --logger_type=logstruct # Use LogStruct format
log_bench log/production.log --logger_type=logstruct

Setup (for Rails apps):
LogBench is automatically enabled in development!
Expand All @@ -32,18 +39,41 @@ def show_version
puts "LogBench #{LogBench::VERSION}"
end

# Handle command line arguments
case ARGV[0]
when "--help", "-h"
show_help
exit 0
when "--version", "-v"
show_version
exit 0
def parse_options
options = {logger_type: :lograge}

# Separate options from positional arguments
args = ARGV.dup
positional_args = []

while args.any?
arg = args.shift
case arg
when "--help", "-h"
show_help
exit 0
when "--version", "-v"
show_version
exit 0
when /\A--logger_type=(.+)\z/
options[:logger_type] = ::Regexp.last_match(1).to_sym
when "--logger_type"
options[:logger_type] = args.shift&.to_sym
else
positional_args << arg
end
end

log_file = positional_args.first || "log/development.log"

[log_file, options]
end

begin
log_file = ARGV[0] || "log/development.log"
log_file, options = parse_options

# Set logger type for CLI usage (this is used by the TUI, not Rails config)
LogBench.cli_logger_type = options[:logger_type]

# Check if log file exists
unless File.exist?(log_file)
Expand All @@ -52,7 +82,7 @@ begin
puts "Make sure:"
puts " 1. You're in a Rails application directory"
puts " 2. The log file exists and has content"
puts " 3. Lograge is configured (see README.md for setup)"
puts " 3. Your logger is configured (lograge or logstruct)"
puts
puts "For help: log_bench --help"
exit 1
Expand All @@ -61,16 +91,16 @@ begin
LogBench::App::Main.new(log_file).run
rescue Interrupt
Curses.close_screen if defined?(Curses)
puts "\nGoodbye! 👋"
puts "\nGoodbye!"
exit 0
rescue => e
Curses.close_screen if defined?(Curses)
puts "❌ Error: #{e.message} \n#{e.backtrace.join("\n")}"
puts
puts "Common issues:"
puts " - Log file doesn't exist or is empty"
puts " - Lograge not configured (see README.md for setup)"
puts " - Log format not supported (LogBench requires lograge JSON format)"
puts " - Logger not configured (see README.md for setup)"
puts " - Log format not supported (LogBench requires lograge or logstruct JSON format)"
puts
puts "For help: log_bench --help"
exit 1
Expand Down
19 changes: 18 additions & 1 deletion lib/log_bench.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,35 @@ module LogBench
class Error < StandardError; end

class << self
attr_accessor :configuration
attr_accessor :configuration, :cli_logger_type

def setup
self.configuration ||= Configuration.new
yield(configuration) if block_given?
# Auto-detect LogStruct if enabled
if defined?(LogStruct) && LogStruct.respond_to?(:enabled?) && LogStruct.enabled?
configuration.logger_type = :logstruct
end
configuration
end

def logger
@logger ||= create_debug_logger
end

# Returns the effective logger type (CLI flag takes precedence over config)
def logger_type
cli_logger_type || configuration&.logger_type || :lograge
end

def logstruct?
logger_type == :logstruct
end

def lograge?
logger_type == :lograge
end

private

def create_debug_logger
Expand Down
30 changes: 27 additions & 3 deletions lib/log_bench/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,37 @@

module LogBench
class Configuration
attr_accessor :show_init_message, :enabled, :base_controller_classes, :configure_lograge_automatically
VALID_LOGGER_TYPES = %i[lograge logstruct].freeze

attr_accessor :show_init_message, :enabled, :base_controller_classes, :configure_logger_automatically
attr_reader :logger_type

def initialize
@show_init_message = :full
@enabled = Rails.env.development?
@enabled = (defined?(Rails) && Rails.respond_to?(:env)) ? Rails.env.development? : false
@base_controller_classes = %w[ApplicationController ActionController::Base]
@configure_lograge_automatically = true # Configure lograge by default
@configure_logger_automatically = true # Configure logger by default
@logger_type = :lograge # Default to lograge for backward compatibility
end

# Backward compatibility alias
alias_method :configure_lograge_automatically, :configure_logger_automatically
alias_method :configure_lograge_automatically=, :configure_logger_automatically=

def logger_type=(value)
value = value.to_sym if value.is_a?(String)
unless VALID_LOGGER_TYPES.include?(value)
raise ArgumentError, "Invalid logger_type: #{value}. Valid options: #{VALID_LOGGER_TYPES.join(", ")}"
end
@logger_type = value
end

def logstruct?
@logger_type == :logstruct
end

def lograge?
@logger_type == :lograge
end
end
end
76 changes: 66 additions & 10 deletions lib/log_bench/configuration_validator.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# frozen_string_literal: true

module LogBench
# Validates that lograge is properly configured for LogBench
# Validates that the logger is properly configured for LogBench
class ConfigurationValidator
class ConfigurationError < StandardError; end
ERROR_CONFIGS = {

LOGRAGE_ERROR_CONFIGS = {
enabled: {
title: "Lograge is not enabled",
description: "LogBench requires lograge to be enabled in your Rails application."
Expand All @@ -23,6 +24,17 @@ class ConfigurationError < StandardError; end
}
}.freeze

LOGSTRUCT_ERROR_CONFIGS = {
not_installed: {
title: "LogStruct gem is not installed",
description: "LogBench with logger_type: :logstruct requires the logstruct gem."
},
not_enabled: {
title: "LogStruct is not enabled",
description: "LogStruct must be enabled for LogBench to work."
}
}.freeze

def self.validate_rails_config!
new.validate_rails_config!
end
Expand All @@ -31,32 +43,60 @@ def validate_rails_config!
return true unless defined?(Rails) && Rails.application
return true unless LogBench.configuration.enabled

if LogBench.configuration.logstruct?
validate_logstruct_config!
else
validate_lograge_config!
end

true
end

private

def validate_logstruct_config!
validate_logstruct_installed!
validate_logstruct_enabled!
end

def validate_lograge_config!
validate_lograge_enabled!
validate_custom_options!
validate_lograge_formatter!
validate_logger_formatter!
end

true
def validate_logstruct_installed!
unless defined?(LogStruct)
raise ConfigurationError, build_logstruct_error_message(:not_installed)
end
end

private
def validate_logstruct_enabled!
return if LogStruct.config.enabled

raise ConfigurationError, build_logstruct_error_message(:not_enabled)
rescue NoMethodError
# LogStruct might not have config method in older versions
nil
end

def validate_lograge_enabled!
unless lograge_config&.enabled
raise ConfigurationError, build_error_message(:enabled)
raise ConfigurationError, build_lograge_error_message(:enabled)
end
end

def validate_custom_options!
unless lograge_config&.custom_options
raise ConfigurationError, build_error_message(:options)
raise ConfigurationError, build_lograge_error_message(:options)
end
end

def validate_lograge_formatter!
formatter = lograge_config&.formatter
unless formatter.is_a?(Lograge::Formatters::Json)
raise ConfigurationError, build_error_message(:lograge_formatter)
raise ConfigurationError, build_lograge_error_message(:lograge_formatter)
end
end

Expand All @@ -67,7 +107,7 @@ def validate_logger_formatter!
# Allow LogBench::JsonFormatter or any custom JSON formatter
# Users might have their own JSON formatters that work with LogBench
unless formatter.is_a?(LogBench::JsonFormatter)
raise ConfigurationError, build_error_message(:logger_formatter)
raise ConfigurationError, build_lograge_error_message(:logger_formatter)
end
end

Expand All @@ -76,8 +116,8 @@ def lograge_config
Rails.application.config.lograge
end

def build_error_message(error_type)
config = ERROR_CONFIGS[error_type]
def build_lograge_error_message(error_type)
config = LOGRAGE_ERROR_CONFIGS[error_type]

<<~ERROR
❌ #{config[:title]}
Expand All @@ -89,5 +129,21 @@ def build_error_message(error_type)
For complete setup: https://github.com/silva96/log_bench#configuration
ERROR
end

def build_logstruct_error_message(error_type)
config = LOGSTRUCT_ERROR_CONFIGS[error_type]

<<~ERROR
❌ #{config[:title]}

#{config[:description]}

Make sure you have:
1. Added 'logstruct' to your Gemfile
2. LogStruct is enabled in your environment

For complete setup: https://github.com/DocSpring/logstruct
ERROR
end
end
end
17 changes: 14 additions & 3 deletions lib/log_bench/log/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class Entry

def initialize(json_data)
self.json_data = json_data
self.timestamp = parse_timestamp(json_data["timestamp"])
self.request_id = json_data["request_id"]
self.content = Parser.normalize_message(json_data["message"])
self.timestamp = parse_timestamp(extract_timestamp(json_data))
# LogStruct uses "req_id", lograge/standard uses "request_id"
self.request_id = json_data["req_id"] || json_data["request_id"]
self.content = Parser.normalize_message(extract_message(json_data))
self.type = :other
end

Expand All @@ -32,6 +33,16 @@ def parse_timestamp(timestamp_str)
rescue ArgumentError
Time.now
end

def extract_timestamp(json_data)
# LogStruct uses "ts", lograge/standard uses "timestamp"
json_data["ts"] || json_data["timestamp"]
end

def extract_message(json_data)
# LogStruct uses "msg", lograge/standard uses "message"
json_data["msg"] || json_data["message"]
end
end
end
end
20 changes: 19 additions & 1 deletion lib/log_bench/log/job_enqueue_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,32 @@ def initialize(json_data)
super
self.type = :job_enqueue
self.job_id = extract_job_id
build_content_for_logstruct if logstruct_format?
end

private

attr_writer :job_id

def extract_job_id
Parser.extract_job_id_from_enqueue(content)
# LogStruct has job_id as a direct field
json_data["job_id"] || Parser.extract_job_id_from_enqueue(content)
end

def logstruct_format?
json_data["evt"] == "schedule" && json_data["src"] == "job"
end

def build_content_for_logstruct
job_class = json_data["job_class"] || "Job"
queue = json_data["queue_name"]
scheduled_at = json_data["scheduled_at"]

parts = ["Enqueued #{job_class} (Job ID: #{job_id})"]
parts << "to #{queue}" if queue
parts << "at #{scheduled_at}" if scheduled_at

@content = parts.join(" ")
end
end
end
Expand Down
Loading