Class: Lumberjack::CaptureDevice

Inherits:
Device::Test
  • Object
show all
Includes:
Enumerable
Defined in:
lib/lumberjack/capture_device.rb

Overview

Lumberjack device for capturing log entries into memory to allow them to be inspected for testing purposes.

Defined Under Namespace

Modules: RSpec Classes: IncludeLogEntryMatcher

Constant Summary collapse

VERSION =
::File.read(::File.join(__dir__, "..", "..", "VERSION")).strip.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ CaptureDevice

Initialize a new CaptureDevice.

Parameters:

  • options (Hash) (defaults to: {})

    Options to pass to the parent Test device.



72
73
74
75
# File 'lib/lumberjack/capture_device.rb', line 72

def initialize(options = {})
  @underlying_device = options[:underlying_device]
  super({max_entries: 1_000_000}.merge(options))
end

Instance Attribute Details

#underlying_deviceLumberjack::Device? (readonly)

The original device from the logger before capture started.

Returns:

  • (Lumberjack::Device, nil)

    The original device, or nil if none was set.



67
68
69
# File 'lib/lumberjack/capture_device.rb', line 67

def underlying_device
  @underlying_device
end

Class Method Details

.capture(logger, write_to_original: true) {|device| ... } ⇒ Lumberjack::CaptureDevice

Capture the entries written by the logger within a block. Within the block all log entries will be written to a CaptureDevice rather than to the normal output for the logger. In addition, the log level will be set to debug. The logger's formatters remain active, so captured entries contain the same formatted values that would have been logged. The device being written to be both yielded to the block as well as returned by the method call.

This method is not thread safe. It swaps the device and log level on the logger itself, so concurrent calls on the same logger will interfere with each other and entries logged by other threads during the block will be captured as well. The log level is set on the current context, so threads spawned within the block may not log at the debug level.

Examples:

Lumberjack::CaptureDevice.capture(logger) do |logs|
  logger.info("This will be captured")
  expect(logs).to include(severity: :info, message: "This will be captured")
end
logs = Lumberjack::CaptureDevice.capture(logger) { logger.info("This will be captured") }
expect(logs).to include(severity: :info, message: "This will be captured")

Parameters:

  • logger (Lumberjack::Logger)

    The logger to capture entries from.

  • write_to_original (Boolean) (defaults to: true)

    If true (the default) the captured entries will be written back to the original device when the block completes. If false, the captured entries will not be written back.

Yields:

  • (device)

    The block to execute while capturing log entries.

Yield Parameters:

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/lumberjack/capture_device.rb', line 45

def capture(logger, write_to_original: true)
  save_device = logger.device
  save_level = logger.level
  device = new(underlying_device: save_device)

  begin
    logger.device = device
    logger.level = :debug
    yield device
  ensure
    logger.device = save_device
    logger.level = save_level
    device.write_to_underlying_device if write_to_original
  end

  device
end

Instance Method Details

#each {|entry| ... } ⇒ Array<Lumberjack::LogEntry>

Iterate over each captured log entry.

Yields:

  • (entry)

    Block to execute for each captured entry.

Yield Parameters:

  • entry (Lumberjack::LogEntry)

    A captured log entry.

Returns:

  • (Array<Lumberjack::LogEntry>)

    The captured entries (when no block given).



213
214
215
# File 'lib/lumberjack/capture_device.rb', line 213

def each(&block)
  entries.each(&block)
end

#entriesArray<Lumberjack::LogEntry>

Return a thread-safe copy of all captured log entries. This must be redefined here because Enumerable#entries would otherwise shadow the thread-safe implementation inherited from Lumberjack::Device::Test.

Returns:

  • (Array<Lumberjack::LogEntry>)

    A copy of all captured log entries.



195
196
197
# File 'lib/lumberjack/capture_device.rb', line 195

def entries
  @lock.synchronize { @buffer.dup }
end

#extract(message: nil, severity: nil, attributes: nil, progname: nil, limit: nil) ⇒ Array<Lumberjack::LogEntry>

Return all the captured entries that match the specified filters. The device entry_formatter is used to match the filters, so unformatted values can be used in the filters if it is set.

For severity, you can specify either a numeric constant (i.e. Logger::WARN) or a symbol (i.e. :warn).

For message and progname you can specify a string to perform an exact match or a regular expression to perform a partial or pattern match. You can also supply any matcher value available in your test library (i.e. in rspec you could use anything or instance_of(Error), etc.).

Examples:

logs.extract(severity: :warn, message: /something happened/, attributes: {user: "john"})

Parameters:

  • message (String, Regexp, nil) (defaults to: nil)

    The message to match against the log entries.

  • severity (String, Symbol, Integer, nil) (defaults to: nil)

    The severity to match against the log entries.

  • attributes (Hash, nil) (defaults to: nil)

    A hash of attribute names to values to match against the log entries. The attributes will match nested attributes using dot notation (e.g. foo.bar will match an attribute with the structure {foo: {bar: "value"}}).

  • progname (String, nil) (defaults to: nil)

    The program name to match against the log entries.

  • limit (Integer, nil) (defaults to: nil)

    The maximum number of entries to return. If nil, all matching entries will be returned.

Returns:

  • (Array<Lumberjack::LogEntry>)

    An array of log entries that match the specified filters.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/lumberjack/capture_device.rb', line 100

def extract(message: nil, severity: nil, attributes: nil, progname: nil, limit: nil)
  matched = []

  matcher = LogEntryMatcher.new(
    message: message,
    severity: severity,
    attributes: attributes,
    progname: progname,
    formatter: entry_formatter
  )

  entries.each do |entry|
    matched << entry if matcher.match?(entry)
    break if limit && matched.size >= limit
  end

  matched
end

#include?(filters) ⇒ Boolean

Return true if the captured log entries match the specified filters. The filters are the same as the ones used by the extract method.

This must be redefined here because Enumerable#include? would otherwise shadow the implementation inherited from Lumberjack::Device::Test.

Examples:

logs.include?(severity: :warn, message: /something happened/, attributes: {user: "john"})

Parameters:

  • filters (Hash)

    The filters to apply to the captured entries.

Options Hash (filters):

  • :message (String, Regexp)

    The message to match against the log entries.

  • :severity (String, Symbol, Integer)

    The severity to match against the log entries.

  • :attributes (Hash)

    A hash of attribute names to values to match against the log entries. The attributes will match nested attributes using dot notation (e.g. foo.bar will match an attribute with the structure {foo: {bar: "value"}}).

  • :progname (String)

    The program name to match against the log entries.

Returns:

  • (Boolean)

    True if any entries match the specified filters, false otherwise.



136
137
138
139
140
141
142
143
144
# File 'lib/lumberjack/capture_device.rb', line 136

def include?(filters)
  filters = filters.transform_keys(&:to_sym)
  unknown_keys = filters.keys - [:message, :severity, :attributes, :progname]
  unless unknown_keys.empty?
    raise ArgumentError, "unknown log filters: #{unknown_keys.map(&:inspect).join(", ")}"
  end

  !!match(**filters)
end

#inspectString

Provide a detailed string representation showing all captured entries.

Returns:

  • (String)

    A formatted string showing all captured log entries.



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/lumberjack/capture_device.rb', line 171

def inspect
  message = +"<##{self.class.name} #{length} #{(length == 1) ? "entry" : "entries"} captured:\n"
  template = Lumberjack::LocalLogTemplate.new
  entries.each do |entry|
    formatted = template.call(entry).split("\n").collect { |line| "  #{line}" }.join("\n")
    message << formatted
    message << "\n"
  end
  message << ">"
  message
end

#lengthInteger Also known as: size

Return the number of captured log entries.

Returns:

  • (Integer)

    The number of captured entries.



202
203
204
# File 'lib/lumberjack/capture_device.rb', line 202

def length
  entries.length
end

#to_sString

Provide a simple string representation showing the count of captured entries.

Returns:

  • (String)

    A brief description of the captured entries count.



186
187
188
# File 'lib/lumberjack/capture_device.rb', line 186

def to_s
  "<##{self.class.name} #{length} #{(length == 1) ? "entry" : "entries"} captured>"
end

#write_to_underlying_device(attributes: nil) ⇒ void

This method returns an undefined value.

Write the captured log entries to the underlying device.

Parameters:

  • attributes (Hash, nil) (defaults to: nil)

    Additional attributes to add to each entry as it is written. Attributes already set on an entry take precedence over these values.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/lumberjack/capture_device.rb', line 151

def write_to_underlying_device(attributes: nil)
  return unless @underlying_device

  if attributes.nil? || attributes.empty?
    write_to(@underlying_device)
  else
    extra_attributes = Lumberjack::Utils.expand_attributes(attributes)
    entries.each do |entry|
      copy = entry.dup
      copy.attributes = extra_attributes.merge(Lumberjack::Utils.expand_attributes(entry.attributes || {}))
      @underlying_device.write(copy)
    end
  end

  nil
end