sumologic-query 1.3.5 → 1.4.1

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 +52 -0
  3. data/README.md +4 -4
  4. data/lib/sumologic/cli/commands/base_command.rb +12 -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 +18 -0
  7. data/lib/sumologic/cli/commands/get_content_command.rb +18 -0
  8. data/lib/sumologic/cli/commands/get_dashboard_command.rb +18 -0
  9. data/lib/sumologic/cli/commands/get_lookup_command.rb +18 -0
  10. data/lib/sumologic/cli/commands/get_monitor_command.rb +18 -0
  11. data/lib/sumologic/cli/commands/list_apps_command.rb +16 -0
  12. data/lib/sumologic/cli/commands/list_collectors_command.rb +1 -7
  13. data/lib/sumologic/cli/commands/list_dashboards_command.rb +18 -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 +18 -0
  17. data/lib/sumologic/cli/commands/list_monitors_command.rb +23 -0
  18. data/lib/sumologic/cli/commands/list_sources_command.rb +2 -9
  19. data/lib/sumologic/cli/commands/search_command.rb +70 -18
  20. data/lib/sumologic/cli.rb +290 -12
  21. data/lib/sumologic/client.rb +207 -12
  22. data/lib/sumologic/configuration.rb +44 -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 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83e78d35f43a7e12f98e3ed11d9d4b0ddd7fe4cf567c281fa4729c8873c583a0
4
- data.tar.gz: 4cc242bc523b5ce0cf709ec9f9aceb44cb1fe5911582983ae80e3a49d00a175d
3
+ metadata.gz: f62e0cd6d7ee0fbfd44b2720b0075e4f1776e086ef1ccbe39ad9e520f539f951
4
+ data.tar.gz: da262a85a0fa2d556c310876d011705edd4fc1b46db47c43d6f892adf71649ff
5
5
  SHA512:
6
- metadata.gz: 7b9d63d998ff14e1b9db86dc982dd84b508f642c01a62883b7bdce997ec21aed3097477956776319f01b68da2e8ed967b713894ed8002353ab99ee9fd72bad85
7
- data.tar.gz: '084c1c7db713d88f88979941d8b6a261f48c4bc533f3ce2ac04cb97ea3bae5fb0762889611dbfffd790740e0c7928acffb9e7df144c6e6d7b0fa129128d49e23'
6
+ metadata.gz: 458d97b1853c3626d85f84770faaf918ebbd2842718ffed416ead1736a8a075bc4061eb040e003a24227e8af6b7f6f125baf2b2aec5627f0398abd6b054fe880
7
+ data.tar.gz: '078544851f936431eea88d6b0783f39c58789072bbfa8fe17f5147c2a9aff9df8bc0b3f5e71c48e7d6793f8ad94a69a134bfabe5ab3d46689ff35613a13446f5'
data/CHANGELOG.md CHANGED
@@ -6,6 +6,58 @@ 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.1](https://github.com/patrick204nqh/sumologic-query/compare/v1.4.0...v1.4.1) (2026-02-11)
10
+
11
+ ### 🎉 New Features
12
+
13
+ - add "Open in Sumo Logic" URL to search output
14
+ - add command recipe and CLI command specs
15
+
16
+ ### 🐛 Bug Fixes
17
+
18
+ - update source_code_uri metadata to use homepage
19
+
20
+ ### 🔧 Refactoring
21
+
22
+ - resolve ADR-006, fix README links, extract command helpers
23
+
24
+ ### 📚 Documentation
25
+
26
+ - consolidate query examples and document v1/v2 API split
27
+
28
+
29
+
30
+ ## [1.4.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.5...v1.4.0) (2026-02-11)
31
+
32
+ ### 🎉 New Features
33
+
34
+ - add comprehensive skills documentation for Sumo Logic CLI commands
35
+ - add export-content command with async job handling
36
+ - add get-content command for path-based content lookup
37
+ - add get-lookup and list-apps commands
38
+ - add list-health-events and list-fields commands
39
+ - migrate monitors to search API with status/query filters
40
+ - add support for retrieving monitors and dashboards in the CLI, refactor monitor collection logic for improved readability
41
+ - Enhance Sumo Logic CLI with monitors, folders, and dashboards commands
42
+ - implement aggregation queries and enhance error handling for rate limits
43
+
44
+ ### 🐛 Bug Fixes
45
+
46
+ - migrate dashboards from v1 to v2 API
47
+
48
+ ### 🔧 Refactoring
49
+
50
+ - clean design for AI agent readiness (v1.4.0)
51
+
52
+ ### 📚 Documentation
53
+
54
+ - update naming conventions to match actual codebase
55
+ - reorganize into SDLC structure
56
+ - clarify discover-sources as search-based technique
57
+ - revise architecture overview with enhanced clarity on design philosophy, component structure, and key features
58
+
59
+
60
+
9
61
  ## [1.3.5](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.4...v1.3.5) (2025-11-19)
10
62
 
11
63
  ### 🎉 New Features
data/README.md CHANGED
@@ -78,7 +78,7 @@ sumo-query search -q "YOUR_QUERY" -f "START" -t "END" [OPTIONS]
78
78
  sumo-query discover-sources [OPTIONS]
79
79
  ```
80
80
 
81
- Finds dynamic source names from log data (CloudWatch, ECS, Lambda streams).
81
+ Discovers source names from log data using search aggregation (`* | count by _sourceName, _sourceCategory`). This is not an official Sumo Logic API — it complements `list-sources` by finding runtime sources (CloudWatch, ECS, Lambda streams) that use dynamic `_sourceName` values.
82
82
 
83
83
  **Options:**
84
84
  - `-f, --from` - Start time (default: `-24h`)
@@ -152,9 +152,9 @@ client.list_all_sources
152
152
  ## Documentation
153
153
 
154
154
  - [Query Examples](examples/queries.md) - Query patterns and examples
155
- - [Quick Reference](docs/tldr.md) - Command cheat sheet
156
- - [Rate Limiting](docs/rate-limiting.md) - Performance tuning
157
- - [Architecture](docs/architecture/) - Design decisions
155
+ - [Quick Reference](docs/sdlc/7-maintain/tldr.md) - Command cheat sheet
156
+ - [Rate Limiting](docs/sdlc/4-develop/rate-limiting.md) - Performance tuning
157
+ - [Architecture](docs/sdlc/3-design/overview.md) - Design decisions
158
158
 
159
159
  ## Contributing
160
160
 
@@ -17,6 +17,18 @@ module Sumologic
17
17
 
18
18
  private
19
19
 
20
+ def list_resource(label:, key:)
21
+ warn "Fetching #{label}..."
22
+ items = yield
23
+ output_json(total: items.size, key => items)
24
+ end
25
+
26
+ def get_resource(label:, id:)
27
+ warn "Fetching #{label} #{id}..."
28
+ result = yield
29
+ output_json(result)
30
+ end
31
+
20
32
  def output_json(data)
21
33
  json_output = JSON.pretty_generate(data)
22
34
 
@@ -31,26 +43,6 @@ module Sumologic
31
43
  puts json_output
32
44
  end
33
45
  end
34
-
35
- def format_collector(collector)
36
- {
37
- id: collector['id'],
38
- name: collector['name'],
39
- collectorType: collector['collectorType'],
40
- alive: collector['alive'],
41
- category: collector['category']
42
- }
43
- end
44
-
45
- def format_source(source)
46
- {
47
- id: source['id'],
48
- name: source['name'],
49
- category: source['category'],
50
- sourceType: source['sourceType'],
51
- alive: source['alive']
52
- }
53
- end
54
46
  end
55
47
  end
56
48
  end
@@ -6,8 +6,8 @@ require_relative '../../utils/time_parser'
6
6
  module Sumologic
7
7
  class CLI < Thor
8
8
  module Commands
9
- # Handles the discover-sources command execution
10
- class DiscoverSourcesCommand < BaseCommand
9
+ # Handles the discover-source-metadata command execution
10
+ class DiscoverSourceMetadataCommand < BaseCommand
11
11
  def execute
12
12
  parse_time_options
13
13
  log_discovery_info
@@ -32,7 +32,7 @@ module Sumologic
32
32
 
33
33
  def log_discovery_info
34
34
  warn '=' * 60
35
- warn 'Discovering Dynamic Source Names'
35
+ warn 'Discovering Source Metadata'
36
36
  warn '=' * 60
37
37
  warn "Time Range: #{@original_from} to #{@original_to}"
38
38
  if @original_from != @parsed_from || @original_to != @parsed_to
@@ -46,7 +46,7 @@ module Sumologic
46
46
  end
47
47
 
48
48
  def perform_discovery
49
- client.discover_dynamic_sources(
49
+ client.discover_source_metadata(
50
50
  from_time: @parsed_from,
51
51
  to_time: @parsed_to,
52
52
  time_zone: @parsed_timezone,
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the export-content command execution
9
+ class ExportContentCommand < BaseCommand
10
+ def execute
11
+ get_resource(label: 'content', id: options[:content_id]) do
12
+ client.export_content(content_id: options[:content_id])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the get-content command execution
9
+ class GetContentCommand < BaseCommand
10
+ def execute
11
+ get_resource(label: 'content at path:', id: options[:path]) do
12
+ client.get_content(path: options[:path])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the get-dashboard command execution
9
+ class GetDashboardCommand < BaseCommand
10
+ def execute
11
+ get_resource(label: 'dashboard', id: options[:dashboard_id]) do
12
+ client.get_dashboard(dashboard_id: options[:dashboard_id])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the get-lookup command execution
9
+ class GetLookupCommand < BaseCommand
10
+ def execute
11
+ get_resource(label: 'lookup table', id: options[:lookup_id]) do
12
+ client.get_lookup(lookup_id: options[:lookup_id])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the get-monitor command execution
9
+ class GetMonitorCommand < BaseCommand
10
+ def execute
11
+ get_resource(label: 'monitor', id: options[:monitor_id]) do
12
+ client.get_monitor(monitor_id: options[:monitor_id])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-apps command execution
9
+ class ListAppsCommand < BaseCommand
10
+ def execute
11
+ list_resource(label: 'app catalog', key: :apps) { client.list_apps }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -8,13 +8,7 @@ module Sumologic
8
8
  # Handles the list-collectors command execution
9
9
  class ListCollectorsCommand < BaseCommand
10
10
  def execute
11
- warn 'Fetching collectors...'
12
- collectors = client.list_collectors
13
-
14
- output_json(
15
- total: collectors.size,
16
- collectors: collectors.map { |c| format_collector(c) }
17
- )
11
+ list_resource(label: 'collectors', key: :collectors) { client.list_collectors }
18
12
  end
19
13
  end
20
14
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-dashboards command execution
9
+ class ListDashboardsCommand < BaseCommand
10
+ def execute
11
+ list_resource(label: 'dashboards', key: :dashboards) do
12
+ client.list_dashboards(limit: options[:limit] || 100)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-fields command execution
9
+ class ListFieldsCommand < BaseCommand
10
+ def execute
11
+ if options[:builtin]
12
+ warn 'Fetching built-in fields...'
13
+ fields = client.list_builtin_fields
14
+ else
15
+ warn 'Fetching custom fields...'
16
+ fields = client.list_fields
17
+ end
18
+
19
+ output_json(
20
+ total: fields.size,
21
+ fields: fields
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-folders command execution
9
+ class ListFoldersCommand < BaseCommand
10
+ def execute
11
+ if options[:tree]
12
+ fetch_tree
13
+ elsif options[:folder_id]
14
+ fetch_folder
15
+ else
16
+ fetch_personal
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_personal
23
+ warn 'Fetching personal folder...'
24
+ folder = client.personal_folder
25
+ output_folder_with_children(folder)
26
+ end
27
+
28
+ def fetch_folder
29
+ folder_id = options[:folder_id]
30
+ warn "Fetching folder #{folder_id}..."
31
+ folder = client.get_folder(folder_id: folder_id)
32
+ output_folder_with_children(folder)
33
+ end
34
+
35
+ def fetch_tree
36
+ folder_id = options[:folder_id]
37
+ max_depth = options[:depth] || 3
38
+
39
+ if folder_id
40
+ warn "Fetching folder tree for #{folder_id} (depth: #{max_depth})..."
41
+ else
42
+ warn "Fetching personal folder tree (depth: #{max_depth})..."
43
+ end
44
+
45
+ tree = client.folder_tree(folder_id: folder_id, max_depth: max_depth)
46
+ output_json(tree)
47
+ end
48
+
49
+ def output_folder_with_children(folder)
50
+ output_json(folder)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-health-events command execution
9
+ class ListHealthEventsCommand < BaseCommand
10
+ def execute
11
+ list_resource(label: 'health events', key: :healthEvents) do
12
+ client.list_health_events(limit: options[:limit] || 100)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+
5
+ module Sumologic
6
+ class CLI < Thor
7
+ module Commands
8
+ # Handles the list-monitors command execution
9
+ # Uses the monitors search API for flat, filterable results
10
+ class ListMonitorsCommand < BaseCommand
11
+ def execute
12
+ list_resource(label: 'monitors', key: :monitors) do
13
+ client.list_monitors(
14
+ query: options[:query],
15
+ status: options[:status],
16
+ limit: options[:limit] || 100
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -24,7 +24,7 @@ module Sumologic
24
24
  output_json(
25
25
  collector_id: options[:collector_id],
26
26
  total: sources.size,
27
- sources: sources.map { |s| format_source(s) }
27
+ sources: sources
28
28
  )
29
29
  end
30
30
 
@@ -37,16 +37,9 @@ module Sumologic
37
37
  output_json(
38
38
  total_collectors: all_sources.size,
39
39
  total_sources: all_sources.sum { |c| c['sources'].size },
40
- data: all_sources.map { |item| format_collector_with_sources(item) }
40
+ data: all_sources
41
41
  )
42
42
  end
43
-
44
- def format_collector_with_sources(item)
45
- {
46
- collector: item['collector'],
47
- sources: item['sources'].map { |s| format_source(s) }
48
- }
49
- end
50
43
  end
51
44
  end
52
45
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
3
4
  require_relative 'base_command'
4
5
  require_relative '../../utils/time_parser'
5
6
 
@@ -46,42 +47,83 @@ module Sumologic
46
47
  end
47
48
  warn "Query: #{options[:query]}"
48
49
  warn "Limit: #{options[:limit] || 'unlimited'}"
50
+ warn "Open in Sumo: #{build_search_url}"
49
51
  warn '-' * 60
50
52
  warn 'Creating search job...'
51
53
  $stderr.puts
52
54
  end
53
55
 
54
56
  def perform_search
55
- client.search(
56
- query: options[:query],
57
- from_time: @parsed_from,
58
- to_time: @parsed_to,
59
- time_zone: @parsed_timezone,
60
- limit: options[:limit]
61
- )
57
+ if aggregate_mode?
58
+ client.search_aggregation(
59
+ query: options[:query],
60
+ from_time: @parsed_from,
61
+ to_time: @parsed_to,
62
+ time_zone: @parsed_timezone,
63
+ limit: options[:limit]
64
+ )
65
+ else
66
+ client.search(
67
+ query: options[:query],
68
+ from_time: @parsed_from,
69
+ to_time: @parsed_to,
70
+ time_zone: @parsed_timezone,
71
+ limit: options[:limit]
72
+ )
73
+ end
74
+ end
75
+
76
+ def aggregate_mode?
77
+ options[:aggregate] || aggregation_query?(options[:query])
78
+ end
79
+
80
+ def aggregation_query?(query)
81
+ query.match?(/\|\s*(count|sum|avg|min|max|pct|first|last|group)\b/i)
62
82
  end
63
83
 
64
84
  def display_results_summary(results)
85
+ result_type = aggregate_mode? ? 'records' : 'messages'
65
86
  warn '=' * 60
66
- warn "Results: #{results.size} messages"
87
+ warn "Results: #{results.size} #{result_type}"
67
88
  warn '=' * 60
68
89
  $stderr.puts
69
90
  end
70
91
 
71
92
  def output_search_results(results)
72
- output_json(
73
- query: options[:query],
74
- from: @parsed_from,
75
- to: @parsed_to,
76
- from_original: @original_from,
77
- to_original: @original_to,
78
- time_zone: @parsed_timezone,
79
- message_count: results.size,
80
- messages: results
81
- )
93
+ if aggregate_mode?
94
+ output_json(
95
+ query: options[:query],
96
+ from: @parsed_from,
97
+ to: @parsed_to,
98
+ from_original: @original_from,
99
+ to_original: @original_to,
100
+ time_zone: @parsed_timezone,
101
+ search_url: build_search_url,
102
+ record_count: results.size,
103
+ records: results
104
+ )
105
+ else
106
+ output_json(
107
+ query: options[:query],
108
+ from: @parsed_from,
109
+ to: @parsed_to,
110
+ from_original: @original_from,
111
+ to_original: @original_to,
112
+ time_zone: @parsed_timezone,
113
+ search_url: build_search_url,
114
+ message_count: results.size,
115
+ messages: results
116
+ )
117
+ end
82
118
  end
83
119
 
84
120
  def launch_interactive_mode(results)
121
+ if aggregate_mode?
122
+ warn 'Interactive mode is not supported for aggregation queries'
123
+ output_search_results(results)
124
+ return
125
+ end
126
+
85
127
  require_relative '../../interactive'
86
128
 
87
129
  formatted_results = build_formatted_results(results)
@@ -97,10 +139,20 @@ module Sumologic
97
139
  'from' => @parsed_from,
98
140
  'to' => @parsed_to,
99
141
  'time_zone' => @parsed_timezone,
142
+ 'search_url' => build_search_url,
100
143
  'message_count' => results.size,
101
144
  'messages' => results
102
145
  }
103
146
  end
147
+
148
+ def build_search_url
149
+ from_ms = (Time.parse("#{@parsed_from}Z").to_f * 1000).to_i
150
+ to_ms = (Time.parse("#{@parsed_to}Z").to_f * 1000).to_i
151
+ encoded_query = ERB::Util.url_encode(options[:query])
152
+ base = client.config.web_ui_base_url
153
+
154
+ "#{base}/ui/#/search/create?query=#{encoded_query}&startTime=#{from_ms}&endTime=#{to_ms}"
155
+ end
104
156
  end
105
157
  end
106
158
  end