1require 'aws-sdk'
 
2require 'ipaddr'
 
3
 
4class SignatureLogs
 
5  class Log
 
6    PATTERN = /(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:, (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))* - .{0}- \[(?<day>[\d]{2})\/(?<month>[\w]+)\/(?<year>[\d]{4})\:(?<hour>[\d]{2})\:(?<min>[\d]{2})\:(?<sec>[\d]{2}) [^$]+\] "(?<method>GET|POST|PUT|DELETE) (?<uri>[^\s]+?) HTTP\/1\.1" (?<response>[\d]+) [\d]+ "(?<referrer>[^\s]+?)" "(?<agent>[^\"]+?)"/
 
7    MONTHS = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]
 
8    attr_reader :message, :data
 
 9
 
10    def initialize(message)
 
11      @message = message
 
12      @data = message.match(PATTERN)
 
13    end
 
 
15    def blank?
 
16      data.nil?
 
17    end
 
 
19    def ip_address
 
20      if present?
 
21        @ip_address ||= data && ::IPAddr.new(data["ip"])
 
22      end
 
23    end
 
 
25    def timestamp
 
26      if present?
 
27        @timestamp ||= data && ::Time.utc(year, month, day, hour, min, sec).in_time_zone
 
28      end
 
29    end
 
 
31    def method
 
32      data["method"] if present?
 
33    end
 
 
35    def uri
 
36      data["uri"] if present?
 
37    end
 
 
39    def response
 
40      data["response"] if present?
 
41    end
 
 
43    def referrer
 
44      data["referrer"] if present?
 
45    end
 
 
47    def agent
 
48      data["agent"] if present?
 
49    end
 
 
51    def ==(other)
 
52      return false unless other.is_a?(self.class)
 
53      message == other.message
 
54    end
 
 
56    private
 
 
58    def year; data["year"].to_i; end
 
59    def month; MONTHS.index(data["month"]) + 1; end
 
60    def day; data["day"].to_i; end
 
61    def hour; data["hour"].to_i; end
 
62    def min; data["min"].to_i; end
 
63    def sec; data["sec"].to_i; end
 
64  end
 
 
66  include Enumerable
 
 
68  attr_reader :signature
 
 
70  delegate :created_at, to: :signature
 
71  delegate :ip_address, to: :signature
 
72  delegate :validated_at, to: :signature
 
73  delegate :validated_ip, to: :signature
 
74  delegate :size, :empty?, to: :logs
 
 
76  class << self
 
77    def find(id)
 
78      new(id)
 
79    end
 
80  end
 
  • Complexity 1 » saikuro
82  def initialize(id)
 
83    @signature = Signature.find(id)
 
84  end
 
  • Complexity 2 » saikuro
86  def each(&block)
 
87    logs.each { |log| yield log }
 
88  end
 
 
90  private
 
  • Complexity 1 » saikuro
92  def client
 
93    @client ||= Aws::CloudWatchLogs::Client.new
 
94  end
 
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
96  def log_group_name
 
97    ENV.fetch("NGINX_LOG_GROUP_NAME", "nginx-access-logs")
 
98  end
 
 99
  • UtilityFunction - doesn't depend on instance state (maybe move it to another class?) » reek
  • Complexity 1 » saikuro
100  def ms(time)
 
101    (time.to_f * 1000).to_i
 
102  end
 
  • UncommunicativeVariableName - has the variable name 'e' » reek
  • Complexity 2 » saikuro
104  def logs
 
105    @logs ||= fetch_events.map { |e| Log.new(e.message) }.reject(&:blank?).sort_by(&:timestamp)
 
106  end
 
  • Complexity 2 » saikuro
108  def fetch_events
 
109    if overlapping?
 
110      fetch_combined_events
 
111    else
 
112      fetch_create_events + fetch_validate_events
 
113    end
 
114  end
 
  • DuplicateMethodCall - calls '5.minutes' 2 times » reek
  • Complexity 4 » saikuro
116  def overlapping?
 
117    return false unless validated_at
 
118    return false unless validated_ip
 
119    return false unless ip_address == validated_ip
 
 
121    (created_at + 5.minutes) >= (validated_at - 5.minutes)
 
122  end
 
  • DuplicateMethodCall - calls '5.minutes' 2 times » reek
  • UncommunicativeVariableName - has the variable name 'e' » reek
  • Complexity 2 » saikuro
124  def fetch_create_events
 
125    request = {
 
126      log_group_name: log_group_name,
 
127      start_time: ms(created_at - 5.minutes),
 
128      end_time: ms(created_at + 5.minutes),
 
129      filter_pattern: ip_address,
 
130      interleaved: true
 
131    }
 
 
133    client.filter_log_events(request).events
 
 
135  rescue Aws::CloudWatchLogs::Errors::ServiceError => e
 
136    []
 
137  end
 
  • DuplicateMethodCall - calls '5.minutes' 2 times » reek
  • TooManyStatements - has approx 6 statements » reek
  • UncommunicativeVariableName - has the variable name 'e' » reek
  • Complexity 4 » saikuro
139  def fetch_validate_events
 
140    return [] unless validated_at
 
141    return [] unless validated_ip
 
 
143    request = {
 
144      log_group_name: log_group_name,
 
145      start_time: ms(validated_at - 5.minutes),
 
146      end_time: ms(validated_at + 5.minutes),
 
147      filter_pattern: validated_ip,
 
148      interleaved: true
 
149    }
 
 
151    client.filter_log_events(request).events
 
 
153  rescue Aws::CloudWatchLogs::Errors::ServiceError => e
 
154    []
 
155  end
 
  • DuplicateMethodCall - calls '5.minutes' 2 times » reek
  • UncommunicativeVariableName - has the variable name 'e' » reek
  • Complexity 2 » saikuro
157  def fetch_combined_events
 
158    request = {
 
159      log_group_name: log_group_name,
 
160      start_time: ms(created_at - 5.minutes),
 
161      end_time: ms(validated_at + 5.minutes),
 
162      filter_pattern: ip_address,
 
163      interleaved: true
 
164    }
 
 
166    client.filter_log_events(request).events
 
 
168  rescue Aws::CloudWatchLogs::Errors::ServiceError => e
 
169    []
 
170  end
 
171end