cloudwatch_query 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +298 -0
- data/Rakefile +8 -0
- data/cloudwatch_query.gemspec +31 -0
- data/lib/cloudwatch_query/client.rb +111 -0
- data/lib/cloudwatch_query/configuration.rb +14 -0
- data/lib/cloudwatch_query/errors.rb +9 -0
- data/lib/cloudwatch_query/parsers/base.rb +24 -0
- data/lib/cloudwatch_query/parsers/rails/rails_log.rb +72 -0
- data/lib/cloudwatch_query/parsers/rails/sub_parsers.rb +175 -0
- data/lib/cloudwatch_query/parsers/rails_parser.rb +144 -0
- data/lib/cloudwatch_query/parsers/registry.rb +93 -0
- data/lib/cloudwatch_query/parsers/sidekiq/sidekiq_log.rb +53 -0
- data/lib/cloudwatch_query/parsers/sidekiq/sub_parsers.rb +78 -0
- data/lib/cloudwatch_query/parsers/sidekiq_parser.rb +130 -0
- data/lib/cloudwatch_query/parsers.rb +19 -0
- data/lib/cloudwatch_query/query.rb +147 -0
- data/lib/cloudwatch_query/result.rb +68 -0
- data/lib/cloudwatch_query/result_set.rb +62 -0
- data/lib/cloudwatch_query/time_helpers.rb +56 -0
- data/lib/cloudwatch_query/version.rb +5 -0
- data/lib/cloudwatch_query.rb +61 -0
- metadata +112 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloudwatchQuery
|
|
4
|
+
class Query
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
DEFAULT_FIELDS = %w[@timestamp @message @logStream @log].freeze
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@log_groups = []
|
|
11
|
+
@conditions = []
|
|
12
|
+
@fields = DEFAULT_FIELDS.dup
|
|
13
|
+
@start_time = nil
|
|
14
|
+
@end_time = nil
|
|
15
|
+
@limit = nil
|
|
16
|
+
@sort_field = "@timestamp"
|
|
17
|
+
@sort_order = "desc"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Log group selection
|
|
21
|
+
def logs(*groups)
|
|
22
|
+
@log_groups.concat(groups.flatten)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
alias log_group logs
|
|
26
|
+
|
|
27
|
+
# Filtering
|
|
28
|
+
def where(**conditions)
|
|
29
|
+
conditions.each do |field, value|
|
|
30
|
+
field_name = field.to_s.start_with?("@") ? field.to_s : field.to_s
|
|
31
|
+
@conditions << "#{field_name} = '#{escape_value(value)}'"
|
|
32
|
+
end
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def contains(text)
|
|
37
|
+
@conditions << "@message like /#{escape_regex(text)}/"
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def matches(pattern)
|
|
42
|
+
@conditions << "@message like /#{pattern}/"
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Time range
|
|
47
|
+
def since(time)
|
|
48
|
+
@start_time = TimeHelpers.to_epoch(time)
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def before(time)
|
|
53
|
+
@end_time = TimeHelpers.to_epoch(time)
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def between(start_time, end_time)
|
|
58
|
+
@start_time = TimeHelpers.to_epoch(start_time)
|
|
59
|
+
@end_time = TimeHelpers.to_epoch(end_time)
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def past(amount, unit)
|
|
64
|
+
seconds = TimeHelpers.duration_in_seconds(amount, unit)
|
|
65
|
+
@start_time = (Time.now - seconds).to_i
|
|
66
|
+
@end_time = Time.now.to_i
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Field selection
|
|
71
|
+
def fields(*field_list)
|
|
72
|
+
@fields = field_list.flatten.map { |f| f.to_s.start_with?("@") ? f.to_s : "@#{f}" }
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def limit(n)
|
|
77
|
+
@limit = n
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def sort(field, order = :desc)
|
|
82
|
+
@sort_field = field.to_s.start_with?("@") ? field.to_s : "@#{field}"
|
|
83
|
+
@sort_order = order.to_s
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Execution
|
|
88
|
+
def execute
|
|
89
|
+
validate!
|
|
90
|
+
client.execute(
|
|
91
|
+
query_string: to_insights_query,
|
|
92
|
+
log_group_names: @log_groups,
|
|
93
|
+
start_time: resolved_start_time,
|
|
94
|
+
end_time: resolved_end_time,
|
|
95
|
+
limit: resolved_limit
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
alias to_a execute
|
|
99
|
+
|
|
100
|
+
def each(&block)
|
|
101
|
+
execute.each(&block)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_insights_query
|
|
105
|
+
parts = []
|
|
106
|
+
parts << "fields #{@fields.join(', ')}"
|
|
107
|
+
@conditions.each { |c| parts << "filter #{c}" }
|
|
108
|
+
parts << "sort #{@sort_field} #{@sort_order}"
|
|
109
|
+
parts << "limit #{resolved_limit}" if resolved_limit
|
|
110
|
+
parts.join(" | ")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def client
|
|
116
|
+
@client ||= Client.new
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def config
|
|
120
|
+
CloudwatchQuery.configuration
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def resolved_start_time
|
|
124
|
+
@start_time || (Time.now - config.default_time_range).to_i
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def resolved_end_time
|
|
128
|
+
@end_time || Time.now.to_i
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def resolved_limit
|
|
132
|
+
@limit || config.default_limit
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def validate!
|
|
136
|
+
raise ConfigError, "No log groups specified" if @log_groups.empty?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def escape_value(value)
|
|
140
|
+
value.to_s.gsub("'", "\\\\'")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def escape_regex(text)
|
|
144
|
+
Regexp.escape(text.to_s)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloudwatchQuery
|
|
4
|
+
class Result
|
|
5
|
+
attr_reader :data, :parsed, :parser_name
|
|
6
|
+
|
|
7
|
+
def initialize(data, registry: nil)
|
|
8
|
+
@data = data.transform_keys(&:to_sym)
|
|
9
|
+
@registry = registry
|
|
10
|
+
@parsed = nil
|
|
11
|
+
@parser_name = nil
|
|
12
|
+
parse_message! if @registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def timestamp
|
|
16
|
+
@data[:timestamp]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
@data[:message]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def log_stream
|
|
24
|
+
@data[:logStream]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def log
|
|
28
|
+
@data[:log]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def [](key)
|
|
32
|
+
@data[key.to_sym]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
@data.dup
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if message was successfully parsed
|
|
40
|
+
def parsed?
|
|
41
|
+
!@parsed.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the type of parsed log (e.g., :rails_request, :sidekiq)
|
|
45
|
+
def log_type
|
|
46
|
+
@parsed&.type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
50
|
+
@data.key?(method_name.to_sym) || super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def method_missing(method_name, *args)
|
|
54
|
+
key = method_name.to_sym
|
|
55
|
+
return @data[key] if @data.key?(key)
|
|
56
|
+
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def parse_message!
|
|
63
|
+
return unless message
|
|
64
|
+
|
|
65
|
+
@parsed, @parser_name = @registry.parse(message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloudwatchQuery
|
|
4
|
+
class ResultSet
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
attr_reader :results, :statistics
|
|
8
|
+
|
|
9
|
+
def initialize(results: [], statistics: {}, registry: nil)
|
|
10
|
+
@results = results.map { |r| Result.new(r, registry: registry) }
|
|
11
|
+
@statistics = statistics
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def each(&block)
|
|
15
|
+
@results.each(&block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def count
|
|
19
|
+
@results.count
|
|
20
|
+
end
|
|
21
|
+
alias size count
|
|
22
|
+
alias length count
|
|
23
|
+
|
|
24
|
+
def empty?
|
|
25
|
+
@results.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def first(n = nil)
|
|
29
|
+
n ? @results.first(n) : @results.first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def last(n = nil)
|
|
33
|
+
n ? @results.last(n) : @results.last
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_a
|
|
37
|
+
@results
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Filter results by log type
|
|
41
|
+
def by_type(type)
|
|
42
|
+
@results.select { |r| r.log_type == type }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get only parsed results
|
|
46
|
+
def parsed
|
|
47
|
+
@results.select(&:parsed?)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get only unparsed results
|
|
51
|
+
def unparsed
|
|
52
|
+
@results.reject(&:parsed?)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Group results by request_id (for Rails logs)
|
|
56
|
+
def group_by_request
|
|
57
|
+
@results
|
|
58
|
+
.select { |r| r.parsed&.respond_to?(:request_id) && r.parsed.request_id }
|
|
59
|
+
.group_by { |r| r.parsed.request_id }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CloudwatchQuery
|
|
4
|
+
module TimeHelpers
|
|
5
|
+
UNIT_MULTIPLIERS = {
|
|
6
|
+
seconds: 1,
|
|
7
|
+
second: 1,
|
|
8
|
+
minutes: 60,
|
|
9
|
+
minute: 60,
|
|
10
|
+
hours: 3600,
|
|
11
|
+
hour: 3600,
|
|
12
|
+
days: 86400,
|
|
13
|
+
day: 86400,
|
|
14
|
+
weeks: 604800,
|
|
15
|
+
week: 604800
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.duration_in_seconds(amount, unit)
|
|
19
|
+
multiplier = UNIT_MULTIPLIERS[unit.to_sym]
|
|
20
|
+
raise ConfigError, "Unknown time unit: #{unit}" unless multiplier
|
|
21
|
+
|
|
22
|
+
amount * multiplier
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.to_epoch(time)
|
|
26
|
+
case time
|
|
27
|
+
when Time, DateTime
|
|
28
|
+
time.to_i
|
|
29
|
+
when Integer
|
|
30
|
+
time
|
|
31
|
+
when String
|
|
32
|
+
Time.parse(time).to_i
|
|
33
|
+
else
|
|
34
|
+
raise ConfigError, "Cannot convert #{time.class} to epoch time"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.parse_relative_time(str)
|
|
39
|
+
return nil unless str.is_a?(String)
|
|
40
|
+
|
|
41
|
+
match = str.match(/^(\d+)(s|m|h|d|w)$/)
|
|
42
|
+
return nil unless match
|
|
43
|
+
|
|
44
|
+
amount = match[1].to_i
|
|
45
|
+
unit = case match[2]
|
|
46
|
+
when "s" then :seconds
|
|
47
|
+
when "m" then :minutes
|
|
48
|
+
when "h" then :hours
|
|
49
|
+
when "d" then :days
|
|
50
|
+
when "w" then :weeks
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Time.now - duration_in_seconds(amount, unit)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cloudwatch_query/version"
|
|
4
|
+
require_relative "cloudwatch_query/errors"
|
|
5
|
+
require_relative "cloudwatch_query/configuration"
|
|
6
|
+
require_relative "cloudwatch_query/time_helpers"
|
|
7
|
+
require_relative "cloudwatch_query/parsers"
|
|
8
|
+
require_relative "cloudwatch_query/result"
|
|
9
|
+
require_relative "cloudwatch_query/result_set"
|
|
10
|
+
require_relative "cloudwatch_query/client"
|
|
11
|
+
require_relative "cloudwatch_query/query"
|
|
12
|
+
|
|
13
|
+
module CloudwatchQuery
|
|
14
|
+
class << self
|
|
15
|
+
def configuration
|
|
16
|
+
@configuration ||= Configuration.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def configure
|
|
20
|
+
yield(configuration)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset_configuration!
|
|
24
|
+
@configuration = Configuration.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Parser registry - manages log message parsers
|
|
28
|
+
def parsers
|
|
29
|
+
@parsers ||= begin
|
|
30
|
+
registry = Parsers::Registry.new
|
|
31
|
+
registry.register(*Parsers.default_parsers)
|
|
32
|
+
registry
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reset parsers to defaults
|
|
37
|
+
def reset_parsers!
|
|
38
|
+
@parsers = nil
|
|
39
|
+
parsers
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Start a query for the specified log groups
|
|
43
|
+
def logs(*groups)
|
|
44
|
+
Query.new.logs(*groups)
|
|
45
|
+
end
|
|
46
|
+
alias log_group logs
|
|
47
|
+
|
|
48
|
+
# Quick search shorthand
|
|
49
|
+
def search(term, groups:, since: nil, limit: nil)
|
|
50
|
+
query = Query.new.logs(*Array(groups)).contains(term)
|
|
51
|
+
query = query.since(since) if since
|
|
52
|
+
query = query.limit(limit) if limit
|
|
53
|
+
query.execute
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# List available log groups
|
|
57
|
+
def list_log_groups(prefix: nil)
|
|
58
|
+
Client.new.list_log_groups(prefix: prefix)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cloudwatch_query
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Igor Irianto
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: aws-sdk-cloudwatchlogs
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webmock
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
description: Query AWS CloudWatch Logs using a fluent, ActiveRecord-style interface.
|
|
56
|
+
Supports chainable queries, automatic Insights query generation, and enumerable
|
|
57
|
+
results.
|
|
58
|
+
email:
|
|
59
|
+
executables: []
|
|
60
|
+
extensions: []
|
|
61
|
+
extra_rdoc_files: []
|
|
62
|
+
files:
|
|
63
|
+
- LICENSE.txt
|
|
64
|
+
- README.md
|
|
65
|
+
- Rakefile
|
|
66
|
+
- cloudwatch_query.gemspec
|
|
67
|
+
- lib/cloudwatch_query.rb
|
|
68
|
+
- lib/cloudwatch_query/client.rb
|
|
69
|
+
- lib/cloudwatch_query/configuration.rb
|
|
70
|
+
- lib/cloudwatch_query/errors.rb
|
|
71
|
+
- lib/cloudwatch_query/parsers.rb
|
|
72
|
+
- lib/cloudwatch_query/parsers/base.rb
|
|
73
|
+
- lib/cloudwatch_query/parsers/rails/rails_log.rb
|
|
74
|
+
- lib/cloudwatch_query/parsers/rails/sub_parsers.rb
|
|
75
|
+
- lib/cloudwatch_query/parsers/rails_parser.rb
|
|
76
|
+
- lib/cloudwatch_query/parsers/registry.rb
|
|
77
|
+
- lib/cloudwatch_query/parsers/sidekiq/sidekiq_log.rb
|
|
78
|
+
- lib/cloudwatch_query/parsers/sidekiq/sub_parsers.rb
|
|
79
|
+
- lib/cloudwatch_query/parsers/sidekiq_parser.rb
|
|
80
|
+
- lib/cloudwatch_query/query.rb
|
|
81
|
+
- lib/cloudwatch_query/result.rb
|
|
82
|
+
- lib/cloudwatch_query/result_set.rb
|
|
83
|
+
- lib/cloudwatch_query/time_helpers.rb
|
|
84
|
+
- lib/cloudwatch_query/version.rb
|
|
85
|
+
homepage: https://github.com/iggredible/cloudwatch_query
|
|
86
|
+
licenses:
|
|
87
|
+
- MIT
|
|
88
|
+
metadata:
|
|
89
|
+
homepage_uri: https://github.com/iggredible/cloudwatch_query
|
|
90
|
+
source_code_uri: https://github.com/iggredible/cloudwatch_query
|
|
91
|
+
changelog_uri: https://github.com/iggredible/cloudwatch_query/blob/main/CHANGELOG.md
|
|
92
|
+
rubygems_mfa_required: 'true'
|
|
93
|
+
post_install_message:
|
|
94
|
+
rdoc_options: []
|
|
95
|
+
require_paths:
|
|
96
|
+
- lib
|
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: 2.7.0
|
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
requirements: []
|
|
108
|
+
rubygems_version: 3.1.6
|
|
109
|
+
signing_key:
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: A Ruby gem for querying AWS CloudWatch Logs with a simple, chainable interface
|
|
112
|
+
test_files: []
|