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



170
171
172
# File 'lib/philiprehberger/structured_logger/logger.rb', line 170

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

#flushObject



166
167
168
# File 'lib/philiprehberger/structured_logger/logger.rb', line 166

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

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



107
108
109
110
111
112
# File 'lib/philiprehberger/structured_logger/logger.rb', line 107

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.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/philiprehberger/structured_logger/logger.rb', line 130

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

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

Variant of #measure that emits the same timing log entry but also returns the block’s return value. Captures and re-raises exceptions like #measure.

Examples:

Capturing a query result

result = logger.measure_value('db.query') { query_database }

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.



162
163
164
# File 'lib/philiprehberger/structured_logger/logger.rb', line 162

def measure_value(event_name, **context, &)
  measure(event_name, **context, &)
end

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



88
89
90
91
92
93
94
95
96
# File 'lib/philiprehberger/structured_logger/logger.rb', line 88

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



98
99
100
101
102
103
104
105
# File 'lib/philiprehberger/structured_logger/logger.rb', line 98

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

#with_tags(*tags) {|optional| ... } ⇒ Object, Hash

Adds the given tags to the logger’s context under the ‘:tags` key, merging with any existing tags (de-duplicated, preserving insertion order). When a block is given, the previous context is restored when the block exits (even on exception). Without a block, the change persists like #with_context.

Examples:

Block form

logger.with_tags('auth', 'request') do
  logger.info('Login attempt')
  # entry includes tags: ['auth', 'request']
end

Parameters:

  • tags (Array<String, Symbol>)

    one or more tags to add.

Yields:

  • (optional)

    executes within the tagged context; the original context is restored on exit.

Returns:

  • (Object, Hash)

    the block’s return value when a block is given, otherwise the new merged context hash.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/philiprehberger/structured_logger/logger.rb', line 70

def with_tags(*tags)
  existing = @context[:tags] || []
  merged_tags = (existing + tags).uniq
  if block_given?
    @monitor.synchronize do
      original = @context
      @context = @context.merge(tags: merged_tags).freeze
      yield
    ensure
      @context = original
    end
  else
    @monitor.synchronize do
      @context = @context.merge(tags: merged_tags).freeze
    end
  end
end