sumologic-query 1.0.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/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +368 -0
- data/bin/sumo-query +121 -0
- data/lib/sumologic/client.rb +203 -0
- data/lib/sumologic/version.rb +5 -0
- data/lib/sumologic.rb +10 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e4adfba9fe6b1417e51cfcd3e1b5697a8ed208687ae17e8a114716c71711cdc0
|
|
4
|
+
data.tar.gz: 331efc0e06d844ea82fada1bf97d5a4183189b9330437e5aab03a8d145a30e57
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bacec0ea516f5d0d8d5ba06ffe70887aefb9e4dc8df6fda259d21817d37ecbece1ed57f0806e3c61f1e3a5bc934c1adfb779048e1b57c3ba1ac121b6bb7a2392
|
|
7
|
+
data.tar.gz: 0f6af034374a1b3f417437a8cb691246393161be4f1b194441afc61128c1bf75c239f537a90aab883ed724e84add224c393437a23ef0d3cd23e2596f01f2c665
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-11-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of sumologic-query CLI tool
|
|
12
|
+
- Core `Sumologic::Client` class for Search Job API
|
|
13
|
+
- Command-line interface with query, time range, and output options
|
|
14
|
+
- Automatic job polling with 20-second intervals
|
|
15
|
+
- Automatic pagination for large result sets (10K messages per request)
|
|
16
|
+
- Support for multiple Sumo Logic deployments (us1, us2, eu, au)
|
|
17
|
+
- Environment variable configuration (SUMO_ACCESS_ID, SUMO_ACCESS_KEY, SUMO_DEPLOYMENT)
|
|
18
|
+
- Debug mode for troubleshooting (SUMO_DEBUG)
|
|
19
|
+
- JSON output format with metadata
|
|
20
|
+
- Zero external dependencies (stdlib only)
|
|
21
|
+
- Comprehensive error handling and user-friendly messages
|
|
22
|
+
- MIT license
|
|
23
|
+
- Complete documentation and examples
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
- Query historical logs via Search Job API
|
|
27
|
+
- Time range filtering (ISO 8601 format)
|
|
28
|
+
- Message limiting
|
|
29
|
+
- Timezone support
|
|
30
|
+
- File or stdout output
|
|
31
|
+
- 5-minute default timeout
|
|
32
|
+
- Graceful cleanup of search jobs
|
|
33
|
+
|
|
34
|
+
[1.0.0]: https://github.com/patrick204nqh/sumologic-query/releases/tag/v1.0.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 patrick204nqh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Sumo Logic Query Tool
|
|
2
|
+
|
|
3
|
+
> A lightweight Ruby CLI for querying Sumo Logic logs quickly. Simple, fast, read-only access to your logs.
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/rb/sumologic-query)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Why This Tool?
|
|
9
|
+
|
|
10
|
+
- **Zero dependencies**: Uses only Ruby stdlib - no external gems required
|
|
11
|
+
- **Fast queries**: Efficient polling and automatic pagination
|
|
12
|
+
- **Simple interface**: Just query, get results, done
|
|
13
|
+
- **Read-only**: No write operations, perfect for safe log access
|
|
14
|
+
- **Lightweight**: ~300 lines of code total
|
|
15
|
+
|
|
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
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Via RubyGems
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
gem install sumologic-query
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Via Homebrew
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
brew tap patrick204nqh/tap
|
|
30
|
+
brew install sumologic-query
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### From Source
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/patrick204nqh/sumologic-query.git
|
|
37
|
+
cd sumologic-query
|
|
38
|
+
bundle install
|
|
39
|
+
bundle exec rake install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### 1. Set Up Credentials
|
|
45
|
+
|
|
46
|
+
Export your Sumo Logic API credentials:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export SUMO_ACCESS_ID="your_access_id"
|
|
50
|
+
export SUMO_ACCESS_KEY="your_access_key"
|
|
51
|
+
export SUMO_DEPLOYMENT="us2" # Optional: us1, us2 (default), eu, au
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Getting credentials:**
|
|
55
|
+
1. Log in to Sumo Logic
|
|
56
|
+
2. Go to **Administration → Security → Access Keys**
|
|
57
|
+
3. Create a new access key or use existing
|
|
58
|
+
4. Copy the Access ID and Access Key
|
|
59
|
+
|
|
60
|
+
### 2. Run Your First Query
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
sumo-query --query 'error' \
|
|
64
|
+
--from '2025-11-13T14:00:00' \
|
|
65
|
+
--to '2025-11-13T15:00:00' \
|
|
66
|
+
--limit 10
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### Basic Command Structure
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
sumo-query --query "YOUR_QUERY" \
|
|
75
|
+
--from "START_TIME" \
|
|
76
|
+
--to "END_TIME" \
|
|
77
|
+
[--output FILE] \
|
|
78
|
+
[--limit N] \
|
|
79
|
+
[--time-zone TZ]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Required Options
|
|
83
|
+
|
|
84
|
+
- `-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
|
|
89
|
+
|
|
90
|
+
- `-z, --time-zone TZ` - Time zone (default: UTC)
|
|
91
|
+
- `-l, --limit N` - Limit number of messages
|
|
92
|
+
- `-o, --output FILE` - Save results to file (default: stdout)
|
|
93
|
+
- `-d, --debug` - Enable debug output
|
|
94
|
+
- `-h, --help` - Show help message
|
|
95
|
+
- `-v, --version` - Show version
|
|
96
|
+
|
|
97
|
+
## Common Query Patterns
|
|
98
|
+
|
|
99
|
+
### Error Analysis
|
|
100
|
+
|
|
101
|
+
```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'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Text Search
|
|
115
|
+
|
|
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'
|
|
121
|
+
|
|
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'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Filtering by Source
|
|
129
|
+
|
|
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'
|
|
135
|
+
|
|
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
|
+
```
|
|
141
|
+
|
|
142
|
+
### Aggregation Queries
|
|
143
|
+
|
|
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'
|
|
149
|
+
|
|
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'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Parsing and Field Extraction
|
|
157
|
+
|
|
158
|
+
```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'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Output Format
|
|
166
|
+
|
|
167
|
+
Results are returned as JSON:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"query": "error",
|
|
172
|
+
"from": "2025-11-13T14:00:00",
|
|
173
|
+
"to": "2025-11-13T15:00:00",
|
|
174
|
+
"time_zone": "UTC",
|
|
175
|
+
"message_count": 42,
|
|
176
|
+
"messages": [
|
|
177
|
+
{
|
|
178
|
+
"map": {
|
|
179
|
+
"_messagetime": "1731506400123",
|
|
180
|
+
"_sourceCategory": "prod/api",
|
|
181
|
+
"_sourceName": "api-server-01",
|
|
182
|
+
"message": "Error processing request: timeout"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
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
|
+
## Performance
|
|
207
|
+
|
|
208
|
+
Query execution time depends on data volume:
|
|
209
|
+
|
|
210
|
+
- **Small queries** (<10K messages): ~30-60 seconds
|
|
211
|
+
- **Medium queries** (10K-100K): ~1-2 minutes
|
|
212
|
+
- **Large queries** (100K+): ~2-5 minutes
|
|
213
|
+
|
|
214
|
+
Default timeout: 5 minutes
|
|
215
|
+
|
|
216
|
+
To improve performance:
|
|
217
|
+
- Narrow your time range
|
|
218
|
+
- Add specific `_sourceCategory` filters
|
|
219
|
+
- Use `--limit` to cap results
|
|
220
|
+
- Use aggregation queries instead of fetching raw messages
|
|
221
|
+
|
|
222
|
+
## Troubleshooting
|
|
223
|
+
|
|
224
|
+
### Authentication Error
|
|
225
|
+
|
|
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.
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
### Running Tests
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
bundle install
|
|
277
|
+
bundle exec rake spec
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Code Quality
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
bundle exec rubocop
|
|
284
|
+
bundle exec rubocop -A # Auto-fix issues
|
|
285
|
+
```
|
|
286
|
+
|
|
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" \
|
|
297
|
+
--from "2025-11-13T14:00:00" \
|
|
298
|
+
--to "2025-11-13T15:00:00"
|
|
299
|
+
```
|
|
300
|
+
|
|
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
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
343
|
+
|
|
344
|
+
1. Fork the repository
|
|
345
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
346
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
347
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
348
|
+
5. Open a Pull Request
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
353
|
+
|
|
354
|
+
## Resources
|
|
355
|
+
|
|
356
|
+
- **Sumo Logic API Docs**: https://help.sumologic.com/docs/api/search-job/
|
|
357
|
+
- **Query Language**: https://help.sumologic.com/docs/search/
|
|
358
|
+
- **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
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
**Note**: This tool provides read-only access to Sumo Logic logs. It does not modify any data or configuration.
|
data/bin/sumo-query
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Simple CLI wrapper for querying Sumo Logic logs
|
|
5
|
+
# Usage: sumo-query --query "error" --from "2025-11-13T14:00:00" --to "2025-11-13T15:00:00"
|
|
6
|
+
|
|
7
|
+
require_relative '../lib/sumologic'
|
|
8
|
+
require 'optparse'
|
|
9
|
+
require 'json'
|
|
10
|
+
|
|
11
|
+
options = {
|
|
12
|
+
time_zone: 'UTC'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
OptionParser.new do |opts|
|
|
16
|
+
opts.banner = <<~BANNER
|
|
17
|
+
Sumo Logic Query Tool - Fast, read-only log access
|
|
18
|
+
|
|
19
|
+
Usage: sumo-query [options]
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
# Error timeline with 5-minute buckets
|
|
23
|
+
sumo-query --query 'error | timeslice 5m | count' \\
|
|
24
|
+
--from '2025-11-13T14:00:00' --to '2025-11-13T15:00:00'
|
|
25
|
+
|
|
26
|
+
# Search for specific text
|
|
27
|
+
sumo-query --query '"connection timeout"' \\
|
|
28
|
+
--from '2025-11-13T14:00:00' --to '2025-11-13T15:00:00' \\
|
|
29
|
+
--limit 100
|
|
30
|
+
|
|
31
|
+
# Filter by source category
|
|
32
|
+
sumo-query --query '_sourceCategory=prod/api error' \\
|
|
33
|
+
--from '2025-11-13T14:00:00' --to '2025-11-13T15:00:00' \\
|
|
34
|
+
--output results.json
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
BANNER
|
|
38
|
+
|
|
39
|
+
opts.on('-q', '--query QUERY', 'Search query (required)') { |v| options[:query] = v }
|
|
40
|
+
opts.on('-f', '--from TIME', 'Start time in ISO 8601 format (required)') { |v| options[:from] = v }
|
|
41
|
+
opts.on('-t', '--to TIME', 'End time in ISO 8601 format (required)') { |v| options[:to] = v }
|
|
42
|
+
opts.on('-z', '--time-zone TZ', 'Time zone (default: UTC)') { |v| options[:time_zone] = v }
|
|
43
|
+
opts.on('-l', '--limit N', Integer, 'Limit number of messages') { |v| options[:limit] = v }
|
|
44
|
+
opts.on('-o', '--output FILE', 'Output file (default: stdout)') { |v| options[:output] = v }
|
|
45
|
+
opts.on('-d', '--debug', 'Enable debug output') { $DEBUG = true }
|
|
46
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
47
|
+
puts opts
|
|
48
|
+
exit
|
|
49
|
+
end
|
|
50
|
+
opts.on('-v', '--version', 'Show version') do
|
|
51
|
+
puts "sumologic-query v#{Sumologic::VERSION}"
|
|
52
|
+
exit
|
|
53
|
+
end
|
|
54
|
+
end.parse!
|
|
55
|
+
|
|
56
|
+
# Validate required options
|
|
57
|
+
unless options[:query] && options[:from] && options[:to]
|
|
58
|
+
warn 'Error: --query, --from, and --to are required'
|
|
59
|
+
warn 'Run with --help for usage information'
|
|
60
|
+
exit 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
# Create client
|
|
65
|
+
client = Sumologic::Client.new
|
|
66
|
+
|
|
67
|
+
warn "Querying Sumo Logic: #{options[:from]} to #{options[:to]}"
|
|
68
|
+
warn "Query: #{options[:query]}"
|
|
69
|
+
warn 'This may take 1-3 minutes depending on data volume...'
|
|
70
|
+
$stderr.puts
|
|
71
|
+
|
|
72
|
+
# Execute search
|
|
73
|
+
results = client.search(
|
|
74
|
+
query: options[:query],
|
|
75
|
+
from_time: options[:from],
|
|
76
|
+
to_time: options[:to],
|
|
77
|
+
time_zone: options[:time_zone],
|
|
78
|
+
limit: options[:limit]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Format output
|
|
82
|
+
output = {
|
|
83
|
+
query: options[:query],
|
|
84
|
+
from: options[:from],
|
|
85
|
+
to: options[:to],
|
|
86
|
+
time_zone: options[:time_zone],
|
|
87
|
+
message_count: results.size,
|
|
88
|
+
messages: results
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
json_output = JSON.pretty_generate(output)
|
|
92
|
+
|
|
93
|
+
# Write to file or stdout
|
|
94
|
+
if options[:output]
|
|
95
|
+
File.write(options[:output], json_output)
|
|
96
|
+
warn "\nResults saved to: #{options[:output]}"
|
|
97
|
+
warn "Message count: #{results.size}"
|
|
98
|
+
else
|
|
99
|
+
puts json_output
|
|
100
|
+
end
|
|
101
|
+
rescue Sumologic::AuthenticationError => e
|
|
102
|
+
warn "\nAuthentication Error: #{e.message}"
|
|
103
|
+
warn "\nPlease set environment variables:"
|
|
104
|
+
warn " export SUMO_ACCESS_ID='your_access_id'"
|
|
105
|
+
warn " export SUMO_ACCESS_KEY='your_access_key'"
|
|
106
|
+
warn " export SUMO_DEPLOYMENT='us2' # Optional, defaults to us2"
|
|
107
|
+
exit 1
|
|
108
|
+
rescue Sumologic::TimeoutError => e
|
|
109
|
+
warn "\nTimeout Error: #{e.message}"
|
|
110
|
+
warn "\nTry:"
|
|
111
|
+
warn ' - Reducing time range'
|
|
112
|
+
warn ' - Using more specific filters in your query'
|
|
113
|
+
warn ' - Adding --limit to cap results'
|
|
114
|
+
exit 1
|
|
115
|
+
rescue Sumologic::Error => e
|
|
116
|
+
warn "\nError: #{e.message}"
|
|
117
|
+
exit 1
|
|
118
|
+
rescue Interrupt
|
|
119
|
+
warn "\nInterrupted by user"
|
|
120
|
+
exit 130
|
|
121
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'base64'
|
|
7
|
+
|
|
8
|
+
module Sumologic
|
|
9
|
+
# Lightweight Sumo Logic Search Job API client
|
|
10
|
+
# Handles historical log queries with automatic polling and pagination
|
|
11
|
+
class Client
|
|
12
|
+
API_VERSION = 'v1'
|
|
13
|
+
DEFAULT_POLL_INTERVAL = 20 # seconds
|
|
14
|
+
DEFAULT_TIMEOUT = 300 # seconds (5 minutes)
|
|
15
|
+
MAX_MESSAGES_PER_REQUEST = 10_000
|
|
16
|
+
|
|
17
|
+
attr_reader :access_id, :access_key, :deployment, :base_url
|
|
18
|
+
|
|
19
|
+
def initialize(access_id: nil, access_key: nil, deployment: nil)
|
|
20
|
+
@access_id = access_id || ENV.fetch('SUMO_ACCESS_ID', nil)
|
|
21
|
+
@access_key = access_key || ENV.fetch('SUMO_ACCESS_KEY', nil)
|
|
22
|
+
@deployment = deployment || ENV['SUMO_DEPLOYMENT'] || 'us2'
|
|
23
|
+
@base_url = deployment_url(@deployment)
|
|
24
|
+
|
|
25
|
+
validate_credentials!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Main search method
|
|
29
|
+
# Returns array of messages/records as JSON
|
|
30
|
+
def search(query:, from_time:, to_time:, time_zone: 'UTC', limit: nil)
|
|
31
|
+
job_id = create_job(query, from_time, to_time, time_zone)
|
|
32
|
+
poll_until_complete(job_id)
|
|
33
|
+
messages = fetch_all_messages(job_id, limit)
|
|
34
|
+
delete_job(job_id)
|
|
35
|
+
messages
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
delete_job(job_id) if job_id
|
|
38
|
+
raise Error, "Search failed: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def validate_credentials!
|
|
44
|
+
raise AuthenticationError, 'SUMO_ACCESS_ID not set' unless @access_id
|
|
45
|
+
raise AuthenticationError, 'SUMO_ACCESS_KEY not set' unless @access_key
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def deployment_url(deployment)
|
|
49
|
+
case deployment
|
|
50
|
+
when /^http/
|
|
51
|
+
deployment # Full URL provided
|
|
52
|
+
when 'us1'
|
|
53
|
+
'https://api.sumologic.com/api/v1'
|
|
54
|
+
when 'us2'
|
|
55
|
+
'https://api.us2.sumologic.com/api/v1'
|
|
56
|
+
when 'eu'
|
|
57
|
+
'https://api.eu.sumologic.com/api/v1'
|
|
58
|
+
when 'au'
|
|
59
|
+
'https://api.au.sumologic.com/api/v1'
|
|
60
|
+
else
|
|
61
|
+
"https://api.#{deployment}.sumologic.com/api/v1"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def auth_header
|
|
66
|
+
encoded = Base64.strict_encode64("#{@access_id}:#{@access_key}")
|
|
67
|
+
"Basic #{encoded}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def create_job(query, from_time, to_time, time_zone)
|
|
71
|
+
uri = URI("#{@base_url}/search/jobs")
|
|
72
|
+
request = Net::HTTP::Post.new(uri)
|
|
73
|
+
request['Authorization'] = auth_header
|
|
74
|
+
request['Content-Type'] = 'application/json'
|
|
75
|
+
request['Accept'] = 'application/json'
|
|
76
|
+
|
|
77
|
+
body = {
|
|
78
|
+
query: query,
|
|
79
|
+
from: from_time,
|
|
80
|
+
to: to_time,
|
|
81
|
+
timeZone: time_zone
|
|
82
|
+
}
|
|
83
|
+
request.body = body.to_json
|
|
84
|
+
|
|
85
|
+
response = http_request(uri, request)
|
|
86
|
+
data = JSON.parse(response.body)
|
|
87
|
+
|
|
88
|
+
raise Error, "Failed to create job: #{data['message']}" unless data['id']
|
|
89
|
+
|
|
90
|
+
log_info "Created search job: #{data['id']}"
|
|
91
|
+
data['id']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def poll_until_complete(job_id, timeout: DEFAULT_TIMEOUT)
|
|
95
|
+
uri = URI("#{@base_url}/search/jobs/#{job_id}")
|
|
96
|
+
start_time = Time.now
|
|
97
|
+
interval = DEFAULT_POLL_INTERVAL
|
|
98
|
+
|
|
99
|
+
loop do
|
|
100
|
+
raise TimeoutError, "Search job timed out after #{timeout} seconds" if Time.now - start_time > timeout
|
|
101
|
+
|
|
102
|
+
request = Net::HTTP::Get.new(uri)
|
|
103
|
+
request['Authorization'] = auth_header
|
|
104
|
+
request['Accept'] = 'application/json'
|
|
105
|
+
|
|
106
|
+
response = http_request(uri, request)
|
|
107
|
+
data = JSON.parse(response.body)
|
|
108
|
+
|
|
109
|
+
state = data['state']
|
|
110
|
+
log_info "Job state: #{state} (#{data['messageCount']} messages, #{data['recordCount']} records)"
|
|
111
|
+
|
|
112
|
+
case state
|
|
113
|
+
when 'DONE GATHERING RESULTS'
|
|
114
|
+
return data
|
|
115
|
+
when 'CANCELLED', 'FORCE PAUSED'
|
|
116
|
+
raise Error, "Search job #{state.downcase}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
sleep interval
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def fetch_all_messages(job_id, limit = nil)
|
|
124
|
+
messages = []
|
|
125
|
+
offset = 0
|
|
126
|
+
total_fetched = 0
|
|
127
|
+
|
|
128
|
+
loop do
|
|
129
|
+
batch_limit = if limit
|
|
130
|
+
[MAX_MESSAGES_PER_REQUEST, limit - total_fetched].min
|
|
131
|
+
else
|
|
132
|
+
MAX_MESSAGES_PER_REQUEST
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
break if batch_limit <= 0
|
|
136
|
+
|
|
137
|
+
uri = URI("#{@base_url}/search/jobs/#{job_id}/messages")
|
|
138
|
+
uri.query = URI.encode_www_form(offset: offset, limit: batch_limit)
|
|
139
|
+
|
|
140
|
+
request = Net::HTTP::Get.new(uri)
|
|
141
|
+
request['Authorization'] = auth_header
|
|
142
|
+
request['Accept'] = 'application/json'
|
|
143
|
+
|
|
144
|
+
response = http_request(uri, request)
|
|
145
|
+
data = JSON.parse(response.body)
|
|
146
|
+
|
|
147
|
+
batch = data['messages'] || []
|
|
148
|
+
messages.concat(batch)
|
|
149
|
+
total_fetched += batch.size
|
|
150
|
+
|
|
151
|
+
log_info "Fetched #{batch.size} messages (total: #{total_fetched})"
|
|
152
|
+
|
|
153
|
+
break if batch.size < batch_limit # No more messages
|
|
154
|
+
break if limit && total_fetched >= limit
|
|
155
|
+
|
|
156
|
+
offset += batch.size
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
messages
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def delete_job(job_id)
|
|
163
|
+
return unless job_id
|
|
164
|
+
|
|
165
|
+
uri = URI("#{@base_url}/search/jobs/#{job_id}")
|
|
166
|
+
request = Net::HTTP::Delete.new(uri)
|
|
167
|
+
request['Authorization'] = auth_header
|
|
168
|
+
|
|
169
|
+
http_request(uri, request)
|
|
170
|
+
log_info "Deleted search job: #{job_id}"
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
log_error "Failed to delete job #{job_id}: #{e.message}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def http_request(uri, request)
|
|
176
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
177
|
+
http.use_ssl = true
|
|
178
|
+
http.read_timeout = 60
|
|
179
|
+
http.open_timeout = 10
|
|
180
|
+
|
|
181
|
+
response = http.request(request)
|
|
182
|
+
|
|
183
|
+
case response.code.to_i
|
|
184
|
+
when 200..299
|
|
185
|
+
response
|
|
186
|
+
when 401, 403
|
|
187
|
+
raise AuthenticationError, "Authentication failed: #{response.body}"
|
|
188
|
+
when 429
|
|
189
|
+
raise Error, "Rate limit exceeded: #{response.body}"
|
|
190
|
+
else
|
|
191
|
+
raise Error, "HTTP #{response.code}: #{response.body}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def log_info(message)
|
|
196
|
+
warn "[Sumologic::Client] #{message}" if ENV['SUMO_DEBUG'] || $DEBUG
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def log_error(message)
|
|
200
|
+
warn "[Sumologic::Client ERROR] #{message}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
data/lib/sumologic.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sumologic-query
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- patrick204nqh
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
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
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.21'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.21'
|
|
69
|
+
description: |
|
|
70
|
+
Simple, fast, read-only access to Sumo Logic logs via the Search Job API.
|
|
71
|
+
No complex features, just quick log queries with automatic pagination and polling.
|
|
72
|
+
Perfect for DevOps, incident investigation, and log analysis workflows.
|
|
73
|
+
email:
|
|
74
|
+
- patrick204nqh@gmail.com
|
|
75
|
+
executables:
|
|
76
|
+
- sumo-query
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- CHANGELOG.md
|
|
81
|
+
- LICENSE
|
|
82
|
+
- README.md
|
|
83
|
+
- bin/sumo-query
|
|
84
|
+
- lib/sumologic.rb
|
|
85
|
+
- lib/sumologic/client.rb
|
|
86
|
+
- lib/sumologic/version.rb
|
|
87
|
+
homepage: https://github.com/patrick204nqh/sumologic-query
|
|
88
|
+
licenses:
|
|
89
|
+
- MIT
|
|
90
|
+
metadata:
|
|
91
|
+
homepage_uri: https://github.com/patrick204nqh/sumologic-query
|
|
92
|
+
source_code_uri: https://github.com/patrick204nqh/sumologic-query
|
|
93
|
+
bug_tracker_uri: https://github.com/patrick204nqh/sumologic-query/issues
|
|
94
|
+
changelog_uri: https://github.com/patrick204nqh/sumologic-query/blob/main/CHANGELOG.md
|
|
95
|
+
rubygems_mfa_required: 'true'
|
|
96
|
+
post_install_message:
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: 2.7.0
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 3.5.22
|
|
112
|
+
signing_key:
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: A lightweight Ruby CLI for querying Sumo Logic logs quickly
|
|
115
|
+
test_files: []
|