class Systemd::Journal

Class to allow interacting with the systemd journal. To read from the journal, instantiate a new {Systemd::Journal}; to write to the journal, use {Systemd::Journal::Writable::ClassMethods#message Journal.message} or {Systemd::Journal::Writable::ClassMethods#print Journal.print}.

Constants

KERNEL_FIELDS

Fields used in messages originating from the kernel.

TRUSTED_FIELDS

Fields generated by the journal and added to each event.

USER_FIELDS

Fields directly passed by client programs and stored in the journal.

VERSION

The version of the systemd-journal gem.

Public Class Methods

catalog_for(message_id) click to toggle source
# File lib/systemd/journal.rb, line 137
def self.catalog_for(message_id)
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)

  rc = Native.sd_journal_get_catalog_for_message_id(
    Systemd::Id128::Native::Id128.from_s(message_id),
    out_ptr
  )
  raise JournalError, rc if rc < 0

  read_and_free_outstr(out_ptr.read_pointer)
end
new(opts = {}) click to toggle source

Returns a new instance of a Journal, opened with the provided options. @param [Hash] opts optional initialization parameters. @option opts [Integer] :flags a set of bitwise OR-ed

{Systemd::Journal::Flags} which control what journal files are opened.
Defaults to `0`, meaning all journals avaiable to the current user.

@option opts [String] :path if provided, open the journal files living

in the provided directory only.  Any provided flags will be ignored
since sd_journal_open_directory does not currently accept any flags.

@option opts [Array] :files if provided, open the provided journal files

only.  Any provided flags will be ignored since sd_journal_open_files
does not currently accept any flags.

@option opts [String] :container if provided, open the journal files from

the container with the provided machine name only.

@example Read only system journal entries

j = Systemd::Journal.new(flags: Systemd::Journal::Flags::SYSTEM_ONLY)

@example Directly open a journal directory

j = Systemd::Journal.new(
  path: '/var/log/journal/5f5777e46c5f4131bd9b71cbed6b9abf'
)
# File lib/systemd/journal.rb, line 47
def initialize(opts = {})
  open_type, flags = validate_options!(opts)
  ptr = FFI::MemoryPointer.new(:pointer, 1)

  @finalize = (opts.key?(:finalize) ? opts.delete(:finalize) : true)
  rc = open_journal(open_type, ptr, opts, flags)
  raise JournalError, rc if rc < 0

  @ptr = ptr.read_pointer
  file_descriptor
  ObjectSpace.define_finalizer(self, self.class.finalize(@ptr)) if @finalize
end
open(opts = {}) { |j| ... } click to toggle source
# File lib/systemd/journal.rb, line 60
def self.open(opts = {})
  j = new(opts.merge(finalize: false))
  yield j
ensure
  j.close if j
end

Private Class Methods

finalize(ptr) click to toggle source
# File lib/systemd/journal.rb, line 300
def self.finalize(ptr)
  proc { Native.sd_journal_close(ptr) unless ptr.nil? }
end
read_and_free_outstr(ptr) click to toggle source

some sd_journal_* functions return strings that we're expected to free ourselves. This function copies the string from a char* to a ruby string, frees the char*, and returns the ruby string.

# File lib/systemd/journal.rb, line 323
def self.read_and_free_outstr(ptr)
  str = ptr.read_string
  LibC.free(ptr)
  str
end

Public Instance Methods

close() click to toggle source

Explicitly close the underlying Journal file. Once this is done, any operations on the instance will fail and raise an exception.

# File lib/systemd/journal.rb, line 206
def close
  return if @ptr.nil?

  ObjectSpace.undefine_finalizer(self) if @finalize
  Native.sd_journal_close(@ptr)

  @ptr = nil
end
closed?() click to toggle source
# File lib/systemd/journal.rb, line 215
def closed?
  @ptr.nil?
end
current_catalog() click to toggle source
# File lib/systemd/journal.rb, line 128
def current_catalog
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)

  rc = Native.sd_journal_get_catalog(@ptr, out_ptr)
  raise JournalError, rc if rc < 0

  Journal.read_and_free_outstr(out_ptr.read_pointer)
end
current_entry() { |key, value| ... } click to toggle source

Read the contents of all fields from the current journal entry. If given a block, it will yield each field in the form of `(fieldname, value)`.

{#move_next} or {#move_previous} must be called at least once after initialization or seeking prior to calling {#current_entry}

@return [Hash] the contents of the current journal entry. @example Print all items in the current entry

j = Systemd::Journal.new
j.move_next
j.current_entry{ |field, value| puts "#{field}: #{value}" }
# File lib/systemd/journal.rb, line 111
def current_entry
  Native.sd_journal_restart_data(@ptr)
  results = {}

  while (kvpair = enumerate_helper(:sd_journal_enumerate_data))
    key, value = kvpair
    results[key] = value
    yield(key, value) if block_given?
  end

  JournalEntry.new(
    results,
    realtime_ts:  read_realtime,
    monotonic_ts: read_monotonic
  )
end
data_threshold() click to toggle source

Get the maximum length of a data field that will be returned. Fields longer than this will be truncated. Default is 64K. @return [Integer] size in bytes.

# File lib/systemd/journal.rb, line 186
def data_threshold
  size_ptr = FFI::MemoryPointer.new(:size_t, 1)
  if (rc = Native.sd_journal_get_data_threshold(@ptr, size_ptr)) < 0
    raise JournalError, rc
  end

  size_ptr.read_size_t
end
data_threshold=(threshold) click to toggle source

Set the maximum length of a data field that will be returned. Fields longer than this will be truncated.

# File lib/systemd/journal.rb, line 197
def data_threshold=(threshold)
  if (rc = Native.sd_journal_set_data_threshold(@ptr, threshold)) < 0
    raise JournalError, rc
  end
end
disk_usage() click to toggle source

Get the number of bytes the Journal is currently using on disk. If {Systemd::Journal::Flags::LOCAL_ONLY} was passed when opening the journal, this value will only reflect the size of journal files of the local host, otherwise of all hosts. @return [Integer] size in bytes

# File lib/systemd/journal.rb, line 175
def disk_usage
  size_ptr = FFI::MemoryPointer.new(:uint64)
  rc = Native.sd_journal_get_usage(@ptr, size_ptr)

  raise JournalError, rc if rc < 0
  size_ptr.read_uint64
end
each() { |current_entry while move_next| ... } click to toggle source

Iterate over each entry in the journal, respecting the applied conjunctions/disjunctions. If a block is given, it is called with each entry until no more entries remain. Otherwise, returns an enumerator which can be chained.

# File lib/systemd/journal.rb, line 71
def each
  return to_enum(:each) unless block_given?

  seek(:head)
  yield current_entry while move_next
end
inspect() click to toggle source

@private

# File lib/systemd/journal.rb, line 220
def inspect
  format(
    '#<%s:0x%016x target: "%s", flags: 0x%08x>',
    self.class.name,
    object_id,
    @open_target,
    @open_flags
  )
end
query_unique(field) click to toggle source

Get the list of unique values stored in the journal for the given field. If passed a block, each possible value will be yielded. @return [Array] the list of possible values. @example Fetch all possible boot ids from the journal

j = Systemd::Journal.new
j.query_unique('_BOOT_ID')
# File lib/systemd/journal.rb, line 155
def query_unique(field)
  results = []

  Native.sd_journal_restart_unique(@ptr)

  rc = Native.sd_journal_query_unique(@ptr, field.to_s.upcase)
  raise JournalError, rc if rc < 0

  while (kvpair = enumerate_helper(:sd_journal_enumerate_unique))
    results << kvpair.last
  end

  results
end
read_field(field) click to toggle source

Read the contents of the provided field from the current journal entry.

{#move_next} or {#move_previous} must be called at least once after
initialization or seeking prior to attempting to read data.

@param [String] field the name of the field to read. @return [String] the value of the requested field. @example Read the `MESSAGE` field from the current entry

j = Systemd::Journal.new
j.move_next
puts j.read_field('MESSAGE')
# File lib/systemd/journal.rb, line 87
def read_field(field)
  len_ptr = FFI::MemoryPointer.new(:size_t, 1)
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)
  field   = field.to_s.upcase
  rc = Native.sd_journal_get_data(@ptr, field, out_ptr, len_ptr)

  raise JournalError, rc if rc < 0

  len = len_ptr.read_size_t
  string_from_out_ptr(out_ptr, len).split('=', 2).last
end

Private Instance Methods

array_to_ptrs(strings) click to toggle source
# File lib/systemd/journal.rb, line 272
def array_to_ptrs(strings)
  ptr = FFI::MemoryPointer.new(:pointer, strings.length + 1)
  strings.each_with_index do |s, i|
    ptr[i].put_pointer(0, FFI::MemoryPointer.from_string(s))
  end
  ptr[strings.length].put_pointer(0, nil)
  ptr
end
enumerate_helper(enum_function) click to toggle source
# File lib/systemd/journal.rb, line 304
def enumerate_helper(enum_function)
  len_ptr = FFI::MemoryPointer.new(:size_t, 1)
  out_ptr = FFI::MemoryPointer.new(:pointer, 1)

  rc = Native.send(enum_function, @ptr, out_ptr, len_ptr)
  raise JournalError, rc if rc < 0
  return nil if rc == 0

  len = len_ptr.read_size_t
  string_from_out_ptr(out_ptr, len).split('=', 2)
end
open_journal(type, ptr, opts, flags) click to toggle source
# File lib/systemd/journal.rb, line 232
def open_journal(type, ptr, opts, flags)
  @open_flags = flags

  case type
  when :path
    @open_target = "path:#{opts[:path]}"
    Native.sd_journal_open_directory(ptr, opts[:path], 0)
  when :files, :file
    files = Array(opts[type])
    @open_target = "file#{files.one? ? '' : 's'}:#{files.join(',')}"
    Native.sd_journal_open_files(ptr, array_to_ptrs(files), 0)
  when :container
    @open_target = "container:#{opts[:container]}"
    Native.sd_journal_open_container(ptr, opts[:container], flags)
  when :local
    @open_target = 'journal:local'
    Native.sd_journal_open(ptr, flags)
  else
    raise ArgumentError, "Unknown open type: #{type}"
  end
end
read_monotonic() click to toggle source
# File lib/systemd/journal.rb, line 262
def read_monotonic
  out  = FFI::MemoryPointer.new(:uint64, 1)
  boot = FFI::MemoryPointer.new(Systemd::Id128::Native::Id128, 1)

  rc = Native.sd_journal_get_monotonic_usec(@ptr, out, boot)
  raise JournalError, rc if rc < 0

  [out.read_uint64, Systemd::Id128::Native::Id128.new(boot).to_s]
end
read_realtime() click to toggle source
# File lib/systemd/journal.rb, line 254
def read_realtime
  out = FFI::MemoryPointer.new(:uint64, 1)
  rc = Native.sd_journal_get_realtime_usec(@ptr, out)
  raise JournalError, rc if rc < 0

  out.read_uint64
end
string_from_out_ptr(p, len) click to toggle source
# File lib/systemd/journal.rb, line 316
def string_from_out_ptr(p, len)
  p.read_pointer.read_string(len)
end
validate_options!(opts) click to toggle source
# File lib/systemd/journal.rb, line 281
def validate_options!(opts)
  exclusive = [:path, :files, :container, :file]
  given = (opts.keys & exclusive)

  raise ArgumentError, "conflicting options: #{given}" if given.length > 1

  type = given.first || :local

  if type == :container && !Native.open_container?
    raise ArgumentError,
          'This native library version does not support opening containers'
  end

  flags = opts[:flags] if [:local, :container].include?(type)
  flags ||= 0

  [type, flags]
end