sumologic-query 1.4.1 → 1.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f62e0cd6d7ee0fbfd44b2720b0075e4f1776e086ef1ccbe39ad9e520f539f951
4
- data.tar.gz: da262a85a0fa2d556c310876d011705edd4fc1b46db47c43d6f892adf71649ff
3
+ metadata.gz: 17737642aa1c9244133b4442fd6dccc156cf39b5a35cc05071a4769800ad0501
4
+ data.tar.gz: 29037e867772bbc70e2cda259879e8b6d55e51b70611b7ef5167c79ab2eee00e
5
5
  SHA512:
6
- metadata.gz: 458d97b1853c3626d85f84770faaf918ebbd2842718ffed416ead1736a8a075bc4061eb040e003a24227e8af6b7f6f125baf2b2aec5627f0398abd6b054fe880
7
- data.tar.gz: '078544851f936431eea88d6b0783f39c58789072bbfa8fe17f5147c2a9aff9df8bc0b3f5e71c48e7d6793f8ad94a69a134bfabe5ab3d46689ff35613a13446f5'
6
+ metadata.gz: 67bd5ba57a5a3ed5739d274a25c29e9ba4c1cd2415976a5bb7cbdfb075276c67b614917a14a312e33fa618c15a665ccb7bf82e34b20602ab800e3c00e65bed8c
7
+ data.tar.gz: 2c3fff993dcf6d5a2d14b5719a3fd23331477dece5a43cad0dc5d42e4ae7065fb94bda7741ead8e9a678839d8b5d063bb237c9c301585008fdba427737e1abd8
data/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and release notes are automatically generated from commit messages.
9
+ ## [1.4.2](https://github.com/patrick204nqh/sumologic-query/compare/v1.4.1...v1.4.2) (2026-02-11)
10
+
11
+ ### 🎉 New Features
12
+
13
+ - add source discovery guidance to skills
14
+ - add filtering options to list-sources
15
+ - add keyword and limit filters to discover-source-metadata
16
+ - add -q/--query and -l/--limit flags to list-collectors
17
+
18
+ ### 🐛 Bug Fixes
19
+
20
+ - support compound relative time expressions like -1h30m
21
+
22
+
23
+
9
24
  ## [1.4.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.4.0...v1.4.1) (2026-02-11)
10
25
 
11
26
  ### 🎉 New Features
@@ -40,6 +40,7 @@ module Sumologic
40
40
  end
41
41
  warn "Time Zone: #{@parsed_timezone}"
42
42
  warn "Filter: #{options[:filter] || 'none (all sources)'}"
43
+ warn "Keyword: #{options[:keyword]}" if options[:keyword]
43
44
  warn '-' * 60
44
45
  warn 'Running aggregation query to discover sources...'
45
46
  $stderr.puts
@@ -50,7 +51,9 @@ module Sumologic
50
51
  from_time: @parsed_from,
51
52
  to_time: @parsed_to,
52
53
  time_zone: @parsed_timezone,
53
- filter: options[:filter]
54
+ filter: options[:filter],
55
+ keyword: options[:keyword],
56
+ limit: options[:limit]
54
57
  )
55
58
  end
56
59
  end
@@ -8,7 +8,12 @@ module Sumologic
8
8
  # Handles the list-collectors command execution
9
9
  class ListCollectorsCommand < BaseCommand
10
10
  def execute
11
- list_resource(label: 'collectors', key: :collectors) { client.list_collectors }
11
+ list_resource(label: 'collectors', key: :collectors) do
12
+ client.list_collectors(
13
+ query: options[:query],
14
+ limit: options[:limit]
15
+ )
16
+ end
12
17
  end
13
18
  end
14
19
  end
@@ -29,10 +29,14 @@ module Sumologic
29
29
  end
30
30
 
31
31
  def list_all_sources
32
- warn 'Fetching all sources from all collectors...'
33
- warn 'This may take a minute...'
32
+ warn 'Fetching sources from collectors...'
34
33
 
35
- all_sources = client.list_all_sources
34
+ all_sources = client.list_all_sources(
35
+ collector: options[:collector],
36
+ name: options[:name],
37
+ category: options[:category],
38
+ limit: options[:limit]
39
+ )
36
40
 
37
41
  output_json(
38
42
  total_collectors: all_sources.size,
data/lib/sumologic/cli.rb CHANGED
@@ -41,7 +41,7 @@ module Sumologic
41
41
  Time Formats:
42
42
  --from and --to support multiple formats:
43
43
  • 'now' - current time
44
- • Relative: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M' (sec/min/hour/day/week/month)
44
+ • Relative: '-30s', '-5m', '-2h', '-1h30m', '-7d', '-1w', '-1M' (compound supported)
45
45
  • Unix timestamp: '1700000000' (seconds since epoch)
46
46
  • ISO 8601: '2025-11-13T14:00:00'
47
47
 
@@ -89,29 +89,36 @@ module Sumologic
89
89
  Commands::SearchCommand.new(options, create_client).execute
90
90
  end
91
91
 
92
- desc 'list-collectors', 'List all Sumo Logic collectors'
92
+ desc 'list-collectors', 'List Sumo Logic collectors with optional filters'
93
93
  long_desc <<~DESC
94
- List all collectors in your Sumo Logic account.
94
+ List collectors in your Sumo Logic account.
95
+ Supports filtering by name/category and limiting results.
95
96
 
96
- Example:
97
+ Examples:
98
+ sumo-query list-collectors -q "my-service" -l 20
97
99
  sumo-query list-collectors --output collectors.json
98
100
  DESC
101
+ option :query, type: :string, aliases: '-q', desc: 'Filter by name or category (case-insensitive)'
102
+ option :limit, type: :numeric, aliases: '-l', desc: 'Maximum collectors to return'
99
103
  def list_collectors
100
104
  Commands::ListCollectorsCommand.new(options, create_client).execute
101
105
  end
102
106
 
103
- desc 'list-sources', 'List sources from collectors'
107
+ desc 'list-sources', 'List sources from collectors with optional filters'
104
108
  long_desc <<~DESC
105
- List all sources from all collectors, or sources from a specific collector.
109
+ List sources from all collectors, or from a specific collector.
110
+ Supports filtering by collector name, source name, and category.
106
111
 
107
112
  Examples:
108
- # List all sources
109
- sumo-query list-sources
110
-
111
- # List sources for specific collector
113
+ sumo-query list-sources --collector "my-service" --name "nginx" -l 20
114
+ sumo-query list-sources --category "production"
112
115
  sumo-query list-sources --collector-id 12345
113
116
  DESC
114
117
  option :collector_id, type: :string, desc: 'Collector ID to list sources for'
118
+ option :collector, type: :string, desc: 'Filter by collector name (case-insensitive)'
119
+ option :name, type: :string, aliases: '-n', desc: 'Filter by source name (case-insensitive)'
120
+ option :category, type: :string, desc: 'Filter by source category (case-insensitive)'
121
+ option :limit, type: :numeric, aliases: '-l', desc: 'Maximum total sources to return'
115
122
  def list_sources
116
123
  Commands::ListSourcesCommand.new(options, create_client).execute
117
124
  end
@@ -131,7 +138,7 @@ module Sumologic
131
138
  Time Formats:
132
139
  --from and --to support multiple formats:
133
140
  • 'now' - current time
134
- • Relative: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M' (sec/min/hour/day/week/month)
141
+ • Relative: '-30s', '-5m', '-2h', '-1h30m', '-7d', '-1w', '-1M' (compound supported)
135
142
  • Unix timestamp: '1700000000' (seconds since epoch)
136
143
  • ISO 8601: '2025-11-13T14:00:00'
137
144
 
@@ -145,8 +152,9 @@ module Sumologic
145
152
  # Filter by source category (ECS only)
146
153
  sumo-query discover-source-metadata --filter '_sourceCategory=*ecs*'
147
154
 
148
- # Discover CloudWatch sources
149
- sumo-query discover-source-metadata --filter '_sourceCategory=*cloudwatch*'
155
+ # Filter results by keyword (matches name or category)
156
+ sumo-query discover-source-metadata --keyword nginx
157
+ sumo-query discover-source-metadata --keyword nginx -l 10
150
158
 
151
159
  # Save to file
152
160
  sumo-query discover-source-metadata --output discovered-sources.json
@@ -158,6 +166,8 @@ module Sumologic
158
166
  option :time_zone, type: :string, default: 'UTC', aliases: '-z',
159
167
  desc: 'Time zone (UTC, EST, AEST, +00:00, America/New_York, Australia/Sydney)'
160
168
  option :filter, type: :string, desc: 'Optional filter query (e.g., _sourceCategory=*ecs*)'
169
+ option :keyword, type: :string, aliases: '-k', desc: 'Filter results by keyword (matches name or category)'
170
+ option :limit, type: :numeric, aliases: '-l', desc: 'Maximum number of sources to return'
161
171
  def discover_source_metadata
162
172
  Commands::DiscoverSourceMetadataCommand.new(options, create_client).execute
163
173
  end
@@ -83,11 +83,13 @@ module Sumologic
83
83
  )
84
84
  end
85
85
 
86
- # List all collectors
86
+ # List collectors with optional filtering
87
87
  #
88
+ # @param query [String, nil] Filter by name or category (case-insensitive)
89
+ # @param limit [Integer, nil] Maximum number of collectors to return
88
90
  # @return [Array<Hash>] Array of collector hashes
89
- def list_collectors
90
- @collector.list
91
+ def list_collectors(query: nil, limit: nil)
92
+ @collector.list(query: query, limit: limit)
91
93
  end
92
94
 
93
95
  # List sources for a specific collector
@@ -98,11 +100,15 @@ module Sumologic
98
100
  @source.list(collector_id: collector_id)
99
101
  end
100
102
 
101
- # List all sources from all collectors
103
+ # List all sources from all collectors with optional filtering
102
104
  #
105
+ # @param collector [String, nil] Filter collectors by name
106
+ # @param name [String, nil] Filter sources by name
107
+ # @param category [String, nil] Filter sources by category
108
+ # @param limit [Integer, nil] Maximum total sources to return
103
109
  # @return [Array<Hash>] Array of { 'collector' => Hash, 'sources' => Array<Hash> }
104
- def list_all_sources
105
- @source.list_all
110
+ def list_all_sources(collector: nil, name: nil, category: nil, limit: nil)
111
+ @source.list_all(collector: collector, name: name, category: category, limit: limit)
106
112
  end
107
113
 
108
114
  # Discover source metadata from actual log data
@@ -111,14 +117,14 @@ module Sumologic
111
117
  # @param from_time [String] Start time (ISO 8601, unix timestamp, or relative)
112
118
  # @param to_time [String] End time
113
119
  # @param time_zone [String] Time zone (default: UTC)
114
- # @param filter [String, nil] Optional filter query to scope results
120
+ # @param options [Hash] Optional filters :filter, :keyword, :limit
115
121
  # @return [Hash] Discovery results with source metadata
116
- def discover_source_metadata(from_time:, to_time:, time_zone: 'UTC', filter: nil)
122
+ def discover_source_metadata(from_time:, to_time:, time_zone: 'UTC', **options)
117
123
  @source_metadata_discovery.discover(
118
124
  from_time: from_time,
119
125
  to_time: to_time,
120
126
  time_zone: time_zone,
121
- filter: filter
127
+ **options
122
128
  )
123
129
  end
124
130
 
@@ -13,9 +13,12 @@ module Sumologic
13
13
  @http = http_client
14
14
  end
15
15
 
16
- # List all collectors
17
- # Returns array of collector objects
18
- def list
16
+ # List collectors with optional client-side filtering
17
+ #
18
+ # @param query [String, nil] Filter by name or category (case-insensitive substring match)
19
+ # @param limit [Integer, nil] Maximum number of collectors to return
20
+ # @return [Array<Hash>] Array of collector objects
21
+ def list(query: nil, limit: nil)
19
22
  data = @http.request(
20
23
  method: :get,
21
24
  path: '/collectors'
@@ -23,10 +26,25 @@ module Sumologic
23
26
 
24
27
  collectors = data['collectors'] || []
25
28
  log_info "Found #{collectors.size} collectors"
29
+
30
+ collectors = filter_by_query(collectors, query) if query
31
+ collectors = collectors.take(limit) if limit
32
+
26
33
  collectors
27
34
  rescue StandardError => e
28
35
  raise Error, "Failed to list collectors: #{e.message}"
29
36
  end
37
+
38
+ private
39
+
40
+ def filter_by_query(collectors, query)
41
+ pattern = query.downcase
42
+ collectors.select do |c|
43
+ name = (c['name'] || '').downcase
44
+ category = (c['category'] || '').downcase
45
+ name.include?(pattern) || category.include?(pattern)
46
+ end
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -31,19 +31,28 @@ module Sumologic
31
31
  raise Error, "Failed to list sources for collector #{collector_id}: #{e.message}"
32
32
  end
33
33
 
34
- # List all sources from all collectors
34
+ # List all sources from all collectors with optional filtering
35
35
  # Returns array of hashes with collector info and their sources
36
36
  # Uses parallel fetching with thread pool for better performance
37
- def list_all
37
+ #
38
+ # @param collector [String, nil] Filter collectors by name (case-insensitive substring)
39
+ # @param name [String, nil] Filter sources by name (case-insensitive substring)
40
+ # @param category [String, nil] Filter sources by category (case-insensitive substring)
41
+ # @param limit [Integer, nil] Maximum total sources to return
42
+ def list_all(collector: nil, name: nil, category: nil, limit: nil)
38
43
  collectors = @collector_client.list
39
44
  active_collectors = collectors.select { |c| c['alive'] }
45
+ active_collectors = filter_collectors(active_collectors, collector) if collector
40
46
 
41
47
  log_info "Fetching sources for #{active_collectors.size} active collectors in parallel..."
42
48
 
43
- result = @fetcher.fetch_all(active_collectors) do |collector|
44
- fetch_collector_sources(collector)
49
+ result = @fetcher.fetch_all(active_collectors) do |c|
50
+ fetch_collector_sources(c)
45
51
  end
46
52
 
53
+ result = filter_sources(result, name: name, category: category)
54
+ result = apply_source_limit(result, limit) if limit
55
+
47
56
  log_info "Total: #{result.size} collectors with sources"
48
57
  result
49
58
  rescue StandardError => e
@@ -52,6 +61,37 @@ module Sumologic
52
61
 
53
62
  private
54
63
 
64
+ def filter_collectors(collectors, pattern)
65
+ pattern = pattern.downcase
66
+ collectors.select { |c| (c['name'] || '').downcase.include?(pattern) }
67
+ end
68
+
69
+ def filter_sources(result, name:, category:)
70
+ matcher = source_matcher(name&.downcase, category&.downcase)
71
+ result.filter_map do |entry|
72
+ filtered = entry['sources'].select(&matcher)
73
+ { 'collector' => entry['collector'], 'sources' => filtered } unless filtered.empty?
74
+ end
75
+ end
76
+
77
+ def source_matcher(name_pattern, cat_pattern)
78
+ lambda do |s|
79
+ (!name_pattern || (s['name'] || '').downcase.include?(name_pattern)) &&
80
+ (!cat_pattern || (s['category'] || '').downcase.include?(cat_pattern))
81
+ end
82
+ end
83
+
84
+ def apply_source_limit(result, limit)
85
+ remaining = limit
86
+ result.each_with_object([]) do |entry, acc|
87
+ break acc if remaining <= 0
88
+
89
+ sources = entry['sources'].take(remaining)
90
+ acc << { 'collector' => entry['collector'], 'sources' => sources }
91
+ remaining -= sources.size
92
+ end
93
+ end
94
+
55
95
  # Fetch sources for a single collector
56
96
  # @return [Hash] collector and sources data
57
97
  def fetch_collector_sources(collector)
@@ -22,8 +22,12 @@ module Sumologic
22
22
  # @param from_time [String] Start time (ISO 8601, unix timestamp, or relative)
23
23
  # @param to_time [String] End time
24
24
  # @param time_zone [String] Time zone (default: UTC)
25
- # @param filter [String, nil] Optional filter query to scope results
26
- def discover(from_time:, to_time:, time_zone: 'UTC', filter: nil)
25
+ # @param options [Hash] Optional filters :filter, :keyword, :limit
26
+ def discover(from_time:, to_time:, time_zone: 'UTC', **options)
27
+ filter = options[:filter]
28
+ keyword = options[:keyword]
29
+ limit = options[:limit]
30
+
27
31
  query = build_query(filter)
28
32
  log_info "Discovering source metadata with query: #{query}"
29
33
  log_info "Time range: #{from_time} to #{to_time} (#{time_zone})"
@@ -39,6 +43,8 @@ module Sumologic
39
43
  )
40
44
 
41
45
  source_models = parse_aggregation_results(records)
46
+ source_models = filter_by_keyword(source_models, keyword) if keyword
47
+ source_models = source_models.take(limit) if limit
42
48
 
43
49
  {
44
50
  'time_range' => {
@@ -47,6 +53,7 @@ module Sumologic
47
53
  'time_zone' => time_zone
48
54
  },
49
55
  'filter' => filter,
56
+ 'keyword' => keyword,
50
57
  'total_sources' => source_models.size,
51
58
  'sources' => source_models.map(&:to_h)
52
59
  }
@@ -56,6 +63,14 @@ module Sumologic
56
63
 
57
64
  private
58
65
 
66
+ def filter_by_keyword(source_models, keyword)
67
+ pattern = keyword.downcase
68
+ source_models.select do |s|
69
+ (s.name || '').downcase.include?(pattern) ||
70
+ (s.category || '').downcase.include?(pattern)
71
+ end
72
+ end
73
+
59
74
  # Build aggregation query to discover sources
60
75
  def build_query(filter)
61
76
  base = filter || '*'
@@ -7,7 +7,7 @@ module Sumologic
7
7
  # Parses various time formats into ISO 8601 strings for the Sumo Logic API
8
8
  # Supports:
9
9
  # - 'now' - current time
10
- # - Relative times: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M'
10
+ # - Relative times: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M', '-1h30m'
11
11
  # - Unix timestamps: '1700000000' or 1700000000
12
12
  # - ISO 8601: '2025-11-13T14:00:00'
13
13
  class TimeParser
@@ -21,7 +21,9 @@ module Sumologic
21
21
  'M' => 2_592_000 # months (30 days approximation)
22
22
  }.freeze
23
23
 
24
- RELATIVE_TIME_REGEX = /^([+-])(\d+)([smhdwM])$/.freeze
24
+ # Matches single or compound relative times: -30m, -1h30m, -2d3h15m
25
+ RELATIVE_TIME_REGEX = /^([+-])(\d+[smhdwM])+$/.freeze
26
+ RELATIVE_COMPONENT_REGEX = /(\d+)([smhdwM])/.freeze
25
27
 
26
28
  class ParseError < StandardError; end
27
29
 
@@ -32,10 +34,8 @@ module Sumologic
32
34
  def self.parse(time_str, _timezone: 'UTC')
33
35
  return parse_now if time_str.to_s.downcase == 'now'
34
36
 
35
- # Try relative time format (e.g., '-30m', '+1h')
36
- if time_str.is_a?(String) && (match = time_str.match(RELATIVE_TIME_REGEX))
37
- return parse_relative_time(match)
38
- end
37
+ # Try relative time format (e.g., '-30m', '+1h', '-1h30m')
38
+ return parse_relative_time(time_str) if time_str.is_a?(String) && time_str.match?(RELATIVE_TIME_REGEX)
39
39
 
40
40
  # Try Unix timestamp (integer or numeric string)
41
41
  return parse_unix_timestamp(time_str) if unix_timestamp?(time_str)
@@ -95,14 +95,14 @@ module Sumologic
95
95
  format_time(Time.now)
96
96
  end
97
97
 
98
- private_class_method def self.parse_relative_time(match)
99
- sign, amount, unit = match.captures
100
- amount = amount.to_i
101
- amount = -amount if sign == '-'
98
+ private_class_method def self.parse_relative_time(time_str)
99
+ sign = time_str[0]
100
+ components = time_str.scan(RELATIVE_COMPONENT_REGEX)
102
101
 
103
- seconds_delta = amount * UNITS[unit]
104
- target_time = Time.now + seconds_delta
102
+ seconds_delta = components.sum { |amount, unit| amount.to_i * UNITS[unit] }
103
+ seconds_delta = -seconds_delta if sign == '-'
105
104
 
105
+ target_time = Time.now + seconds_delta
106
106
  format_time(target_time)
107
107
  end
108
108
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sumologic
4
- VERSION = '1.4.1'
4
+ VERSION = '1.4.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sumologic-query
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - patrick204nqh