systemd-journal 2.0.0 → 2.1.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +18 -6
- data/Gemfile +2 -2
- data/README.md +10 -0
- data/Rakefile +9 -17
- data/examples/journal_directory.rb +1 -1
- data/examples/ssh_watcher.rb +6 -6
- data/lib/systemd/ffi_size_t.rb +5 -5
- data/lib/systemd/id128.rb +12 -14
- data/lib/systemd/journal/fields.rb +9 -9
- data/lib/systemd/journal/filterable.rb +29 -0
- data/lib/systemd/journal/flags.rb +3 -3
- data/lib/systemd/journal/native.rb +30 -30
- data/lib/systemd/journal/navigable.rb +78 -29
- data/lib/systemd/journal/version.rb +1 -1
- data/lib/systemd/journal/waitable.rb +4 -4
- data/lib/systemd/journal/writable.rb +11 -11
- data/lib/systemd/journal.rb +48 -37
- data/lib/systemd/journal_entry.rb +10 -5
- data/lib/systemd/journal_error.rb +2 -2
- data/lib/systemd-journal.rb +1 -1
- data/lib/systemd.rb +1 -1
- data/spec/spec_helper.rb +8 -8
- data/spec/systemd/id128_spec.rb +19 -17
- data/spec/systemd/journal_entry_spec.rb +32 -32
- data/spec/systemd/journal_spec.rb +173 -125
- data/spec/systemd_spec.rb +5 -5
- data/systemd-journal.gemspec +23 -25
- metadata +18 -28
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "systemd/journal/native"
|
2
|
+
require "systemd/journal_error"
|
3
3
|
|
4
4
|
module Systemd
|
5
5
|
class Journal
|
@@ -8,21 +8,21 @@ module Systemd
|
|
8
8
|
# (https://github.com/zonque/systemd-journal.gem).
|
9
9
|
module Writable
|
10
10
|
# system is unusable
|
11
|
-
LOG_EMERG
|
11
|
+
LOG_EMERG = 0
|
12
12
|
# action must be taken immediately
|
13
|
-
LOG_ALERT
|
13
|
+
LOG_ALERT = 1
|
14
14
|
# critical conditions
|
15
|
-
LOG_CRIT
|
15
|
+
LOG_CRIT = 2
|
16
16
|
# error conditions
|
17
|
-
LOG_ERR
|
17
|
+
LOG_ERR = 3
|
18
18
|
# warning conditions
|
19
19
|
LOG_WARNING = 4
|
20
20
|
# normal but significant condition
|
21
|
-
LOG_NOTICE
|
21
|
+
LOG_NOTICE = 5
|
22
22
|
# informational
|
23
|
-
LOG_INFO
|
23
|
+
LOG_INFO = 6
|
24
24
|
# debug-level messages
|
25
|
-
LOG_DEBUG
|
25
|
+
LOG_DEBUG = 7
|
26
26
|
|
27
27
|
# @private
|
28
28
|
def self.included(base)
|
@@ -66,7 +66,7 @@ module Systemd
|
|
66
66
|
# severity of the event.
|
67
67
|
# @param [String] message the content of the message to write.
|
68
68
|
def print(level, message)
|
69
|
-
rc = Native.sd_journal_print(level, message.to_s.gsub(
|
69
|
+
rc = Native.sd_journal_print(level, message.to_s.gsub("%", "%%"))
|
70
70
|
raise JournalError, rc if rc < 0
|
71
71
|
end
|
72
72
|
|
@@ -74,7 +74,7 @@ module Systemd
|
|
74
74
|
# @param [Hash] contents the set of key-value pairs defining the event.
|
75
75
|
def message(contents)
|
76
76
|
items = contents.flat_map do |k, v|
|
77
|
-
value = v.to_s.gsub(
|
77
|
+
value = v.to_s.gsub("%", "%%")
|
78
78
|
[:string, "#{k.to_s.upcase}=#{value}"]
|
79
79
|
end
|
80
80
|
# add a null pointer to terminate the varargs
|
data/lib/systemd/journal.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
1
|
+
require "systemd/journal/version"
|
2
|
+
require "systemd/journal/native"
|
3
|
+
require "systemd/journal/flags"
|
4
|
+
require "systemd/journal/writable"
|
5
|
+
require "systemd/journal/fields"
|
6
|
+
require "systemd/journal/navigable"
|
7
|
+
require "systemd/journal/filterable"
|
8
|
+
require "systemd/journal/waitable"
|
9
|
+
require "systemd/journal/shim"
|
10
|
+
require "systemd/journal_error"
|
11
|
+
require "systemd/journal_entry"
|
12
|
+
require "systemd/id128"
|
13
|
+
require "systemd/ffi_size_t"
|
14
|
+
require "systemd"
|
15
15
|
|
16
16
|
module Systemd
|
17
17
|
# Class to allow interacting with the systemd journal.
|
@@ -26,11 +26,14 @@ module Systemd
|
|
26
26
|
include Systemd::Journal::Filterable
|
27
27
|
include Systemd::Journal::Waitable
|
28
28
|
|
29
|
+
# Returns the iterations to auto reopen
|
30
|
+
attr_reader :auto_reopen
|
31
|
+
|
29
32
|
# Returns a new instance of a Journal, opened with the provided options.
|
30
33
|
# @param [Hash] opts optional initialization parameters.
|
31
34
|
# @option opts [Integer] :flags a set of bitwise OR-ed
|
32
35
|
# {Systemd::Journal::Flags} which control what journal files are opened.
|
33
|
-
# Defaults to `0`, meaning all journals
|
36
|
+
# Defaults to `0`, meaning all journals available to the current user.
|
34
37
|
# @option opts [String] :path if provided, open the journal files living
|
35
38
|
# in the provided directory only. Any provided flags will be ignored
|
36
39
|
# since sd_journal_open_directory does not currently accept any flags.
|
@@ -46,9 +49,15 @@ module Systemd
|
|
46
49
|
# path: '/var/log/journal/5f5777e46c5f4131bd9b71cbed6b9abf'
|
47
50
|
# )
|
48
51
|
def initialize(opts = {})
|
52
|
+
@reopen_options = opts.dup # retain the options for auto reopen
|
49
53
|
open_type, flags = validate_options!(opts)
|
50
54
|
ptr = FFI::MemoryPointer.new(:pointer, 1)
|
51
55
|
|
56
|
+
@reopen_filter_conditions = [] # retain the conditions for auto reopen
|
57
|
+
@auto_reopen = (opts.key?(:auto_reopen) ? opts.delete(:auto_reopen) : false)
|
58
|
+
if @auto_reopen
|
59
|
+
@auto_reopen = @auto_reopen.is_a?(Integer) ? @auto_reopen : ITERATIONS_TO_AUTO_REOPEN
|
60
|
+
end
|
52
61
|
@finalize = (opts.key?(:finalize) ? opts.delete(:finalize) : true)
|
53
62
|
rc = open_journal(open_type, ptr, opts, flags)
|
54
63
|
raise JournalError, rc if rc < 0
|
@@ -62,7 +71,7 @@ module Systemd
|
|
62
71
|
j = new(opts.merge(finalize: false))
|
63
72
|
yield j
|
64
73
|
ensure
|
65
|
-
j
|
74
|
+
j&.close
|
66
75
|
end
|
67
76
|
|
68
77
|
# Iterate over each entry in the journal, respecting the applied
|
@@ -88,13 +97,13 @@ module Systemd
|
|
88
97
|
def read_field(field)
|
89
98
|
len_ptr = FFI::MemoryPointer.new(:size_t, 1)
|
90
99
|
out_ptr = FFI::MemoryPointer.new(:pointer, 1)
|
91
|
-
field
|
100
|
+
field = field.to_s.upcase
|
92
101
|
rc = Native.sd_journal_get_data(@ptr, field, out_ptr, len_ptr)
|
93
102
|
|
94
103
|
raise JournalError, rc if rc < 0
|
95
104
|
|
96
105
|
len = len_ptr.read_size_t
|
97
|
-
string_from_out_ptr(out_ptr, len).split(
|
106
|
+
string_from_out_ptr(out_ptr, len).split("=", 2).last
|
98
107
|
end
|
99
108
|
|
100
109
|
# Read the contents of all fields from the current journal entry.
|
@@ -121,7 +130,7 @@ module Systemd
|
|
121
130
|
|
122
131
|
JournalEntry.new(
|
123
132
|
results,
|
124
|
-
realtime_ts:
|
133
|
+
realtime_ts: read_realtime,
|
125
134
|
monotonic_ts: read_monotonic
|
126
135
|
)
|
127
136
|
end
|
@@ -228,6 +237,21 @@ module Systemd
|
|
228
237
|
)
|
229
238
|
end
|
230
239
|
|
240
|
+
# @private
|
241
|
+
def self.finalize(ptr)
|
242
|
+
proc { Native.sd_journal_close(ptr) unless ptr.nil? }
|
243
|
+
end
|
244
|
+
|
245
|
+
# @private
|
246
|
+
# some sd_journal_* functions return strings that we're expected to free
|
247
|
+
# ourselves. This function copies the string from a char* to a ruby string,
|
248
|
+
# frees the char*, and returns the ruby string.
|
249
|
+
def self.read_and_free_outstr(ptr)
|
250
|
+
str = ptr.read_string
|
251
|
+
Shim.free(ptr)
|
252
|
+
str
|
253
|
+
end
|
254
|
+
|
231
255
|
private
|
232
256
|
|
233
257
|
def open_journal(type, ptr, opts, flags)
|
@@ -239,13 +263,13 @@ module Systemd
|
|
239
263
|
Native.sd_journal_open_directory(ptr, opts[:path], 0)
|
240
264
|
when :files, :file
|
241
265
|
files = Array(opts[type])
|
242
|
-
@open_target = "file#{files.one? ?
|
266
|
+
@open_target = "file#{files.one? ? "" : "s"}:#{files.join(",")}"
|
243
267
|
Native.sd_journal_open_files(ptr, array_to_ptrs(files), 0)
|
244
268
|
when :container
|
245
269
|
@open_target = "container:#{opts[:container]}"
|
246
270
|
Native.sd_journal_open_container(ptr, opts[:container], flags)
|
247
271
|
when :local
|
248
|
-
@open_target =
|
272
|
+
@open_target = "journal:local"
|
249
273
|
Native.sd_journal_open(ptr, flags)
|
250
274
|
else
|
251
275
|
raise ArgumentError, "Unknown open type: #{type}"
|
@@ -261,7 +285,7 @@ module Systemd
|
|
261
285
|
end
|
262
286
|
|
263
287
|
def read_monotonic
|
264
|
-
out
|
288
|
+
out = FFI::MemoryPointer.new(:uint64, 1)
|
265
289
|
boot = FFI::MemoryPointer.new(Systemd::Id128::Native::Id128, 1)
|
266
290
|
|
267
291
|
rc = Native.sd_journal_get_monotonic_usec(@ptr, out, boot)
|
@@ -289,7 +313,7 @@ module Systemd
|
|
289
313
|
|
290
314
|
if type == :container && !Native.open_container?
|
291
315
|
raise ArgumentError,
|
292
|
-
|
316
|
+
"This native library version does not support opening containers"
|
293
317
|
end
|
294
318
|
|
295
319
|
flags = opts[:flags] if [:local, :container].include?(type)
|
@@ -298,10 +322,6 @@ module Systemd
|
|
298
322
|
[type, flags]
|
299
323
|
end
|
300
324
|
|
301
|
-
def self.finalize(ptr)
|
302
|
-
proc { Native.sd_journal_close(ptr) unless ptr.nil? }
|
303
|
-
end
|
304
|
-
|
305
325
|
def enumerate_helper(enum_function)
|
306
326
|
len_ptr = FFI::MemoryPointer.new(:size_t, 1)
|
307
327
|
out_ptr = FFI::MemoryPointer.new(:pointer, 1)
|
@@ -311,20 +331,11 @@ module Systemd
|
|
311
331
|
return nil if rc == 0
|
312
332
|
|
313
333
|
len = len_ptr.read_size_t
|
314
|
-
string_from_out_ptr(out_ptr, len).split(
|
334
|
+
string_from_out_ptr(out_ptr, len).split("=", 2)
|
315
335
|
end
|
316
336
|
|
317
337
|
def string_from_out_ptr(p, len)
|
318
338
|
p.read_pointer.read_string(len)
|
319
339
|
end
|
320
|
-
|
321
|
-
# some sd_journal_* functions return strings that we're expected to free
|
322
|
-
# ourselves. This function copies the string from a char* to a ruby string,
|
323
|
-
# frees the char*, and returns the ruby string.
|
324
|
-
def self.read_and_free_outstr(ptr)
|
325
|
-
str = ptr.read_string
|
326
|
-
Shim.free(ptr)
|
327
|
-
str
|
328
|
-
end
|
329
340
|
end
|
330
341
|
end
|
@@ -12,8 +12,8 @@ module Systemd
|
|
12
12
|
# with a given journal entry.
|
13
13
|
def initialize(entry, context = {})
|
14
14
|
inspect = []
|
15
|
-
@entry
|
16
|
-
@ctx
|
15
|
+
@entry = entry
|
16
|
+
@ctx = context
|
17
17
|
@fields = entry.map do |key, value|
|
18
18
|
name = key.downcase.to_sym
|
19
19
|
define_singleton_method(name) { value } unless respond_to?(name)
|
@@ -21,7 +21,7 @@ module Systemd
|
|
21
21
|
inspect.push("#{name}: '#{value}'")
|
22
22
|
name
|
23
23
|
end
|
24
|
-
@inspect = inspect.join(
|
24
|
+
@inspect = inspect.join(", ")
|
25
25
|
end
|
26
26
|
|
27
27
|
# Returns the wall-clock time that this entry was received by the journal.
|
@@ -43,7 +43,12 @@ module Systemd
|
|
43
43
|
def method_missing(m, *args)
|
44
44
|
# not all journal entries will have all fields. don't raise an error
|
45
45
|
# unless the user passed arguments.
|
46
|
-
super
|
46
|
+
super unless args.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
def respond_to_missing?(m, include_private = false)
|
51
|
+
fields&.include?(m) || super
|
47
52
|
end
|
48
53
|
|
49
54
|
# Get the value of a given field in the entry, or nil if it doesn't exist
|
@@ -94,7 +99,7 @@ module Systemd
|
|
94
99
|
|
95
100
|
# @private
|
96
101
|
def inspect
|
97
|
-
format(
|
102
|
+
format("#<%s:0x%016x %s>", self.class.name, object_id, @inspect)
|
98
103
|
end
|
99
104
|
|
100
105
|
private
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "ffi"
|
2
2
|
|
3
3
|
module Systemd
|
4
|
-
# This
|
4
|
+
# This exception is raised whenever a sd_journal_* call returns an error.
|
5
5
|
class JournalError < StandardError
|
6
6
|
# Returns the (positive) error number.
|
7
7
|
attr_reader :code
|
data/lib/systemd-journal.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# included for backwards compatibility with older versions
|
2
|
-
require
|
2
|
+
require "systemd/journal"
|
data/lib/systemd.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "rspec"
|
2
|
+
require "json"
|
3
|
+
require "simplecov"
|
4
4
|
|
5
5
|
module SpecHelper
|
6
6
|
def fixture_dir
|
7
|
-
@path ||= File.join(File.expand_path(
|
7
|
+
@path ||= File.join(File.expand_path("..", __FILE__), "fixtures")
|
8
8
|
end
|
9
9
|
|
10
10
|
def journal_file
|
11
|
-
@file ||= File.join(fixture_dir,
|
11
|
+
@file ||= File.join(fixture_dir, "test.journal")
|
12
12
|
end
|
13
13
|
|
14
14
|
def journal_json
|
15
|
-
@json ||= JSON.parse(File.read(File.join(fixture_dir,
|
15
|
+
@json ||= JSON.parse(File.read(File.join(fixture_dir, "test.json")))
|
16
16
|
end
|
17
17
|
|
18
18
|
def entry_field(index, name)
|
@@ -21,9 +21,9 @@ module SpecHelper
|
|
21
21
|
end
|
22
22
|
|
23
23
|
SimpleCov.start do
|
24
|
-
add_filter
|
24
|
+
add_filter ".bundle/"
|
25
25
|
end
|
26
|
-
require
|
26
|
+
require "systemd/journal"
|
27
27
|
|
28
28
|
RSpec.configure do |config|
|
29
29
|
config.disable_monkey_patching!
|
data/spec/systemd/id128_spec.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Systemd::Id128 do
|
4
4
|
subject(:id128) { Systemd::Id128 }
|
5
5
|
|
6
|
-
describe
|
7
|
-
it
|
6
|
+
describe "machine_id" do
|
7
|
+
it "should be a 128 bit hexadecimal string" do
|
8
8
|
expect(id128.machine_id).to match(/[0-9a-f]{16}/)
|
9
9
|
end
|
10
10
|
|
11
|
-
it
|
11
|
+
it "should match when called twice" do
|
12
12
|
m1 = id128.machine_id
|
13
13
|
m2 = id128.machine_id
|
14
14
|
expect(m1).to eq(m2)
|
@@ -17,26 +17,28 @@ RSpec.describe Systemd::Id128 do
|
|
17
17
|
|
18
18
|
# travis-ci does not boot with systemd so these cases
|
19
19
|
# will raise exceptions.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
unless ENV["TRAVIS"]
|
21
|
+
context "when booted under systemd" do
|
22
|
+
describe "boot_id" do
|
23
|
+
it "should be a 128 bit hexadecimal string" do
|
24
|
+
expect(id128.boot_id).to match(/[0-9a-f]{16}/)
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
it "should match when called twice" do
|
28
|
+
b1 = id128.boot_id
|
29
|
+
b2 = id128.boot_id
|
30
|
+
expect(b1).to eq(b2)
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
|
-
end
|
34
|
+
end
|
33
35
|
|
34
|
-
describe
|
35
|
-
it
|
36
|
+
describe "random" do
|
37
|
+
it "should be a 128 bit hexadecimal string" do
|
36
38
|
expect(id128.random).to match(/[0-9a-f]{16}/)
|
37
39
|
end
|
38
40
|
|
39
|
-
it
|
41
|
+
it "should return a different value when called again" do
|
40
42
|
r1 = id128.random
|
41
43
|
r2 = id128.random
|
42
44
|
expect(r1).to_not eq(r2)
|
@@ -1,81 +1,81 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Systemd::JournalEntry do
|
4
|
-
let(:msg)
|
5
|
-
let(:pid)
|
6
|
-
let(:hash)
|
4
|
+
let(:msg) { "test message" }
|
5
|
+
let(:pid) { "123" }
|
6
|
+
let(:hash) { {"_PID" => pid, "MESSAGE" => msg} }
|
7
7
|
subject(:entry) { Systemd::JournalEntry.new(hash) }
|
8
8
|
|
9
|
-
describe
|
10
|
-
it
|
9
|
+
describe "initialize" do
|
10
|
+
it "takes a hash as an argument" do
|
11
11
|
expect { Systemd::JournalEntry.new(hash) }.to_not raise_error
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
describe
|
16
|
-
it
|
15
|
+
describe "[]" do
|
16
|
+
it "accepts symbols as a field name" do
|
17
17
|
expect(entry[:message]).to eq(msg)
|
18
18
|
end
|
19
19
|
|
20
|
-
it
|
21
|
-
expect(entry[
|
20
|
+
it "accepts strings as a field name" do
|
21
|
+
expect(entry["message"]).to eq(msg)
|
22
22
|
end
|
23
23
|
|
24
|
-
it
|
25
|
-
expect(entry[
|
24
|
+
it "doesnt care about case" do
|
25
|
+
expect(entry["MeSSage"]).to eq(msg)
|
26
26
|
end
|
27
27
|
|
28
|
-
it
|
29
|
-
expect(entry[
|
28
|
+
it "returns nil if not found" do
|
29
|
+
expect(entry["missing"]).to be nil
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
describe
|
34
|
-
it
|
33
|
+
describe "each" do
|
34
|
+
it "is chainable as an enumerator" do
|
35
35
|
expect(entry.each.class).to be(Enumerator)
|
36
36
|
end
|
37
37
|
|
38
|
-
it
|
38
|
+
it "yields each key/value in turn" do
|
39
39
|
items = entry.map { |k, v| [k, v] }
|
40
|
-
expect(items).to eq([[
|
40
|
+
expect(items).to eq([["_PID", pid], ["MESSAGE", msg]])
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
describe
|
45
|
-
it
|
44
|
+
describe "to_h" do
|
45
|
+
it "returns a deep copy of the entry" do
|
46
46
|
copy = subject.to_h
|
47
47
|
expect(copy).to eq(hash)
|
48
|
-
expect { copy[
|
48
|
+
expect { copy["_PID"] << "3" }.to_not change { subject._pid }
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
describe
|
53
|
-
context
|
54
|
-
it
|
52
|
+
describe "catalog" do
|
53
|
+
context "without a catalog" do
|
54
|
+
it "returns nil if the entry has no catalog" do
|
55
55
|
expect(entry.catalog).to be nil
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
context
|
60
|
-
let(:catalog) {
|
59
|
+
context "with a catalog" do
|
60
|
+
let(:catalog) { "Process @_PID@ said @MESSAGE@" }
|
61
61
|
subject(:entry) do
|
62
|
-
Systemd::JournalEntry.new(hash.merge(message_id:
|
62
|
+
Systemd::JournalEntry.new(hash.merge(message_id: "123"))
|
63
63
|
end
|
64
64
|
|
65
65
|
before(:each) do
|
66
66
|
allow(Systemd::Journal).to receive(:catalog_for).and_return(catalog)
|
67
67
|
end
|
68
68
|
|
69
|
-
it
|
70
|
-
expect(entry.catalog).to eq(
|
69
|
+
it "does field substitution by default" do
|
70
|
+
expect(entry.catalog).to eq("Process 123 said test message")
|
71
71
|
end
|
72
72
|
|
73
|
-
it
|
73
|
+
it "does field substitution when requested" do
|
74
74
|
expect(entry.catalog(replace: true))
|
75
|
-
.to eq(
|
75
|
+
.to eq("Process 123 said test message")
|
76
76
|
end
|
77
77
|
|
78
|
-
it
|
78
|
+
it "skips field substitution if requested" do
|
79
79
|
expect(entry.catalog(replace: false)).to eq(catalog)
|
80
80
|
end
|
81
81
|
end
|