sumologic-query 1.1.1 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94a41b01c95f9975b2caa3fd6e35ed59f3a0a50ea1bef0a382984db8b28a2df1
4
- data.tar.gz: 2a2af32a87f880dfbea771d08e1ba0aff16015a14da80742ee90f8bcaa8f653c
3
+ metadata.gz: 7b7bbf8f091fa6d3c6e213a34c1ba72952c43aa995814834379c30a051d3f4f5
4
+ data.tar.gz: 4e7bc3f985d01543a5af7ce89d7746944107d4fd99ecf4367c276a5e550dbdca
5
5
  SHA512:
6
- metadata.gz: 42e019ecf001cf27fe9c4a6fb09ab3830c94e4d198b150ea9a34854a4cb77c72b0d769b61cce671dc3fb548b2d5bf6f7b8df97fcea08175bde3897f205ff8d24
7
- data.tar.gz: 5f83a1f886d1b2d3e995be899b9b093bfb03738b9626ca5a5c7c2c06fecf88da037d017fe894b5c0ad014ec3a9a5bdabc0173eeb748b6e07bb013452e1cfd833
6
+ metadata.gz: 4f34b22f610d453649fff4926c7dfdff0fd2d5ff18a2e5fa6994cf1f41635990c28ace805fb2508520a1f016899c99f71ba31be2d5b0771a8b30d9e781f310a2
7
+ data.tar.gz: 0a12a47f8ddfeda0bab912f2011a033b121e01f3c63b19d9fa80e7cfa0a7df32a7277784a92a88ca988819a1f42c717c61dbbae79d5bab154161d9cff5f571a8
data/README.md CHANGED
@@ -1,19 +1,20 @@
1
1
  # Sumo Logic Query Tool
2
2
 
3
- > A lightweight Ruby CLI for querying Sumo Logic logs quickly. Simple, fast, read-only access to your logs.
3
+ > A lightweight Ruby CLI for querying Sumo Logic logs and metadata. Simple, fast, read-only access to your logs.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/sumologic-query.svg)](https://badge.fury.io/rb/sumologic-query)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ## Why This Tool?
9
9
 
10
- - **Zero dependencies**: Uses only Ruby stdlib - no external gems required
10
+ - **Minimal dependencies**: Uses only Ruby stdlib + Thor for CLI
11
11
  - **Fast queries**: Efficient polling and automatic pagination
12
12
  - **Simple interface**: Just query, get results, done
13
13
  - **Read-only**: No write operations, perfect for safe log access
14
- - **Lightweight**: ~300 lines of code total
14
+ - **Modular architecture**: Clean separation of concerns (HTTP, Search, Metadata)
15
+ - **Metadata support**: List collectors and sources alongside log queries
15
16
 
16
- All existing Ruby Sumo Logic gems are unmaintained (2-9 years dormant). This tool provides a fresh, minimal approach focused solely on querying logs.
17
+ All existing Ruby Sumo Logic gems are unmaintained (2-9 years dormant). This tool provides a fresh, minimal approach focused on querying logs and metadata.
17
18
 
18
19
  ## Installation
19
20
 
@@ -48,7 +49,7 @@ Export your Sumo Logic API credentials:
48
49
  ```bash
49
50
  export SUMO_ACCESS_ID="your_access_id"
50
51
  export SUMO_ACCESS_KEY="your_access_key"
51
- export SUMO_DEPLOYMENT="us2" # Optional: us1, us2 (default), eu, au
52
+ export SUMO_DEPLOYMENT="us2" # Optional: us1, us2 (default), eu, au, etc.
52
53
  ```
53
54
 
54
55
  **Getting credentials:**
@@ -60,18 +61,27 @@ export SUMO_DEPLOYMENT="us2" # Optional: us1, us2 (default), eu, au
60
61
  ### 2. Run Your First Query
61
62
 
62
63
  ```bash
63
- sumo-query --query 'error' \
64
+ # Search logs
65
+ sumo-query search --query 'error' \
64
66
  --from '2025-11-13T14:00:00' \
65
67
  --to '2025-11-13T15:00:00' \
66
68
  --limit 10
69
+
70
+ # List collectors
71
+ sumo-query collectors
72
+
73
+ # List sources
74
+ sumo-query sources
67
75
  ```
68
76
 
69
77
  ## Usage
70
78
 
71
- ### Basic Command Structure
79
+ The CLI provides three main commands:
80
+
81
+ ### Search Logs
72
82
 
73
83
  ```bash
74
- sumo-query --query "YOUR_QUERY" \
84
+ sumo-query search --query "YOUR_QUERY" \
75
85
  --from "START_TIME" \
76
86
  --to "END_TIME" \
77
87
  [--output FILE] \
@@ -79,87 +89,85 @@ sumo-query --query "YOUR_QUERY" \
79
89
  [--time-zone TZ]
80
90
  ```
81
91
 
82
- ### Required Options
83
-
92
+ **Required options:**
84
93
  - `-q, --query QUERY` - Sumo Logic query string
85
- - `-f, --from TIME` - Start time in ISO 8601 format
86
- - `-t, --to TIME` - End time in ISO 8601 format
87
-
88
- ### Optional Options
94
+ - `-f, --from TIME` - Start time (ISO 8601 format)
95
+ - `-t, --to TIME` - End time (ISO 8601 format)
89
96
 
97
+ **Optional options:**
90
98
  - `-z, --time-zone TZ` - Time zone (default: UTC)
91
99
  - `-l, --limit N` - Limit number of messages
92
- - `-o, --output FILE` - Save results to file (default: stdout)
100
+ - `-o, --output FILE` - Save to file (default: stdout)
93
101
  - `-d, --debug` - Enable debug output
94
- - `-h, --help` - Show help message
95
- - `-v, --version` - Show version
96
102
 
97
- ## Common Query Patterns
98
-
99
- ### Error Analysis
103
+ ### List Collectors
100
104
 
101
105
  ```bash
102
- # Find all errors in a time window
103
- sumo-query --query 'error' \
104
- --from '2025-11-13T14:00:00' \
105
- --to '2025-11-13T15:00:00' \
106
- --output errors.json
107
-
108
- # Error timeline with 5-minute buckets
109
- sumo-query --query 'error | timeslice 5m | count by _timeslice' \
110
- --from '2025-11-13T14:00:00' \
111
- --to '2025-11-13T15:00:00'
106
+ sumo-query collectors [--output FILE]
112
107
  ```
113
108
 
114
- ### Text Search
109
+ Lists all collectors in your account with status and metadata.
115
110
 
116
- ```bash
117
- # Search for specific text
118
- sumo-query --query '"connection timeout"' \
119
- --from '2025-11-13T14:00:00' \
120
- --to '2025-11-13T15:00:00'
111
+ ### List Sources
121
112
 
122
- # Case-insensitive search
123
- sumo-query --query 'timeout OR failure OR exception' \
124
- --from '2025-11-13T14:00:00' \
125
- --to '2025-11-13T15:00:00'
113
+ ```bash
114
+ sumo-query sources [--output FILE]
126
115
  ```
127
116
 
128
- ### Filtering by Source
117
+ Lists all sources from active collectors.
129
118
 
130
- ```bash
131
- # Filter by source category
132
- sumo-query --query '_sourceCategory=prod/api error' \
133
- --from '2025-11-13T14:00:00' \
134
- --to '2025-11-13T15:00:00'
119
+ **See [examples/queries.md](examples/queries.md) for more query patterns and examples.**
135
120
 
136
- # Multiple sources
137
- sumo-query --query '(_sourceCategory=prod/api OR _sourceCategory=prod/web) AND error' \
138
- --from '2025-11-13T14:00:00' \
139
- --to '2025-11-13T15:00:00'
140
- ```
121
+ ## Ruby Library Usage
141
122
 
142
- ### Aggregation Queries
123
+ Use the library directly in your Ruby code:
143
124
 
144
- ```bash
145
- # Count by field
146
- sumo-query --query '* | count by status_code' \
147
- --from '2025-11-13T14:00:00' \
148
- --to '2025-11-13T15:00:00'
125
+ ```ruby
126
+ require 'sumologic'
149
127
 
150
- # Top 10 slowest requests
151
- sumo-query --query 'duration_ms > 1000 | sort by duration_ms desc | limit 10' \
152
- --from '2025-11-13T14:00:00' \
153
- --to '2025-11-13T15:00:00'
128
+ # Initialize client
129
+ client = Sumologic::Client.new(
130
+ access_id: ENV['SUMO_ACCESS_ID'],
131
+ access_key: ENV['SUMO_ACCESS_KEY'],
132
+ deployment: 'us2'
133
+ )
134
+
135
+ # Search logs
136
+ results = client.search(
137
+ query: 'error',
138
+ from_time: '2025-11-13T14:00:00',
139
+ to_time: '2025-11-13T15:00:00',
140
+ time_zone: 'UTC',
141
+ limit: 1000
142
+ )
143
+
144
+ results.each do |message|
145
+ puts message['map']['message']
146
+ end
147
+
148
+ # List collectors
149
+ collectors = client.list_collectors
150
+
151
+ # List all sources
152
+ sources = client.list_all_sources
154
153
  ```
155
154
 
156
- ### Parsing and Field Extraction
155
+ **See [docs/api-reference.md](docs/api-reference.md) for complete API documentation.**
156
+
157
+ ## Time Formats
158
+
159
+ Use ISO 8601 format for timestamps:
157
160
 
158
161
  ```bash
159
- # Parse specific fields
160
- sumo-query --query '* | parse "user_id=* " as user_id | count by user_id' \
161
- --from '2025-11-13T14:00:00' \
162
- --to '2025-11-13T15:00:00'
162
+ # UTC timestamps (default)
163
+ --from "2025-11-13T14:30:00" --to "2025-11-13T15:00:00"
164
+
165
+ # With timezone
166
+ --from "2025-11-13T14:30:00" --time-zone "America/New_York"
167
+
168
+ # Using shell helpers
169
+ --from "$(date -u -v-1H '+%Y-%m-%dT%H:%M:%S')" # 1 hour ago
170
+ --to "$(date -u '+%Y-%m-%dT%H:%M:%S')" # now
163
171
  ```
164
172
 
165
173
  ## Output Format
@@ -186,157 +194,54 @@ Results are returned as JSON:
186
194
  }
187
195
  ```
188
196
 
189
- ## Time Formats
190
-
191
- Use ISO 8601 format for timestamps:
192
-
193
- ```bash
194
- # UTC timestamps (default)
195
- --from "2025-11-13T14:30:00"
196
- --to "2025-11-13T15:00:00"
197
-
198
- # With timezone
199
- --from "2025-11-13T14:30:00" --time-zone "America/New_York"
200
-
201
- # Alternative: Relative times (in your shell)
202
- --from "$(date -u -v-1H '+%Y-%m-%dT%H:%M:%S')" # 1 hour ago
203
- --to "$(date -u '+%Y-%m-%dT%H:%M:%S')" # now
204
- ```
205
-
206
197
  ## Performance
207
198
 
208
199
  Query execution time depends on data volume:
209
200
 
210
- - **Small queries** (<10K messages): ~30-60 seconds
211
- - **Medium queries** (10K-100K): ~1-2 minutes
212
- - **Large queries** (100K+): ~2-5 minutes
201
+ | Messages | Typical Time |
202
+ |----------|--------------|
203
+ | < 10K | 30-60 seconds |
204
+ | 10K-100K | 1-2 minutes |
205
+ | 100K+ | 2-5 minutes |
213
206
 
214
- Default timeout: 5 minutes
215
-
216
- To improve performance:
207
+ **Tips for faster queries:**
217
208
  - Narrow your time range
218
- - Add specific `_sourceCategory` filters
209
+ - Add `_sourceCategory` filters
219
210
  - Use `--limit` to cap results
220
- - Use aggregation queries instead of fetching raw messages
221
-
222
- ## Troubleshooting
211
+ - Use aggregation queries instead of raw messages
223
212
 
224
- ### Authentication Error
213
+ ## Documentation
225
214
 
226
- ```
227
- Error: SUMO_ACCESS_ID not set
228
- ```
229
-
230
- **Solution**: Export your credentials:
231
- ```bash
232
- export SUMO_ACCESS_ID="your_access_id"
233
- export SUMO_ACCESS_KEY="your_access_key"
234
- ```
235
-
236
- ### Timeout Error
237
-
238
- ```
239
- Timeout Error: Search job timed out after 300 seconds
240
- ```
241
-
242
- **Solutions**:
243
- - Reduce time range
244
- - Add more specific filters (`_sourceCategory`, `_sourceName`)
245
- - Use `--limit` to cap results
246
- - Consider using aggregation instead of raw messages
247
-
248
- ### Empty Results
249
-
250
- ```json
251
- {
252
- "message_count": 0,
253
- "messages": []
254
- }
255
- ```
256
-
257
- **Check**:
258
- - Time range matches your expected data
259
- - Query syntax is valid (test in Sumo Logic UI first)
260
- - Source categories are correct
261
- - Time zone is correct (default is UTC)
262
-
263
- ### Rate Limit Error
264
-
265
- ```
266
- HTTP 429: Rate limit exceeded
267
- ```
268
-
269
- **Solution**: Wait 1-2 minutes between queries. Sumo Logic enforces rate limits per account.
215
+ - **[Quick Reference (tldr)](docs/tldr.md)** - Concise command examples in tldr format
216
+ - **[Query Examples](examples/queries.md)** - Common query patterns and use cases
217
+ - **[API Reference](docs/api-reference.md)** - Complete Ruby library documentation
218
+ - **[Architecture](docs/architecture.md)** - How the tool works internally
219
+ - **[Troubleshooting](docs/troubleshooting.md)** - Common issues and solutions
270
220
 
271
221
  ## Development
272
222
 
273
- ### Running Tests
223
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and contribution guidelines.
224
+
225
+ Quick start:
274
226
 
275
227
  ```bash
228
+ # Clone and install
229
+ git clone https://github.com/patrick204nqh/sumologic-query.git
230
+ cd sumologic-query
276
231
  bundle install
277
- bundle exec rake spec
278
- ```
279
232
 
280
- ### Code Quality
233
+ # Run tests
234
+ bundle exec rspec
281
235
 
282
- ```bash
236
+ # Run linter
283
237
  bundle exec rubocop
284
- bundle exec rubocop -A # Auto-fix issues
285
- ```
286
238
 
287
- ### Running Locally
288
-
289
- ```bash
290
- # Without installing
291
- bundle exec bin/sumo-query --query "error" \
292
- --from "2025-11-13T14:00:00" \
293
- --to "2025-11-13T15:00:00"
294
-
295
- # With debug output
296
- SUMO_DEBUG=1 bundle exec bin/sumo-query --query "error" \
239
+ # Test locally
240
+ bundle exec bin/sumo-query search --query "error" \
297
241
  --from "2025-11-13T14:00:00" \
298
242
  --to "2025-11-13T15:00:00"
299
243
  ```
300
244
 
301
- ## How It Works
302
-
303
- This tool uses the Sumo Logic Search Job API:
304
-
305
- 1. **Create Job** - POST to `/api/v1/search/jobs` with your query
306
- 2. **Poll Status** - GET `/api/v1/search/jobs/:id` every 20 seconds until complete
307
- 3. **Fetch Messages** - GET `/api/v1/search/jobs/:id/messages` (automatically paginated)
308
- 4. **Clean Up** - DELETE `/api/v1/search/jobs/:id`
309
-
310
- All steps are handled automatically. You just provide the query and get results.
311
-
312
- ## API Reference
313
-
314
- ### Ruby Library Usage
315
-
316
- You can also use the library directly in your Ruby code:
317
-
318
- ```ruby
319
- require 'sumologic'
320
-
321
- client = Sumologic::Client.new(
322
- access_id: 'your_access_id',
323
- access_key: 'your_access_key',
324
- deployment: 'us2'
325
- )
326
-
327
- results = client.search(
328
- query: 'error',
329
- from_time: '2025-11-13T14:00:00',
330
- to_time: '2025-11-13T15:00:00',
331
- time_zone: 'UTC',
332
- limit: 1000
333
- )
334
-
335
- results.each do |message|
336
- puts message['map']['message']
337
- end
338
- ```
339
-
340
245
  ## Contributing
341
246
 
342
247
  Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
@@ -351,17 +256,17 @@ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for det
351
256
 
352
257
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
353
258
 
259
+ ## Support
260
+
261
+ - **Issues**: [GitHub Issues](https://github.com/patrick204nqh/sumologic-query/issues)
262
+ - **Discussions**: [GitHub Discussions](https://github.com/patrick204nqh/sumologic-query/discussions)
263
+ - **Documentation**: [docs/](docs/)
264
+
354
265
  ## Resources
355
266
 
356
267
  - **Sumo Logic API Docs**: https://help.sumologic.com/docs/api/search-job/
357
268
  - **Query Language**: https://help.sumologic.com/docs/search/
358
269
  - **Bug Reports**: https://github.com/patrick204nqh/sumologic-query/issues
359
- - **Feature Requests**: https://github.com/patrick204nqh/sumologic-query/issues
360
-
361
- ## Support
362
-
363
- - **Issues**: [GitHub Issues](https://github.com/patrick204nqh/sumologic-query/issues)
364
- - **Discussions**: [GitHub Discussions](https://github.com/patrick204nqh/sumologic-query/discussions)
365
270
 
366
271
  ---
367
272
 
data/lib/sumologic/cli.rb CHANGED
@@ -89,6 +89,12 @@ module Sumologic
89
89
  end
90
90
  end
91
91
 
92
+ desc 'version', 'Show version information'
93
+ def version
94
+ puts "sumo-query version #{Sumologic::VERSION}"
95
+ end
96
+ map %w[-v --version] => :version
97
+
92
98
  default_task :search
93
99
 
94
100
  private
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sumologic
4
+ module Metadata
5
+ # Handles parallel fetching of sources from multiple collectors
6
+ class ParallelFetcher
7
+ def initialize(max_threads: 10)
8
+ @max_threads = max_threads
9
+ end
10
+
11
+ # Fetch sources for collectors in parallel
12
+ # Returns array of results with collector info and sources
13
+ def fetch_all(collectors, &block)
14
+ result = []
15
+ mutex = Mutex.new
16
+ queue = create_work_queue(collectors)
17
+ threads = create_workers(queue, result, mutex, &block)
18
+
19
+ threads.each(&:join)
20
+ result
21
+ end
22
+
23
+ private
24
+
25
+ def create_work_queue(collectors)
26
+ queue = Queue.new
27
+ collectors.each { |collector| queue << collector }
28
+ queue
29
+ end
30
+
31
+ def create_workers(queue, result, mutex, &block)
32
+ worker_count = [@max_threads, queue.size].min
33
+
34
+ Array.new(worker_count) do
35
+ Thread.new { process_queue(queue, result, mutex, &block) }
36
+ end
37
+ end
38
+
39
+ def process_queue(queue, result, mutex, &block)
40
+ until queue.empty?
41
+ collector = pop_safely(queue)
42
+ break unless collector
43
+
44
+ process_collector(collector, result, mutex, &block)
45
+ end
46
+ end
47
+
48
+ def pop_safely(queue)
49
+ queue.pop(true)
50
+ rescue ThreadError
51
+ nil
52
+ end
53
+
54
+ def process_collector(collector, result, mutex, &block)
55
+ collector_result = block.call(collector)
56
+
57
+ mutex.synchronize do
58
+ result << collector_result if collector_result
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'parallel_fetcher'
4
+
3
5
  module Sumologic
4
6
  module Metadata
5
7
  # Handles source metadata operations
@@ -7,6 +9,7 @@ module Sumologic
7
9
  def initialize(http_client:, collector_client:)
8
10
  @http = http_client
9
11
  @collector_client = collector_client
12
+ @parallel_fetcher = ParallelFetcher.new(max_threads: 10)
10
13
  end
11
14
 
12
15
  # List sources for a specific collector
@@ -26,30 +29,15 @@ module Sumologic
26
29
 
27
30
  # List all sources from all collectors
28
31
  # Returns array of hashes with collector info and their sources
32
+ # Uses parallel fetching with thread pool for better performance
29
33
  def list_all
30
34
  collectors = @collector_client.list
31
- result = []
32
-
33
- collectors.each do |collector|
34
- next unless collector['alive'] # Skip offline collectors
35
-
36
- collector_id = collector['id']
37
- collector_name = collector['name']
35
+ active_collectors = collectors.select { |c| c['alive'] }
38
36
 
39
- log_info "Fetching sources for collector: #{collector_name} (#{collector_id})"
37
+ log_info "Fetching sources for #{active_collectors.size} active collectors in parallel..."
40
38
 
41
- sources = list(collector_id: collector_id)
42
-
43
- result << {
44
- 'collector' => {
45
- 'id' => collector_id,
46
- 'name' => collector_name,
47
- 'collectorType' => collector['collectorType']
48
- },
49
- 'sources' => sources
50
- }
51
- rescue StandardError => e
52
- log_error "Failed to fetch sources for collector #{collector_name}: #{e.message}"
39
+ result = @parallel_fetcher.fetch_all(active_collectors) do |collector|
40
+ fetch_collector_sources(collector)
53
41
  end
54
42
 
55
43
  log_info "Total: #{result.size} collectors with sources"
@@ -60,6 +48,27 @@ module Sumologic
60
48
 
61
49
  private
62
50
 
51
+ # Fetch sources for a single collector
52
+ def fetch_collector_sources(collector)
53
+ collector_id = collector['id']
54
+ collector_name = collector['name']
55
+
56
+ log_info "Fetching sources for collector: #{collector_name} (#{collector_id})"
57
+ sources = list(collector_id: collector_id)
58
+
59
+ {
60
+ 'collector' => {
61
+ 'id' => collector_id,
62
+ 'name' => collector_name,
63
+ 'collectorType' => collector['collectorType']
64
+ },
65
+ 'sources' => sources
66
+ }
67
+ rescue StandardError => e
68
+ log_error "Failed to fetch sources for collector #{collector_name}: #{e.message}"
69
+ nil
70
+ end
71
+
63
72
  def log_info(message)
64
73
  warn "[Sumologic::Metadata::Source] #{message}" if ENV['SUMO_DEBUG'] || $DEBUG
65
74
  end
@@ -11,6 +11,7 @@ module Sumologic
11
11
 
12
12
  # Poll until job completes or times out
13
13
  # Returns final job status data
14
+ # Starts polling immediately, then applies exponential backoff
14
15
  def poll(job_id)
15
16
  start_time = Time.now
16
17
  interval = @config.initial_poll_interval
@@ -32,6 +33,7 @@ module Sumologic
32
33
  raise Error, "Search job #{state.downcase}"
33
34
  end
34
35
 
36
+ # Sleep after checking status (not before first check)
35
37
  sleep interval
36
38
  poll_count += 1
37
39
  interval = calculate_next_interval(interval)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sumologic
4
- VERSION = '1.1.1'
4
+ VERSION = '1.2.0'
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.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - patrick204nqh
@@ -102,6 +102,7 @@ files:
102
102
  - lib/sumologic/http/authenticator.rb
103
103
  - lib/sumologic/http/client.rb
104
104
  - lib/sumologic/metadata/collector.rb
105
+ - lib/sumologic/metadata/parallel_fetcher.rb
105
106
  - lib/sumologic/metadata/source.rb
106
107
  - lib/sumologic/search/job.rb
107
108
  - lib/sumologic/search/paginator.rb