remote_syslog 1.6.5 → 1.6.6.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -71,6 +71,12 @@ to `a.server.com:514`:
71
71
 
72
72
  $ remote_syslog -D -d a.server.com -f local0 -P /tmp /var/log/mysqld.log
73
73
 
74
+ ### Windows
75
+
76
+ To run in Windows, start in a DOS Prompt or batch file and do not daemonize:
77
+
78
+ C:\> remote_syslog -D
79
+
74
80
  ## Auto-starting at boot
75
81
 
76
82
  The gem includes sample [init files] such as [remote_syslog.init.d]. remote_syslog will
@@ -141,13 +147,19 @@ Provide a client certificate when connecting via TLS:
141
147
 
142
148
  ### Detecting new files
143
149
 
144
- All input files (filenames or globs) are re-checked for new files every 60
145
- seconds. Ruby's `Dir.glob` is used.
150
+ remote_syslog automatically detects and activates new log files that match
151
+ its file specifiers. For example, `*.log` may be provided as a file specifier,
152
+ and remote_syslog will detect a `some.log` file created after it was started.
153
+ Globs are re-checked every 60 seconds. Ruby's `Dir.glob` is used.
146
154
 
147
- This means that files may be added and removed as long as they match a glob
148
- path provided to `remote_syslog`. Also, explicitly-provided filenames need
149
- not exist when `remote_syslog` is started. `remote_syslog` can be preconfigured
150
- to monitor log files which are created later (or may never be created).
155
+ Also, explicitly-provided filenames need not exist when `remote_syslog` is
156
+ started. `remote_syslog` can be pre-configured to monitor log files which are
157
+ created later (or may never be created).
158
+
159
+ If globs are specified on the command-line, enclose each one in single-quotes
160
+ (`'*.log'`) so the shell passes the raw glob string to remote_syslog (rather
161
+ than the current set of matches). This is not necessary for globs defined in
162
+ the config file.
151
163
 
152
164
 
153
165
  ### Multiple instances
data/bin/remote_syslog CHANGED
@@ -3,8 +3,4 @@
3
3
  require 'remote_syslog'
4
4
  require 'remote_syslog/cli'
5
5
 
6
- EM.error_handler { |e|
7
- puts "Unhandled Exception: #{e.class}: #{e.message}:\n\t#{e.backtrace.join("\n\t")}"
8
- }
9
-
10
6
  RemoteSyslog::Cli.process!(ARGV)
@@ -0,0 +1,103 @@
1
+ require 'eventmachine'
2
+ require 'servolux'
3
+
4
+ require 'remote_syslog/eventmachine_reader'
5
+ require 'remote_syslog/file_tail_reader'
6
+ require 'remote_syslog/glob_watch'
7
+ require 'remote_syslog/message_generator'
8
+ require 'remote_syslog/udp_endpoint'
9
+ require 'remote_syslog/tls_endpoint'
10
+
11
+ module RemoteSyslog
12
+ class Agent < Servolux::Server
13
+ # Who should we connect to?
14
+ attr_accessor :destination_host, :destination_port
15
+
16
+ # Should use TLS?
17
+ attr_accessor :tls
18
+
19
+ # TLS settings
20
+ attr_accessor :client_cert_chain, :client_private_key, :server_cert
21
+
22
+ # syslog defaults
23
+ attr_accessor :facility, :severity, :hostname
24
+
25
+ # Other settings
26
+ attr_accessor :strip_color, :parse_fields, :exclude_pattern
27
+
28
+ # Files
29
+ attr_reader :files
30
+
31
+ # How often should we check for new files?
32
+ attr_accessor :glob_check_interval
33
+
34
+ # Should we use eventmachine to tail?
35
+ attr_accessor :eventmachine_tail
36
+
37
+ def initialize(options = {})
38
+ @files = []
39
+ @glob_check_interval = 60
40
+ @eventmachine_tail = options.fetch(:eventmachine_tail, true)
41
+
42
+ unless logger = options[:logger]
43
+ logger = Logger.new(STDERR)
44
+ logger.level = Logger::ERROR
45
+ end
46
+
47
+ super('remote_syslog', :logger => logger, :pid_file => options[:pid_file])
48
+ end
49
+
50
+ def files=(files)
51
+ @files = [ @files, files ].flatten.compact.uniq
52
+ end
53
+
54
+ def watch_file(file)
55
+ if eventmachine_tail
56
+ RemoteSyslog::EventMachineReader.new(file,
57
+ :callback => @message_generator.method(:transmit),
58
+ :logger => logger)
59
+ else
60
+ RemoteSyslog::FileTailReader.new(file,
61
+ :callback => @message_generator.method(:transmit),
62
+ :logger => logger)
63
+ end
64
+ end
65
+
66
+ def run
67
+ EventMachine.run do
68
+ EM.error_handler do |e|
69
+ logger.error "Unhandled EventMachine Exception: #{e.class}: #{e.message}:\n\t#{e.backtrace.join("\n\t")}"
70
+ end
71
+
72
+ if @tls
73
+ max_message_size = 10240
74
+
75
+ connection = TlsEndpoint.new(@destination_host, @destination_port,
76
+ :client_cert_chain => @client_cert_chain,
77
+ :client_private_key => @client_private_key,
78
+ :server_cert => @server_cert,
79
+ :logger => logger)
80
+ else
81
+ max_message_size = 1024
82
+ connection = UdpEndpoint.new(@destination_host, @destination_port,
83
+ :logger => logger)
84
+ end
85
+
86
+ @message_generator = RemoteSyslog::MessageGenerator.new(connection,
87
+ :facility => @facility, :severity => @severity,
88
+ :strip_color => @strip_color, :hostname => @hostname,
89
+ :parse_fields => @parse_fields, :exclude_pattern => @exclude_pattern,
90
+ :max_message_size => max_message_size)
91
+
92
+ files.each do |file|
93
+ RemoteSyslog::GlobWatch.new(file, @glob_check_interval,
94
+ method(:watch_file))
95
+ end
96
+ end
97
+ end
98
+
99
+ def before_stopping
100
+ EM.stop
101
+ end
102
+ end
103
+ end
@@ -1,8 +1,9 @@
1
1
  require 'optparse'
2
2
  require 'yaml'
3
3
  require 'pathname'
4
- require 'daemons'
4
+ require 'servolux'
5
5
 
6
+ require 'remote_syslog/agent'
6
7
 
7
8
  module RemoteSyslog
8
9
  class Cli
@@ -11,18 +12,29 @@ module RemoteSyslog
11
12
  'rfc3339' => /^(\S+) (\S+) ([^: ]+):? (.*)$/
12
13
  }
13
14
 
15
+ DEFAULT_PID_FILES = [
16
+ "/var/run/remote_syslog.pid",
17
+ "#{ENV['HOME']}/run/remote_syslog.pid",
18
+ "#{ENV['HOME']}/tmp/remote_syslog.pid",
19
+ "#{ENV['HOME']}/remote_syslog.pid",
20
+ "#{ENV['TMPDIR']}/remote_syslog.pid",
21
+ "/tmp/remote_syslog.pid"
22
+ ]
23
+
24
+ DEFAULT_CONFIG_FILE = '/etc/log_files.yml'
25
+
14
26
  def self.process!(argv)
15
27
  c = new(argv)
16
28
  c.parse
17
29
  c.run
18
30
  end
19
31
 
32
+ attr_reader :program_name
33
+
20
34
  def initialize(argv)
21
35
  @argv = argv
36
+ @program_name = File.basename($0)
22
37
 
23
- @app_name = File.basename($0) || 'remote_syslog'
24
-
25
- @configfile = '/etc/log_files.yml'
26
38
  @strip_color = false
27
39
  @exclude_pattern = nil
28
40
 
@@ -32,168 +44,214 @@ module RemoteSyslog
32
44
  :backtrace => false,
33
45
  :monitor => false,
34
46
  }
47
+
48
+ @agent = RemoteSyslog::Agent.new(:pid_file => default_pid_file)
35
49
  end
36
50
 
37
- def pid_file=(v)
38
- m = v.match(%r{^(.+/)?([^/]+?)(\.pid)?$})
39
- if m[1]
40
- @daemonize_options[:dir_mode] = :normal
41
- @daemonize_options[:dir] = m[1]
42
- end
51
+ def is_file_writable?(file)
52
+ directory = File.dirname(file)
53
+
54
+ (File.directory?(directory) && File.writable?(directory) && !File.exists?(file)) || File.writable?(file)
55
+ end
43
56
 
44
- @app_name = m[2]
57
+ def default_pid_file
58
+ DEFAULT_PID_FILES.each do |file|
59
+ return file if is_file_writable?(file)
60
+ end
45
61
  end
46
62
 
47
63
  def parse
48
64
  op = OptionParser.new do |opts|
49
- opts.banner = "Usage: remote_syslog [options] [<logfile>...]"
50
- opts.separator ''
51
- opts.separator "Example: remote_syslog -c configs/logs.yml -p 12345 /var/log/mysqld.log"
65
+ opts.banner = "Usage: #{program_name} [OPTION]... <FILE>..."
52
66
  opts.separator ''
67
+
53
68
  opts.separator "Options:"
54
69
 
55
70
  opts.on("-c", "--configfile PATH", "Path to config (/etc/log_files.yml)") do |v|
56
- @configfile = File.expand_path(v)
71
+ @configfile = v
57
72
  end
58
73
  opts.on("-d", "--dest-host HOSTNAME", "Destination syslog hostname or IP (logs.papertrailapp.com)") do |v|
59
- @dest_host = v
74
+ @agent.destination_host = v
60
75
  end
61
76
  opts.on("-p", "--dest-port PORT", "Destination syslog port (514)") do |v|
62
- @dest_port = v
77
+ @agent.destination_port = v
63
78
  end
64
79
  opts.on("-D", "--no-detach", "Don't daemonize and detach from the terminal") do
65
80
  @no_detach = true
66
81
  end
67
82
  opts.on("-f", "--facility FACILITY", "Facility (user)") do |v|
68
- @facility = v
83
+ @agent.facility = v
69
84
  end
70
85
  opts.on("--hostname HOST", "Local hostname to send from") do |v|
71
- @hostname = v
86
+ @agent.hostname = v
72
87
  end
73
- opts.on("-P", "--pid-dir DIRECTORY", "Directory to write .pid file in (/var/run/)") do |v|
74
- @daemonize_options[:dir_mode] = :normal
75
- @daemonize_options[:dir] = v
88
+ opts.on("-P", "--pid-dir DIRECTORY", "DEPRECATED: Directory to write .pid file in") do |v|
89
+ puts "Warning: --pid-dir is deprecated. Please use --pid-file FILENAME instead"
90
+ @pid_directory = v
76
91
  end
77
- opts.on("--pid-file FILENAME", "PID filename (<program name>.pid)") do |v|
78
- self.pid_file = v
92
+ opts.on("--pid-file FILENAME", "Location of the PID file (default #{@agent.pid_file})") do |v|
93
+ @agent.pid_file = v
79
94
  end
80
95
  opts.on("--parse-syslog", "Parse file as syslog-formatted file") do
81
- @parse_fields = FIELD_REGEXES['syslog']
96
+ @agent.parse_fields = FIELD_REGEXES['syslog']
82
97
  end
83
98
  opts.on("-s", "--severity SEVERITY", "Severity (notice)") do |v|
84
- @severity = v
99
+ @agent.severity = v
85
100
  end
86
101
  opts.on("--strip-color", "Strip color codes") do
87
- @strip_color = true
102
+ @agent.strip_color = true
88
103
  end
89
104
  opts.on("--tls", "Connect via TCP with TLS") do
90
- @tls = true
105
+ @agent.tls = true
106
+ end
107
+
108
+
109
+ opts.on("--new-file-check-interval INTERVAL", OptionParser::DecimalInteger,
110
+ "Time between checks for new files") do |v|
111
+ @agent.glob_check_interval = v
91
112
  end
92
- opts.on_tail("-h", "--help", "Show this message") do
113
+
114
+ opts.separator ''
115
+ opts.separator 'Advanced options:'
116
+
117
+ opts.on("--[no-]eventmachine-tail", "Enable or disable using eventmachine-tail") do |v|
118
+ @agent.eventmachine_tail = v
119
+ end
120
+ opts.on("--debug-log FILE", "Log internal debug messages") do |v|
121
+ level = @agent.logger.level
122
+ @agent.logger = Logger.new(v)
123
+ @agent.logger.level = level
124
+ end
125
+
126
+ severities = Logger::Severity.constants + Logger::Severity.constants.map { |s| s.downcase }
127
+ opts.on("--debug-level LEVEL", severities, "Log internal debug messages at level") do |v|
128
+ @agent.logger.level = Logger::Severity.const_get(v.upcase)
129
+ end
130
+
131
+ opts.separator ""
132
+ opts.separator "Common options:"
133
+
134
+ opts.on("-h", "--help", "Show this message") do
93
135
  puts opts
94
136
  exit
95
137
  end
138
+
139
+ opts.on("--version", "Show version") do
140
+ puts RemoteSyslog::VERSION
141
+ exit(0)
142
+ end
143
+
144
+ opts.separator ''
145
+ opts.separator "Example:"
146
+ opts.separator " $ #{program_name} -c configs/logs.yml -p 12345 /var/log/mysqld.log"
96
147
  end
97
148
 
98
149
  op.parse!(@argv)
99
150
 
100
151
  @files = @argv.dup.delete_if { |a| a.empty? }
101
152
 
102
- parse_config
103
-
104
- @dest_host ||= 'logs.papertrailapp.com'
105
- @dest_port ||= 514
153
+ if @configfile
154
+ if File.exists?(@configfile)
155
+ parse_config(@configfile)
156
+ else
157
+ error "The config file specified could not be found: #{@configfile}"
158
+ end
159
+ elsif File.exists?(DEFAULT_CONFIG_FILE)
160
+ parse_config(DEFAULT_CONFIG_FILE)
161
+ end
106
162
 
107
163
  if @files.empty?
108
- puts "No filenames provided and #{@configfile} not found or malformed."
109
- puts ''
110
- puts op
111
- exit
164
+ error "You must specify at least one file to watch"
112
165
  end
113
166
 
167
+ @agent.destination_host ||= 'logs.papertrailapp.com'
168
+ @agent.destination_port ||= 514
169
+
114
170
  # handle relative paths before Daemonize changes the wd to / and expand wildcards
115
171
  @files = @files.flatten.map { |f| File.expand_path(f) }.uniq
116
172
 
173
+ @agent.files = @files
174
+
175
+ if @pid_directory
176
+ if @agent.pid_file
177
+ @agent.pid_file = File.expand_path("#{@pid_directory}/#{@agent.pid_file}")
178
+ else
179
+ @agent.pid_file = File.expand_path("#{@pid_directory}/remote_syslog.pid")
180
+ end
181
+ end
182
+
183
+ @agent.pid_file ||= default_pid_file
184
+
185
+ if !@no_detach && !::Servolux.fork?
186
+ @no_detach = true
187
+
188
+ puts "Fork is not supported in this Ruby environment. Running in foreground."
189
+ end
190
+ rescue OptionParser::ParseError => e
191
+ error e.message, true
117
192
  end
118
193
 
119
- def parse_config
120
- if File.exist?(@configfile)
121
- config = YAML.load_file(@configfile)
194
+ def parse_config(file)
195
+ config = YAML.load_file(file)
122
196
 
123
- @files += Array(config['files'])
197
+ @files += Array(config['files'])
124
198
 
125
- if config['destination'] && config['destination']['host']
126
- @dest_host ||= config['destination']['host']
127
- end
199
+ if config['destination'] && config['destination']['host']
200
+ @agent.destination_host ||= config['destination']['host']
201
+ end
128
202
 
129
- if config['destination'] && config['destination']['port']
130
- @dest_port ||= config['destination']['port']
131
- end
203
+ if config['destination'] && config['destination']['port']
204
+ @agent.destination_port ||= config['destination']['port']
205
+ end
132
206
 
133
- if config['hostname']
134
- @hostname = config['hostname']
135
- end
207
+ if config['hostname']
208
+ @agent.hostname = config['hostname']
209
+ end
136
210
 
137
- @server_cert = config['ssl_server_cert']
138
- @client_cert_chain = config['ssl_client_cert_chain']
139
- @client_private_key = config['ssl_client_private_key']
211
+ @agent.server_cert = config['ssl_server_cert']
212
+ @agent.client_cert_chain = config['ssl_client_cert_chain']
213
+ @agent.client_private_key = config['ssl_client_private_key']
140
214
 
141
- if config['parse_fields']
142
- @parse_fields = FIELD_REGEXES[config['parse_fields']] || Regexp.new(config['parse_fields'])
143
- end
215
+ if config['parse_fields']
216
+ @agent.parse_fields = FIELD_REGEXES[config['parse_fields']] || Regexp.new(config['parse_fields'])
217
+ end
144
218
 
145
- if config['exclude_patterns']
146
- @exclude_pattern = Regexp.new(config['exclude_patterns'].map { |r| "(#{r})" }.join('|'))
147
- end
219
+ if config['exclude_patterns']
220
+ @agent.exclude_pattern = Regexp.new(config['exclude_patterns'].map { |r| "(#{r})" }.join('|'))
148
221
  end
149
222
  end
150
223
 
151
224
  def run
152
- puts "Watching #{@files.length} files/paths. Sending to #{@dest_host}:#{@dest_port} (#{@tls ? 'TCP/TLS' : 'UDP'})."
153
-
154
225
  if @no_detach
155
- start
226
+ puts "Watching #{@agent.files.length} files/paths. Sending to #{@agent.destination_host}:#{@agent.destination_port} (#{@agent.tls ? 'TCP/TLS' : 'UDP'})."
227
+ @agent.run
156
228
  else
157
- Daemons.run_proc(@app_name, @daemonize_options) do
158
- start
229
+ daemon = Servolux::Daemon.new(:server => @agent)
230
+
231
+ if daemon.alive?
232
+ error "Already running at #{@agent.pid_file}. To run another instance, specify a different `--pid-file`.", true
159
233
  end
234
+
235
+ puts "Watching #{@agent.files.length} files/paths. Sending to #{@agent.destination_host}:#{@agent.destination_port} (#{@agent.tls ? 'TCP/TLS' : 'UDP'})."
236
+ daemon.startup
160
237
  end
238
+ rescue Servolux::Daemon::StartupError => e
239
+ case message = e.message[/^(Child raised error: )?(.*)$/, 2]
240
+ when /#<Errno::EACCES: (.*)>$/
241
+ error $1
242
+ else
243
+ error message
244
+ end
245
+ rescue Interrupt
246
+ exit(0)
161
247
  end
162
248
 
163
- def start
164
- EventMachine.run do
165
- if @tls
166
- connection = TlsEndpoint.new(@dest_host, @dest_port,
167
- :client_cert_chain => @client_cert_chain,
168
- :client_private_key => @client_private_key,
169
- :server_cert => @server_cert)
170
- else
171
- connection = UdpEndpoint.new(@dest_host, @dest_port)
172
- end
173
-
174
- @files.each do |path|
175
- begin
176
- glob_check_interval = 60
177
- exclude_files = []
178
- max_message_size = 1024
179
-
180
- if @tls
181
- max_message_size = 10240
182
- end
183
-
184
- EventMachine::FileGlobWatchTail.new(path, RemoteSyslog::Reader,
185
- glob_check_interval, exclude_files,
186
- @dest_host, @dest_port,
187
- :socket => connection, :facility => @facility,
188
- :severity => @severity, :strip_color => @strip_color,
189
- :hostname => @hostname, :parse_fields => @parse_fields,
190
- :exclude_pattern => @exclude_pattern,
191
- :max_message_size => max_message_size)
192
- rescue Errno::ENOENT => e
193
- puts "#{path} not found, continuing. (#{e.message})"
194
- end
195
- end
249
+ def error(message, try_help = false)
250
+ puts "#{program_name}: #{message}"
251
+ if try_help
252
+ puts "Try `#{program_name} --help' for more information."
196
253
  end
254
+ exit(1)
197
255
  end
198
256
  end
199
257
  end
@@ -0,0 +1,43 @@
1
+ require 'eventmachine'
2
+ require 'eventmachine-tail'
3
+ require 'em-dns-resolver'
4
+
5
+ # Force eventmachine-tail not to change the encoding
6
+ # This will allow ruby 1.9 to deal with any file data
7
+ old_verbose, $VERBOSE = $VERBOSE, nil
8
+ EventMachine::FileTail::FORCE_ENCODING = false
9
+ $VERBOSE = old_verbose
10
+
11
+ module RemoteSyslog
12
+ class EventMachineReader < EventMachine::FileTail
13
+ def initialize(path, options = {}, &block)
14
+ @callback = options[:callback] || block
15
+ @buffer = BufferedTokenizer.new
16
+ @logger = options[:logger] || Logger.new(STDERR)
17
+
18
+ @tag = options[:program] || File.basename(path)
19
+
20
+ # Remove characters that can't be in a tag
21
+ @tag = @tag.gsub(%r{[: \]\[\\]+}, '-')
22
+
23
+ # Make sure the tag isn't too long
24
+ if @tag.length > 32
25
+ @tag = @tag[0..31]
26
+ end
27
+
28
+ @logger.debug "Watching #{path} with EventMachineReader"
29
+
30
+ super(path, -1)
31
+ end
32
+
33
+ def receive_data(data)
34
+ @buffer.extract(data).each do |line|
35
+ @callback.call(@tag, line)
36
+ end
37
+ end
38
+
39
+ def on_exception(exception)
40
+ @logger.error "Exception: #{exception.class}: #{exception.message}\n\t#{exception.backtrace.join("\n\t")}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+ require 'file/tail'
2
+
3
+ module RemoteSyslog
4
+ class FileTailReader
5
+ def initialize(path, options = {}, &block)
6
+ @path = path
7
+ @callback = options[:callback] || block
8
+ @logger = options[:logger] || Logger.new(STDERR)
9
+ @tag = options[:program] || File.basename(path)
10
+
11
+ # Remove characters that can't be in a tag
12
+ @tag = @tag.gsub(%r{[: \]\[\\]+}, '-')
13
+
14
+ # Make sure the tag isn't too long
15
+ if @tag.length > 32
16
+ @tag = @tag[0..31]
17
+ end
18
+
19
+ @logger.debug "Watching #{path} with FileTailReader"
20
+
21
+ start
22
+ end
23
+
24
+ def start
25
+ @thread = Thread.new do
26
+ run
27
+ end
28
+ end
29
+
30
+ def run
31
+ File::Tail::Logfile.tail(@path) do |line|
32
+ EventMachine.schedule do
33
+ @callback.call(@tag, line)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ require 'eventmachine-tail'
2
+
3
+ module RemoteSyslog
4
+ class GlobWatch < EventMachine::FileGlobWatch
5
+ def initialize(path, interval, callback)
6
+ super(path, interval)
7
+ @callback = callback
8
+ end
9
+
10
+ def file_found(path)
11
+ @callback.call(path)
12
+ end
13
+
14
+ def file_deleted(path)
15
+ # Nothing to do
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ require 'socket'
2
+ require 'syslog_protocol'
3
+
4
+ module RemoteSyslog
5
+ class MessageGenerator
6
+ COLORED_REGEXP = /\e\[(?:(?:[0-9]{1,3});){0,2}(?:[0-9]{1,3})m/
7
+
8
+ def initialize(socket, options = {})
9
+ @socket = socket
10
+
11
+ @parse_fields = options[:parse_fields]
12
+ @strip_color = options[:strip_color]
13
+ @exclude_pattern = options[:exclude_pattern]
14
+ @max_message_size = options[:max_message_size] || 1024
15
+
16
+ @packet = SyslogProtocol::Packet.new
17
+
18
+ if options[:hostname] && options[:hostname] != ''
19
+ local_hostname = options[:hostname]
20
+ else
21
+ local_hostname = (Socket.gethostname rescue `hostname`.chomp)[/^([^\.]+)/, 1]
22
+
23
+ if local_hostname.nil? || local_hostname == ''
24
+ local_hostname = 'localhost'
25
+ end
26
+ end
27
+
28
+ @packet.hostname = local_hostname
29
+ @packet.facility = options[:facility] || 'user'
30
+ @packet.severity = options[:severity] || 'notice'
31
+ end
32
+
33
+ def transmit(tag, message)
34
+ return if @exclude_pattern && message =~ @exclude_pattern
35
+
36
+ message = message.gsub(COLORED_REGEXP, '') if @strip_color
37
+
38
+ packet = @packet.dup
39
+ packet.content = message
40
+
41
+ if @parse_fields && message =~ @parse_fields
42
+ packet.hostname = $2 if $2 && $2 != ''
43
+ packet.tag = $3 if $3 && $3 != ''
44
+ packet.content = $4 if $4 && $4 != ''
45
+ end
46
+
47
+ unless packet.tag
48
+ packet.tag = tag
49
+ end
50
+
51
+ @socket.write(packet.assemble(@max_message_size))
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,5 @@
1
+ require 'eventmachine'
2
+
1
3
  module RemoteSyslog
2
4
  class TlsEndpoint
3
5
  class Handler < EventMachine::Connection
@@ -29,12 +31,15 @@ module RemoteSyslog
29
31
  attr_accessor :connection
30
32
  attr_reader :server_cert, :client_cert_chain, :client_private_key
31
33
 
34
+ attr_reader :logger
35
+
32
36
  def initialize(address, port, options = {})
33
37
  @address = address
34
38
  @port = port.to_i
35
39
  @client_cert_chain = options[:client_cert_chain]
36
40
  @client_private_key = options[:client_private_key]
37
41
  @queue_limit = options[:queue_limit] || 10_000
42
+ @logger = options[:logger] || Logger.new(STDERR)
38
43
 
39
44
  if options[:server_cert]
40
45
  @server_cert = OpenSSL::X509::Certificate.new(File.read(options[:server_cert]))
@@ -51,6 +56,12 @@ module RemoteSyslog
51
56
  connect
52
57
  end
53
58
 
59
+ def connection=(conn)
60
+ port, ip = Socket.unpack_sockaddr_in(conn.get_peername)
61
+ logger.debug "Connected to #{ip}:#{port}"
62
+ @connection = conn
63
+ end
64
+
54
65
  def resolve_address
55
66
  request = EventMachine::DnsResolver.resolve(@address)
56
67
  request.callback do |addrs|
@@ -63,6 +74,7 @@ module RemoteSyslog
63
74
  end
64
75
 
65
76
  def connect
77
+ logger.debug "Connecting to #{address}:#{@port}"
66
78
  EventMachine.connect(address, @port, TlsEndpoint::Handler, self)
67
79
  end
68
80
 
@@ -86,4 +98,4 @@ module RemoteSyslog
86
98
  end
87
99
  end
88
100
  end
89
- end
101
+ end
@@ -1,9 +1,14 @@
1
+ require 'eventmachine'
2
+
1
3
  module RemoteSyslog
2
4
  class UdpEndpoint
3
- def initialize(address, port)
5
+ attr_reader :logger
6
+
7
+ def initialize(address, port, options = {})
4
8
  @address = address
5
9
  @port = port.to_i
6
10
  @socket = EventMachine.open_datagram_socket('0.0.0.0', 0)
11
+ @logger = options[:logger] || Logger.new(STDERR)
7
12
 
8
13
  # Try to resolve the address
9
14
  resolve_address
@@ -29,4 +34,4 @@ module RemoteSyslog
29
34
  @socket.send_datagram(value, address, @port)
30
35
  end
31
36
  end
32
- end
37
+ end
data/lib/remote_syslog.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  module RemoteSyslog
2
- VERSION = "1.6.5"
2
+ VERSION = "1.6.6.rc1"
3
3
  end
4
4
 
5
- require 'remote_syslog/reader'
6
- require 'remote_syslog/tls_endpoint'
7
- require 'remote_syslog/udp_endpoint'
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  ## If your rubyforge_project name is different, then edit it and comment out
9
9
  ## the sub! line in the Rakefile
10
10
  s.name = 'remote_syslog'
11
- s.version = '1.6.5'
12
- s.date = '2012-05-10'
11
+ s.version = '1.6.6.rc1'
12
+ s.date = '2012-08-13'
13
13
  s.rubyforge_project = 'remote_syslog'
14
14
 
15
15
  ## Make sure your summary is short. The description may be as long
@@ -40,7 +40,8 @@ Gem::Specification.new do |s|
40
40
  ## List your runtime dependencies here. Runtime dependencies are those
41
41
  ## that are needed for an end user to actually USE your code.
42
42
  #s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
43
- s.add_dependency 'daemons'
43
+ s.add_dependency 'servolux'
44
+ s.add_dependency 'file-tail'
44
45
  s.add_dependency 'eventmachine', [ '>= 0.12.10', '< 1.1' ]
45
46
  s.add_dependency 'eventmachine-tail'
46
47
  s.add_dependency 'syslog_protocol', [ '~> 0.9.2' ]
@@ -67,8 +68,12 @@ Gem::Specification.new do |s|
67
68
  examples/remote_syslog.supervisor.conf
68
69
  examples/remote_syslog.upstart.conf
69
70
  lib/remote_syslog.rb
71
+ lib/remote_syslog/agent.rb
70
72
  lib/remote_syslog/cli.rb
71
- lib/remote_syslog/reader.rb
73
+ lib/remote_syslog/eventmachine_reader.rb
74
+ lib/remote_syslog/file_tail_reader.rb
75
+ lib/remote_syslog/glob_watch.rb
76
+ lib/remote_syslog/message_generator.rb
72
77
  lib/remote_syslog/tls_endpoint.rb
73
78
  lib/remote_syslog/udp_endpoint.rb
74
79
  remote_syslog.gemspec
metadata CHANGED
@@ -1,89 +1,118 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: remote_syslog
3
- version: !ruby/object:Gem::Version
4
- version: 1.6.5
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 1
7
+ - 6
8
+ - 6
9
+ - rc1
10
+ version: 1.6.6.rc1
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Troy Davis
9
14
  - Eric Lindvall
10
15
  autorequire:
11
16
  bindir: bin
12
17
  cert_chain: []
13
- date: 2012-05-10 00:00:00.000000000Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: daemons
17
- requirement: &70356082564840 !ruby/object:Gem::Requirement
18
- none: false
19
- requirements:
20
- - - ! '>='
21
- - !ruby/object:Gem::Version
22
- version: '0'
18
+
19
+ date: 2012-08-13 00:00:00 -07:00
20
+ default_executable: remote_syslog
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: servolux
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 0
31
+ version: "0"
23
32
  type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: file-tail
24
36
  prerelease: false
25
- version_requirements: *70356082564840
26
- - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
27
47
  name: eventmachine
28
- requirement: &70356082564260 !ruby/object:Gem::Requirement
29
- none: false
30
- requirements:
31
- - - ! '>='
32
- - !ruby/object:Gem::Version
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ - 12
56
+ - 10
33
57
  version: 0.12.10
34
58
  - - <
35
- - !ruby/object:Gem::Version
36
- version: '1.1'
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 1
62
+ - 1
63
+ version: "1.1"
37
64
  type: :runtime
38
- prerelease: false
39
- version_requirements: *70356082564260
40
- - !ruby/object:Gem::Dependency
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
41
67
  name: eventmachine-tail
42
- requirement: &70356082563580 !ruby/object:Gem::Requirement
43
- none: false
44
- requirements:
45
- - - ! '>='
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
68
  prerelease: false
50
- version_requirements: *70356082563580
51
- - !ruby/object:Gem::Dependency
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :runtime
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
52
79
  name: syslog_protocol
53
- requirement: &70356082563020 !ruby/object:Gem::Requirement
54
- none: false
55
- requirements:
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ requirements:
56
83
  - - ~>
57
- - !ruby/object:Gem::Version
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ - 9
88
+ - 2
58
89
  version: 0.9.2
59
90
  type: :runtime
60
- prerelease: false
61
- version_requirements: *70356082563020
62
- - !ruby/object:Gem::Dependency
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
63
93
  name: em-resolv-replace
64
- requirement: &70356082562600 !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :runtime
71
94
  prerelease: false
72
- version_requirements: *70356082562600
73
- description: Lightweight daemon to tail one or more log files and transmit UDP syslog
74
- messages to a remote syslog host (centralized log aggregation). Generates UDP packets
75
- itself instead of depending on a system syslog daemon, so it doesn't affect system-wide
76
- logging configuration.
77
- email:
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ type: :runtime
103
+ version_requirements: *id006
104
+ description: Lightweight daemon to tail one or more log files and transmit UDP syslog messages to a remote syslog host (centralized log aggregation). Generates UDP packets itself instead of depending on a system syslog daemon, so it doesn't affect system-wide logging configuration.
105
+ email:
78
106
  - troy@sevenscale.com
79
107
  - eric@sevenscale.com
80
- executables:
108
+ executables:
81
109
  - remote_syslog
82
110
  extensions: []
83
- extra_rdoc_files:
111
+
112
+ extra_rdoc_files:
84
113
  - README.md
85
114
  - LICENSE
86
- files:
115
+ files:
87
116
  - Gemfile
88
117
  - LICENSE
89
118
  - README.md
@@ -96,35 +125,46 @@ files:
96
125
  - examples/remote_syslog.supervisor.conf
97
126
  - examples/remote_syslog.upstart.conf
98
127
  - lib/remote_syslog.rb
128
+ - lib/remote_syslog/agent.rb
99
129
  - lib/remote_syslog/cli.rb
100
- - lib/remote_syslog/reader.rb
130
+ - lib/remote_syslog/eventmachine_reader.rb
131
+ - lib/remote_syslog/file_tail_reader.rb
132
+ - lib/remote_syslog/glob_watch.rb
133
+ - lib/remote_syslog/message_generator.rb
101
134
  - lib/remote_syslog/tls_endpoint.rb
102
135
  - lib/remote_syslog/udp_endpoint.rb
103
136
  - remote_syslog.gemspec
137
+ has_rdoc: true
104
138
  homepage: http://github.com/papertrail/remote_syslog
105
139
  licenses: []
140
+
106
141
  post_install_message:
107
- rdoc_options:
142
+ rdoc_options:
108
143
  - --charset=UTF-8
109
- require_paths:
144
+ require_paths:
110
145
  - lib
111
- required_ruby_version: !ruby/object:Gem::Requirement
112
- none: false
113
- requirements:
114
- - - ! '>='
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
- required_rubygems_version: !ruby/object:Gem::Requirement
118
- none: false
119
- requirements:
120
- - - ! '>='
121
- - !ruby/object:Gem::Version
122
- version: '0'
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ segments:
151
+ - 0
152
+ version: "0"
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">"
156
+ - !ruby/object:Gem::Version
157
+ segments:
158
+ - 1
159
+ - 3
160
+ - 1
161
+ version: 1.3.1
123
162
  requirements: []
163
+
124
164
  rubyforge_project: remote_syslog
125
- rubygems_version: 1.8.7
165
+ rubygems_version: 1.3.6
126
166
  signing_key:
127
167
  specification_version: 2
128
- summary: Monitor plain text log file(s) for new entries and send to remote syslog
129
- collector
168
+ summary: Monitor plain text log file(s) for new entries and send to remote syslog collector
130
169
  test_files: []
170
+
@@ -1,87 +0,0 @@
1
- require 'socket'
2
- require 'eventmachine'
3
- require 'eventmachine-tail'
4
- require 'em-dns-resolver'
5
- require 'syslog_protocol'
6
-
7
- # Force eventmachine-tail not to change the encoding
8
- # This will allow ruby 1.9 to deal with any file data
9
- old_verbose, $VERBOSE = $VERBOSE, nil
10
- EventMachine::FileTail::FORCE_ENCODING = false
11
- $VERBOSE = old_verbose
12
-
13
- module RemoteSyslog
14
- class Reader < EventMachine::FileTail
15
- COLORED_REGEXP = /\e\[(?:(?:[0-9]{1,3});){0,2}(?:[0-9]{1,3})m/
16
-
17
- def initialize(path, destination_address, destination_port, options = {})
18
- super(path, -1)
19
-
20
- @parse_fields = options[:parse_fields]
21
- @strip_color = options[:strip_color]
22
- @exclude_pattern = options[:exclude_pattern]
23
- @max_message_size = options[:max_message_size] || 1024
24
-
25
- @socket = options[:socket] || UdpEndpoint.new(destination_address, destination_port)
26
-
27
- @buffer = BufferedTokenizer.new
28
-
29
- @packet = SyslogProtocol::Packet.new
30
-
31
- if options[:hostname] && options[:hostname] != ''
32
- local_hostname = options[:hostname]
33
- else
34
- local_hostname = (Socket.gethostname rescue `hostname`.chomp)[/^([^\.]+)/, 1]
35
-
36
- if local_hostname.nil? || local_hostname.empty?
37
- local_hostname = 'localhost'
38
- end
39
- end
40
-
41
- @packet.hostname = local_hostname
42
- @packet.facility = options[:facility] || 'user'
43
- @packet.severity = options[:severity] || 'notice'
44
-
45
- tag = options[:program] || File.basename(path) || File.basename($0)
46
-
47
- # Remove characters that can't be in a tag
48
- tag = tag.gsub(%r{[: \]\[\\]+}, '-')
49
-
50
- # Make sure the tag isn't too long
51
- if tag.length > 32
52
- tag = tag[0..31]
53
- end
54
-
55
- @packet.tag = tag
56
- end
57
-
58
- def receive_data(data)
59
- @buffer.extract(data).each do |line|
60
- transmit(line)
61
- end
62
- end
63
-
64
- def transmit(message)
65
- return if @exclude_pattern && message =~ @exclude_pattern
66
-
67
- message = message.gsub(COLORED_REGEXP, '') if @strip_color
68
-
69
- packet = @packet.dup
70
- packet.content = message
71
-
72
- if @parse_fields
73
- if message =~ @parse_fields
74
- packet.hostname = $2 if $2 && $2 != ''
75
- packet.tag = $3 if $3 && $3 != ''
76
- packet.content = $4 if $4 && $4 != ''
77
- end
78
- end
79
-
80
- @socket.write(packet.assemble(@max_message_size))
81
- end
82
-
83
- def on_exception(exception)
84
- puts "Exception: #{exception.class}: #{exception.message}\n\t#{exception.backtrace.join("\n\t")}"
85
- end
86
- end
87
- end