Class: A2A::Utils::StructuredLogger

Inherits:
Object
  • Object
show all
Defined in:
lib/a2a/utils/structured_logger.rb

Constant Summary collapse

LEVELS =

Log levels

{
  debug: Logger::DEBUG,
  info: Logger::INFO,
  warn: Logger::WARN,
  error: Logger::ERROR,
  fatal: Logger::FATAL
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output: $stdout, level: :info, service_name: "a2a-ruby", version: A2A::VERSION, correlation_id: nil) ⇒ StructuredLogger

Initialize structured logger

Parameters:

  • output (IO, String) (defaults to: $stdout)

    Output destination (IO object or file path)

  • level (Symbol, Integer) (defaults to: :info)

    Log level

  • service_name (String) (defaults to: "a2a-ruby")

    Name of the service

  • version (String) (defaults to: A2A::VERSION)

    Version of the service

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

    Correlation ID for request tracking



36
37
38
39
40
41
42
43
44
45
# File 'lib/a2a/utils/structured_logger.rb', line 36

def initialize(output: $stdout, level: :info, service_name: "a2a-ruby", version: A2A::VERSION,
               correlation_id: nil)
  @logger = Logger.new(output)
  @logger.level = LEVELS[level] || level
  @logger.formatter = method(:format_log_entry)
  @service_name = service_name
  @version = version
  @correlation_id = correlation_id || generate_correlation_id
  @start_time = Time.now
end

Instance Attribute Details

#correlation_idObject

Returns the value of attribute correlation_id.



25
26
27
# File 'lib/a2a/utils/structured_logger.rb', line 25

def correlation_id
  @correlation_id
end

#loggerObject (readonly)

Returns the value of attribute logger.



26
27
28
# File 'lib/a2a/utils/structured_logger.rb', line 26

def logger
  @logger
end

#service_nameObject (readonly)

Returns the value of attribute service_name.



26
27
28
# File 'lib/a2a/utils/structured_logger.rb', line 26

def service_name
  @service_name
end

#versionObject (readonly)

Returns the value of attribute version.



26
27
28
# File 'lib/a2a/utils/structured_logger.rb', line 26

def version
  @version
end

Instance Method Details

#build_log_entry(level, message, **context) ⇒ Hash (private)

Build structured log entry

Parameters:

  • level (Symbol)

    Log level

  • message (String)

    Log message

  • context (Hash)

    Additional context

Returns:

  • (Hash)

    Structured log entry



261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/a2a/utils/structured_logger.rb', line 261

def build_log_entry(level, message, **context)
  {
    timestamp: Time.now.utc.iso8601(3),
    level: level.to_s.upcase,
    message: message,
    service: @service_name,
    version: @version,
    correlation_id: @correlation_id,
    thread_id: Thread.current.object_id,
    **context
  }
end

#child(**context) ⇒ StructuredLogger

Create a child logger with additional context

Parameters:

  • context (Hash)

    Additional context to include in all log entries

Returns:



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/a2a/utils/structured_logger.rb', line 195

def child(**context)
  child_logger = self.class.new(
    output: @logger.instance_variable_get(:@logdev).dev,
    level: @logger.level,
    service_name: @service_name,
    version: @version,
    correlation_id: @correlation_id
  )

  child_logger.instance_variable_set(:@additional_context, context)
  child_logger
end

#debug(message, **context) ⇒ Object

Log a debug message

Parameters:

  • message (String)

    Log message

  • context (Hash)

    Additional context



52
53
54
# File 'lib/a2a/utils/structured_logger.rb', line 52

def debug(message, **context)
  log(:debug, message, **context)
end

#error(message, error: nil, **context) ⇒ Object

Log an error message

Parameters:

  • message (String)

    Log message

  • error (Exception, nil) (defaults to: nil)

    Exception object

  • context (Hash)

    Additional context



80
81
82
83
# File 'lib/a2a/utils/structured_logger.rb', line 80

def error(message, error: nil, **context)
  context[:error] = format_error(error) if error
  log(:error, message, **context)
end

#fatal(message, error: nil, **context) ⇒ Object

Log a fatal message

Parameters:

  • message (String)

    Log message

  • error (Exception, nil) (defaults to: nil)

    Exception object

  • context (Hash)

    Additional context



91
92
93
94
# File 'lib/a2a/utils/structured_logger.rb', line 91

def fatal(message, error: nil, **context)
  context[:error] = format_error(error) if error
  log(:fatal, message, **context)
end

#format_error(error) ⇒ Hash (private)

Format error information

Parameters:

  • error (Exception)

    Exception object

Returns:

  • (Hash)

    Formatted error information



297
298
299
300
301
302
303
# File 'lib/a2a/utils/structured_logger.rb', line 297

def format_error(error)
  {
    class: error.class.name,
    message: error.message,
    backtrace: error.backtrace&.first(10) # Limit backtrace length
  }
end

#format_log_entry(severity, _datetime, _progname, msg) ⇒ String (private)

Format log entry for output

Parameters:

  • severity (String)

    Log severity

  • datetime (Time)

    Log timestamp

  • progname (String)

    Program name

  • msg (Hash)

    Log message data

Returns:

  • (String)

    Formatted log entry



282
283
284
285
286
287
288
289
290
# File 'lib/a2a/utils/structured_logger.rb', line 282

def format_log_entry(severity, _datetime, _progname, msg)
  if msg.is_a?(Hash)
    "#{JSON.generate(msg)}\n"
  else
    # Fallback for non-structured messages
    entry = build_log_entry(severity.downcase.to_sym, msg.to_s)
    "#{JSON.generate(entry)}\n"
  end
end

#generate_correlation_idString

Generate a new correlation ID

Returns:

  • (String)

    New correlation ID



217
218
219
# File 'lib/a2a/utils/structured_logger.rb', line 217

def generate_correlation_id
  SecureRandom.hex(8)
end

#http_request(method:, url:, status:, duration:, **context) ⇒ Object

Log HTTP request/response

Parameters:

  • method (String)

    HTTP method

  • url (String)

    Request URL

  • status (Integer)

    Response status

  • duration (Float)

    Request duration

  • context (Hash)

    Additional context



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/a2a/utils/structured_logger.rb', line 132

def http_request(method:, url:, status:, duration:, **context)
  log_data = {
    http_method: method.to_s.upcase,
    url: url,
    status_code: status,
    duration: duration,
    **context
  }

  level = case status
          when 200..299 then :info
          when 300..399 then :info
          when 400..499 then :warn
          when 500..599 then :error
          else :info
          end

  log(level, "HTTP #{method.upcase} #{url}", **log_data)
end

#info(message, **context) ⇒ Object

Log an info message

Parameters:

  • message (String)

    Log message

  • context (Hash)

    Additional context



61
62
63
# File 'lib/a2a/utils/structured_logger.rb', line 61

def info(message, **context)
  log(:info, message, **context)
end

#log(level, message, **context) ⇒ Object (private)

Log a message with structured data

Parameters:

  • level (Symbol)

    Log level

  • message (String)

    Log message

  • context (Hash)

    Additional context



243
244
245
246
247
248
249
250
251
252
# File 'lib/a2a/utils/structured_logger.rb', line 243

def log(level, message, **context)
  return unless @logger.public_send("#{level}?")

  # Merge additional context from child loggers
  additional_context = instance_variable_get(:@additional_context) || {}
  context = additional_context.merge(context)

  log_entry = build_log_entry(level, message, **context)
  @logger.public_send(level, log_entry)
end

#metric(metric_name, value, unit: nil, **tags) ⇒ Object

Log performance metrics

Parameters:

  • metric_name (String)

    Name of the metric

  • value (Numeric)

    Metric value

  • unit (String) (defaults to: nil)

    Unit of measurement

  • tags (Hash)

    Metric tags



179
180
181
182
183
184
185
186
187
188
# File 'lib/a2a/utils/structured_logger.rb', line 179

def metric(metric_name, value, unit: nil, **tags)
  log_data = {
    metric_name: metric_name,
    metric_value: value,
    metric_unit: unit,
    metric_tags: tags
  }.compact

  log(:info, "Metric: #{metric_name}", **log_data)
end

#statsHash

Get logger statistics

Returns:

  • (Hash)

    Logger statistics



225
226
227
228
229
230
231
232
233
# File 'lib/a2a/utils/structured_logger.rb', line 225

def stats
  {
    service_name: @service_name,
    version: @version,
    correlation_id: @correlation_id,
    uptime: Time.now - @start_time,
    log_level: @logger.level
  }
end

#task_operation(operation:, task_id:, context_id: nil, status: nil, **context) ⇒ Object

Log task operation

Parameters:

  • operation (String)

    Task operation (create, update, cancel, etc.)

  • task_id (String)

    Task ID

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

    Context ID

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

    Task status

  • context (Hash)

    Additional context



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/a2a/utils/structured_logger.rb', line 160

def task_operation(operation:, task_id:, context_id: nil, status: nil, **context)
  log_data = {
    operation: operation,
    task_id: task_id,
    context_id: context_id,
    task_status: status,
    **context
  }.compact

  log(:info, "Task #{operation}", **log_data)
end

#timed(message, level: :info, **context) { ... } ⇒ Object

Log with timing information

Parameters:

  • message (String)

    Log message

  • level (Symbol) (defaults to: :info)

    Log level

  • context (Hash)

    Additional context

Yields:

  • Block to time

Returns:

  • (Object)

    Result of the block



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/a2a/utils/structured_logger.rb', line 104

def timed(message, level: :info, **context)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

  begin
    result = yield
    duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time

    log(level, "#{message} completed", duration: duration, **context)
    result
  rescue StandardError => e
    duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time

    error("#{message} failed",
          error: e,
          duration: duration,
          **context)
    raise
  end
end

#warn(message, **context) ⇒ Object

Log a warning message

Parameters:

  • message (String)

    Log message

  • context (Hash)

    Additional context



70
71
72
# File 'lib/a2a/utils/structured_logger.rb', line 70

def warn(message, **context)
  log(:warn, message, **context)
end