sumologic-query 1.3.5 → 1.4.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +1 -1
  4. data/lib/sumologic/cli/commands/base_command.rb +0 -20
  5. data/lib/sumologic/cli/commands/{discover_sources_command.rb → discover_source_metadata_command.rb} +4 -4
  6. data/lib/sumologic/cli/commands/export_content_command.rb +20 -0
  7. data/lib/sumologic/cli/commands/get_content_command.rb +20 -0
  8. data/lib/sumologic/cli/commands/get_dashboard_command.rb +20 -0
  9. data/lib/sumologic/cli/commands/get_lookup_command.rb +20 -0
  10. data/lib/sumologic/cli/commands/get_monitor_command.rb +20 -0
  11. data/lib/sumologic/cli/commands/list_apps_command.rb +22 -0
  12. data/lib/sumologic/cli/commands/list_collectors_command.rb +1 -1
  13. data/lib/sumologic/cli/commands/list_dashboards_command.rb +22 -0
  14. data/lib/sumologic/cli/commands/list_fields_command.rb +27 -0
  15. data/lib/sumologic/cli/commands/list_folders_command.rb +55 -0
  16. data/lib/sumologic/cli/commands/list_health_events_command.rb +22 -0
  17. data/lib/sumologic/cli/commands/list_monitors_command.rb +27 -0
  18. data/lib/sumologic/cli/commands/list_sources_command.rb +2 -9
  19. data/lib/sumologic/cli/commands/search_command.rb +56 -18
  20. data/lib/sumologic/cli.rb +290 -12
  21. data/lib/sumologic/client.rb +207 -12
  22. data/lib/sumologic/configuration.rb +23 -9
  23. data/lib/sumologic/http/client.rb +76 -11
  24. data/lib/sumologic/http/connection_pool.rb +7 -5
  25. data/lib/sumologic/http/response_handler.rb +65 -1
  26. data/lib/sumologic/metadata/app.rb +34 -0
  27. data/lib/sumologic/metadata/content.rb +95 -0
  28. data/lib/sumologic/metadata/dashboard.rb +104 -0
  29. data/lib/sumologic/metadata/field.rb +49 -0
  30. data/lib/sumologic/metadata/folder.rb +89 -0
  31. data/lib/sumologic/metadata/health_event.rb +35 -0
  32. data/lib/sumologic/metadata/lookup_table.rb +34 -0
  33. data/lib/sumologic/metadata/models.rb +2 -80
  34. data/lib/sumologic/metadata/monitor.rb +113 -0
  35. data/lib/sumologic/metadata/source.rb +5 -7
  36. data/lib/sumologic/metadata/{dynamic_source_discovery.rb → source_metadata_discovery.rb} +7 -7
  37. data/lib/sumologic/version.rb +1 -1
  38. data/lib/sumologic.rb +23 -1
  39. metadata +23 -4
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'loggable'
4
+
5
+ module Sumologic
6
+ module Metadata
7
+ # Handles health event operations
8
+ # Uses GET /v1/healthEvents endpoint
9
+ class HealthEvent
10
+ include Loggable
11
+
12
+ def initialize(http_client:)
13
+ @http = http_client
14
+ end
15
+
16
+ # List all health events
17
+ #
18
+ # @param limit [Integer] Maximum events to return (default: 100)
19
+ # @return [Array<Hash>] Array of health event data
20
+ def list(limit: 100)
21
+ data = @http.request(
22
+ method: :get,
23
+ path: '/healthEvents',
24
+ query_params: { limit: limit }
25
+ )
26
+
27
+ events = data['data'] || []
28
+ log_info "Fetched #{events.size} health events"
29
+ events
30
+ rescue StandardError => e
31
+ raise Error, "Failed to list health events: #{e.message}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'loggable'
4
+
5
+ module Sumologic
6
+ module Metadata
7
+ # Handles lookup table operations
8
+ # Uses GET /v1/lookupTables/{id} endpoint
9
+ # Note: There is no list-all endpoint for lookup tables
10
+ class LookupTable
11
+ include Loggable
12
+
13
+ def initialize(http_client:)
14
+ @http = http_client
15
+ end
16
+
17
+ # Get a specific lookup table by ID
18
+ #
19
+ # @param lookup_id [String] The lookup table ID
20
+ # @return [Hash] Lookup table data
21
+ def get(lookup_id)
22
+ data = @http.request(
23
+ method: :get,
24
+ path: "/lookupTables/#{lookup_id}"
25
+ )
26
+
27
+ log_info "Retrieved lookup table: #{data['name']} (#{lookup_id})"
28
+ data
29
+ rescue StandardError => e
30
+ raise Error, "Failed to get lookup table #{lookup_id}: #{e.message}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,64 +2,8 @@
2
2
 
3
3
  module Sumologic
4
4
  module Metadata
5
- # Value object representing a Sumo Logic Collector
6
- class CollectorModel
7
- attr_reader :id, :name, :collector_type, :alive, :category
8
-
9
- def initialize(data)
10
- @id = data['id']
11
- @name = data['name']
12
- @collector_type = data['collectorType']
13
- @alive = data['alive']
14
- @category = data['category']
15
- end
16
-
17
- # Convert to hash for JSON serialization
18
- def to_h
19
- {
20
- 'id' => @id,
21
- 'name' => @name,
22
- 'collectorType' => @collector_type,
23
- 'alive' => @alive,
24
- 'category' => @category
25
- }.compact
26
- end
27
-
28
- def active?
29
- @alive == true
30
- end
31
- end
32
-
33
- # Value object representing a static Source from collectors API
34
- class SourceModel
35
- attr_reader :id, :name, :category, :source_type, :alive
36
-
37
- def initialize(data)
38
- @id = data['id']
39
- @name = data['name']
40
- @category = data['category']
41
- @source_type = data['sourceType']
42
- @alive = data['alive']
43
- end
44
-
45
- # Convert to hash for JSON serialization
46
- def to_h
47
- {
48
- 'id' => @id,
49
- 'name' => @name,
50
- 'category' => @category,
51
- 'sourceType' => @source_type,
52
- 'alive' => @alive
53
- }.compact
54
- end
55
-
56
- def active?
57
- @alive == true
58
- end
59
- end
60
-
61
- # Value object representing a Dynamic Source discovered from logs
62
- class DynamicSourceModel
5
+ # Value object representing source metadata discovered from logs
6
+ class SourceMetadata
63
7
  attr_reader :name, :category, :message_count
64
8
 
65
9
  def initialize(name:, category:, message_count:)
@@ -82,27 +26,5 @@ module Sumologic
82
26
  other.message_count <=> @message_count
83
27
  end
84
28
  end
85
-
86
- # Value object for collector with its sources
87
- class CollectorWithSources
88
- attr_reader :collector, :sources
89
-
90
- def initialize(collector:, sources:)
91
- @collector = collector.is_a?(CollectorModel) ? collector : CollectorModel.new(collector)
92
- @sources = sources.map { |s| s.is_a?(SourceModel) ? s : SourceModel.new(s) }
93
- end
94
-
95
- # Convert to hash for JSON serialization
96
- def to_h
97
- {
98
- 'collector' => @collector.to_h,
99
- 'sources' => @sources.map(&:to_h)
100
- }
101
- end
102
-
103
- def source_count
104
- @sources.size
105
- end
106
- end
107
29
  end
108
30
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'loggable'
4
+ require_relative 'models'
5
+
6
+ module Sumologic
7
+ module Metadata
8
+ # Handles monitor metadata operations via the search API
9
+ # Uses GET /v1/monitors/search instead of recursive folder traversal
10
+ class Monitor
11
+ include Loggable
12
+
13
+ VALID_STATUSES = %w[Normal Critical Warning MissingData Disabled AllTriggered].freeze
14
+
15
+ def initialize(http_client:)
16
+ @http = http_client
17
+ end
18
+
19
+ # List monitors using the search API
20
+ # Supports server-side filtering by status and query
21
+ #
22
+ # @param query [String, nil] Search query to filter by name/description
23
+ # @param status [String, nil] Monitor status filter
24
+ # (Normal, Critical, Warning, MissingData, Disabled, AllTriggered)
25
+ # @param limit [Integer] Maximum number of monitors to return (default: 100)
26
+ # @return [Array<Hash>] Array of monitor data with path info
27
+ def list(query: nil, status: nil, limit: 100)
28
+ validate_status!(status) if status
29
+
30
+ monitors = []
31
+ offset = 0
32
+
33
+ loop do
34
+ batch_limit = [limit - monitors.size, 100].min
35
+ query_params = build_search_params(query: query, status: status, limit: batch_limit, offset: offset)
36
+
37
+ data = @http.request(
38
+ method: :get,
39
+ path: '/monitors/search',
40
+ query_params: query_params
41
+ )
42
+
43
+ items = extract_monitors(data)
44
+ monitors.concat(items)
45
+
46
+ log_info "Fetched #{items.size} monitors (total: #{monitors.size})"
47
+
48
+ break if items.size < batch_limit || monitors.size >= limit
49
+
50
+ offset += batch_limit
51
+ end
52
+
53
+ monitors.take(limit)
54
+ rescue StandardError => e
55
+ raise Error, "Failed to list monitors: #{e.message}"
56
+ end
57
+
58
+ # Get a specific monitor by ID
59
+ # Returns full monitor details including query and triggers
60
+ #
61
+ # @param monitor_id [String] The monitor ID
62
+ # @return [Hash] Monitor data
63
+ def get(monitor_id)
64
+ data = @http.request(
65
+ method: :get,
66
+ path: "/monitors/#{monitor_id}"
67
+ )
68
+
69
+ log_info "Retrieved monitor: #{data['name']} (#{monitor_id})"
70
+ data
71
+ rescue StandardError => e
72
+ raise Error, "Failed to get monitor #{monitor_id}: #{e.message}"
73
+ end
74
+
75
+ private
76
+
77
+ def build_search_params(query: nil, status: nil, limit: 100, offset: 0)
78
+ params = { limit: limit, offset: offset }
79
+
80
+ # The search endpoint requires a query parameter
81
+ # Use monitorStatus filter within the query string
82
+ query_parts = []
83
+ query_parts << query if query && !query.empty?
84
+ query_parts << "monitorStatus:#{status}" if status
85
+
86
+ # Default to empty query if no filters (returns all monitors)
87
+ params[:query] = query_parts.empty? ? '' : query_parts.join(' ')
88
+ params
89
+ end
90
+
91
+ def extract_monitors(data)
92
+ items = data || []
93
+ items = [] unless items.is_a?(Array)
94
+
95
+ items.filter_map do |item|
96
+ monitor = item['item'] || item
97
+ next unless monitor['contentType'] == 'Monitor'
98
+
99
+ # Merge path into the monitor data for output
100
+ monitor['path'] = item['path'] if item['path']
101
+ monitor
102
+ end
103
+ end
104
+
105
+ def validate_status!(status)
106
+ return if VALID_STATUSES.include?(status)
107
+
108
+ raise Error,
109
+ "Invalid monitor status '#{status}'. Valid values: #{VALID_STATUSES.join(', ')}"
110
+ end
111
+ end
112
+ end
113
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative 'collector_source_fetcher'
4
4
  require_relative 'loggable'
5
- require_relative 'models'
6
5
 
7
6
  module Sumologic
8
7
  module Metadata
@@ -54,7 +53,7 @@ module Sumologic
54
53
  private
55
54
 
56
55
  # Fetch sources for a single collector
57
- # Returns CollectorWithSources model
56
+ # @return [Hash] collector and sources data
58
57
  def fetch_collector_sources(collector)
59
58
  collector_id = collector['id']
60
59
  collector_name = collector['name']
@@ -62,11 +61,10 @@ module Sumologic
62
61
  log_info "Fetching sources for collector: #{collector_name} (#{collector_id})"
63
62
  sources = list(collector_id: collector_id)
64
63
 
65
- # Create model and convert to hash for backward compatibility
66
- CollectorWithSources.new(
67
- collector: collector,
68
- sources: sources
69
- ).to_h
64
+ {
65
+ 'collector' => collector,
66
+ 'sources' => sources
67
+ }
70
68
  rescue StandardError => e
71
69
  log_error "Failed to fetch sources for collector #{collector_name}: #{e.message}"
72
70
  nil
@@ -5,9 +5,9 @@ require_relative 'models'
5
5
 
6
6
  module Sumologic
7
7
  module Metadata
8
- # Discovers dynamic source names from actual log data via Search API
8
+ # Discovers source metadata from actual log data via Search API
9
9
  # Useful for CloudWatch/ECS sources that use dynamic _sourceName values
10
- class DynamicSourceDiscovery
10
+ class SourceMetadataDiscovery
11
11
  include Loggable
12
12
 
13
13
  def initialize(http_client:, search_job:, config: nil)
@@ -16,7 +16,7 @@ module Sumologic
16
16
  @config = config || Configuration.new
17
17
  end
18
18
 
19
- # Discover dynamic source names from logs
19
+ # Discover source metadata from logs
20
20
  # Returns hash with ALL unique source names found
21
21
  #
22
22
  # @param from_time [String] Start time (ISO 8601, unix timestamp, or relative)
@@ -25,7 +25,7 @@ module Sumologic
25
25
  # @param filter [String, nil] Optional filter query to scope results
26
26
  def discover(from_time:, to_time:, time_zone: 'UTC', filter: nil)
27
27
  query = build_query(filter)
28
- log_info "Discovering dynamic sources with query: #{query}"
28
+ log_info "Discovering source metadata with query: #{query}"
29
29
  log_info "Time range: #{from_time} to #{to_time} (#{time_zone})"
30
30
 
31
31
  # Fetch aggregated records to find all unique sources
@@ -51,7 +51,7 @@ module Sumologic
51
51
  'sources' => source_models.map(&:to_h)
52
52
  }
53
53
  rescue StandardError => e
54
- raise Error, "Failed to discover dynamic sources: #{e.message}"
54
+ raise Error, "Failed to discover source metadata: #{e.message}"
55
55
  end
56
56
 
57
57
  private
@@ -67,7 +67,7 @@ module Sumologic
67
67
  end
68
68
 
69
69
  # Parse aggregation records from search API
70
- # Returns array of DynamicSourceModel objects
70
+ # @return [Array<SourceMetadata>]
71
71
  def parse_aggregation_results(records)
72
72
  return [] if records.empty?
73
73
 
@@ -135,7 +135,7 @@ module Sumologic
135
135
  # Build and sort model objects from source hash
136
136
  def build_source_models(sources_hash)
137
137
  source_models = sources_hash.values.map do |source_data|
138
- DynamicSourceModel.new(
138
+ SourceMetadata.new(
139
139
  name: source_data[:name],
140
140
  category: source_data[:category],
141
141
  message_count: source_data[:count]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sumologic
4
- VERSION = '1.3.5'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/sumologic.rb CHANGED
@@ -11,6 +11,20 @@ module Sumologic
11
11
 
12
12
  # Timeout errors during search job execution
13
13
  class TimeoutError < Error; end
14
+
15
+ # Rate limit errors (429 responses)
16
+ # Includes retry_after when available from X-RateLimit-Reset or Retry-After headers
17
+ class RateLimitError < Error
18
+ attr_reader :retry_after, :limit, :remaining, :reset_at
19
+
20
+ def initialize(message, retry_after: nil, limit: nil, remaining: nil, reset_at: nil)
21
+ super(message)
22
+ @retry_after = retry_after
23
+ @limit = limit
24
+ @remaining = remaining
25
+ @reset_at = reset_at
26
+ end
27
+ end
14
28
  end
15
29
 
16
30
  # Load configuration first
@@ -35,7 +49,15 @@ require_relative 'sumologic/metadata/models'
35
49
  require_relative 'sumologic/metadata/collector'
36
50
  require_relative 'sumologic/metadata/collector_source_fetcher'
37
51
  require_relative 'sumologic/metadata/source'
38
- require_relative 'sumologic/metadata/dynamic_source_discovery'
52
+ require_relative 'sumologic/metadata/source_metadata_discovery'
53
+ require_relative 'sumologic/metadata/monitor'
54
+ require_relative 'sumologic/metadata/folder'
55
+ require_relative 'sumologic/metadata/dashboard'
56
+ require_relative 'sumologic/metadata/health_event'
57
+ require_relative 'sumologic/metadata/field'
58
+ require_relative 'sumologic/metadata/lookup_table'
59
+ require_relative 'sumologic/metadata/app'
60
+ require_relative 'sumologic/metadata/content'
39
61
 
40
62
  # Load main client (facade)
41
63
  require_relative 'sumologic/client'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sumologic-query
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - patrick204nqh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-19 00:00:00.000000000 Z
11
+ date: 2026-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -98,8 +98,19 @@ files:
98
98
  - lib/sumologic.rb
99
99
  - lib/sumologic/cli.rb
100
100
  - lib/sumologic/cli/commands/base_command.rb
101
- - lib/sumologic/cli/commands/discover_sources_command.rb
101
+ - lib/sumologic/cli/commands/discover_source_metadata_command.rb
102
+ - lib/sumologic/cli/commands/export_content_command.rb
103
+ - lib/sumologic/cli/commands/get_content_command.rb
104
+ - lib/sumologic/cli/commands/get_dashboard_command.rb
105
+ - lib/sumologic/cli/commands/get_lookup_command.rb
106
+ - lib/sumologic/cli/commands/get_monitor_command.rb
107
+ - lib/sumologic/cli/commands/list_apps_command.rb
102
108
  - lib/sumologic/cli/commands/list_collectors_command.rb
109
+ - lib/sumologic/cli/commands/list_dashboards_command.rb
110
+ - lib/sumologic/cli/commands/list_fields_command.rb
111
+ - lib/sumologic/cli/commands/list_folders_command.rb
112
+ - lib/sumologic/cli/commands/list_health_events_command.rb
113
+ - lib/sumologic/cli/commands/list_monitors_command.rb
103
114
  - lib/sumologic/cli/commands/list_sources_command.rb
104
115
  - lib/sumologic/cli/commands/search_command.rb
105
116
  - lib/sumologic/client.rb
@@ -118,12 +129,20 @@ files:
118
129
  - lib/sumologic/interactive/fzf_viewer/fzf_config.rb
119
130
  - lib/sumologic/interactive/fzf_viewer/header_builder.rb
120
131
  - lib/sumologic/interactive/fzf_viewer/searchable_builder.rb
132
+ - lib/sumologic/metadata/app.rb
121
133
  - lib/sumologic/metadata/collector.rb
122
134
  - lib/sumologic/metadata/collector_source_fetcher.rb
123
- - lib/sumologic/metadata/dynamic_source_discovery.rb
135
+ - lib/sumologic/metadata/content.rb
136
+ - lib/sumologic/metadata/dashboard.rb
137
+ - lib/sumologic/metadata/field.rb
138
+ - lib/sumologic/metadata/folder.rb
139
+ - lib/sumologic/metadata/health_event.rb
124
140
  - lib/sumologic/metadata/loggable.rb
141
+ - lib/sumologic/metadata/lookup_table.rb
125
142
  - lib/sumologic/metadata/models.rb
143
+ - lib/sumologic/metadata/monitor.rb
126
144
  - lib/sumologic/metadata/source.rb
145
+ - lib/sumologic/metadata/source_metadata_discovery.rb
127
146
  - lib/sumologic/search/job.rb
128
147
  - lib/sumologic/search/message_fetcher.rb
129
148
  - lib/sumologic/search/poller.rb