Class: Philiprehberger::StructuredLogger::Logger

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/structured_logger/logger.rb

Constant Summary collapse

LEVELS =
{ debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze
CORRELATION_ID_KEY =
:philiprehberger_structured_logger_correlation_id

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**opts) ⇒ Logger

Returns a new instance of Logger.



15
16
17
18
19
20
21
22
23
24
# File 'lib/philiprehberger/structured_logger/logger.rb', line 15

def initialize(**opts)
  @level = opts.fetch(:level, :debug)
  @context = opts.fetch(:context, {}).freeze
  @sampling = opts.fetch(:sampling, {})
  @async = opts.fetch(:async, false)
  @buffer_size = opts.fetch(:buffer_size, 1000)
  @monitor = Monitor.new

  @outputs = OutputBuilder.call(opts, @async, @buffer_size)
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



13
14
15
# File 'lib/philiprehberger/structured_logger/logger.rb', line 13

def context
  @context
end

#levelObject

Returns the value of attribute level.



13
14
15
# File 'lib/philiprehberger/structured_logger/logger.rb', line 13

def level
  @level
end

Instance Method Details

#add_output(io, level: nil, formatter: nil) ⇒ Object



31
32
33
34
35
# File 'lib/philiprehberger/structured_logger/logger.rb', line 31

def add_output(io, level: nil, formatter: nil)
  resolved = StructuredLogger.resolve_formatter(formatter)
  wrapped = @async ? AsyncWriter.new(io, buffer_size: @buffer_size) : io
  @monitor.synchronize { @outputs << { io: wrapped, level: level, formatter: resolved } }
end

#child(**extra) ⇒ Object



37
38
39
40
41
# File 'lib/philiprehberger/structured_logger/logger.rb', line 37

def child(**extra)
  clone = self.class.allocate
  clone.send(:initialize_child, @outputs, @level, @context.merge(extra), @sampling, @monitor)
  clone
end

#closeObject



118
119
120
# File 'lib/philiprehberger/structured_logger/logger.rb', line 118

def close
  @monitor.synchronize { @outputs.each { |out| out[:io].close if out[:io].is_a?(AsyncWriter) } }
end

#flushObject



114
115
116
# File 'lib/philiprehberger/structured_logger/logger.rb', line 114

def flush
  @monitor.synchronize { @outputs.each { |out| out[:io].flush if out[:io].respond_to?(:flush) } }
end

#log_exception(exception, level: :error, **extra) ⇒ Object



72
73
74
75
76
77
# File 'lib/philiprehberger/structured_logger/logger.rb', line 72

def log_exception(exception, level: :error, **extra)
  log(level, exception.message,
      error_class: exception.class.name,
      backtrace: exception.backtrace || [],
      **extra)
end

#measure(event_name, **context) { ... } ⇒ Object

Yields to the given block, measures its monotonic wall-clock duration, and emits a single info-level log entry describing the outcome. On success, the block’s return value is returned. On exception, the failure is logged and the original exception is re-raised.

Examples:

Measuring a database query

logger.measure('db.query', table: 'users') { User.find(1) }
# logs event: 'db.query', table: 'users', duration_ms: 12.345

Parameters:

  • event_name (String, Symbol)

    the event name to record as the ‘event` field in the log entry.

  • context (Hash)

    extra context merged into the log entry.

Yields:

  • executes the measured block.

Returns:

  • (Object)

    the block’s return value on success.

Raises:

  • re-raises any exception raised by the block.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/philiprehberger/structured_logger/logger.rb', line 95

def measure(event_name, **context)
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  begin
    result = yield
    duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0).round(3)
    log(:info, event_name.to_s, event: event_name, duration_ms: duration_ms, **context)
    result
  rescue StandardError => e
    duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0).round(3)
    log(:info, event_name.to_s,
        event: event_name,
        duration_ms: duration_ms,
        error: e.message,
        error_class: e.class.name,
        **context)
    raise
  end
end

#silence(temp_level = :fatal, &block) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/philiprehberger/structured_logger/logger.rb', line 53

def silence(temp_level = :fatal, &block)
  @monitor.synchronize do
    original = @level
    @level = temp_level
    block.call
  ensure
    @level = original
  end
end

#with_context(**extra, &block) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/philiprehberger/structured_logger/logger.rb', line 43

def with_context(**extra, &block)
  @monitor.synchronize do
    original = @context
    @context = @context.merge(extra).freeze
    block.call
  ensure
    @context = original
  end
end

#with_correlation_id(id = nil, &block) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/philiprehberger/structured_logger/logger.rb', line 63

def with_correlation_id(id = nil, &block)
  id ||= SecureRandom.uuid
  previous = Thread.current[CORRELATION_ID_KEY]
  Thread.current[CORRELATION_ID_KEY] = id
  block.call
ensure
  Thread.current[CORRELATION_ID_KEY] = previous
end