remote_syslog 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -90,6 +90,41 @@ Only 1 destination server is supported; the command-line argument wins.
90
90
  port: 12345
91
91
 
92
92
 
93
+ ### Optional: Parse fields from messages written by syslogd
94
+
95
+ This is not needed for most configurations.
96
+
97
+ In cases where logs from multiple programs are in the same file (for example,
98
+ ``/var/log/messages``), the log line may include text that is not part of the
99
+ log message, like a timestamp, hostname, or program name. remote_syslog can
100
+ parse the program, hostname, and/or message text so that the message has
101
+ accurate metadata.
102
+
103
+ To do that, add an optional top-level configuration option `parse_fields`
104
+ with the name of a predefined regex (by remote_syslog) or a regex string. To
105
+ use the predefined regex for standard syslog messages, provide:
106
+
107
+ parse_fields: syslog
108
+
109
+ The `syslog` regex is `(\w+ \d+ \S+) (\S+) ([^:]+): (.*)`. It parses this:
110
+
111
+ Jul 18 08:25:08 hostname programname[1234]: The log message
112
+
113
+ Or provide your own regex that includes these 4 backreferences, in order:
114
+ timestamp, system name, program name, message. Match and return empty
115
+ strings for any empty positions where the log value should be ignored.
116
+ For example, in the log:
117
+
118
+ something-meaningless The log message
119
+
120
+ You could ignore the first word, returning 3 empty values then the log
121
+ message with:
122
+
123
+ parse_fields: "something-meaningless ()()()(.*)"
124
+
125
+ Per-file parsing is not supported. Run multiple instances.
126
+
127
+
93
128
  ## Reporting bugs
94
129
 
95
130
  1. See whether the issue has already been reported: <https://github.com/papertrail/remote_syslog/issues/>
data/bin/remote_syslog CHANGED
@@ -1,121 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'optparse'
4
- require 'yaml'
5
- require 'pathname'
6
- require 'socket'
7
-
8
- require 'daemons'
9
- require 'eventmachine'
10
- require 'eventmachine-tail'
11
-
12
3
  require 'remote_syslog'
4
+ require 'remote_syslog/cli'
13
5
 
14
- def remote_syslog_daemon(args)
15
- options = {
16
- :configfile => '/etc/log_files.yml',
17
- :dest_host => 'logs.papertrailapp.com',
18
- :dest_port => 514
19
- }
20
- daemonize_options = {
21
- :app_name => File.basename($0) || "remote_syslog",
22
- :ARGV => ['start'],
23
- :dir_mode => :system,
24
- :multiple => false,
25
- :ontop => false,
26
- :mode => :exec,
27
- :backtrace => false,
28
- :monitor => false,
29
- :strip_color => false
30
- }
31
-
32
- op = OptionParser.new do |opts|
33
- opts.banner = "Usage: remote_syslog [options] <path to add'l log 1> .. <path to add'l log n>"
34
- opts.separator ''
35
- opts.separator "Example: remote_syslog -c configs/logs.yml -p 12345 /var/log/mysqld.log"
36
- opts.separator ''
37
- opts.separator "Options:"
38
-
39
- opts.on("-c", "--configfile PATH", "Path to config (/etc/log_files.yml)") do |v|
40
- options[:configfile] = File.expand_path(v)
41
- end
42
- opts.on("-d", "--dest-host HOSTNAME", "Destination syslog hostname or IP (logs.papertrailapp.com)") do |v|
43
- options[:dest_host] = v
44
- end
45
- opts.on("-D", "--no-detach", "Don't daemonize and detach from the terminal") do
46
- daemonize_options[:ontop] = true
47
- daemonize_options[:ARGV] = ['run']
48
- # write PID file in . because /var/run is sometimes only writable by root
49
- daemonize_options[:dir_mode] = :script
50
- daemonize_options[:dir] = '.'
51
- end
52
- opts.on("-f", "--facility FACILITY", "Facility (user)") do |v|
53
- options[:facility] = v.upcase
54
- end
55
- opts.on("-p", "--dest-port PORT", "Destination syslog port (514)") do |v|
56
- options[:dest_port] = v
57
- end
58
- opts.on("-P", "--pid-dir DIRECTORY", "Directory to write .pid file in (/var/run/)") do |v|
59
- daemonize_options[:dir_mode] = :script
60
- daemonize_options[:dir] = v
61
- end
62
- opts.on("-s", "--severity SEVERITY", "Severity (notice)") do |v|
63
- options[:severity] = v.upcase
64
- end
65
- opts.on("--strip-color", "Strip color codes") do
66
- options[:strip_color] = true
67
- end
68
- opts.on_tail("-h", "--help", "Show this message") do
69
- puts opts
70
- exit
71
- end
72
- end
73
-
74
- op.parse!
75
-
76
- files = ARGV
77
- if File.exist?(options[:configfile])
78
- config = open(options[:configfile]) do |f|
79
- YAML.load(f)
80
- end
81
-
82
- files += config['files']
83
- if config['destination']
84
- options[:dest_host] = config['destination']['host'] if config['destination']['host']
85
- options[:dest_port] = config['destination']['port'] if config['destination']['port']
86
- end
87
- if config['hostname']
88
- options[:hostname] = config['hostname']
89
- end
90
- elsif files.empty?
91
- puts "No filenames provided and #{options[:configfile]} not found."
92
- puts ''
93
- puts op
94
- exit
95
- end
96
-
97
- # handle relative paths before Daemonize changes the wd to / and expand wildcards
98
- files = files.map { |f| Dir.glob(f) }.flatten.map { |f| File.expand_path(f) }.uniq
99
-
100
- Daemons.run_proc(daemonize_options[:app_name], daemonize_options) do
101
- EventMachine.run do
102
- socket = EventMachine.open_datagram_socket('0.0.0.0', 0)
103
-
104
- files.each do |path|
105
- begin
106
- EventMachine::file_tail(File.expand_path(path), RemoteSyslog::Reader,
107
- options[:dest_host], options[:dest_port],
108
- :socket => socket, :facility => options[:facility],
109
- :severity => options[:severity], :strip_color => options[:strip_color],
110
- :hostname => options[:hostname])
111
-
112
- rescue Errno::ENOENT => e
113
- puts "#{File.expand_path(path)} not found, continuing. (#{e.message})"
114
- end
115
- end
116
- end
117
- end
118
-
119
- end
120
-
121
- remote_syslog_daemon(ARGV)
6
+ RemoteSyslog::Cli.process!(ARGV)
@@ -0,0 +1,5 @@
1
+ files: [/var/log/messages]
2
+ parse_fields: syslog # predefined regex name or double-quoted regex
3
+ destination:
4
+ host: logs.papertrailapp.com
5
+ port: 12345 # optional, defaults to 514
data/lib/remote_syslog.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module RemoteSyslog
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
4
4
 
5
- require 'remote_syslog/levels'
6
5
  require 'remote_syslog/reader'
@@ -0,0 +1,168 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+ require 'pathname'
4
+ require 'daemons'
5
+
6
+
7
+ module RemoteSyslog
8
+ class Cli
9
+ FIELD_REGEXES = {
10
+ 'syslog' => /^(\w+ \d+ \S+) (\w+) ([^: ]+):? (.*)$/,
11
+ }
12
+
13
+ def self.process!(argv)
14
+ c = new(argv)
15
+ c.parse
16
+ c.run
17
+ end
18
+
19
+ def initialize(argv)
20
+ @argv = argv
21
+
22
+ @app_name = File.basename($0) || 'remote_syslog'
23
+
24
+ @configfile = '/etc/log_files.yml'
25
+ @strip_color = false
26
+
27
+ @daemonize_options = {
28
+ :dir_mode => :system,
29
+ :backtrace => false,
30
+ :monitor => false,
31
+ }
32
+ end
33
+
34
+ def pid_file=(v)
35
+ m = v.match(%r{^(.+/)?([^/]+?)(\.pid)?$})
36
+ if m[1]
37
+ @daemonize_options[:dir_mode] = :normal
38
+ @daemonize_options[:dir] = m[1]
39
+ end
40
+
41
+ @app_name = m[2]
42
+ end
43
+
44
+ def parse
45
+ op = OptionParser.new do |opts|
46
+ opts.banner = "Usage: remote_syslog [options] <path to add'l log 1> .. <path to add'l log n>"
47
+ opts.separator ''
48
+ opts.separator "Example: remote_syslog -c configs/logs.yml -p 12345 /var/log/mysqld.log"
49
+ opts.separator ''
50
+ opts.separator "Options:"
51
+
52
+ opts.on("-c", "--configfile PATH", "Path to config (/etc/log_files.yml)") do |v|
53
+ @configfile = File.expand_path(v)
54
+ end
55
+ opts.on("-d", "--dest-host HOSTNAME", "Destination syslog hostname or IP (logs.papertrailapp.com)") do |v|
56
+ @dest_host = v
57
+ end
58
+ opts.on("-p", "--dest-port PORT", "Destination syslog port (514)") do |v|
59
+ @dest_port = v
60
+ end
61
+ opts.on("-D", "--no-detach", "Don't daemonize and detach from the terminal") do
62
+ @no_detach = true
63
+ end
64
+ opts.on("-f", "--facility FACILITY", "Facility (user)") do |v|
65
+ @facility = v
66
+ end
67
+ opts.on("--hostname HOST", "Local hostname to send from") do |v|
68
+ @hostname = v
69
+ end
70
+ opts.on("-P", "--pid-dir DIRECTORY", "Directory to write .pid file in (/var/run/)") do |v|
71
+ @daemonize_options[:dir_mode] = :normal
72
+ @daemonize_options[:dir] = v
73
+ end
74
+ opts.on("--pid-file FILENAME", "PID filename (<program name>.pid)") do |v|
75
+ self.pid_file = v
76
+ end
77
+ opts.on("--parse-syslog", "Parse file as syslog-formatted file") do
78
+ @parse_fields = FIELD_REGEXES['syslog']
79
+ end
80
+ opts.on("-s", "--severity SEVERITY", "Severity (notice)") do |v|
81
+ @severity = v
82
+ end
83
+ opts.on("--strip-color", "Strip color codes") do
84
+ @strip_color = true
85
+ end
86
+ opts.on_tail("-h", "--help", "Show this message") do
87
+ puts opts
88
+ exit
89
+ end
90
+ end
91
+
92
+ op.parse!(@argv)
93
+
94
+ @files = @argv
95
+
96
+ parse_config
97
+
98
+ @dest_host ||= 'logs.papertrailapp.com'
99
+ @dest_port ||= 514
100
+
101
+ if @files.empty?
102
+ puts "No filenames provided and #{@configfile} not found."
103
+ puts ''
104
+ puts op
105
+ exit
106
+ end
107
+
108
+ # handle relative paths before Daemonize changes the wd to / and expand wildcards
109
+ @files = @files.map { |f| Dir.glob(f) }.flatten.map { |f| File.expand_path(f) }.uniq
110
+
111
+ end
112
+
113
+ def parse_config
114
+ if File.exist?(@configfile)
115
+ config = open(@configfile) do |f|
116
+ YAML.load(f)
117
+ end
118
+
119
+ @files += Array(config['files'])
120
+
121
+ if config['destination'] && config['destination']['host']
122
+ @dest_host ||= config['destination']['host']
123
+ end
124
+
125
+ if config['destination'] && config['destination']['port']
126
+ @dest_port ||= config['destination']['port']
127
+ end
128
+
129
+ if config['hostname']
130
+ @hostname = config['hostname']
131
+ end
132
+
133
+ if config['parse_fields']
134
+ @parse_fields = FIELD_REGEXES[config['parse_fields']] || Regexp.new(config['parse_fields'])
135
+ end
136
+ end
137
+ end
138
+
139
+ def run
140
+ if @no_detach
141
+ start
142
+ else
143
+ Daemons.run_proc(@app_name, @daemonize_options) do
144
+ start
145
+ end
146
+ end
147
+ end
148
+
149
+ def start
150
+ EventMachine.run do
151
+ socket = EventMachine.open_datagram_socket('0.0.0.0', 0)
152
+
153
+ @files.each do |path|
154
+ begin
155
+ EventMachine::file_tail(path, RemoteSyslog::Reader,
156
+ @dest_host, @dest_port,
157
+ :socket => socket, :facility => @facility,
158
+ :severity => @severity, :strip_color => @strip_color,
159
+ :hostname => @hostname, :parse_fields => @parse_fields)
160
+
161
+ rescue Errno::ENOENT => e
162
+ puts "#{path} not found, continuing. (#{e.message})"
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -1,3 +1,4 @@
1
+ require 'socket'
1
2
  require 'eventmachine'
2
3
  require 'eventmachine-tail'
3
4
  require 'em-dns-resolver'
@@ -13,6 +14,7 @@ module RemoteSyslog
13
14
  @destination_address = destination_address
14
15
  @destination_port = destination_port.to_i
15
16
 
17
+ @parse_fields = options[:parse_fields]
16
18
  @strip_color = options[:strip_color]
17
19
 
18
20
  @socket = options[:socket] || EventMachine.open_datagram_socket('0.0.0.0', 0)
@@ -31,6 +33,11 @@ module RemoteSyslog
31
33
  @packet.severity = options[:severity] || 'notice'
32
34
  @packet.tag = options[:program] || File.basename(path) || File.basename($0)
33
35
 
36
+ # Make sure the tag isn't too long
37
+ if @packet.tag.length > 32
38
+ @packet.tag = @packet.tag[0..31]
39
+ end
40
+
34
41
  # Try to resolve the destination address
35
42
  resolve_destination_address
36
43
 
@@ -63,6 +70,14 @@ module RemoteSyslog
63
70
  packet = @packet.dup
64
71
  packet.content = message
65
72
 
73
+ if @parse_fields
74
+ if message =~ @parse_fields
75
+ packet.hostname = $2 if $2 && $2 != ''
76
+ packet.tag = $3 if $3 && $2 != ''
77
+ packet.content = $4 if $4 && $4 != ''
78
+ end
79
+ end
80
+
66
81
  @socket.send_datagram(packet.assemble, destination_address, @destination_port)
67
82
  end
68
83
  end
@@ -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.1.1'
12
- s.date = '2011-07-18'
11
+ s.version = '1.2.0'
12
+ s.date = '2011-07-25'
13
13
  s.rubyforge_project = 'remote_syslog'
14
14
 
15
15
  ## Make sure your summary is short. The description may be as long
@@ -61,10 +61,11 @@ Gem::Specification.new do |s|
61
61
  Rakefile
62
62
  bin/remote_syslog
63
63
  examples/log_files.yml.example
64
+ examples/log_files.yml.example.syslog
64
65
  examples/remote_syslog.init.d
65
66
  examples/remote_syslog.supervisor.conf
66
67
  lib/remote_syslog.rb
67
- lib/remote_syslog/levels.rb
68
+ lib/remote_syslog/cli.rb
68
69
  lib/remote_syslog/reader.rb
69
70
  remote_syslog.gemspec
70
71
  ]
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remote_syslog
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
5
- prerelease:
4
+ prerelease: false
6
5
  segments:
7
6
  - 1
8
- - 1
9
- - 1
10
- version: 1.1.1
7
+ - 2
8
+ - 0
9
+ version: 1.2.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Troy Davis
@@ -16,18 +15,16 @@ autorequire:
16
15
  bindir: bin
17
16
  cert_chain: []
18
17
 
19
- date: 2011-07-18 00:00:00 -07:00
18
+ date: 2011-07-25 00:00:00 -07:00
20
19
  default_executable: remote_syslog
21
20
  dependencies:
22
21
  - !ruby/object:Gem::Dependency
23
22
  name: daemons
24
23
  prerelease: false
25
24
  requirement: &id001 !ruby/object:Gem::Requirement
26
- none: false
27
25
  requirements:
28
26
  - - ">="
29
27
  - !ruby/object:Gem::Version
30
- hash: 3
31
28
  segments:
32
29
  - 0
33
30
  version: "0"
@@ -37,11 +34,9 @@ dependencies:
37
34
  name: eventmachine
38
35
  prerelease: false
39
36
  requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
37
  requirements:
42
38
  - - ~>
43
39
  - !ruby/object:Gem::Version
44
- hash: 59
45
40
  segments:
46
41
  - 0
47
42
  - 12
@@ -53,11 +48,9 @@ dependencies:
53
48
  name: eventmachine-tail
54
49
  prerelease: false
55
50
  requirement: &id003 !ruby/object:Gem::Requirement
56
- none: false
57
51
  requirements:
58
52
  - - ">="
59
53
  - !ruby/object:Gem::Version
60
- hash: 3
61
54
  segments:
62
55
  - 0
63
56
  version: "0"
@@ -67,11 +60,9 @@ dependencies:
67
60
  name: syslog_protocol
68
61
  prerelease: false
69
62
  requirement: &id004 !ruby/object:Gem::Requirement
70
- none: false
71
63
  requirements:
72
64
  - - ">="
73
65
  - !ruby/object:Gem::Version
74
- hash: 3
75
66
  segments:
76
67
  - 0
77
68
  version: "0"
@@ -81,11 +72,9 @@ dependencies:
81
72
  name: em-resolv-replace
82
73
  prerelease: false
83
74
  requirement: &id005 !ruby/object:Gem::Requirement
84
- none: false
85
75
  requirements:
86
76
  - - ">="
87
77
  - !ruby/object:Gem::Version
88
- hash: 3
89
78
  segments:
90
79
  - 0
91
80
  version: "0"
@@ -109,10 +98,11 @@ files:
109
98
  - Rakefile
110
99
  - bin/remote_syslog
111
100
  - examples/log_files.yml.example
101
+ - examples/log_files.yml.example.syslog
112
102
  - examples/remote_syslog.init.d
113
103
  - examples/remote_syslog.supervisor.conf
114
104
  - lib/remote_syslog.rb
115
- - lib/remote_syslog/levels.rb
105
+ - lib/remote_syslog/cli.rb
116
106
  - lib/remote_syslog/reader.rb
117
107
  - remote_syslog.gemspec
118
108
  has_rdoc: true
@@ -125,27 +115,23 @@ rdoc_options:
125
115
  require_paths:
126
116
  - lib
127
117
  required_ruby_version: !ruby/object:Gem::Requirement
128
- none: false
129
118
  requirements:
130
119
  - - ">="
131
120
  - !ruby/object:Gem::Version
132
- hash: 3
133
121
  segments:
134
122
  - 0
135
123
  version: "0"
136
124
  required_rubygems_version: !ruby/object:Gem::Requirement
137
- none: false
138
125
  requirements:
139
126
  - - ">="
140
127
  - !ruby/object:Gem::Version
141
- hash: 3
142
128
  segments:
143
129
  - 0
144
130
  version: "0"
145
131
  requirements: []
146
132
 
147
133
  rubyforge_project: remote_syslog
148
- rubygems_version: 1.4.2
134
+ rubygems_version: 1.3.6
149
135
  signing_key:
150
136
  specification_version: 2
151
137
  summary: Monitor plain text log file(s) for new entries and send to remote syslog collector
@@ -1,37 +0,0 @@
1
- module RemoteSyslog
2
- class Levels
3
- SEVERITIES = {
4
- :emerg => 0,
5
- :alert => 1,
6
- :crit => 2,
7
- :err => 3,
8
- :warning => 4,
9
- :notice => 5,
10
- :info => 6,
11
- :debug => 7
12
- }.freeze
13
-
14
- FACILITIES = {
15
- :kern => (0<<3),
16
- :user => (1<<3),
17
- :mail => (2<<3),
18
- :daemon => (3<<3),
19
- :auth => (4<<3),
20
- :syslog => (5<<3),
21
- :lpr => (6<<3),
22
- :news => (7<<3),
23
- :uucp => (8<<3),
24
- :cron => (9<<3),
25
- :authpriv => (10<<3),
26
- :ftp => (11<<3),
27
- :local0 => (16<<3),
28
- :local1 => (17<<3),
29
- :local2 => (18<<3),
30
- :local3 => (19<<3),
31
- :local4 => (20<<3),
32
- :local5 => (21<<3),
33
- :local6 => (22<<3),
34
- :local7 => (23<<3)
35
- }.freeze
36
- end
37
- end