syslog_protocol 0.9.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/Gemfile +2 -0
- data/README.md +84 -0
- data/Rakefile +150 -0
- data/lib/syslog_protocol.rb +8 -0
- data/lib/syslog_protocol/common.rb +79 -0
- data/lib/syslog_protocol/logger.rb +21 -0
- data/lib/syslog_protocol/packet.rb +122 -0
- data/lib/syslog_protocol/parser.rb +57 -0
- data/syslog_protocol.gemspec +81 -0
- data/test/helper.rb +13 -0
- data/test/test_logger.rb +28 -0
- data/test/test_packet.rb +92 -0
- data/test/test_parser.rb +51 -0
- metadata +99 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Syslog protocol
|
2
|
+
|
3
|
+
roughly conforms to the murky shade of grey known as http://www.faqs.org/rfcs/rfc3164.html
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
|
7
|
+
### Manipulate packets manually
|
8
|
+
|
9
|
+
require 'syslog_protocol'
|
10
|
+
|
11
|
+
p = SyslogProtocol::Packet.new
|
12
|
+
p.hostname = "space_station"
|
13
|
+
p.facility = "kern"
|
14
|
+
p.severity = "warn"
|
15
|
+
p.tag = "test"
|
16
|
+
p.content = "flight control broken"
|
17
|
+
p.to_s
|
18
|
+
# => "<4>Aug 1 14:01:17 space_station flight control broken"
|
19
|
+
p.pri
|
20
|
+
# => 4
|
21
|
+
p.facility
|
22
|
+
# => 0
|
23
|
+
p.facility_name
|
24
|
+
# => "kern"
|
25
|
+
p.severity_name
|
26
|
+
# => "warn"
|
27
|
+
p.warn?
|
28
|
+
# => true
|
29
|
+
p.info?
|
30
|
+
# => false
|
31
|
+
|
32
|
+
|
33
|
+
### Use a Logger to generate packets
|
34
|
+
|
35
|
+
require 'syslog_protocol'
|
36
|
+
|
37
|
+
logger = SyslogProtocol::Logger.new("space_station", "uucp")
|
38
|
+
logger.debug("looking for uucp on board the space station")
|
39
|
+
# => "<67>Aug 1 14:02:29 space_station looking for uucp on board the space station"
|
40
|
+
logger.emerg("omg we cant find uucp on the space station")
|
41
|
+
# => "<64>Aug 1 14:03:56 space_station omg we cant find uucp on the space station"
|
42
|
+
|
43
|
+
|
44
|
+
### Parse packets
|
45
|
+
|
46
|
+
require 'syslog_protocol'
|
47
|
+
|
48
|
+
p = SyslogProtocol.parse("<34>Oct 11 22:14:15 space_station space is really getting to me")
|
49
|
+
p.facility
|
50
|
+
# => 4
|
51
|
+
p.severity_name
|
52
|
+
# => "crit"
|
53
|
+
p.time
|
54
|
+
# => Sun Oct 11 22:14:15 -0700 2009
|
55
|
+
p.content
|
56
|
+
# => "space is really getting to me"
|
57
|
+
|
58
|
+
|
59
|
+
### It yells at you for trying to abuse the protocol
|
60
|
+
|
61
|
+
p = SyslogProtocol::Packet.new
|
62
|
+
p.facility = 34534534
|
63
|
+
# => ArgumentError: Facility must be within 0-23
|
64
|
+
p.hostname = "my host"
|
65
|
+
# => ArgumentError: Hostname may not contain spaces
|
66
|
+
p.hostname = "h\000stname"
|
67
|
+
# => ArgumentError: Hostname may only contain ASCII characters 33-126
|
68
|
+
# ...etc.
|
69
|
+
# It will also unintelligently truncate messages > 1024 bytes so beware.
|
70
|
+
|
71
|
+
|
72
|
+
## Caveats
|
73
|
+
|
74
|
+
Syslog is a terrible and loosely defined protocol. Many devices and programs do not
|
75
|
+
conform to it and so their packets may not be parsed correctly by this interpretation,
|
76
|
+
nor may the packets generated by this necessarily be recognized by other devices or programs ;)
|
77
|
+
|
78
|
+
This is probably wrong and buggy, and i know the code is ugly, thanks.
|
79
|
+
|
80
|
+
Good luck.
|
81
|
+
|
82
|
+
## TODO
|
83
|
+
|
84
|
+
* Update to more closely map to the ruby `syslog` API where possible
|
data/Rakefile
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rake/rdoctask'
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
#
|
86
|
+
# Packaging tasks
|
87
|
+
#
|
88
|
+
#############################################################################
|
89
|
+
|
90
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
91
|
+
task :release => :build do
|
92
|
+
unless `git branch` =~ /^\* master$/
|
93
|
+
puts "You must be on the master branch to release!"
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
97
|
+
sh "git tag v#{version}"
|
98
|
+
sh "git push origin master"
|
99
|
+
sh "git push origin v#{version}"
|
100
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Build #{gem_file} into the pkg directory"
|
104
|
+
task :build => :gemspec do
|
105
|
+
sh "mkdir -p pkg"
|
106
|
+
sh "gem build #{gemspec_file}"
|
107
|
+
sh "mv #{gem_file} pkg"
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Generate #{gemspec_file}"
|
111
|
+
task :gemspec => :validate do
|
112
|
+
# read spec file and split out manifest section
|
113
|
+
spec = File.read(gemspec_file)
|
114
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
115
|
+
|
116
|
+
# replace name version and date
|
117
|
+
replace_header(head, :name)
|
118
|
+
replace_header(head, :version)
|
119
|
+
replace_header(head, :date)
|
120
|
+
#comment this out if your rubyforge_project has a different name
|
121
|
+
replace_header(head, :rubyforge_project)
|
122
|
+
|
123
|
+
# determine file list from git ls-files
|
124
|
+
files = `git ls-files`.
|
125
|
+
split("\n").
|
126
|
+
sort.
|
127
|
+
reject { |file| file =~ /^\./ }.
|
128
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
129
|
+
map { |file| " #{file}" }.
|
130
|
+
join("\n")
|
131
|
+
|
132
|
+
# piece file back together and write
|
133
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
134
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
135
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
136
|
+
puts "Updated #{gemspec_file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
desc "Validate #{gemspec_file}"
|
140
|
+
task :validate do
|
141
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
142
|
+
unless libfiles.empty?
|
143
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
144
|
+
exit!
|
145
|
+
end
|
146
|
+
unless Dir['VERSION*'].empty?
|
147
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
148
|
+
exit!
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module SyslogProtocol
|
2
|
+
# These hashes stolen from Syslog.pm
|
3
|
+
|
4
|
+
FACILITIES = {
|
5
|
+
'kern' => 0,
|
6
|
+
'user' => 1,
|
7
|
+
'mail' => 2,
|
8
|
+
'daemon' => 3,
|
9
|
+
'auth' => 4,
|
10
|
+
'syslog' => 5,
|
11
|
+
'lpr' => 6,
|
12
|
+
'news' => 7,
|
13
|
+
'uucp' => 8,
|
14
|
+
'cron' => 9,
|
15
|
+
'authpriv' => 10,
|
16
|
+
'ftp' => 11,
|
17
|
+
'ntp' => 12,
|
18
|
+
'audit' => 13,
|
19
|
+
'alert' => 14,
|
20
|
+
'at' => 15,
|
21
|
+
'local0' => 16,
|
22
|
+
'local1' => 17,
|
23
|
+
'local2' => 18,
|
24
|
+
'local3' => 19,
|
25
|
+
'local4' => 20,
|
26
|
+
'local5' => 21,
|
27
|
+
'local6' => 22,
|
28
|
+
'local7' => 23
|
29
|
+
}
|
30
|
+
|
31
|
+
FACILITY_INDEX = {
|
32
|
+
0 => 'kern',
|
33
|
+
1 => 'user',
|
34
|
+
2 => 'mail',
|
35
|
+
3 => 'daemon',
|
36
|
+
4 => 'auth',
|
37
|
+
5 => 'syslog',
|
38
|
+
6 => 'lpr',
|
39
|
+
7 => 'news',
|
40
|
+
8 => 'uucp',
|
41
|
+
9 => 'cron',
|
42
|
+
10 => 'authpriv',
|
43
|
+
11 => 'ftp',
|
44
|
+
12 => 'ntp',
|
45
|
+
13 => 'audit',
|
46
|
+
14 => 'alert',
|
47
|
+
15 => 'at',
|
48
|
+
16 => 'local0',
|
49
|
+
17 => 'local1',
|
50
|
+
18 => 'local2',
|
51
|
+
19 => 'local3',
|
52
|
+
20 => 'local4',
|
53
|
+
21 => 'local5',
|
54
|
+
22 => 'local6',
|
55
|
+
23 => 'local7'
|
56
|
+
}
|
57
|
+
|
58
|
+
SEVERITIES = {
|
59
|
+
'emerg' => 0,
|
60
|
+
'alert' => 1,
|
61
|
+
'crit' => 2,
|
62
|
+
'err' => 3,
|
63
|
+
'warn' => 4,
|
64
|
+
'notice' => 5,
|
65
|
+
'info' => 6,
|
66
|
+
'debug' => 7
|
67
|
+
}
|
68
|
+
|
69
|
+
SEVERITY_INDEX = {
|
70
|
+
0 => 'emerg',
|
71
|
+
1 => 'alert',
|
72
|
+
2 => 'crit',
|
73
|
+
3 => 'err',
|
74
|
+
4 => 'warn',
|
75
|
+
5 => 'notice',
|
76
|
+
6 => 'info',
|
77
|
+
7 => 'debug'
|
78
|
+
}
|
79
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SyslogProtocol
|
2
|
+
class Logger
|
3
|
+
def initialize(hostname, tag, facility)
|
4
|
+
@packet = Packet.new
|
5
|
+
@packet.hostname = hostname
|
6
|
+
@packet.tag = tag
|
7
|
+
@packet.facility = facility
|
8
|
+
end
|
9
|
+
|
10
|
+
SEVERITIES.each do |k,v|
|
11
|
+
define_method(k) do |content|
|
12
|
+
raise ArgumentError.new("Message may not be omitted") unless content and content.length > 0
|
13
|
+
|
14
|
+
p = @packet.dup
|
15
|
+
p.severity = k
|
16
|
+
p.content = content
|
17
|
+
p.assemble
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module SyslogProtocol
|
2
|
+
class Packet
|
3
|
+
attr_reader :facility, :severity, :hostname, :tag
|
4
|
+
attr_accessor :time, :content
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
assemble
|
8
|
+
end
|
9
|
+
|
10
|
+
def assemble
|
11
|
+
unless @hostname and @facility and @severity and @tag
|
12
|
+
raise "Could not assemble packet without hostname, tag, facility, and severity"
|
13
|
+
end
|
14
|
+
data = "<#{pri}>#{generate_timestamp} #{@hostname} #{@tag}: #{@content}"
|
15
|
+
while data.bytesize > 1024
|
16
|
+
data = data[0..(data.length-2)]
|
17
|
+
end
|
18
|
+
data
|
19
|
+
end
|
20
|
+
|
21
|
+
def facility=(f)
|
22
|
+
if f.is_a? Integer
|
23
|
+
if (0..23).include?(f)
|
24
|
+
@facility = f
|
25
|
+
else
|
26
|
+
raise ArgumentError.new "Facility must be within 0-23"
|
27
|
+
end
|
28
|
+
elsif f.is_a? String
|
29
|
+
if facility = FACILITIES[f]
|
30
|
+
@facility = facility
|
31
|
+
else
|
32
|
+
raise ArgumentError.new "'#{f}' is not a designated facility"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise ArgumentError.new "Facility must be a designated number or string"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def tag=(t)
|
40
|
+
unless t && t.is_a?(String) && t.length > 0
|
41
|
+
raise ArgumentError, "Tag must not be omitted"
|
42
|
+
end
|
43
|
+
if t.length > 32
|
44
|
+
raise ArgumentError, "Tag must not be longer than 32 characters"
|
45
|
+
end
|
46
|
+
if t =~ /\s/
|
47
|
+
raise ArgumentError, "Tag may not contain spaces"
|
48
|
+
end
|
49
|
+
if t =~ /[^\x21-\x7E]/
|
50
|
+
raise ArgumentError, "Tag may only contain ASCII characters 33-126"
|
51
|
+
end
|
52
|
+
|
53
|
+
@tag = t
|
54
|
+
end
|
55
|
+
|
56
|
+
def severity=(s)
|
57
|
+
if s.is_a? Integer
|
58
|
+
if (0..7).include?(s)
|
59
|
+
@severity = s
|
60
|
+
else
|
61
|
+
raise ArgumentError.new "Severity must be within 0-7"
|
62
|
+
end
|
63
|
+
elsif s.is_a? String
|
64
|
+
if severity = SEVERITIES[s]
|
65
|
+
@severity = severity
|
66
|
+
else
|
67
|
+
raise ArgumentError.new "'#{s}' is not a designated severity"
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ArgumentError.new "Severity must be a designated number or string"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def hostname=(h)
|
75
|
+
unless h and h.is_a? String and h.length > 0
|
76
|
+
raise ArgumentError.new("Hostname may not be omitted")
|
77
|
+
end
|
78
|
+
if h =~ /\s/
|
79
|
+
raise ArgumentError.new("Hostname may not contain spaces")
|
80
|
+
end
|
81
|
+
if h =~ /[^\x21-\x7E]/
|
82
|
+
raise ArgumentError.new("Hostname may only contain ASCII characters 33-126")
|
83
|
+
end
|
84
|
+
@hostname = h
|
85
|
+
end
|
86
|
+
|
87
|
+
def facility_name
|
88
|
+
FACILITY_INDEX[@facility]
|
89
|
+
end
|
90
|
+
|
91
|
+
def severity_name
|
92
|
+
SEVERITY_INDEX[@severity]
|
93
|
+
end
|
94
|
+
|
95
|
+
def pri
|
96
|
+
(@facility * 8) + @severity
|
97
|
+
end
|
98
|
+
|
99
|
+
def pri=(p)
|
100
|
+
unless p.is_a? Integer and (0..191).include?(p)
|
101
|
+
raise ArgumentError.new "PRI must be a number between 0 and 191"
|
102
|
+
end
|
103
|
+
@facility = p / 8
|
104
|
+
@severity = p - (@facility * 8)
|
105
|
+
end
|
106
|
+
|
107
|
+
def generate_timestamp
|
108
|
+
time = @time || Time.now
|
109
|
+
# The timestamp format requires that a day with fewer than 2 digits have
|
110
|
+
# what would normally be a preceding zero, be instead an extra space.
|
111
|
+
day = time.strftime("%d")
|
112
|
+
day = day.sub(/^0/, ' ') if day =~ /^0\d/
|
113
|
+
time.strftime("%b #{day} %H:%M:%S")
|
114
|
+
end
|
115
|
+
|
116
|
+
SEVERITIES.each do |k,v|
|
117
|
+
define_method("#{k}?") {SEVERITIES[k] == @severity}
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module SyslogProtocol
|
4
|
+
|
5
|
+
def self.parse(msg, origin=nil)
|
6
|
+
packet = Packet.new
|
7
|
+
original_msg = msg.dup
|
8
|
+
pri = parse_pri(msg)
|
9
|
+
if pri and (pri = pri.to_i).is_a? Integer and (0..191).include?(pri)
|
10
|
+
packet.pri = pri
|
11
|
+
else
|
12
|
+
# If there isn't a valid PRI, treat the entire message as content
|
13
|
+
packet.pri = 13
|
14
|
+
packet.time = Time.now
|
15
|
+
packet.hostname = origin || 'unknown'
|
16
|
+
packet.content = original_msg
|
17
|
+
return packet
|
18
|
+
end
|
19
|
+
time = parse_time(msg)
|
20
|
+
if time
|
21
|
+
packet.time = Time.parse(time)
|
22
|
+
else
|
23
|
+
packet.time = Time.now
|
24
|
+
end
|
25
|
+
hostname = parse_hostname(msg)
|
26
|
+
packet.hostname = hostname || origin
|
27
|
+
if m = msg.match(/^(\w+)(: | )(.*)$/)
|
28
|
+
packet.tag = m[1]
|
29
|
+
packet.content = m[3]
|
30
|
+
else
|
31
|
+
packet.tag = 'unknown'
|
32
|
+
packet.content = msg
|
33
|
+
end
|
34
|
+
packet
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.parse_pri(msg)
|
40
|
+
pri = msg.slice!(/<(\d\d?\d?)>/)
|
41
|
+
pri = pri.slice(/\d\d?\d?/) if pri
|
42
|
+
if !pri or (pri =~ /^0/ and pri !~ /^0$/)
|
43
|
+
return nil
|
44
|
+
else
|
45
|
+
return pri
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse_time(msg)
|
50
|
+
msg.slice!(/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\s|[1-9])\d\s\d\d:\d\d:\d\d\s/)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_hostname(msg)
|
54
|
+
msg.slice!(/^[\x21-\x7E]+\s/).rstrip
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'syslog_protocol'
|
16
|
+
s.version = '0.9.0'
|
17
|
+
s.date = "2009-08-01"
|
18
|
+
# s.rubyforge_project = 'syslog_protocol'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "Syslog protocol parser and generator"
|
23
|
+
s.description = "Syslog protocol parser and generator"
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["Jake Douglas", 'Eric Lindvall']
|
29
|
+
s.email = [ "jakecdouglas@gmail.com", 'eric@5stops.com' ]
|
30
|
+
s.homepage = 'https://github.com/eric/syslog_protocol'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## This sections is only necessary if you have C extensions.
|
37
|
+
# s.require_paths << 'ext'
|
38
|
+
# s.extensions = %w[ext/extconf.rb]
|
39
|
+
|
40
|
+
## If your gem includes any executables, list them here.
|
41
|
+
# s.executables = ["name"]
|
42
|
+
# s.default_executable = 'name'
|
43
|
+
|
44
|
+
## Specify any RDoc options here. You'll want to add your README and
|
45
|
+
## LICENSE files to the extra_rdoc_files list.
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.extra_rdoc_files = %w[README.md]
|
48
|
+
|
49
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
50
|
+
## that are needed for an end user to actually USE your code.
|
51
|
+
# s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
|
52
|
+
|
53
|
+
## List your development dependencies here. Development dependencies are
|
54
|
+
## those that are only needed during development
|
55
|
+
s.add_development_dependency('bacon', [ '~> 1.1.0' ])
|
56
|
+
|
57
|
+
## Leave this section as-is. It will be automatically generated from the
|
58
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
59
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
60
|
+
# = MANIFEST =
|
61
|
+
s.files = %w[
|
62
|
+
Gemfile
|
63
|
+
README.md
|
64
|
+
Rakefile
|
65
|
+
lib/syslog_protocol.rb
|
66
|
+
lib/syslog_protocol/common.rb
|
67
|
+
lib/syslog_protocol/logger.rb
|
68
|
+
lib/syslog_protocol/packet.rb
|
69
|
+
lib/syslog_protocol/parser.rb
|
70
|
+
syslog_protocol.gemspec
|
71
|
+
test/helper.rb
|
72
|
+
test/test_logger.rb
|
73
|
+
test/test_packet.rb
|
74
|
+
test/test_parser.rb
|
75
|
+
]
|
76
|
+
# = MANIFEST =
|
77
|
+
|
78
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
79
|
+
## matches what you actually use.
|
80
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
81
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_logger.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe "syslog logger" do
|
4
|
+
|
5
|
+
it "create a new logger with hostname and facility" do
|
6
|
+
lambda {@logger = SyslogProto::Logger.new("space_station", 'test', "local0")}.should.not.raise
|
7
|
+
end
|
8
|
+
|
9
|
+
it "hostname and facility must conform to the requirements of a Packet" do
|
10
|
+
lambda {SyslogProto::Logger.new("space station", "some shit", 'test test')}.should.raise ArgumentError
|
11
|
+
end
|
12
|
+
|
13
|
+
it "generates packets" do
|
14
|
+
# We have to set a time so we have a consistant timestamp to check against..
|
15
|
+
p = @logger.instance_variable_get("@packet")
|
16
|
+
p.time = Time.now
|
17
|
+
ts = p.generate_timestamp
|
18
|
+
@logger.debug("vacuum tubez are operational").should.equal "<135>#{ts} space_station test: vacuum tubez are operational"
|
19
|
+
@logger.info("firing thrusters at 13 degrees").should.equal "<134>#{ts} space_station test: firing thrusters at 13 degrees"
|
20
|
+
@logger.notice("the hyper drive has been activated").should.equal "<133>#{ts} space_station test: the hyper drive has been activated"
|
21
|
+
@logger.warn("meteorites incoming!").should.equal "<132>#{ts} space_station test: meteorites incoming!"
|
22
|
+
@logger.err("vacuum tube 3 in hyper drive failed").should.equal "<131>#{ts} space_station test: vacuum tube 3 in hyper drive failed"
|
23
|
+
@logger.crit("wing struck by a meteorite!").should.equal "<130>#{ts} space_station test: wing struck by a meteorite!"
|
24
|
+
@logger.alert("LEAKING ATMOSPHERE").should.equal "<129>#{ts} space_station test: LEAKING ATMOSPHERE"
|
25
|
+
@logger.emerg("LEAKING ASTRONAUTS WE ARE DONE").should.equal "<128>#{ts} space_station test: LEAKING ASTRONAUTS WE ARE DONE"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/test/test_packet.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe "a syslog packet" do
|
4
|
+
|
5
|
+
@p = SyslogProto::Packet.new
|
6
|
+
|
7
|
+
it "should embarrass a person who does not set the fields" do
|
8
|
+
lambda { @p.to_s }.should.raise RuntimeError
|
9
|
+
end
|
10
|
+
|
11
|
+
it "hostname may not be omitted" do
|
12
|
+
lambda {@p.hostname = ""}.should.raise ArgumentError
|
13
|
+
end
|
14
|
+
|
15
|
+
it "hostname may only contain ASCII characters 33-126 (no spaces!)" do
|
16
|
+
lambda {@p.hostname = "linux box"}.should.raise ArgumentError
|
17
|
+
lambda {@p.hostname = "\000" + "linuxbox"}.should.raise ArgumentError
|
18
|
+
lambda {@p.hostname = "space_station"}.should.not.raise
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'tag may only contain ASCII characters 33-126 (no spaces!)' do
|
22
|
+
lambda {@p.tag = "linux box"}.should.raise ArgumentError
|
23
|
+
lambda {@p.tag = "\000" + "linuxbox"}.should.raise ArgumentError
|
24
|
+
lambda {@p.tag = "test"}.should.not.raise
|
25
|
+
end
|
26
|
+
|
27
|
+
it "facility may only be set within 0-23 or with a proper string name" do
|
28
|
+
lambda {@p.facility = 666}.should.raise ArgumentError
|
29
|
+
lambda {@p.facility = "mir space station"}.should.raise ArgumentError
|
30
|
+
|
31
|
+
lambda {@p.facility = 16}.should.not.raise
|
32
|
+
@p.facility.should.equal 16
|
33
|
+
lambda {@p.facility = 'local0'}.should.not.raise
|
34
|
+
@p.facility.should.equal 16
|
35
|
+
end
|
36
|
+
|
37
|
+
it "severity may only be set within 0-7 or with a proper string name" do
|
38
|
+
lambda {@p.severity = 9876}.should.raise ArgumentError
|
39
|
+
lambda {@p.severity = "omgbroken"}.should.raise ArgumentError
|
40
|
+
|
41
|
+
lambda {@p.severity = 6}.should.not.raise
|
42
|
+
@p.severity.should.equal 6
|
43
|
+
lambda {@p.severity = 'info'}.should.not.raise
|
44
|
+
@p.severity.should.equal 6
|
45
|
+
end
|
46
|
+
|
47
|
+
it "severity can be checked using 'some_severity?' methods" do
|
48
|
+
@p.info?.should.equal true
|
49
|
+
@p.alert?.should.equal false
|
50
|
+
@p.emerg?.should.equal false
|
51
|
+
end
|
52
|
+
|
53
|
+
it "PRI is calculated from the facility and severity" do
|
54
|
+
@p.pri.should.equal 134
|
55
|
+
end
|
56
|
+
|
57
|
+
it "PRI may only be within 0-191" do
|
58
|
+
lambda {@p.pri = 22331}.should.raise ArgumentError
|
59
|
+
lambda {@p.pri = "foo"}.should.raise ArgumentError
|
60
|
+
end
|
61
|
+
|
62
|
+
it "facility and severity are deduced and set from setting a valid PRI" do
|
63
|
+
@p.pri = 165
|
64
|
+
@p.severity.should.equal 5
|
65
|
+
@p.facility.should.equal 20
|
66
|
+
end
|
67
|
+
|
68
|
+
it "return the proper names for facility and severity" do
|
69
|
+
@p.severity_name.should.equal 'notice'
|
70
|
+
@p.facility_name.should.equal 'local4'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "set a message, which apparently can be anything" do
|
74
|
+
@p.content = "exploring ze black hole"
|
75
|
+
@p.content.should.equal "exploring ze black hole"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "timestamp must conform to the retarded format" do
|
79
|
+
@p.generate_timestamp.should.match /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\s|[1-9])\d\s\d\d:\d\d:\d\d/
|
80
|
+
end
|
81
|
+
|
82
|
+
it "use the current time and assemble the packet" do
|
83
|
+
timestamp = @p.generate_timestamp
|
84
|
+
@p.to_s.should.equal "<165>#{timestamp} space_station test: exploring ze black hole"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "packets larger than 1024 will be truncated" do
|
88
|
+
@p.content = "space warp" * 1000
|
89
|
+
@p.to_s.bytesize.should.equal 1024
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/test/test_parser.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe "syslog packet parser" do
|
4
|
+
|
5
|
+
it "parse some valid packets" do
|
6
|
+
p = SyslogProto.parse("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8")
|
7
|
+
p.facility.should.equal 4
|
8
|
+
p.severity.should.equal 2
|
9
|
+
p.pri.should.equal 34
|
10
|
+
p.hostname.should.equal "mymachine"
|
11
|
+
p.tag.should.equal 'su'
|
12
|
+
p.content.should.equal "'su root' failed for lonvick on /dev/pts/8"
|
13
|
+
p.time.should.equal Time.parse("Oct 11 22:14:15")
|
14
|
+
|
15
|
+
p = SyslogProto.parse("<13>Feb 5 17:32:18 10.0.0.99 test: Use the BFG!")
|
16
|
+
p.facility.should.equal 1
|
17
|
+
p.severity.should.equal 5
|
18
|
+
p.pri.should.equal 13
|
19
|
+
p.hostname.should.equal "10.0.0.99"
|
20
|
+
p.tag.should.equal 'test'
|
21
|
+
p.content.should.equal "Use the BFG!"
|
22
|
+
p.time.should.equal Time.parse("Feb 5 17:32:18")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "treat a packet with no valid PRI as all content, setting defaults" do
|
26
|
+
p = SyslogProto.parse("nomnom")
|
27
|
+
p.facility.should.equal 1
|
28
|
+
p.severity.should.equal 5
|
29
|
+
p.pri.should.equal 13
|
30
|
+
p.hostname.should.equal 'unknown'
|
31
|
+
p.content.should.equal "nomnom"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "PRI with preceding 0's shall be considered invalid" do
|
35
|
+
p = SyslogProto.parse("<045>Oct 11 22:14:15 space_station my PRI is not valid")
|
36
|
+
p.facility.should.equal 1
|
37
|
+
p.severity.should.equal 5
|
38
|
+
p.pri.should.equal 13
|
39
|
+
p.hostname.should.equal 'unknown'
|
40
|
+
p.content.should.equal "<045>Oct 11 22:14:15 space_station my PRI is not valid"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "allow the user to pass an origin to be used as the hostname if packet is invalid" do
|
44
|
+
p = SyslogProto.parse("<045>Oct 11 22:14:15 space_station my PRI is not valid", '127.0.0.1')
|
45
|
+
p.facility.should.equal 1
|
46
|
+
p.severity.should.equal 5
|
47
|
+
p.pri.should.equal 13
|
48
|
+
p.hostname.should.equal '127.0.0.1'
|
49
|
+
p.content.should.equal "<045>Oct 11 22:14:15 space_station my PRI is not valid"
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: syslog_protocol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jake Douglas
|
14
|
+
- Eric Lindvall
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2009-08-01 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: bacon
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 19
|
31
|
+
segments:
|
32
|
+
- 1
|
33
|
+
- 1
|
34
|
+
- 0
|
35
|
+
version: 1.1.0
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id001
|
38
|
+
description: Syslog protocol parser and generator
|
39
|
+
email:
|
40
|
+
- jakecdouglas@gmail.com
|
41
|
+
- eric@5stops.com
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
extra_rdoc_files:
|
47
|
+
- README.md
|
48
|
+
files:
|
49
|
+
- Gemfile
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- lib/syslog_protocol.rb
|
53
|
+
- lib/syslog_protocol/common.rb
|
54
|
+
- lib/syslog_protocol/logger.rb
|
55
|
+
- lib/syslog_protocol/packet.rb
|
56
|
+
- lib/syslog_protocol/parser.rb
|
57
|
+
- syslog_protocol.gemspec
|
58
|
+
- test/helper.rb
|
59
|
+
- test/test_logger.rb
|
60
|
+
- test/test_packet.rb
|
61
|
+
- test/test_parser.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: https://github.com/eric/syslog_protocol
|
64
|
+
licenses: []
|
65
|
+
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --charset=UTF-8
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 2
|
95
|
+
summary: Syslog protocol parser and generator
|
96
|
+
test_files:
|
97
|
+
- test/test_logger.rb
|
98
|
+
- test/test_packet.rb
|
99
|
+
- test/test_parser.rb
|