log2mail 0.0.1.pre2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/INSTALL +51 -0
- data/LICENSE.txt +22 -0
- data/README.md +123 -0
- data/Rakefile +20 -0
- data/bin/log2mail.rb +4 -0
- data/features/configtest-mode.feature +22 -0
- data/features/log2mail_configurations/config_1 +7 -0
- data/features/step_definitions/log2mail_steps.rb +41 -0
- data/lib/ext/kernel.rb +13 -0
- data/lib/ext/main.rb +24 -0
- data/lib/ext/string.rb +23 -0
- data/lib/ext/syslog_logger.rb +208 -0
- data/lib/log2mail/config.rb +216 -0
- data/lib/log2mail/console/commands.rb +56 -0
- data/lib/log2mail/console/logger.rb +23 -0
- data/lib/log2mail/console.rb +35 -0
- data/lib/log2mail/error.rb +5 -0
- data/lib/log2mail/file/parser.rb +49 -0
- data/lib/log2mail/file.rb +95 -0
- data/lib/log2mail/hit.rb +13 -0
- data/lib/log2mail/logger_formatter.rb +30 -0
- data/lib/log2mail/main.rb +148 -0
- data/lib/log2mail/report.rb +92 -0
- data/lib/log2mail/version.rb +6 -0
- data/lib/log2mail/watcher.rb +61 -0
- data/lib/log2mail.rb +8 -0
- data/log2mail.gemspec +35 -0
- data/man/log2mail.1 +121 -0
- data/man/log2mail.1.html +207 -0
- data/man/log2mail.1.ronn +108 -0
- data/spec/factories.rb +101 -0
- data/spec/log2mail/config_spec.rb +103 -0
- data/spec/log2mail/file/parser_spec.rb +61 -0
- data/spec/log2mail/report_spec.rb +63 -0
- data/spec/log2mail/watcher_spec.rb +93 -0
- data/spec/spec_helper.rb +112 -0
- metadata +260 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
module Log2mail
|
2
|
+
class Config
|
3
|
+
|
4
|
+
class <<self
|
5
|
+
def parse_config(config_path)
|
6
|
+
Log2mail::Config.new(config_path)
|
7
|
+
# pp config.config
|
8
|
+
# config.files.each do |f|
|
9
|
+
# puts "File: #{f}"
|
10
|
+
# config.patterns_for_file(f).each do |pattern|
|
11
|
+
# puts " Pattern: #{pattern}; mailto: " + config.mailtos_for_pattern( f, pattern ).join(', ')
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# attr_reader :config
|
18
|
+
|
19
|
+
INT_OPTIONS = [:sendtime, :resendtime, :maxlines]
|
20
|
+
STR_OPTIONS = [:fromaddr, :sendmail]
|
21
|
+
PATH_OPTIONS = [:template]
|
22
|
+
|
23
|
+
def initialize(config_paths)
|
24
|
+
$logger.debug "Reading configuration from #{config_paths}"
|
25
|
+
@config_paths = Array(config_paths)
|
26
|
+
expand_paths
|
27
|
+
read_configuration
|
28
|
+
validate_configuration
|
29
|
+
end
|
30
|
+
|
31
|
+
# returns all the paths of all files needed to be watched
|
32
|
+
def files
|
33
|
+
@config.keys - [:defaults]
|
34
|
+
end
|
35
|
+
|
36
|
+
def file_patterns
|
37
|
+
h = {}
|
38
|
+
files.each do |file|
|
39
|
+
h[file] = patterns_for_file(file)
|
40
|
+
end
|
41
|
+
h
|
42
|
+
end
|
43
|
+
|
44
|
+
def patterns_for_file( file )
|
45
|
+
Hash(@config[file][:patterns]).keys + \
|
46
|
+
Hash(defaults[:patterns]).keys
|
47
|
+
end
|
48
|
+
|
49
|
+
def mailtos_for_pattern( file, pattern )
|
50
|
+
m = []
|
51
|
+
m.concat Hash( config_file_pattern(file, pattern)[:mailtos] ).keys
|
52
|
+
m.concat Hash(Hash(Hash(defaults[:patterns])[pattern])[:mailtos]).keys
|
53
|
+
m.concat Array(defaults[:mailtos]) if m.empty?
|
54
|
+
m.uniq
|
55
|
+
end
|
56
|
+
|
57
|
+
def settings_for_mailto( file, pattern, mailto )
|
58
|
+
h = defaults.reject {|k,v| k==:mailtos}
|
59
|
+
h.merge config_file_mailto(file, pattern, mailto)
|
60
|
+
end
|
61
|
+
|
62
|
+
def defaults
|
63
|
+
Hash(@config[:defaults])
|
64
|
+
end
|
65
|
+
|
66
|
+
def formatted( show_effective )
|
67
|
+
Terminal::Table.new do |t|
|
68
|
+
settings_header = show_effective ? 'Effective Settings' : 'Settings'
|
69
|
+
t << ['File', 'Pattern', 'Recipient', settings_header]
|
70
|
+
t << :separator
|
71
|
+
files.each do |file|
|
72
|
+
patterns_for_file(file).each do |pattern|
|
73
|
+
mailtos_for_pattern(file, pattern).each do |mailto|
|
74
|
+
settings = []
|
75
|
+
if show_effective
|
76
|
+
settings_for_mailto(file, pattern, mailto).each_pair \
|
77
|
+
{ |k,v| settings << '%s=%s' % [k,v] }
|
78
|
+
else
|
79
|
+
config_file_mailto(file, pattern, mailto).each_pair \
|
80
|
+
{ |k,v| settings << '%s=%s' % [k,v] }
|
81
|
+
end
|
82
|
+
t.add_row [file, pattern, mailto, settings.join($/)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def config_file(file)
|
92
|
+
Hash(@config[file])
|
93
|
+
end
|
94
|
+
|
95
|
+
def config_file_pattern(file, pattern)
|
96
|
+
Hash( Hash( config_file(file)[:patterns] )[pattern] )
|
97
|
+
end
|
98
|
+
|
99
|
+
def config_file_mailtos(file, pattern)
|
100
|
+
Hash( config_file_pattern(file, pattern)[:mailtos] )
|
101
|
+
end
|
102
|
+
|
103
|
+
def config_file_mailto(file, pattern, mailto)
|
104
|
+
Hash( config_file_mailtos(file, pattern)[mailto] )
|
105
|
+
end
|
106
|
+
|
107
|
+
def expand_paths
|
108
|
+
expanded_paths = []
|
109
|
+
@config_paths.each do |path|
|
110
|
+
if ::File.directory?(path)
|
111
|
+
expanded_paths.concat Dir.glob( ::File.join( path, '*[^~#]' ) )
|
112
|
+
else
|
113
|
+
expanded_paths << path
|
114
|
+
end
|
115
|
+
end
|
116
|
+
@config_paths = expanded_paths
|
117
|
+
end
|
118
|
+
|
119
|
+
# tries to follow original code at https://github.com/lordlamer/log2mail/blob/master/config.cc#L192
|
120
|
+
def read_configuration
|
121
|
+
@config = {}
|
122
|
+
@config_paths.each do |file|
|
123
|
+
@section = nil; @pattern = nil; @mailto = nil
|
124
|
+
# section, pattern, mailto are reset for every file (but not when included by 'include')
|
125
|
+
parse_file( file )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def parse_file( filename )
|
130
|
+
IO.readlines(filename).each_with_index do |line, lineno|
|
131
|
+
parse(filename, line, lineno + 1)
|
132
|
+
end
|
133
|
+
rescue Errno::ENOENT
|
134
|
+
fail Error, "Configuration file or directory not found (or not readable): #{filename}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse(file, line, lineno)
|
138
|
+
line.strip!
|
139
|
+
return if line =~ /^#/
|
140
|
+
return if line =~ /^$/
|
141
|
+
line =~ /^(\S+)\s*=?\s*"?(.*?)"?(\s*#.*)?$/ # drop double quotes on right hand side; drop comments
|
142
|
+
key, value = $1.to_sym, $2.strip
|
143
|
+
if key == :include # include shall work everywhere
|
144
|
+
parse_file( ::File.join(Pathname(file).parent, value) )
|
145
|
+
return
|
146
|
+
end
|
147
|
+
if key == :defaults and value.empty? # section: specifies top level; must be 'defaults' or 'file'
|
148
|
+
@section = key
|
149
|
+
@pattern = nil; @mailto = nil
|
150
|
+
fail Error, "Invalid section. Section 'defaults' already specified." if @config[@section]
|
151
|
+
@config[@section] = {}
|
152
|
+
elsif key == :file
|
153
|
+
@section = value
|
154
|
+
@pattern = nil; @mailto = nil
|
155
|
+
@config[@section] ||= {}
|
156
|
+
elsif key == :pattern # must come inside 'file' (or 'defaults')
|
157
|
+
# fail "Invalid section. All statements must appear after 'defaults' or 'file=...'" unless @section
|
158
|
+
@pattern = value; @mailto = nil
|
159
|
+
@config[@section][:patterns] ||= {}
|
160
|
+
warning { "Redefining pattern section '#{value}' which has been defined already for '#{@section}'." } \
|
161
|
+
if @config[@section][:patterns][value]
|
162
|
+
@config[@section][:patterns][value] = {}
|
163
|
+
elsif key == :mailto and @section != :defaults # must come inside 'pattern' (or 'defaults')
|
164
|
+
fail Error, "'mailto' statements only allowed inside 'pattern' or 'defaults'." unless @pattern
|
165
|
+
@mailto = value
|
166
|
+
@config[@section][:patterns][@pattern][:mailtos] ||= {}
|
167
|
+
warning { "Redefining mailto section '#{value}' which has been defined already for '#{@section}'." } \
|
168
|
+
if @config[@section][:patterns][@pattern][:mailtos][value]
|
169
|
+
@config[@section][:patterns][@pattern][:mailtos][value] = {}
|
170
|
+
else # everything else must come inside 'defaults' or 'mailto'
|
171
|
+
fail Error, "'#{key}' must be set within 'defaults' or 'mailto'." unless @section == :defaults or @mailto
|
172
|
+
if INT_OPTIONS.include?(key)
|
173
|
+
value = value.to_i
|
174
|
+
elsif STR_OPTIONS.include?(key)
|
175
|
+
elsif PATH_OPTIONS.include?(key)
|
176
|
+
value = ::File.expand_path( value, Pathname(file).parent ) unless Pathname(value).absolute?
|
177
|
+
elsif key == :mailto # special handling for 'mailto' in 'defaults' section
|
178
|
+
@config[:defaults][:mailtos] ||= []
|
179
|
+
@config[:defaults][:mailtos] << value
|
180
|
+
return # skip the 'mailto' entry itself
|
181
|
+
else
|
182
|
+
fail Error, "'#{key}' is an unknown configuration statement."
|
183
|
+
end
|
184
|
+
if @section == :defaults and !@pattern and !@mailto
|
185
|
+
warning { "Redefining value for '#{key}'." } if @config[@section][key]
|
186
|
+
@config[@section][key] = value
|
187
|
+
else
|
188
|
+
warning { "Redefining value for '#{key}'." } \
|
189
|
+
if @config[@section][:patterns][@pattern][:mailtos][@mailto][key]
|
190
|
+
@config[@section][:patterns][@pattern][:mailtos][@mailto][key] = value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
rescue
|
194
|
+
fail Error, "#{file} (line #{lineno}): #{$!.message}#$/[#{$!.class} at #{$!.backtrace.first}]"
|
195
|
+
end
|
196
|
+
|
197
|
+
def validate_configuration
|
198
|
+
files.each do |file|
|
199
|
+
patterns_for_file(file).each do |pattern|
|
200
|
+
mailtos = mailtos_for_pattern(file, pattern)
|
201
|
+
$logger.warn "Pattern #{file}:#{pattern} has no recipients." if mailtos.empty?
|
202
|
+
end
|
203
|
+
end
|
204
|
+
# FIXME: empty configuration should cause FATAL error
|
205
|
+
# TODO: illegal regexp pattern should cause ERROR
|
206
|
+
end
|
207
|
+
|
208
|
+
def warning(&block)
|
209
|
+
file = block.binding.eval('file')
|
210
|
+
lineno = block.binding.eval('lineno')
|
211
|
+
message = block.call
|
212
|
+
$logger.warn "#{file} (line #{lineno}): #{message}#$/[at #{caller.first}]"
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Log2mail
|
2
|
+
|
3
|
+
module Console::Commands
|
4
|
+
|
5
|
+
class <<self
|
6
|
+
|
7
|
+
def desc(description)
|
8
|
+
@desc = description
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_added(meth)
|
12
|
+
@@meths ||= []
|
13
|
+
meth = meth.to_s
|
14
|
+
@@meths << [meth.gsub('_', ' '), @desc] if @desc
|
15
|
+
# puts "Added method #{meth}. Desc: #{@desc.inspect}."
|
16
|
+
# puts @@meths.inspect
|
17
|
+
# meth
|
18
|
+
# super
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def commands
|
26
|
+
@@meths.map { |m| m.first.gsub(' ', '_') }
|
27
|
+
end
|
28
|
+
|
29
|
+
public
|
30
|
+
|
31
|
+
desc "show list of available commands"
|
32
|
+
def help
|
33
|
+
@command_table ||= Terminal::Table.new :headings => ['command','description'], :rows => @@meths.sort
|
34
|
+
puts @command_table
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "end console session"
|
39
|
+
def quit; end
|
40
|
+
def exit; end
|
41
|
+
|
42
|
+
desc "show configuration"
|
43
|
+
def config
|
44
|
+
config_path = ask('configuration path? ').chomp
|
45
|
+
config = Log2mail::Config.parse_config config_path
|
46
|
+
puts "Defaults:"
|
47
|
+
puts config.defaults
|
48
|
+
puts "Settings:"
|
49
|
+
puts config.formatted(false)
|
50
|
+
puts "Effective settings:"
|
51
|
+
puts config.formatted(true)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Log2mail
|
2
|
+
|
3
|
+
module Console::Logger
|
4
|
+
|
5
|
+
def info(msg)
|
6
|
+
puts msg
|
7
|
+
end
|
8
|
+
|
9
|
+
def fatal(msg)
|
10
|
+
puts "FATAL: " + msg
|
11
|
+
end
|
12
|
+
|
13
|
+
def warn(msg)
|
14
|
+
puts "WARNING: " + msg
|
15
|
+
end
|
16
|
+
|
17
|
+
def debug(msg)
|
18
|
+
puts "DEBUG: " + msg
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Log2mail::Console
|
2
|
+
|
3
|
+
require_relative 'console/logger'
|
4
|
+
require_relative 'console/commands'
|
5
|
+
include Log2mail::Console::Commands
|
6
|
+
require 'highline/import'
|
7
|
+
|
8
|
+
def run
|
9
|
+
|
10
|
+
# PFUSCH!!!
|
11
|
+
# Log2mail::Config.extend Log2mail::Console::Logger
|
12
|
+
# Log2mail::Config.include Log2mail::Console::Logger
|
13
|
+
|
14
|
+
loop do
|
15
|
+
input = ask('log2mail.rb % ').chomp
|
16
|
+
# command, *params = input.split /\s/
|
17
|
+
command = input
|
18
|
+
next if command.empty?
|
19
|
+
command.gsub!(' ', '_')
|
20
|
+
(quit; return) if ['quit', 'exit'].include?(command)
|
21
|
+
if self.commands.include?(command)
|
22
|
+
send(command)
|
23
|
+
else
|
24
|
+
puts "Unknown command. Use 'help' for more information."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue EOFError
|
28
|
+
quit; return
|
29
|
+
end
|
30
|
+
|
31
|
+
def quit
|
32
|
+
puts "quitting..."
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Log2mail
|
2
|
+
|
3
|
+
module File::Parser
|
4
|
+
|
5
|
+
def parse(multiline_text)
|
6
|
+
return [] unless multiline_text
|
7
|
+
empty_buf! unless @buf
|
8
|
+
# add new text to parse buffer
|
9
|
+
@buf = @buf << multiline_text
|
10
|
+
hits = []
|
11
|
+
@patterns.each do |pattern|
|
12
|
+
matches = []
|
13
|
+
if pattern.from_string?
|
14
|
+
matches.concat @buf.lines.find_all{ |line| line.match(pattern) }
|
15
|
+
matches.map!(&:chomp)
|
16
|
+
else
|
17
|
+
matches.concat @buf.gsub(pattern).collect.to_a
|
18
|
+
end
|
19
|
+
hits.concat matches.map { |match| Hit.new( match, pattern, @path ) }
|
20
|
+
end
|
21
|
+
unless hits.empty?
|
22
|
+
log 'pattern match: ' + hits.inspect
|
23
|
+
empty_buf! # match -> clear buffer
|
24
|
+
else
|
25
|
+
cleanup_buf! # no match -> just keep what's necessary
|
26
|
+
end
|
27
|
+
hits
|
28
|
+
end
|
29
|
+
|
30
|
+
# ---------------------
|
31
|
+
# - Buffer management -
|
32
|
+
# ---------------------
|
33
|
+
|
34
|
+
def empty_buf!
|
35
|
+
@buf = ''
|
36
|
+
end
|
37
|
+
|
38
|
+
def cleanup_buf!
|
39
|
+
# FIXME: cleanup should clean up to latest match only OR upto last line (if from_string true)
|
40
|
+
if @patterns.all? {|p| p.from_string? }
|
41
|
+
empty_buf!
|
42
|
+
else
|
43
|
+
@buf = @buf.byteslice(-@maxbufsize/32,@maxbufsize) if @buf.bytesize > @maxbufsize
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
module Log2mail
|
3
|
+
|
4
|
+
class File
|
5
|
+
|
6
|
+
require_relative 'file/parser'
|
7
|
+
include Parser
|
8
|
+
|
9
|
+
|
10
|
+
# FIXME: redundant
|
11
|
+
def log(msg, sev = ::Logger::DEBUG)
|
12
|
+
$logger.log sev, '%s: %s%s [%s]' % [@path, msg, $/, caller.first]
|
13
|
+
end
|
14
|
+
|
15
|
+
def warn(msg)
|
16
|
+
log(msg, ::Logger::WARN)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :path, :patterns
|
20
|
+
|
21
|
+
def initialize( path, patterns, maxbufsize = 65536 )
|
22
|
+
@path = path
|
23
|
+
self.patterns = patterns
|
24
|
+
@maxbufsize = maxbufsize
|
25
|
+
log "Maximum buffer size: #{@maxbufsize}"
|
26
|
+
log "Patterns: #{patterns.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def patterns=(patterns)
|
30
|
+
@patterns = patterns.map(&:to_r)
|
31
|
+
end
|
32
|
+
|
33
|
+
# ----------------------------
|
34
|
+
# - File management (public) -
|
35
|
+
# ----------------------------
|
36
|
+
|
37
|
+
def open
|
38
|
+
@f = ::File.open(@path, 'r', :encoding => "BINARY")
|
39
|
+
log "file opened"
|
40
|
+
@ino = @f.stat.ino
|
41
|
+
@size = 0
|
42
|
+
@f
|
43
|
+
rescue Errno::ENOENT
|
44
|
+
warn "does not exist"
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def seek_to_end
|
49
|
+
@f.seek(0, IO::SEEK_END)
|
50
|
+
@size = @f.stat.size
|
51
|
+
@f
|
52
|
+
end
|
53
|
+
|
54
|
+
def read_to_end
|
55
|
+
return unless @f
|
56
|
+
s = @f.gets(nil)
|
57
|
+
@size += s.length
|
58
|
+
s
|
59
|
+
end
|
60
|
+
|
61
|
+
def eof?
|
62
|
+
!@f or @f.eof?
|
63
|
+
end
|
64
|
+
|
65
|
+
def rotated?
|
66
|
+
if inum_changed?
|
67
|
+
log "inode number changed"
|
68
|
+
true
|
69
|
+
elsif file_size_changed?
|
70
|
+
log "file size changed; probably truncated"
|
71
|
+
true
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# -------------------
|
80
|
+
# - File management -
|
81
|
+
# -------------------
|
82
|
+
|
83
|
+
def inum_changed?
|
84
|
+
::File.stat(@path).ino != @ino
|
85
|
+
rescue Errno::ENOENT
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def file_size_changed?
|
90
|
+
@f.stat.size != @size
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
data/lib/log2mail/hit.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# provides a formatter to be used on TTY
|
2
|
+
module Log2mail::LoggerFormatter
|
3
|
+
|
4
|
+
class <<self
|
5
|
+
|
6
|
+
LEVELS = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL", "UNKNOWN"]
|
7
|
+
|
8
|
+
# http://blog.bigbinary.com/2014/03/03/logger-formatting-in-rails.html
|
9
|
+
def msg2str(msg)
|
10
|
+
case msg
|
11
|
+
when ::String
|
12
|
+
msg
|
13
|
+
when ::Exception
|
14
|
+
"#{ msg.message } (#{ msg.class })\n [" <<
|
15
|
+
( $verbose ? \
|
16
|
+
(msg.backtrace || []).join("#$/ ") : \
|
17
|
+
(msg.backtrace || []).first
|
18
|
+
) << ']'
|
19
|
+
else
|
20
|
+
msg.inspect
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(severity, datetime, progname, msg)
|
25
|
+
sev = severity.instance_of?(Fixnum) ? LEVELS[severity] : severity
|
26
|
+
'%s: %s' % [sev, msg2str(msg)] + $/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
Main {
|
2
|
+
|
3
|
+
CONFIG_DEFAULT = '/etc/log2mail/config'
|
4
|
+
PID_DEFAULT = '/var/run/log2mail.rb.pid'
|
5
|
+
LOG_DEFAULT = '/var/log/log2mail.rb'
|
6
|
+
|
7
|
+
version Log2mail::VERSION
|
8
|
+
|
9
|
+
### daemonizes! DOES NOT WORK CORRECTLY => implementing own forking
|
10
|
+
# daemonizes!
|
11
|
+
|
12
|
+
$logger = logger
|
13
|
+
logger_level Logger::INFO
|
14
|
+
logger.formatter = Log2mail::LoggerFormatter if logger.tty?
|
15
|
+
|
16
|
+
environment('LOG2MAIL_CONF') {
|
17
|
+
argument_required
|
18
|
+
defaults CONFIG_DEFAULT
|
19
|
+
description 'the configuration file or directory'
|
20
|
+
synopsis 'env LOG2MAIL_CONF=config_path'
|
21
|
+
}
|
22
|
+
option('config', 'c') {
|
23
|
+
argument_required
|
24
|
+
defaults CONFIG_DEFAULT
|
25
|
+
# synopsis '--config=config_path, -c'
|
26
|
+
description 'path of configuration file or directory'
|
27
|
+
}
|
28
|
+
# option('pidfile', 'p') {
|
29
|
+
# description 'path of PID file'
|
30
|
+
# }
|
31
|
+
option('verbose', 'v')
|
32
|
+
|
33
|
+
usage['MAN PAGE'] = "type 'gem man log2mail' for more information"
|
34
|
+
|
35
|
+
# usage['EXAMPLE USAGE'] = <<-EXAMPLES
|
36
|
+
# env LOG2MAIL_CONF=/usr/local/etc/log2mail.conf #{$0} start
|
37
|
+
# starts as daemon using configuration from '/usr/local/etc/log2mail.conf'
|
38
|
+
# EXAMPLES
|
39
|
+
#
|
40
|
+
# usage['CONFIGURATION FILE EXAMPLE'] = <<-EXAMPLES
|
41
|
+
# defaults
|
42
|
+
# mailto = your@mail.address
|
43
|
+
# file = /var/log/mail.log
|
44
|
+
# pattern = status=bounced # report bounced mail
|
45
|
+
# file = /var/log/syslog
|
46
|
+
# pattern = /(warning|error)/i # report warnings and errors from syslog
|
47
|
+
# EXAMPLES
|
48
|
+
|
49
|
+
def after_parse_parameters
|
50
|
+
if params['verbose'].given?
|
51
|
+
logger.level = Logger::DEBUG
|
52
|
+
$verbose = true
|
53
|
+
else
|
54
|
+
logger.level = logger_level
|
55
|
+
end
|
56
|
+
@config_path = params['config'].given? ? params['config'].value : params['LOG2MAIL_CONF'].value
|
57
|
+
@config_path ||= CONFIG_DEFAULT
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns the pid(s) of the daemon
|
61
|
+
def daemon_pids
|
62
|
+
prog_name = Log2mail::PROGNAME
|
63
|
+
own_pid = Process.pid
|
64
|
+
# FIXME: finding daemon pids by using pgrep is NOT 'optimal' :-)
|
65
|
+
`pgrep -f #{prog_name}`.split.map(&:to_i) - [own_pid]
|
66
|
+
end
|
67
|
+
|
68
|
+
def daemon_running?
|
69
|
+
!daemon_pids.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
mode 'start' do
|
73
|
+
# option('daemonize', '-D') { description 'daemonize into background'}
|
74
|
+
option('nofork', '-N') { description 'no daemonizing, stay in foreground'}
|
75
|
+
option('sleeptime') {
|
76
|
+
argument_required
|
77
|
+
description 'polling interval [seconds]'
|
78
|
+
cast :int
|
79
|
+
defaults 60
|
80
|
+
}
|
81
|
+
option('maxbufsize') { argument_required; cast :int; default 65536 }
|
82
|
+
def run
|
83
|
+
fail "Not starting. Daemon running." if daemon_running? and !params['nofork'].value
|
84
|
+
config = Log2mail::Config.parse_config @config_path
|
85
|
+
unless params['nofork'].value
|
86
|
+
Process.daemon
|
87
|
+
$PROGRAM_NAME = Log2mail::PROGNAME
|
88
|
+
$logger = Syslog::Logger.new(Log2mail::PROGNAME)
|
89
|
+
$logger.formatter = Log2mail::LoggerFormatter
|
90
|
+
def $logger.log(*a,&b)
|
91
|
+
add(*a,&b)
|
92
|
+
end
|
93
|
+
logger $logger
|
94
|
+
logger.info{'Deamon started.'}
|
95
|
+
trap(:INT) do
|
96
|
+
# for whatever reason, SIGINT is NOT LOGGED automatically like other signals (SIGTERM)
|
97
|
+
fatal "SIGINT"
|
98
|
+
exit(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
Log2mail::Watcher.new(config, params['sleeptime'].value).run
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
mode 'stop' do
|
106
|
+
def run
|
107
|
+
if daemon_running?
|
108
|
+
daemon_pids.each do |pid|
|
109
|
+
info "Interrupting pid #{pid}..."
|
110
|
+
Process.kill(:INT, pid)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
warn "No running daemon found."
|
114
|
+
end
|
115
|
+
rescue Errno::ENOENT
|
116
|
+
fail "Require 'pgrep' on path."
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# TODO: add restart mode
|
121
|
+
|
122
|
+
mode 'status' do
|
123
|
+
def run
|
124
|
+
unless daemon_running?
|
125
|
+
warn "No running daemon found."
|
126
|
+
exit 1
|
127
|
+
else
|
128
|
+
info 'Daemon running. PID: %s' % daemon_pids.join(', ')
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
mode 'configtest' do
|
134
|
+
option('effective', 'e') { description 'show effective settings' }
|
135
|
+
def run
|
136
|
+
config = Log2mail::Config.parse_config @config_path
|
137
|
+
puts config.formatted(params['effective'].value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
mode 'console' do
|
142
|
+
def run
|
143
|
+
Log2mail::Console.new.run
|
144
|
+
end
|
145
|
+
end if Log2mail.const_defined?(:Console)
|
146
|
+
|
147
|
+
alias_method :run, :help!
|
148
|
+
}
|