logviewer 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/LICENSE +21 -0
- data/README.md +128 -0
- data/bin/logviewer +5 -0
- data/lib/logviewer/version.rb +3 -0
- data/lib/logviewer.rb +282 -0
- data/logviewer.gemspec +26 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5af89372bbff4a9d1a87c53c87be966b685532e5734cddaca3d1f338ecca806d
|
4
|
+
data.tar.gz: 04acdaa2c421345747cdfdaf0445aa139252bac918b74df4d41fa34b0a6451b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d55746ec6dddde8279deb41c50429acb38f6e3c9bd55350388aa8cda585a33c482b64e6d84acd31b45708e9f84aa239b02a6e6494a82d4f65e992b724e5417b
|
7
|
+
data.tar.gz: b895f2d31c99001de43836e96c3dacd092b87c1e16fd761baf93321b2fee5a2300228614281266d07dc90926f79031fa7b2547ad647b7efa645aee01bdea8115
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Justin Bishop
|
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,128 @@
|
|
1
|
+
# LogViewer
|
2
|
+
|
3
|
+
A Ruby gem that converts NDJSON log files into a readable HTML format for easy viewing in your browser.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Converts NDJSON log files to HTML tables
|
8
|
+
- Filters logs by minimum level (trace, debug, info, warning, error, fatal)
|
9
|
+
- Displays key fields: level, text, file, and method
|
10
|
+
- Color-coded log levels for easy identification
|
11
|
+
- Responsive design that works well in any browser
|
12
|
+
- Automatically opens the generated HTML file in your default browser
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Install the gem from RubyGems:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
gem install logviewer
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Basic Usage
|
25
|
+
|
26
|
+
```bash
|
27
|
+
logviewer example.ndjson
|
28
|
+
```
|
29
|
+
|
30
|
+
This will:
|
31
|
+
1. Parse the NDJSON file
|
32
|
+
2. Include all log levels (debug and above)
|
33
|
+
3. Generate an HTML file in `/tmp/`
|
34
|
+
4. Open the HTML file in your default browser
|
35
|
+
|
36
|
+
### Filter by Log Level
|
37
|
+
|
38
|
+
```bash
|
39
|
+
logviewer --level info example.ndjson
|
40
|
+
```
|
41
|
+
|
42
|
+
Only shows log entries with level "info" and above (info, warning, error, fatal).
|
43
|
+
|
44
|
+
### Command Line Options
|
45
|
+
|
46
|
+
- `-l, --level LEVEL`: Set minimum log level (trace, debug, info, warning, error, fatal)
|
47
|
+
- `-v, --version`: Show version
|
48
|
+
- `-h, --help`: Show help message
|
49
|
+
|
50
|
+
### Examples
|
51
|
+
|
52
|
+
```bash
|
53
|
+
# Show all logs
|
54
|
+
logviewer app.ndjson
|
55
|
+
|
56
|
+
# Show only warnings and above
|
57
|
+
logviewer --level warning app.ndjson
|
58
|
+
|
59
|
+
# Show only errors and fatal logs
|
60
|
+
logviewer -l error system.ndjson
|
61
|
+
|
62
|
+
# Show version
|
63
|
+
logviewer --version
|
64
|
+
```
|
65
|
+
|
66
|
+
## Expected Log Format
|
67
|
+
|
68
|
+
The tool expects NDJSON (newline-delimited JSON) files where each line contains a JSON object with these fields:
|
69
|
+
|
70
|
+
- `level`: Log level (trace, debug, info, warning, error, fatal)
|
71
|
+
- `text`: The log message
|
72
|
+
- `file`: Source file name
|
73
|
+
- `method`: Function/method name
|
74
|
+
|
75
|
+
Example log entry:
|
76
|
+
```json
|
77
|
+
{"level":"info","text":"User logged in successfully","file":"auth.rb","method":"login"}
|
78
|
+
```
|
79
|
+
|
80
|
+
## Output
|
81
|
+
|
82
|
+
The generated HTML file will be saved in `/tmp/` with a timestamp and automatically opened in your browser. The HTML includes:
|
83
|
+
|
84
|
+
- A responsive table layout
|
85
|
+
- Color-coded log levels
|
86
|
+
- Sticky header for easy navigation
|
87
|
+
- Hover effects for better readability
|
88
|
+
- File and method names in monospace font
|
89
|
+
|
90
|
+
## Development
|
91
|
+
|
92
|
+
After checking out the repo, run the following commands to set up development:
|
93
|
+
|
94
|
+
```bash
|
95
|
+
# Install dependencies
|
96
|
+
bundle install
|
97
|
+
|
98
|
+
# Build the gem
|
99
|
+
rake build
|
100
|
+
|
101
|
+
# Install locally for testing
|
102
|
+
rake install
|
103
|
+
|
104
|
+
# Clean build artifacts
|
105
|
+
rake clean
|
106
|
+
```
|
107
|
+
|
108
|
+
### Building and Publishing
|
109
|
+
|
110
|
+
```bash
|
111
|
+
# Build the gem
|
112
|
+
rake build
|
113
|
+
|
114
|
+
# Install locally
|
115
|
+
rake install
|
116
|
+
|
117
|
+
# Push to RubyGems (requires authentication)
|
118
|
+
rake push
|
119
|
+
```
|
120
|
+
|
121
|
+
## Requirements
|
122
|
+
|
123
|
+
- Ruby 2.5.0 or higher
|
124
|
+
- macOS (uses `open` command to launch browser)
|
125
|
+
|
126
|
+
## License
|
127
|
+
|
128
|
+
MIT
|
data/bin/logviewer
ADDED
data/lib/logviewer.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'optparse'
|
3
|
+
require 'fileutils'
|
4
|
+
require_relative 'logviewer/version'
|
5
|
+
|
6
|
+
module LogViewer
|
7
|
+
class CLI
|
8
|
+
LOG_LEVELS = {
|
9
|
+
'trace' => 0,
|
10
|
+
'debug' => 1,
|
11
|
+
'info' => 2,
|
12
|
+
'warning' => 3,
|
13
|
+
'error' => 4,
|
14
|
+
'fatal' => 5
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(args = ARGV)
|
18
|
+
@args = args
|
19
|
+
@min_level = 'debug'
|
20
|
+
@input_file = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_options
|
24
|
+
OptionParser.new do |opts|
|
25
|
+
opts.banner = "Usage: logviewer [options] <ndjson_file>"
|
26
|
+
|
27
|
+
opts.on('-l', '--level LEVEL', 'Minimum log level (trace, debug, info, warning, error, fatal)') do |level|
|
28
|
+
level = level.downcase
|
29
|
+
if LOG_LEVELS.key?(level)
|
30
|
+
@min_level = level
|
31
|
+
else
|
32
|
+
puts "Invalid log level: #{level}"
|
33
|
+
puts "Valid levels: #{LOG_LEVELS.keys.join(', ')}"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-v', '--version', 'Show version') do
|
39
|
+
puts "logviewer #{LogViewer::VERSION}"
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-h', '--help', 'Show this help message') do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
end.parse!(@args)
|
48
|
+
|
49
|
+
if @args.empty?
|
50
|
+
puts "Error: Please provide an NDJSON file path"
|
51
|
+
puts "Usage: logviewer [options] <ndjson_file>"
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
|
55
|
+
@input_file = @args[0]
|
56
|
+
|
57
|
+
unless File.exist?(@input_file)
|
58
|
+
puts "Error: File not found: #{@input_file}"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_include_log?(level)
|
64
|
+
return true unless level
|
65
|
+
LOG_LEVELS[level.downcase] >= LOG_LEVELS[@min_level]
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_logs
|
69
|
+
logs = []
|
70
|
+
|
71
|
+
File.foreach(@input_file) do |line|
|
72
|
+
begin
|
73
|
+
log_entry = JSON.parse(line.strip)
|
74
|
+
|
75
|
+
if should_include_log?(log_entry['level'])
|
76
|
+
logs << {
|
77
|
+
level: log_entry['level'] || 'unknown',
|
78
|
+
text: log_entry['text'] || '',
|
79
|
+
file: log_entry['file'] || '',
|
80
|
+
method: log_entry['method'] || ''
|
81
|
+
}
|
82
|
+
end
|
83
|
+
rescue JSON::ParserError => e
|
84
|
+
puts "Warning: Skipping invalid JSON line: #{e.message}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
logs
|
89
|
+
end
|
90
|
+
|
91
|
+
def level_color(level)
|
92
|
+
case level.downcase
|
93
|
+
when 'trace'
|
94
|
+
'#6c757d'
|
95
|
+
when 'debug'
|
96
|
+
'#6c757d'
|
97
|
+
when 'info'
|
98
|
+
'#0d6efd'
|
99
|
+
when 'warning'
|
100
|
+
'#fd7e14'
|
101
|
+
when 'error'
|
102
|
+
'#dc3545'
|
103
|
+
when 'fatal'
|
104
|
+
'#6f42c1'
|
105
|
+
else
|
106
|
+
'#000000'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def generate_html(logs)
|
111
|
+
html = <<~HTML
|
112
|
+
<!DOCTYPE html>
|
113
|
+
<html lang="en">
|
114
|
+
<head>
|
115
|
+
<meta charset="UTF-8">
|
116
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
117
|
+
<title>Log Viewer - #{File.basename(@input_file)}</title>
|
118
|
+
<style>
|
119
|
+
body {
|
120
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
121
|
+
margin: 0;
|
122
|
+
padding: 20px;
|
123
|
+
background-color: #f8f9fa;
|
124
|
+
}
|
125
|
+
.container {
|
126
|
+
max-width: 1400px;
|
127
|
+
margin: 0 auto;
|
128
|
+
background: white;
|
129
|
+
border-radius: 8px;
|
130
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
131
|
+
overflow: hidden;
|
132
|
+
}
|
133
|
+
.header {
|
134
|
+
background: #343a40;
|
135
|
+
color: white;
|
136
|
+
padding: 20px;
|
137
|
+
text-align: center;
|
138
|
+
}
|
139
|
+
.header h1 {
|
140
|
+
margin: 0;
|
141
|
+
font-size: 24px;
|
142
|
+
}
|
143
|
+
.header p {
|
144
|
+
margin: 5px 0 0 0;
|
145
|
+
opacity: 0.8;
|
146
|
+
}
|
147
|
+
.table-container {
|
148
|
+
overflow-x: auto;
|
149
|
+
}
|
150
|
+
table {
|
151
|
+
width: 100%;
|
152
|
+
border-collapse: collapse;
|
153
|
+
font-size: 14px;
|
154
|
+
}
|
155
|
+
th {
|
156
|
+
background: #e9ecef;
|
157
|
+
padding: 12px;
|
158
|
+
text-align: left;
|
159
|
+
font-weight: 600;
|
160
|
+
border-bottom: 2px solid #dee2e6;
|
161
|
+
position: sticky;
|
162
|
+
top: 0;
|
163
|
+
}
|
164
|
+
td {
|
165
|
+
padding: 10px 12px;
|
166
|
+
border-bottom: 1px solid #dee2e6;
|
167
|
+
vertical-align: top;
|
168
|
+
}
|
169
|
+
tr:hover {
|
170
|
+
background-color: #f8f9fa;
|
171
|
+
}
|
172
|
+
.level {
|
173
|
+
font-weight: bold;
|
174
|
+
text-transform: uppercase;
|
175
|
+
font-size: 12px;
|
176
|
+
white-space: nowrap;
|
177
|
+
}
|
178
|
+
.text {
|
179
|
+
max-width: 600px;
|
180
|
+
word-wrap: break-word;
|
181
|
+
white-space: pre-wrap;
|
182
|
+
}
|
183
|
+
.file {
|
184
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
185
|
+
font-size: 12px;
|
186
|
+
color: #666;
|
187
|
+
max-width: 200px;
|
188
|
+
word-wrap: break-word;
|
189
|
+
}
|
190
|
+
.method {
|
191
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
192
|
+
font-size: 12px;
|
193
|
+
color: #333;
|
194
|
+
font-weight: 500;
|
195
|
+
}
|
196
|
+
.empty {
|
197
|
+
color: #999;
|
198
|
+
font-style: italic;
|
199
|
+
}
|
200
|
+
</style>
|
201
|
+
</head>
|
202
|
+
<body>
|
203
|
+
<div class="container">
|
204
|
+
<div class="header">
|
205
|
+
<h1>Log Viewer</h1>
|
206
|
+
<p>#{File.basename(@input_file)} • #{logs.length} entries • Level: #{@min_level.upcase}+</p>
|
207
|
+
</div>
|
208
|
+
<div class="table-container">
|
209
|
+
<table>
|
210
|
+
<thead>
|
211
|
+
<tr>
|
212
|
+
<th style="width: 80px;">Level</th>
|
213
|
+
<th>Text</th>
|
214
|
+
<th style="width: 200px;">File</th>
|
215
|
+
<th style="width: 150px;">Method</th>
|
216
|
+
</tr>
|
217
|
+
</thead>
|
218
|
+
<tbody>
|
219
|
+
HTML
|
220
|
+
|
221
|
+
logs.each do |log|
|
222
|
+
level_style = "color: #{level_color(log[:level])}"
|
223
|
+
text_content = log[:text].empty? ? '<span class="empty">-</span>' : log[:text]
|
224
|
+
file_content = log[:file].empty? ? '<span class="empty">-</span>' : log[:file]
|
225
|
+
method_content = log[:method].empty? ? '<span class="empty">-</span>' : log[:method]
|
226
|
+
|
227
|
+
html += <<~HTML
|
228
|
+
<tr>
|
229
|
+
<td class="level" style="#{level_style}">#{log[:level]}</td>
|
230
|
+
<td class="text">#{text_content}</td>
|
231
|
+
<td class="file">#{file_content}</td>
|
232
|
+
<td class="method">#{method_content}</td>
|
233
|
+
</tr>
|
234
|
+
HTML
|
235
|
+
end
|
236
|
+
|
237
|
+
html += <<~HTML
|
238
|
+
</tbody>
|
239
|
+
</table>
|
240
|
+
</div>
|
241
|
+
</div>
|
242
|
+
</body>
|
243
|
+
</html>
|
244
|
+
HTML
|
245
|
+
|
246
|
+
html
|
247
|
+
end
|
248
|
+
|
249
|
+
def run
|
250
|
+
parse_options
|
251
|
+
|
252
|
+
puts "Parsing log file: #{@input_file}"
|
253
|
+
puts "Minimum log level: #{@min_level}"
|
254
|
+
|
255
|
+
logs = parse_logs
|
256
|
+
puts "Found #{logs.length} log entries matching criteria"
|
257
|
+
|
258
|
+
if logs.empty?
|
259
|
+
puts "No log entries found matching the specified criteria."
|
260
|
+
exit 0
|
261
|
+
end
|
262
|
+
|
263
|
+
html_content = generate_html(logs)
|
264
|
+
|
265
|
+
# Use /tmp directory for HTML files
|
266
|
+
tmp_dir = '/tmp'
|
267
|
+
|
268
|
+
# Generate output filename
|
269
|
+
base_name = File.basename(@input_file, '.*')
|
270
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
271
|
+
output_file = File.join(tmp_dir, "#{base_name}_#{timestamp}.html")
|
272
|
+
|
273
|
+
# Write HTML file
|
274
|
+
File.write(output_file, html_content)
|
275
|
+
puts "HTML file created: #{output_file}"
|
276
|
+
|
277
|
+
# Open in browser
|
278
|
+
system('open', output_file)
|
279
|
+
puts "Opening in browser..."
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
data/logviewer.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'lib/logviewer/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "logviewer"
|
5
|
+
spec.version = LogViewer::VERSION
|
6
|
+
spec.authors = ["Justin Bishop"]
|
7
|
+
spec.email = ["jubishop@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Convert NDJSON log files to HTML for easy viewing"
|
10
|
+
spec.description = "A command-line tool that converts NDJSON log files into readable HTML format with filtering capabilities by log level"
|
11
|
+
spec.homepage = "https://github.com/jubishop/logviewer"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.required_ruby_version = ">= 2.5.0"
|
15
|
+
|
16
|
+
spec.files = Dir["lib/**/*", "bin/*", "README.md", "LICENSE", "*.gemspec"]
|
17
|
+
spec.bindir = "bin"
|
18
|
+
spec.executables = ["logviewer"]
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# No runtime dependencies - using only Ruby stdlib (json, optparse, fileutils)
|
22
|
+
|
23
|
+
# Development dependencies
|
24
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
25
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logviewer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Bishop
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-06-04 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: bundler
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.0'
|
19
|
+
type: :development
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rake
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '13.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '13.0'
|
40
|
+
description: A command-line tool that converts NDJSON log files into readable HTML
|
41
|
+
format with filtering capabilities by log level
|
42
|
+
email:
|
43
|
+
- jubishop@gmail.com
|
44
|
+
executables:
|
45
|
+
- logviewer
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- bin/logviewer
|
52
|
+
- lib/logviewer.rb
|
53
|
+
- lib/logviewer/version.rb
|
54
|
+
- logviewer.gemspec
|
55
|
+
homepage: https://github.com/jubishop/logviewer
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata: {}
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 2.5.0
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.6.6
|
74
|
+
specification_version: 4
|
75
|
+
summary: Convert NDJSON log files to HTML for easy viewing
|
76
|
+
test_files: []
|