systemd-journal 0.1.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb534e3c9f9996d4a6364655a349306492464e69
4
- data.tar.gz: 27bea2ecb30d9a9fef2767050ba1df2c2bd01e46
3
+ metadata.gz: 871a098e73eadfe6cd53fc8066537c37e1a1681b
4
+ data.tar.gz: cb944399a1624c724b469ca41b13a3ccb4bddcb0
5
5
  SHA512:
6
- metadata.gz: b4443ed8d9c9820483dbab619a5616d896bb5d4de1722894f0d840800a29ac32c07036f396ba9eb8fa5ade33ff633203a55a483fb238a68f6a67351ce3b532dd
7
- data.tar.gz: 5d491da1ce955d6ac9ac2ffbdda95af9e633404d0db9282705cb84138c8cbf66099f27dee17ab63964231cb1609b9667f986b64a6b8b4e3af2098a18f56550cf
6
+ metadata.gz: a399b0ddb5e9eb60bb305b631bb531588352f0015a1ac0b64880fa289877a716c44eaa34d887461a17144af21f5c520a6d759c8030c315538c117160ead48724
7
+ data.tar.gz: af798df9d31d0cc9e742104de505964bf1e07f94dfb74918407cdce0996cde716a96b440482614c57050afabc56c62aa82d218edc53fb20f3685e29ec5c1d719
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
- # Systemd::Journal
1
+ # Systemd::Journal [![Gem Version](https://badge.fury.io/rb/systemd-journal.png)](http://badge.fury.io/rb/systemd-journal) [![Build Status](https://travis-ci.org/ledbettj/systemd-journal.png?branch=master)](https://travis-ci.org/ledbettj/systemd-journal)
2
2
 
3
3
  Ruby bindings for reading from the systemd journal.
4
4
 
5
- * [documentation](http://rubydoc.info/github/ledbettj/systemd-journal/Systemd/Journal)
5
+ * [documentation](http://rubydoc.info/gems/systemd-journal)
6
6
 
7
7
  ## Installation
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
11
- gem 'systemd-journal', '~> 0.1.0'
11
+ gem 'systemd-journal', '~> 1.0.0'
12
12
 
13
13
  And then execute:
14
14
 
@@ -16,29 +16,33 @@ And then execute:
16
16
 
17
17
  ## Usage
18
18
 
19
- For example, printing all messages:
19
+ Print all messages as they occur:
20
20
 
21
21
  require 'systemd/journal'
22
22
 
23
23
  j = Systemd::Journal.new
24
-
25
- while j.move_next
26
- puts j.read_field('MESSAGE')
24
+ j.seek(:tail)
25
+
26
+ j.watch do |entry|
27
+ puts entry.message
27
28
  end
28
-
29
- Or to print all data in each entry:
29
+
30
+ Filter messages included in the journal:
30
31
 
31
32
  require 'systemd/journal'
32
-
33
+
33
34
  j = Systemd::Journal.new
34
-
35
+
36
+ # only display entries from SSHD with priority 6.
37
+ j.add_match(:priority, 6)
38
+ j.add_match(:_exe, '/usr/bin/sshd')
39
+
35
40
  while j.move_next
36
- j.current_entry do |key, value|
37
- puts "#{key}: #{value}"
38
- end
39
- puts "\n"
41
+ puts j.current_entry.message
40
42
  end
41
43
 
44
+ See the documentation for more examples.
45
+
42
46
  ## Contributing
43
47
 
44
48
  1. Fork it
data/Rakefile CHANGED
@@ -13,5 +13,9 @@ YARD::Rake::YardocTask.new do |t|
13
13
  end
14
14
 
15
15
  RSpec::Core::RakeTask.new(:spec) do |t|
16
- t.rspec_opts = "--color"
16
+ opts = ['--color']
17
+ opts << '--require ./spec/no_ffi.rb' if ENV['TRAVIS']
18
+ t.rspec_opts = opts.join(' ')
17
19
  end
20
+
21
+ task default: :spec
@@ -8,9 +8,7 @@ if ARGV.length == 0
8
8
  end
9
9
 
10
10
  j = Systemd::Journal.new(path: ARGV[0])
11
- j.seek(:head)
12
11
 
13
- while j.move_next
14
- entry = j.current_entry
15
- puts "PID #{entry['_PID']}: #{entry['MESSAGE']}"
12
+ j.each do |entry|
13
+ puts entry
16
14
  end
@@ -8,16 +8,11 @@ class SSHWatcher
8
8
  end
9
9
 
10
10
  def run
11
- @journal.add_match('_EXE', '/usr/bin/sshd')
11
+ @journal.add_match(:_exe, '/usr/bin/sshd')
12
12
  # skip all existing entries -- sd_journal_seek_tail() is currently broken.
13
13
  while @journal.move_next ; end
14
14
 
15
- while true
16
- if @journal.wait(1_000_000 * 5) != :nop
17
- process_event(@journal.current_entry) while @journal.move_next
18
- end
19
- end
20
-
15
+ @journal.watch{ |entry| process_event(entry) }
21
16
  end
22
17
 
23
18
  private
@@ -25,9 +20,9 @@ class SSHWatcher
25
20
  LOGIN_REGEXP = /Accepted\s+(?<auth_method>[^\s]+)\s+for\s+(?<user>[^\s]+)\s+from\s+(?<address>[^\s]+)/
26
21
 
27
22
  def process_event(entry)
28
- if (m = entry['MESSAGE'].match(LOGIN_REGEXP))
23
+ if (m = entry.message.match(LOGIN_REGEXP))
29
24
  timestamp = DateTime.strptime(
30
- (entry['_SOURCE_REALTIME_TIMESTAMP'].to_i / 1_000_000).to_s,
25
+ (entry._source_realtime_timestamp.to_i / 1_000_000).to_s,
31
26
  "%s"
32
27
  )
33
28
  puts "login via #{m[:auth_method]} for #{m[:user]} from #{m[:address]} at #{timestamp.ctime}"
@@ -0,0 +1,19 @@
1
+ require 'ffi'
2
+
3
+ # @private
4
+ class FFI::MemoryPointer
5
+
6
+ # monkey patch a read_size_t and write_size_t method onto
7
+ # FFI::MemoryPointer if necessary.
8
+ case (p = FFI::MemoryPointer.new(:size_t, 1)).size
9
+ when 4
10
+ alias_method(:read_size_t, :read_uint32) unless p.respond_to?(:read_size_t)
11
+ alias_method(:write_size_t, :write_uint32) unless p.respond_to?(:write_size_t)
12
+ when 8
13
+ alias_method(:read_size_t, :read_uint64) unless p.respond_to?(:read_size_t)
14
+ alias_method(:write_size_t, :write_uint64) unless p.respond_to?(:write_size_t)
15
+ else
16
+ raise RuntimeError.new("unsupported size_t width: #{p.size}")
17
+ end
18
+
19
+ end
data/lib/systemd/id128.rb CHANGED
@@ -46,9 +46,15 @@ module Systemd
46
46
 
47
47
  # providing bindings to the systemd-id128 library.
48
48
  module Native
49
- require 'ffi'
50
- extend FFI::Library
51
- ffi_lib %w[libsystemd-id128.so libsystemd-id128.so.0]
49
+ unless $NO_FFI_SPEC
50
+ require 'ffi'
51
+ extend FFI::Library
52
+ ffi_lib %w[libsystemd-id128.so libsystemd-id128.so.0]
53
+
54
+ attach_function :sd_id128_get_machine, [:pointer], :int
55
+ attach_function :sd_id128_get_boot, [:pointer], :int
56
+ attach_function :sd_id128_randomize, [:pointer], :int
57
+ end
52
58
 
53
59
  class Id128 < FFI::Union
54
60
  layout :bytes, [:uint8, 16],
@@ -59,9 +65,6 @@ module Systemd
59
65
  ("%02x" * 16) % self[:bytes].to_a
60
66
  end
61
67
  end
62
- attach_function :sd_id128_get_machine, [:pointer], :int
63
- attach_function :sd_id128_get_boot, [:pointer], :int
64
- attach_function :sd_id128_randomize, [:pointer], :int
65
68
  end
66
69
  end
67
70
  end
@@ -33,6 +33,14 @@ module Systemd
33
33
  # {Systemd::Journal}
34
34
  module ClassMethods
35
35
 
36
+ # write the value of the c errno constant to the systemd journal in the
37
+ # style of the perror() function.
38
+ # @param [String] message the text to prefix the error message with.
39
+ def perror(message)
40
+ rc = Native::sd_journal_perror(message)
41
+ raise JournalError.new(rc) if rc < 0
42
+ end
43
+
36
44
  # write a simple message to the systemd journal.
37
45
  # @param [Integer] level one of the LOG_* constants defining the
38
46
  # severity of the event.
@@ -1,8 +1,9 @@
1
1
  module Systemd
2
2
  class Journal
3
3
  # contains a set of constants which maybe bitwise OR-ed together and passed
4
- # to the Journal constructor:
5
- # `Journal.new(flags: Systemd::Journal::Flags::LOCAL_ONLY)`
4
+ # to the Journal constructor.
5
+ # @example
6
+ # Systemd::Journal.new(flags: Systemd::Journal::Flags::LOCAL_ONLY)
6
7
  module Flags
7
8
  # Only open journal files generated on the local machine.
8
9
  LOCAL_ONLY = 1
@@ -22,11 +22,18 @@ module Systemd
22
22
  attach_function :sd_journal_seek_tail, [:pointer], :int
23
23
  attach_function :sd_journal_seek_realtime_usec, [:pointer, :uint64], :int
24
24
 
25
+ attach_function :sd_journal_get_cursor, [:pointer, :pointer], :int
26
+ attach_function :sd_journal_seek_cursor, [:pointer, :string], :int
27
+ attach_function :sd_journal_test_cursor, [:pointer, :string], :int
28
+
25
29
  # data reading
26
30
  attach_function :sd_journal_get_data, [:pointer, :string, :pointer, :pointer], :int
27
31
  attach_function :sd_journal_restart_data, [:pointer], :void
28
32
  attach_function :sd_journal_enumerate_data, [:pointer, :pointer, :pointer], :int
29
33
 
34
+ attach_function :sd_journal_get_data_threshold, [:pointer, :pointer], :int
35
+ attach_function :sd_journal_set_data_threshold, [:pointer, :size_t], :int
36
+
30
37
  # querying
31
38
  attach_function :sd_journal_query_unique, [:pointer, :string], :int
32
39
  attach_function :sd_journal_enumerate_unique, [:pointer, :pointer, :pointer], :int
@@ -38,7 +45,7 @@ module Systemd
38
45
  :append,
39
46
  :invalidate
40
47
  ]
41
- attach_function :sd_journal_wait, [:pointer, :uint64], :wake_reason
48
+ attach_function :sd_journal_wait, [:pointer, :uint64], :wake_reason, blocking: true
42
49
 
43
50
  # filtering
44
51
  attach_function :sd_journal_add_match, [:pointer, :string, :size_t], :int
@@ -47,12 +54,12 @@ module Systemd
47
54
  attach_function :sd_journal_add_conjunction, [:pointer], :int
48
55
 
49
56
  # writing
50
- attach_function :sd_journal_print, [:int, :string], :int
51
- attach_function :sd_journal_send, [:varargs], :int
52
-
57
+ attach_function :sd_journal_print, [:int, :string], :int
58
+ attach_function :sd_journal_send, [:varargs], :int
59
+ attach_function :sd_journal_perror, [:string], :int
53
60
  # misc
54
61
  attach_function :sd_journal_get_usage, [:pointer, :pointer], :int
55
62
  end
56
63
 
57
- end
64
+ end unless $NO_FFI_SPEC
58
65
  end
@@ -1,6 +1,6 @@
1
1
  module Systemd
2
2
  class Journal
3
3
  # The version of the systemd-journal gem.
4
- VERSION = '0.1.4'
4
+ VERSION = '1.0.0'
5
5
  end
6
6
  end
@@ -3,8 +3,11 @@ require 'systemd/journal/flags'
3
3
  require 'systemd/journal/compat'
4
4
  require 'systemd/journal/fields'
5
5
  require 'systemd/journal_error'
6
+ require 'systemd/journal_entry'
6
7
  require 'systemd/id128'
7
8
 
9
+ require 'systemd/ffi_size_t'
10
+
8
11
  module Systemd
9
12
  # Class to allow interacting with the systemd journal.
10
13
  # To read from the journal, instantiate a new {Systemd::Journal}; to write to
@@ -12,6 +15,7 @@ module Systemd
12
15
  # {Systemd::Journal::Compat::ClassMethods#message Journal.message} or
13
16
  # {Systemd::Journal::Compat::ClassMethods#print Journal.print}.
14
17
  class Journal
18
+ include Enumerable
15
19
  include Systemd::Journal::Compat
16
20
 
17
21
  # Returns a new instance of a Journal, opened with the provided options.
@@ -45,17 +49,62 @@ module Systemd
45
49
  ObjectSpace.define_finalizer(self, self.class.finalize(@ptr))
46
50
  end
47
51
 
52
+ # Iterate over each entry in the journal, respecting the applied
53
+ # conjunctions/disjunctions.
54
+ # If a block is given, it is called with each entry until no more
55
+ # entries remain. Otherwise, returns an enumerator which can be chained.
56
+ def each
57
+ return to_enum(:each) unless block_given?
58
+
59
+ seek(:head)
60
+ yield current_entry while move_next
61
+ end
62
+
63
+ # Move the read pointer by `offset` entries.
64
+ # @param [Integer] offset how many entries to move the read pointer by. If
65
+ # this value is positive, the read pointer moves forward. Otherwise, it
66
+ # moves backwards.
67
+ # @return [Integer] the number of entries the read pointer actually moved.
68
+ def move(offset)
69
+ offset > 0 ? move_next_skip(offset) : move_previous_skip(-offset)
70
+ end
71
+
72
+ # Filter the journal at a high level.
73
+ # Takes any number of arguments; each argument should be a hash representing
74
+ # a condition to filter based on. Fields inside the hash will be ANDed
75
+ # together. Each hash will be ORed with the others. Fields in hashes with
76
+ # Arrays as values are treated as an OR statement, since otherwise they
77
+ # would never match.
78
+ # @example
79
+ # j = Systemd::Journal.filter(
80
+ # {_systemd_unit: 'session-4.scope'},
81
+ # {priority: [4, 6]},
82
+ # {_exe: '/usr/bin/sshd', priority: 1}
83
+ # )
84
+ # # equivalent to
85
+ # (_systemd_unit == 'session-4.scope') ||
86
+ # (priority == 4 || priority == 6) ||
87
+ # (_exe == '/usr/bin/sshd' && priority == 1)
88
+ def filter(*conditions)
89
+ clear_filters
90
+
91
+ last_index = conditions.length - 1
92
+
93
+ conditions.each_with_index do |condition, index|
94
+ add_filters(condition)
95
+ add_disjunction unless index == last_index
96
+ end
97
+ end
98
+
48
99
  # Move the read pointer to the next entry in the journal.
49
100
  # @return [Boolean] True if moving to the next entry was successful.
50
101
  # @return [Boolean] False if unable to move to the next entry, indicating
51
102
  # that the pointer has reached the end of the journal.
52
103
  def move_next
53
- case (rc = Native::sd_journal_next(@ptr))
54
- when 0 then false
55
- when 1 then true
56
- else
104
+ if (rc = Native::sd_journal_next(@ptr)) < 0
57
105
  raise JournalError.new(rc) if rc < 0
58
106
  end
107
+ rc > 0
59
108
  end
60
109
 
61
110
  # Move the read pointer forward by `amount` entries.
@@ -73,12 +122,10 @@ module Systemd
73
122
  # @return [Boolean] False if unable to move to the previous entry,
74
123
  # indicating that the pointer has reached the beginning of the journal.
75
124
  def move_previous
76
- case (rc = Native::sd_journal_previous(@ptr))
77
- when 0 then false # EOF
78
- when 1 then true
79
- else
125
+ if (rc = Native::sd_journal_previous(@ptr)) < 0
80
126
  raise JournalError.new(rc) if rc < 0
81
127
  end
128
+ rc > 0
82
129
  end
83
130
 
84
131
  # Move the read pointer backwards by `amount` entries.
@@ -98,7 +145,9 @@ module Systemd
98
145
  # @param [Symbol, Time] whence one of :head, :tail, or a Time instance.
99
146
  # `:head` (or `:start`) will seek to the beginning of the journal.
100
147
  # `:tail` (or `:end`) will seek to the end of the journal. When a `Time`
101
- # is provided, seek to the journal entry logged closest to that time.
148
+ # is provided, seek to the journal entry logged closest to that time. When
149
+ # a String is provided, assume it is a cursor from {#cursor} and seek to
150
+ # that entry.
102
151
  # @return [True]
103
152
  def seek(whence)
104
153
  rc = case whence
@@ -110,6 +159,8 @@ module Systemd
110
159
  if whence.is_a?(Time)
111
160
  # TODO: is this right? who knows.
112
161
  Native::sd_journal_seek_realtime_usec(@ptr, whence.to_i * 1_000_000)
162
+ elsif whence.is_a?(String)
163
+ Native::sd_journal_seek_cursor(@ptr, whence)
113
164
  else
114
165
  raise ArgumentError.new("Unknown seek type: #{whence.class}")
115
166
  end
@@ -133,11 +184,11 @@ module Systemd
133
184
  len_ptr = FFI::MemoryPointer.new(:size_t, 1)
134
185
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)
135
186
 
136
- rc = Native::sd_journal_get_data(@ptr, field, out_ptr, len_ptr)
187
+ rc = Native::sd_journal_get_data(@ptr, field.to_s.upcase, out_ptr, len_ptr)
137
188
 
138
189
  raise JournalError.new(rc) if rc < 0
139
190
 
140
- len = read_size_t(len_ptr)
191
+ len = len_ptr.read_size_t
141
192
  out_ptr.read_pointer.read_string_length(len).split('=', 2).last
142
193
  end
143
194
 
@@ -161,7 +212,7 @@ module Systemd
161
212
  results = {}
162
213
 
163
214
  while (rc = Native::sd_journal_enumerate_data(@ptr, out_ptr, len_ptr)) > 0
164
- len = read_size_t(len_ptr)
215
+ len = len_ptr.read_size_t
165
216
  key, value = out_ptr.read_pointer.read_string_length(len).split('=', 2)
166
217
  results[key] = value
167
218
 
@@ -170,7 +221,7 @@ module Systemd
170
221
 
171
222
  raise JournalError.new(rc) if rc < 0
172
223
 
173
- results
224
+ JournalEntry.new(results)
174
225
  end
175
226
 
176
227
  # Get the list of unique values stored in the journal for the given field.
@@ -186,18 +237,17 @@ module Systemd
186
237
  # end
187
238
  def query_unique(field)
188
239
  results = []
189
- field = field.to_s.upcase
190
240
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)
191
241
  len_ptr = FFI::MemoryPointer.new(:size_t, 1)
192
242
 
193
243
  Native::sd_journal_restart_unique(@ptr)
194
244
 
195
- if (rc = Native::sd_journal_query_unique(@ptr, field)) < 0
245
+ if (rc = Native::sd_journal_query_unique(@ptr, field.to_s.upcase)) < 0
196
246
  raise JournalError.new(rc)
197
247
  end
198
248
 
199
249
  while (rc = Native::sd_journal_enumerate_unique(@ptr, out_ptr, len_ptr)) > 0
200
- len = read_size_t(len_ptr)
250
+ len = len_ptr.read_size_t
201
251
  results << out_ptr.read_pointer.read_string_length(len).split('=', 2).last
202
252
 
203
253
  yield results.last if block_given?
@@ -214,41 +264,73 @@ module Systemd
214
264
  # @example Wait for an event for a maximum of 3 seconds
215
265
  # j = Systemd::Journal.new
216
266
  # j.seek(:tail)
217
- # if j.wait(3 * 1_000_000) != :nop
267
+ # if j.wait(3 * 1_000_000)
218
268
  # # event occurred
219
269
  # end
220
- # @return [Symbol] :nop if the wait time was reached (no events occured).
270
+ # @return [Nil] if the wait time was reached (no events occured).
221
271
  # @return [Symbol] :append if new entries were appened to the journal.
222
272
  # @return [Symbol] :invalidate if journal files were added/removed/rotated.
223
273
  def wait(timeout_usec = -1)
224
274
  rc = Native::sd_journal_wait(@ptr, timeout_usec)
225
275
  raise JournalError.new(rc) if rc.is_a?(Fixnum) && rc < 0
226
- rc
276
+ rc == :nop ? nil : rc
277
+ end
278
+
279
+ # Blocks and waits for new entries to be appended to the journal. When new
280
+ # entries are written, yields them in turn. Note that this function does
281
+ # not automatically seek to the end of the journal prior to waiting.
282
+ # This method Does not return.
283
+ # @example Print out events as they happen
284
+ # j = Systemd::Journal.new
285
+ # j.seek(:tail)
286
+ # j.watch do |event|
287
+ # puts event.message
288
+ # end
289
+ def watch
290
+ while true
291
+ if wait
292
+ yield current_entry while move_next
293
+ end
294
+ end
227
295
  end
228
296
 
229
297
  # Add a filter to journal, such that only entries where the given filter
230
298
  # matches are returned.
231
- # {#move_next} or {#move_previous} must be invoked after adding a match
299
+ # {#move_next} or {#move_previous} must be invoked after adding a filter
232
300
  # before attempting to read from the journal.
233
301
  # @param [String] field the column to filter on, e.g. _PID, _EXE.
234
302
  # @param [String] value the match to search for, e.g. '/usr/bin/sshd'
235
303
  # @return [nil]
236
- def add_match(field, value)
304
+ def add_filter(field, value)
237
305
  match = "#{field.to_s.upcase}=#{value}"
238
306
  rc = Native::sd_journal_add_match(@ptr, match, match.length)
239
307
  raise JournalError.new(rc) if rc < 0
240
308
  end
241
309
 
310
+ # Add a set of filters to the journal, such that only entries where the
311
+ # given filters match are returned.
312
+ # @param [Hash] filters a set of field/filter value pairs.
313
+ # If the filter value is an array, each value in the array is added
314
+ # and entries where the specified field matches any of the values is
315
+ # returned.
316
+ # @example Filter by PID and EXE
317
+ # j.add_filters(_pid: 6700, _exe: '/usr/bin/sshd')
318
+ def add_filters(filters)
319
+ filters.each do |field, value|
320
+ Array(value).each{ |v| add_filter(field, v) }
321
+ end
322
+ end
323
+
242
324
  # Add an OR condition to the filter. All previously added matches
243
- # and any matches added afterwards will be OR-ed together.
325
+ # will be ORed with the terms following the disjunction.
244
326
  # {#move_next} or {#move_previous} must be invoked after adding a match
245
327
  # before attempting to read from the journal.
246
328
  # @return [nil]
247
329
  # @example Filter entries returned using an OR condition
248
330
  # j = Systemd::Journal.new
249
- # j.add_match('PRIORITY', 5)
250
- # j.add_match('_EXE', '/usr/bin/sshd')
331
+ # j.add_filter('PRIORITY', 5)
251
332
  # j.add_disjunction
333
+ # j.add_filter('_EXE', '/usr/bin/sshd')
252
334
  # while j.move_next
253
335
  # # current_entry is either an sshd event or
254
336
  # # has priority 5
@@ -258,16 +340,16 @@ module Systemd
258
340
  raise JournalError.new(rc) if rc < 0
259
341
  end
260
342
 
261
- # Add an AND condition to the filter. All previously added matches
262
- # and any matches added afterwards will be AND-ed together.
343
+ # Add an AND condition to the filter. All previously added terms will be
344
+ # ANDed together with terms following the conjunction.
263
345
  # {#move_next} or {#move_previous} must be invoked after adding a match
264
346
  # before attempting to read from the journal.
265
347
  # @return [nil]
266
348
  # @example Filter entries returned using an AND condition
267
349
  # j = Systemd::Journal.new
268
- # j.add_match('PRIORITY', 5)
269
- # j.add_match('_EXE', '/usr/bin/sshd')
350
+ # j.add_filter('PRIORITY', 5)
270
351
  # j.add_conjunction
352
+ # j.add_filter('_EXE', '/usr/bin/sshd')
271
353
  # while j.move_next
272
354
  # # current_entry is an sshd event with priority 5
273
355
  # end
@@ -276,9 +358,9 @@ module Systemd
276
358
  raise JournalError.new(rc) if rc < 0
277
359
  end
278
360
 
279
- # Remove all matches and conjunctions/disjunctions.
361
+ # Remove all filters and conjunctions/disjunctions.
280
362
  # @return [nil]
281
- def clear_matches
363
+ def clear_filters
282
364
  Native::sd_journal_flush_matches(@ptr)
283
365
  end
284
366
 
@@ -295,21 +377,55 @@ module Systemd
295
377
  size_ptr.read_uint64
296
378
  end
297
379
 
298
- private
380
+ # Get the maximum length of a data field that will be returned.
381
+ # Fields longer than this will be truncated. Default is 64K.
382
+ # @return [Integer] size in bytes.
383
+ def data_threshold
384
+ size_ptr = FFI::MemoryPointer.new(:size_t, 1)
385
+ if (rc = Native::sd_journal_get_data_threshold(@ptr, size_ptr)) < 0
386
+ raise JournalError.new(rc)
387
+ end
299
388
 
300
- def self.finalize(ptr)
301
- proc{ Native::sd_journal_close(ptr) unless ptr.nil? }
389
+ size_ptr.read_size_t
390
+ end
391
+
392
+ # Set the maximum length of a data field that will be returned.
393
+ # Fields longer than this will be truncated.
394
+ def data_threshold=(threshold)
395
+ if (rc = Native::sd_journal_set_data_threshold(@ptr, threshold)) < 0
396
+ raise JournalError.new(rc)
397
+ end
398
+ end
399
+
400
+ # returns a string representing the current read position.
401
+ # This string can be passed to {#seek} or {#cursor?}.
402
+ # @return [String] a cursor token.
403
+ def cursor
404
+ out_ptr = FFI::MemoryPointer.new(:pointer, 1)
405
+ if (rc = Native.sd_journal_get_cursor(@ptr, out_ptr)) < 0
406
+ raise JournalError.new(rc)
407
+ end
408
+
409
+ out_ptr.read_pointer.read_string
302
410
  end
303
411
 
304
- def read_size_t(ptr)
305
- case ptr.size
306
- when 8
307
- ptr.read_uint64
308
- when 4
309
- ptr.read_uint32
310
- else
311
- raise StandardError.new("Unhandled size_t size: #{ptr.size}")
412
+ # Check if the read position is currently at the entry represented by the
413
+ # provided cursor value.
414
+ # @param c [String] a cursor token returned from {#cursor}.
415
+ # @return [Boolean] True if the current entry is the one represented by the
416
+ # provided cursor, False otherwise.
417
+ def cursor?(c)
418
+ if (rc = Native.sd_journal_test_cursor(@ptr, c)) < 0
419
+ raise JournalError.new(rc)
312
420
  end
421
+
422
+ rc > 0
423
+ end
424
+
425
+ private
426
+
427
+ def self.finalize(ptr)
428
+ proc{ Native::sd_journal_close(ptr) unless ptr.nil? }
313
429
  end
314
430
 
315
431
  end
@@ -0,0 +1,28 @@
1
+ module Systemd
2
+ class JournalEntry
3
+ include Enumerable
4
+
5
+ attr_reader :fields
6
+
7
+ def initialize(entry)
8
+ @entry = entry
9
+ @fields = entry.map do |key, value|
10
+ name = key.downcase.to_sym
11
+ define_singleton_method(name){ value } unless respond_to?(name)
12
+ name
13
+ end
14
+
15
+ end
16
+
17
+ def [](key)
18
+ @entry[key] || @entry[key.to_s.upcase]
19
+ end
20
+
21
+ def each
22
+ return to_enum(:each) unless block_given?
23
+
24
+ @entry.each{ |key, value| yield [key, value] }
25
+ end
26
+
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ require 'ffi'
2
+
1
3
  module Systemd
2
4
  # This execption is raised whenever a sd_journal_* call returns an error.
3
5
  class JournalError < StandardError
data/spec/no_ffi.rb ADDED
@@ -0,0 +1,4 @@
1
+ $NO_FFI_SPEC = true
2
+ module Systemd::Journal::Native ; end
3
+
4
+ puts "Warning: running specs without libsystemd-journal and libsystemd-id128"
data/spec/spec_helper.rb CHANGED
@@ -3,3 +3,37 @@ require 'simplecov'
3
3
 
4
4
  SimpleCov.start
5
5
  require 'systemd/journal'
6
+
7
+ RSpec.configure do |config|
8
+ config.before(:each) do
9
+
10
+ # Stub open and close calls
11
+ dummy_open = ->(ptr, flags, path=nil) do
12
+ ptr.write_pointer(nil)
13
+ 0
14
+ end
15
+
16
+ Systemd::Journal::Native.stub(:sd_journal_open, &dummy_open)
17
+ Systemd::Journal::Native.stub(:sd_journal_open_directory, &dummy_open)
18
+ Systemd::Journal::Native.stub(:sd_journal_close).and_return(0)
19
+
20
+ # Raise an exception if any native calls are actually called
21
+ native_calls = Systemd::Journal::Native.methods.select do |m|
22
+ m.to_s.start_with?("sd_")
23
+ end
24
+
25
+ native_calls -= [
26
+ :sd_journal_open, :sd_journal_open_directory, :sd_journal_close
27
+ ]
28
+
29
+ build_err_proc = ->(method_name) do
30
+ return ->(*params) do
31
+ raise RuntimeError.new("#{method_name} called without being stubbed.")
32
+ end
33
+ end
34
+
35
+ native_calls.each do |meth|
36
+ Systemd::Journal::Native.stub(meth, &build_err_proc.call(meth))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Systemd::JournalEntry do
4
+ subject do
5
+ Systemd::JournalEntry.new(
6
+ '_PID' => '125',
7
+ '_EXE' => '/usr/bin/sshd',
8
+ 'PRIORITY' => '4',
9
+ 'OBJECT_ID'=> ':)'
10
+ )
11
+ end
12
+
13
+ it 'allows enumerating entries' do
14
+ expect{ |b| subject.each(&b) }.to yield_successive_args(
15
+ ['_PID', '125'],
16
+ ['_EXE', '/usr/bin/sshd'],
17
+ ['PRIORITY', '4'],
18
+ ['OBJECT_ID', ':)']
19
+ )
20
+ end
21
+
22
+ it 'responds to field names as methods' do
23
+ subject._pid.should eq('125')
24
+ subject.priority.should eq('4')
25
+ end
26
+
27
+ it 'doesnt overwrite existing methods' do
28
+ subject.object_id.should_not eq(':)')
29
+ end
30
+
31
+ it 'allows accessing via [string]' do
32
+ subject['OBJECT_ID'].should eq(':)')
33
+ end
34
+
35
+ it 'allows accessing via [symbol]' do
36
+ subject[:object_id].should eq(':)')
37
+ end
38
+
39
+ it 'lists all fields it contains' do
40
+ subject.fields.should eq([:_pid, :_exe, :priority, :object_id])
41
+ end
42
+
43
+ end
@@ -1,18 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Systemd::Journal do
4
-
5
- before(:each) do
6
- # don't actually make native API calls.
7
- dummy_open = ->(ptr, flags, path=nil) do
8
- ptr.write_pointer(nil)
9
- 0
10
- end
11
-
12
- Systemd::Journal::Native.stub(:sd_journal_open, &dummy_open)
13
- Systemd::Journal::Native.stub(:sd_journal_open_directory, &dummy_open)
14
- Systemd::Journal::Native.stub(:sd_journal_close).and_return(0)
15
- end
16
4
 
17
5
  describe '#initialize' do
18
6
  it 'opens a directory if a path is passed' do
@@ -34,6 +22,20 @@ describe Systemd::Journal do
34
22
  end
35
23
  end
36
24
 
25
+ describe '#move' do
26
+ it 'calls move_next_skip if the value is positive' do
27
+ j = Systemd::Journal.new
28
+ j.should_receive(:move_next_skip).with(5)
29
+ j.move(5)
30
+ end
31
+
32
+ it 'calls move_next_previous otherwise' do
33
+ j = Systemd::Journal.new
34
+ j.should_receive(:move_previous_skip).with(5)
35
+ j.move(-5)
36
+ end
37
+ end
38
+
37
39
  ['next', 'previous'].each do |direction|
38
40
  describe "#move_#{direction}" do
39
41
  it 'returns true on a successful move' do
@@ -76,6 +78,33 @@ describe Systemd::Journal do
76
78
  end
77
79
  end
78
80
 
81
+ describe '#each' do
82
+ it 'should reposition to the head of the journal' do
83
+ j = Systemd::Journal.new
84
+ j.should_receive(:seek).with(:head).and_return(0)
85
+ j.stub(:move_next).and_return(nil)
86
+ j.each{|e| nil }
87
+ end
88
+
89
+ it 'should return an enumerator if no block is given' do
90
+ j = Systemd::Journal.new
91
+ j.each.class.should eq(Enumerator)
92
+ end
93
+
94
+ it 'should return each entry in the journal' do
95
+ entries = [{'_PID' => 1}, {'_PID' => 2}]
96
+ entry = nil
97
+
98
+ j = Systemd::Journal.new
99
+ j.stub(:seek).and_return(0)
100
+ j.stub(:current_entry) { entry }
101
+ j.stub(:move_next) { entry = entries.shift }
102
+
103
+ j.map{|e| e['_PID'] }.should eq([1, 2])
104
+ end
105
+
106
+ end
107
+
79
108
  describe '#seek' do
80
109
  it 'moves to the first entry of the file' do
81
110
  j = Systemd::Journal.new
@@ -94,44 +123,226 @@ describe Systemd::Journal do
94
123
  Systemd::Journal::Native.should_receive(:sd_journal_seek_realtime_usec).and_return(0)
95
124
  j.seek(Time.now).should eq(true)
96
125
  end
126
+
127
+ it 'seeks based on a cursor when a string is provided' do
128
+ j = Systemd::Journal.new
129
+
130
+ Systemd::Journal::Native.should_receive(:sd_journal_seek_cursor).
131
+ with(anything, "123").and_return(0)
132
+
133
+ j.seek("123")
134
+ end
135
+
136
+ it 'throws an exception if it doesnt understand the type' do
137
+ j = Systemd::Journal.new
138
+ expect { j.seek(Object.new) }.to raise_error(ArgumentError)
139
+ end
97
140
  end
98
141
 
99
142
  describe '#read_field' do
100
- pending
143
+ it 'raises an exception if the call fails' do
144
+ Systemd::Journal::Native.should_receive(:sd_journal_get_data).and_return(-1)
145
+
146
+ j = Systemd::Journal.new
147
+ expect{ j.read_field(:message) }.to raise_error(Systemd::JournalError)
148
+ end
149
+
150
+ it 'parses the returned value correctly.' do
151
+ j = Systemd::Journal.new
152
+
153
+ Systemd::Journal::Native.should_receive(:sd_journal_get_data) do |ptr, field, out_ptr, len_ptr|
154
+ dummy = "MESSAGE=hello world"
155
+ out_ptr.write_pointer(FFI::MemoryPointer.from_string(dummy))
156
+ len_ptr.write_size_t(dummy.size)
157
+ 0
158
+ end
159
+
160
+ j.read_field(:message).should eq("hello world")
161
+ end
101
162
  end
102
163
 
103
164
  describe '#current_entry' do
104
- pending
165
+ before(:each) do
166
+ Systemd::Journal::Native.should_receive(:sd_journal_restart_data).and_return(nil)
167
+ end
168
+
169
+ it 'raises an exception if the call fails' do
170
+ j = Systemd::Journal.new
171
+ Systemd::Journal::Native.should_receive(:sd_journal_enumerate_data).and_return(-1)
172
+ expect { j.current_entry }.to raise_error(Systemd::JournalError)
173
+ end
174
+
175
+ it 'returns the correct data' do
176
+ j = Systemd::Journal.new
177
+ results = ['_PID=100', 'MESSAGE=hello world']
178
+
179
+ Systemd::Journal::Native.should_receive(:sd_journal_enumerate_data).exactly(3).times do |ptr, out_ptr, len_ptr|
180
+ if results.any?
181
+ x = results.shift
182
+ out_ptr.write_pointer(FFI::MemoryPointer.from_string(x))
183
+ len_ptr.write_size_t(x.length)
184
+ 1
185
+ else
186
+ 0
187
+ end
188
+ end
189
+
190
+ entry = j.current_entry
191
+
192
+ entry._pid.should eq('100')
193
+ entry.message.should eq('hello world')
194
+
195
+ end
105
196
  end
106
197
 
107
198
  describe '#query_unique' do
108
- pending
199
+ before(:each) do
200
+ Systemd::Journal::Native.should_receive(:sd_journal_restart_unique).and_return(nil)
201
+ end
202
+
203
+ it 'raises an exception if the call fails' do
204
+ j = Systemd::Journal.new
205
+ Systemd::Journal::Native.should_receive(:sd_journal_query_unique).and_return(-1)
206
+ expect { j.query_unique(:_pid) }.to raise_error(Systemd::JournalError)
207
+ end
208
+
209
+ it 'raises an exception if the call fails (2)' do
210
+ j = Systemd::Journal.new
211
+ Systemd::Journal::Native.should_receive(:sd_journal_query_unique).and_return(0)
212
+ Systemd::Journal::Native.should_receive(:sd_journal_enumerate_unique).and_return(-1)
213
+ expect { j.query_unique(:_pid) }.to raise_error(Systemd::JournalError)
214
+ end
215
+
216
+ it 'returns the correct data' do
217
+ j = Systemd::Journal.new
218
+ results = ['_PID=100', '_PID=200', '_PID=300']
219
+
220
+ Systemd::Journal::Native.should_receive(:sd_journal_query_unique).and_return(0)
221
+
222
+ Systemd::Journal::Native.should_receive(:sd_journal_enumerate_unique).exactly(4).times do |ptr, out_ptr, len_ptr|
223
+ if results.any?
224
+ x = results.shift
225
+ out_ptr.write_pointer(FFI::MemoryPointer.from_string(x))
226
+ len_ptr.write_size_t(x.length)
227
+ 1
228
+ else
229
+ 0
230
+ end
231
+ end
232
+
233
+ j.query_unique(:_pid).should eq(['100', '200', '300'])
234
+ end
235
+
109
236
  end
110
237
 
111
238
  describe '#wait' do
112
- pending
239
+ it 'raises an exception if the call fails' do
240
+ Systemd::Journal::Native.should_receive(:sd_journal_wait).and_return(-1)
241
+
242
+ j = Systemd::Journal.new
243
+ expect{ j.wait(100) }.to raise_error(Systemd::JournalError)
244
+ end
245
+
246
+ it 'returns the reason we were woken up' do
247
+ j = Systemd::Journal.new
248
+ Systemd::Journal::Native.should_receive(:sd_journal_wait).and_return(:append)
249
+ j.wait(100).should eq(:append)
250
+ end
251
+
252
+ it 'returns nil if we reached the timeout.' do
253
+ j = Systemd::Journal.new
254
+ Systemd::Journal::Native.should_receive(:sd_journal_wait).and_return(:nop)
255
+ j.wait(100).should eq(nil)
256
+ end
257
+ end
258
+
259
+ describe '#add_filter' do
260
+ it 'raises an exception if the call fails' do
261
+ Systemd::Journal::Native.should_receive(:sd_journal_add_match).and_return(-1)
262
+
263
+ j = Systemd::Journal.new
264
+ expect{ j.add_filter(:message, "test") }.to raise_error(Systemd::JournalError)
265
+ end
266
+
267
+ it 'formats the arguments appropriately' do
268
+ Systemd::Journal::Native.should_receive(:sd_journal_add_match).
269
+ with(anything, "MESSAGE=test", "MESSAGE=test".length).
270
+ and_return(0)
271
+
272
+ Systemd::Journal.new.add_filter(:message, "test")
273
+ end
113
274
  end
114
275
 
115
- describe '#add_match' do
116
- pending
276
+ describe '#add_filters' do
277
+ it 'calls add_filter for each parameter' do
278
+ j = Systemd::Journal.new
279
+ j.should_receive(:add_filter).with(:priority, 1)
280
+ j.should_receive(:add_filter).with(:_exe, '/usr/bin/sshd')
281
+
282
+ j.add_filters(priority: 1, _exe: '/usr/bin/sshd')
283
+ end
284
+
285
+ it 'expands array arguments to multiple add_filter calls' do
286
+ j = Systemd::Journal.new
287
+ j.should_receive(:add_filter).with(:priority, 1)
288
+ j.should_receive(:add_filter).with(:priority, 2)
289
+ j.should_receive(:add_filter).with(:priority, 3)
290
+
291
+ j.add_filters(priority: [1,2,3])
292
+ end
293
+ end
294
+
295
+ describe '#filter' do
296
+ it 'clears the existing filters' do
297
+ j = Systemd::Journal.new
298
+ j.should_receive(:clear_filters)
299
+
300
+ j.filter({})
301
+ end
302
+
303
+ it 'adds disjunctions between terms' do
304
+ j = Systemd::Journal.new
305
+ j.stub(:clear_filters).and_return(nil)
306
+
307
+ j.should_receive(:add_filter).with(:priority, 1).ordered
308
+ j.should_receive(:add_disjunction).ordered
309
+ j.should_receive(:add_filter).with(:message, 'hello').ordered
310
+
311
+ j.filter({priority: 1}, {message: 'hello'})
312
+
313
+ end
117
314
  end
118
315
 
119
316
  describe '#add_conjunction' do
120
- pending
317
+ it 'raises an exception if the call fails' do
318
+ Systemd::Journal::Native.should_receive(:sd_journal_add_conjunction).and_return(-1)
319
+
320
+ j = Systemd::Journal.new
321
+ expect{ j.add_conjunction }.to raise_error(Systemd::JournalError)
322
+ end
121
323
  end
122
324
 
123
325
  describe '#add_disjunction' do
124
- pending
326
+ it 'raises an exception if the call fails' do
327
+ Systemd::Journal::Native.should_receive(:sd_journal_add_disjunction).and_return(-1)
328
+
329
+ j = Systemd::Journal.new
330
+ expect{ j.add_disjunction }.to raise_error(Systemd::JournalError)
331
+ end
125
332
  end
126
333
 
127
- describe '#clear_matches' do
128
- pending
334
+ describe '#clear_filters' do
335
+ it 'flushes the matches' do
336
+ j = Systemd::Journal.new
337
+ Systemd::Journal::Native.should_receive(:sd_journal_flush_matches).and_return(nil)
338
+ j.clear_filters
339
+ end
129
340
  end
130
341
 
131
342
  describe '#disk_usage' do
132
343
  it 'returns the size used on disk' do
133
344
  Systemd::Journal::Native.should_receive(:sd_journal_get_usage) do |ptr, size_ptr|
134
- size_ptr.size == 8 ? size_ptr.write_uint64(12) : size_ptr.write_uint32(12)
345
+ size_ptr.write_size_t(12)
135
346
  0
136
347
  end
137
348
  j = Systemd::Journal.new
@@ -145,4 +356,95 @@ describe Systemd::Journal do
145
356
  end
146
357
  end
147
358
 
359
+ describe '#data_threshold=' do
360
+ it 'sets the data threshold' do
361
+ j = Systemd::Journal.new
362
+
363
+ Systemd::Journal::Native.should_receive(:sd_journal_set_data_threshold).
364
+ with(anything, 0x1234).and_return(0)
365
+
366
+ j.data_threshold = 0x1234
367
+ end
368
+
369
+ it 'raises a JournalError on failure' do
370
+ j = Systemd::Journal.new
371
+
372
+ Systemd::Journal::Native.should_receive(:sd_journal_set_data_threshold).
373
+ with(anything, 0x1234).and_return(-1)
374
+
375
+ expect { j.data_threshold = 0x1234 }.to raise_error(Systemd::JournalError)
376
+ end
377
+ end
378
+
379
+ describe '#data_threshold' do
380
+ it 'gets the data threshold' do
381
+ j = Systemd::Journal.new
382
+
383
+ Systemd::Journal::Native.should_receive(:sd_journal_get_data_threshold) do |ptr, size_ptr|
384
+ size_ptr.write_size_t(0x1234)
385
+ 0
386
+ end
387
+ j.data_threshold.should eq(0x1234)
388
+ end
389
+
390
+ it 'raises a JournalError on failure' do
391
+ j = Systemd::Journal.new
392
+
393
+ Systemd::Journal::Native.should_receive(:sd_journal_get_data_threshold).
394
+ and_return(-3)
395
+
396
+ expect{ j.data_threshold }.to raise_error(Systemd::JournalError)
397
+ end
398
+
399
+ end
400
+
401
+ describe '#cursor?' do
402
+ it 'returns true if the current cursor is the provided value' do
403
+ j = Systemd::Journal.new
404
+ Systemd::Journal::Native.should_receive(:sd_journal_test_cursor).
405
+ with(anything, "1234").and_return(1)
406
+
407
+ j.cursor?("1234").should eq(true)
408
+ end
409
+
410
+ it 'returns false otherwise' do
411
+ j = Systemd::Journal.new
412
+ Systemd::Journal::Native.should_receive(:sd_journal_test_cursor).
413
+ with(anything, "1234").and_return(0)
414
+
415
+ j.cursor?("1234").should eq(false)
416
+ end
417
+
418
+ it 'raises a JournalError on failure' do
419
+ j = Systemd::Journal.new
420
+
421
+ Systemd::Journal::Native.should_receive(:sd_journal_test_cursor).
422
+ and_return(-3)
423
+
424
+ expect{ j.cursor?('123') }.to raise_error(Systemd::JournalError)
425
+ end
426
+
427
+ end
428
+
429
+ describe '#cursor' do
430
+ it 'returns the current cursor' do
431
+ j = Systemd::Journal.new
432
+ Systemd::Journal::Native.should_receive(:sd_journal_get_cursor) do |ptr, out_ptr|
433
+ out_ptr.write_pointer(FFI::MemoryPointer.from_string("5678"))
434
+ 0
435
+ end
436
+ j.cursor.should eq("5678")
437
+ end
438
+
439
+ it 'raises a JournalError on failure' do
440
+ j = Systemd::Journal.new
441
+
442
+ Systemd::Journal::Native.should_receive(:sd_journal_get_cursor).
443
+ and_return(-3)
444
+
445
+ expect{ j.cursor }.to raise_error(Systemd::JournalError)
446
+ end
447
+
448
+ end
449
+
148
450
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: systemd-journal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Ledbetter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-01 00:00:00.000000000 Z
12
+ date: 2013-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -69,6 +69,7 @@ files:
69
69
  - examples/journal_directory.rb
70
70
  - examples/ssh_watcher.rb
71
71
  - lib/systemd-journal.rb
72
+ - lib/systemd/ffi_size_t.rb
72
73
  - lib/systemd/id128.rb
73
74
  - lib/systemd/journal.rb
74
75
  - lib/systemd/journal/compat.rb
@@ -76,9 +77,12 @@ files:
76
77
  - lib/systemd/journal/flags.rb
77
78
  - lib/systemd/journal/native.rb
78
79
  - lib/systemd/journal/version.rb
80
+ - lib/systemd/journal_entry.rb
79
81
  - lib/systemd/journal_error.rb
82
+ - spec/no_ffi.rb
80
83
  - spec/spec_helper.rb
81
84
  - spec/systemd/id128_spec.rb
85
+ - spec/systemd/journal_entry_spec.rb
82
86
  - spec/systemd/journal_spec.rb
83
87
  - systemd-journal.gemspec
84
88
  homepage: https://github.com/ledbettj/systemd-journal
@@ -106,7 +110,9 @@ signing_key:
106
110
  specification_version: 4
107
111
  summary: Ruby bindings to libsystemd-journal
108
112
  test_files:
113
+ - spec/no_ffi.rb
109
114
  - spec/spec_helper.rb
110
115
  - spec/systemd/id128_spec.rb
116
+ - spec/systemd/journal_entry_spec.rb
111
117
  - spec/systemd/journal_spec.rb
112
118
  has_rdoc: