Module: Legion::API::Routes::Logs

Defined in:
lib/legion/api/logs.rb

Constant Summary collapse

VALID_LEVELS =
%w[error warn].freeze

Class Method Summary collapse

Class Method Details

.build_log_payload(body, level, source) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/legion/api/logs.rb', line 56

def self.build_log_payload(body, level, source)
  payload = {
    level:           level,
    message:         body[:message].to_s,
    timestamp:       Time.now.utc.iso8601(3),
    node:            Legion::Settings[:client][:name],
    legion_versions: Legion::Logging::EventBuilder.send(:legion_versions),
    ruby_version:    "#{RUBY_VERSION} #{RUBY_PLATFORM}",
    pid:             ::Process.pid,
    component_type:  body[:component_type].to_s.then { |t| t.empty? ? 'cli' : t },
    source:          source
  }
  payload[:exception_class] = body[:exception_class] if body[:exception_class]
  payload[:backtrace]       = body[:backtrace]        if body[:backtrace]
  payload[:command]         = body[:command]          if body[:command]
  payload[:error_fingerprint] = fingerprint_for(body, payload) if body[:exception_class]
  payload
end

.fingerprint_for(body, payload) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/legion/api/logs.rb', line 75

def self.fingerprint_for(body, payload)
  Legion::Logging::EventBuilder.fingerprint(
    exception_class: body[:exception_class].to_s,
    message:         body[:message].to_s,
    caller_file:     '',
    caller_line:     0,
    caller_function: '',
    gem_name:        '',
    component_type:  payload[:component_type],
    backtrace:       Array(body[:backtrace])
  )
end

.register_ingest(app) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/legion/api/logs.rb', line 15

def self.register_ingest(app)
  app.post '/api/logs' do
    body = parse_request_body
    Legion::API::Routes::Logs.validate_log_request!(self, body)

    level   = body[:level].to_s
    source  = body[:source].to_s.then { |s| s.empty? ? 'unknown' : s }
    payload = Legion::API::Routes::Logs.build_log_payload(body, level, source)
    key     = Legion::API::Routes::Logs.routing_key_for(body, level, source)

    exchange = Legion::Transport::Exchanges::Logging.cached_instance || Legion::Transport::Exchanges::Logging.new
    exchange.publish(
      Legion::JSON.dump(payload),
      routing_key:      key,
      content_type:     'application/json',
      content_encoding: 'identity',
      type:             'log',
      persistent:       true,
      app_id:           'legion',
      headers:          { 'legion_protocol_version' => '2.0' }
    )

    json_response({ published: true, routing_key: key }, status_code: 201)
  rescue StandardError => e
    Legion::Logging.error "API POST /api/logs: #{e.class} - #{e.message}" if defined?(Legion::Logging)
    halt 500, json_error('publish_error', e.message, status_code: 500)
  end
end

.registered(app) ⇒ Object



11
12
13
# File 'lib/legion/api/logs.rb', line 11

def self.registered(app)
  register_ingest(app)
end

.routing_key_for(body, level, source) ⇒ Object



88
89
90
91
# File 'lib/legion/api/logs.rb', line 88

def self.routing_key_for(body, level, source)
  kind = body[:exception_class] ? 'exception' : 'log'
  "legion.logging.#{kind}.#{level}.cli.#{source}"
end

.validate_log_request!(ctx, body) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/legion/api/logs.rb', line 44

def self.validate_log_request!(ctx, body)
  unless VALID_LEVELS.include?(body[:level].to_s)
    Legion::Logging.warn 'API POST /api/logs returned 422: level must be error or warn' if defined?(Legion::Logging)
    ctx.halt 422, ctx.json_error('invalid_level', 'level must be "error" or "warn"', status_code: 422)
  end

  return unless body[:message].to_s.strip.empty?

  Legion::Logging.warn 'API POST /api/logs returned 422: message is required' if defined?(Legion::Logging)
  ctx.halt 422, ctx.json_error('missing_field', 'message is required', status_code: 422)
end