remote_syslog 1.1.1 → 1.2.0

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