bin/betterlog in betterlog-0.1.0 vs bin/betterlog in betterlog-0.2.0

- old
+ new

@@ -1,240 +1,241 @@ #!/usr/bin/env ruby # vim: set ft=ruby et sw=2 ts=2: require 'betterlog' -require 'tins/go' -require 'file-tail' require 'complex_config/rude' require 'zlib' +require 'file/tail' -class Betterlog - def initialize(args = ARGV.dup) - STDOUT.sync = true - @args = args - @opts = Tins::GO.go 'cfhp:e:s:S:n:F:', @args, defaults: { ?c => true, ?p => ?d } - filter_severities - @opts[?h] and usage - end +module Betterlog + class App + def initialize(args = ARGV.dup) + STDOUT.sync = true + @args = args + @opts = Tins::GO.go 'cfhp:e:s:S:n:F:', @args, defaults: { ?c => true, ?p => ?d } + filter_severities + @opts[?h] and usage + end - def usage - puts <<~end - Usage: #{prog} [OPTIONS] [LOGFILES] + def usage + puts <<~end + Usage: #{prog} [OPTIONS] [LOGFILES] - Options are + Options are - -c to enable colors during pretty printing - -f to follow the log files - -h to display this help - -p FORMAT to pretty print the log file if possible - -e EMITTER only output events from these emitters - -s MATCH only display events matching this search string - -S SEVERITY only output events with severity, e. g. -S '>=warn' - -n NUMBER rewind this many lines backwards before tailing log file - -F SHORTCUT to open the config files with SHORTCUT + -c to enable colors during pretty printing + -f to follow the log files + -h to display this help + -p FORMAT to pretty print the log file if possible + -e EMITTER only output events from these emitters + -s MATCH only display events matching this search string + -S SEVERITY only output events with severity, e. g. -S '>=warn' + -n NUMBER rewind this many lines backwards before tailing log file + -F SHORTCUT to open the config files with SHORTCUT - FORMAT values are: #{Array(cc.log.formats?&.attribute_names) * ?,} + FORMAT values are: #{Array(cc.log.formats?&.attribute_names) * ?,} - SEVERITY values are: #{Log::Severity.all * ?|} + SEVERITY values are: #{Log::Severity.all * ?|} - Config file SHORTCUTs are: #{Array(cc.log.config_files?&.attribute_names) * ?,} + Config file SHORTCUTs are: #{Array(cc.log.config_files?&.attribute_names) * ?,} - Note, that you can use multiple SHORTCUTs via "-F foo -F bar". + Note, that you can use multiple SHORTCUTs via "-F foo -F bar". - Examples: + Examples: - - Follow rails log in long format with colors for errors or greater: + - Follow rails log in long format with colors for errors or greater: - $ betterlog -f -F rails -p long -c -S ">=error" + $ betterlog -f -F rails -p long -c -S ">=error" - - Follow rails AND redis logs with default format in colors - including the last 10 lines: + - Follow rails AND redis logs with default format in colors + including the last 10 lines: - $ betterlog -f -F rails -F redis -pd -c -n 10 + $ betterlog -f -F rails -F redis -pd -c -n 10 - - Filter stdin from file unicorn.log with default format in color: + - Filter stdin from file unicorn.log with default format in color: - $ betterlog -pd -c <unicorn.log + $ betterlog -pd -c <unicorn.log - - Filter the last 10 lines of file unicorn.log with default format - in color: + - Filter the last 10 lines of file unicorn.log with default format + in color: - $ betterlog -c -pd -n 10 unicorn.log + $ betterlog -c -pd -n 10 unicorn.log - - Filter the last 10 lines of file unicorn.log as JSON events: + - Filter the last 10 lines of file unicorn.log as JSON events: - $ betterlog -n 10 unicorn.log + $ betterlog -n 10 unicorn.log + end + exit(0) end - exit(0) - end - private\ - def filter_severities - @severities = Log::Severity.all - if severity = @opts[?S] - severity.each do |s| - if s =~ /\A(>=?|<=?)(.+)/ - gs = Log::Severity.new($2) - @severities.select! { |x| x.send($1, gs) } - else - gs = Log::Severity.new(s) - @severities.select! { |x| x == gs } + private\ + def filter_severities + @severities = Log::Severity.all + if severity = @opts[?S] + severity.each do |s| + if s =~ /\A(>=?|<=?)(.+)/ + gs = Log::Severity.new($2) + @severities.select! { |x| x.send($1, gs) } + else + gs = Log::Severity.new(s) + @severities.select! { |x| x == gs } + end end end end - end - def prog - File.basename($0) - end + def prog + File.basename($0) + end - def emitters - Array(@opts[?e]) - end - - def search_matched?(event) - case @opts[?s] - when /:\?\z/ - event[$`].present? - when /:([^:]+)\z/ - event[$`].full?(:include?, $1) - when String - event.to_json.include?(@opts[?s]) - else - return true + def emitters + Array(@opts[?e]) end - end - def output_log_event(prefix, event) - return unless @severities.include?(event.severity) - return if emitters.full? && !emitters.include?(event.emitter) - search_matched?(event) or return - if format = @opts[?p] - puts event.format(pretty: :format, color: @opts[?c], format: format) - else - puts "#{prefix}#{event}" + def search_matched?(event) + case @opts[?s] + when /:\?\z/ + event[$`].present? + when /:([^:]+)\z/ + event[$`].full?(:include?, $1) + when String + event.to_json.include?(@opts[?s]) + else + return true + end end - end - def output_log_line(l, filename) - l.blank? and return - prefix = - if filename && @args.size > 1 - "#{filename}: " + def output_log_event(prefix, event) + return unless @severities.include?(event.severity) + return if emitters.full? && !emitters.include?(event.emitter) + search_matched?(event) or return + if format = @opts[?p] + puts event.format(pretty: :format, color: @opts[?c], format: format) + else + puts "#{prefix}#{event}" end - if event = Log::Event.parse(l) - filename and event[:file] = filename - output_log_event(prefix, event) - elsif l =~ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3})\d* (.*)/ - event = Log::Event.new( - timestamp: $1, - message: Term::ANSIColor.uncolor($2), - type: 'isoprefix', - ) - filename and event[:file] = filename - output_log_event(prefix, event) - else - @opts[?e] or puts "#{prefix}#{l}" end - rescue - @opts[?e] or puts "#{prefix}#{l}" - end - def query_config_file_configuration - if @opts[?F] - if cfs = cc.log.config_files? - @opts[?F].each do |f| - @args.concat cfs[f] + def output_log_line(l, filename) + l.blank? and return + prefix = + if filename && @args.size > 1 + "#{filename}: " end + if event = Log::Event.parse(l) + filename and event[:file] = filename + output_log_event(prefix, event) + elsif l =~ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3})\d* (.*)/ + event = Log::Event.new( + timestamp: $1, + message: Term::ANSIColor.uncolor($2), + type: 'isoprefix', + ) + filename and event[:file] = filename + output_log_event(prefix, event) else - fail "no config files for #{@opts[?F]} defined" + @opts[?e] or puts "#{prefix}#{l}" end - else - if @args.empty? and r = cc.log.config_files?&.rails? - @args.concat r - end - if @args.empty? - fail "filenames to follow needed" - end + rescue + @opts[?e] or puts "#{prefix}#{l}" end - @args.uniq! - end - def follow_files - group = File::Tail::Group.new - @args.each do |f| - if File.exist?(f) - group.add_filename f, @opts[?n].to_i + def query_config_file_configuration + if @opts[?F] + if cfs = cc.log.config_files? + @opts[?F].each do |f| + @args.concat cfs[f] + end + else + fail "no config files for #{@opts[?F]} defined" + end else - STDERR.puts "file #{f.inspect} does not exist, skip it!" + if @args.empty? and r = cc.log.config_files?&.rails? + @args.concat r + end + if @args.empty? + fail "filenames to follow needed" + end end + @args.uniq! end - group.each_file { |f| f.max_interval = 1 } - t = Thread.new do - group.tail { |l| output_log_line(l, l.file.path) } - end - t.join - rescue Interrupt - end - def filter_argv - for fn in @args - unless File.exist?(fn) - STDERR.puts "file #{fn.inspect} does not exist, skip it!" - next + def follow_files + group = File::Tail::Group.new + @args.each do |f| + if File.exist?(f) + group.add_filename f, @opts[?n].to_i + else + STDERR.puts "file #{f.inspect} does not exist, skip it!" + end end - if fn.end_with?('.gz') - Zlib::GzipReader.open(fn) do |f| - f.extend(File::Tail) - f.each_line do |l| - output_log_line(l, fn) - end + group.each_file { |f| f.max_interval = 1 } + t = Thread.new do + group.tail { |l| output_log_line(l, l.file.path) } + end + t.join + rescue Interrupt + end + + def filter_argv + for fn in @args + unless File.exist?(fn) + STDERR.puts "file #{fn.inspect} does not exist, skip it!" + next end - else - File::Tail::Logfile.open(fn, backward: @opts[?n].to_i) do |f| - f.each_line do |l| - output_log_line(l, fn) + if fn.end_with?('.gz') + Zlib::GzipReader.open(fn) do |f| + f.extend(File::Tail) + f.each_line do |l| + output_log_line(l, fn) + end end + else + File::Tail::Logfile.open(fn, backward: @opts[?n].to_i) do |f| + f.each_line do |l| + output_log_line(l, fn) + end + end end end end - end - def filter_stdin - STDIN.each_line do |l| - output_log_line(l, nil) + def filter_stdin + STDIN.each_line do |l| + output_log_line(l, nil) + end end - end - def output_log_sources - if @args.empty? - STDERR.puts "#{prog} tracking stdin\nseverities: #{@severities * ?|}" - else - STDERR.puts "#{prog} tracking files:\n"\ - "#{@args.map { |a| ' ' + a.inspect }.join(' ')}\n"\ - "severities: #{@severities * ?|}\n" + def output_log_sources + if @args.empty? + STDERR.puts "#{prog} tracking stdin\nseverities: #{@severities * ?|}" + else + STDERR.puts "#{prog} tracking files:\n"\ + "#{@args.map { |a| ' ' + a.inspect }.join(' ')}\n"\ + "severities: #{@severities * ?|}\n" + end end - end - def run - if @opts[?f] - query_config_file_configuration - output_log_sources - follow_files - elsif @opts[?F] && @args.empty? - query_config_file_configuration - output_log_sources - filter_argv - elsif !@args.empty? - output_log_sources - filter_argv - else - output_log_sources - filter_stdin + def run + if @opts[?f] + query_config_file_configuration + output_log_sources + follow_files + elsif @opts[?F] && @args.empty? + query_config_file_configuration + output_log_sources + filter_argv + elsif !@args.empty? + output_log_sources + filter_argv + else + output_log_sources + filter_stdin + end end end end if File.basename($0) == File.basename(__FILE__) - Betterlog.new(ARGV).run + Betterlog::App.new(ARGV).run end