remote_syslog 1.6.5 → 1.6.6.rc1

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.
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