log2mail 0.0.1.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|