logviewer 1.0.0 → 1.5.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 +4 -4
- data/README.md +38 -10
- data/lib/logviewer/version.rb +1 -1
- data/lib/logviewer.rb +78 -17
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6474de992dc2dea2da7f2dc840c7b8a40ebd00b7bb95d41b5f3c8daf735c825
|
4
|
+
data.tar.gz: b77161d3a5ff3ad409d2103fb9244e3e80e04d75f0acea8da23de4d4ca3f8bf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 543273a8c965e6263d458d9fb728b8f76aacb9d1dc9d70907ae837d7555c7a428aa076d9e35402e25057696edf6f8d56e3947745ed3549bfdd283d8c169fe35b
|
7
|
+
data.tar.gz: 15c08fa5c88dd659e0963efe156a8cc02894cc18b07023851391d880f3935e4211ea0d490917f6b38980bf89d853e3dea974f11b71477b5fa4d7a2bf3d614ec6
|
data/README.md
CHANGED
@@ -6,8 +6,11 @@ A Ruby gem that converts NDJSON log files into a readable HTML format for easy v
|
|
6
6
|
|
7
7
|
- Converts NDJSON log files to HTML tables
|
8
8
|
- Filters logs by minimum level (trace, debug, info, warning, error, fatal)
|
9
|
-
- Displays key fields: level, text, file, and method
|
9
|
+
- Displays key fields: timestamp, level, tag, text, file, line, and method
|
10
|
+
- Human-readable timestamp formatting (MM/DD HH:MM:SS)
|
11
|
+
- Simplified file paths (shows only filename, not full path)
|
10
12
|
- Color-coded log levels for easy identification
|
13
|
+
- Large, readable fonts throughout the interface (18px base size)
|
11
14
|
- Responsive design that works well in any browser
|
12
15
|
- Automatically opens the generated HTML file in your default browser
|
13
16
|
|
@@ -24,11 +27,15 @@ gem install logviewer
|
|
24
27
|
### Basic Usage
|
25
28
|
|
26
29
|
```bash
|
30
|
+
# With a specific file
|
27
31
|
logviewer example.ndjson
|
32
|
+
|
33
|
+
# Without specifying a file (auto-detects most recent .ndjson file)
|
34
|
+
logviewer
|
28
35
|
```
|
29
36
|
|
30
37
|
This will:
|
31
|
-
1. Parse the NDJSON file
|
38
|
+
1. Parse the NDJSON file (or auto-detect the most recent .ndjson file in current directory)
|
32
39
|
2. Include all log levels (debug and above)
|
33
40
|
3. Generate an HTML file in `/tmp/`
|
34
41
|
4. Open the HTML file in your default browser
|
@@ -41,6 +48,16 @@ logviewer --level info example.ndjson
|
|
41
48
|
|
42
49
|
Only shows log entries with level "info" and above (info, warning, error, fatal).
|
43
50
|
|
51
|
+
### Auto-Detection of Log Files
|
52
|
+
|
53
|
+
When no file is specified, LogViewer automatically searches the current directory for `.ndjson` files and uses the one with the most recent modification date:
|
54
|
+
|
55
|
+
```bash
|
56
|
+
logviewer --level info
|
57
|
+
```
|
58
|
+
|
59
|
+
This will find the most recent `.ndjson` file in the current directory and apply the specified log level filter.
|
60
|
+
|
44
61
|
### Command Line Options
|
45
62
|
|
46
63
|
- `-l, --level LEVEL`: Set minimum log level (trace, debug, info, warning, error, fatal)
|
@@ -50,13 +67,16 @@ Only shows log entries with level "info" and above (info, warning, error, fatal)
|
|
50
67
|
### Examples
|
51
68
|
|
52
69
|
```bash
|
53
|
-
# Show all logs
|
70
|
+
# Show all logs from a specific file
|
54
71
|
logviewer app.ndjson
|
55
72
|
|
56
|
-
#
|
57
|
-
logviewer
|
73
|
+
# Auto-detect most recent .ndjson file and show all logs
|
74
|
+
logviewer
|
75
|
+
|
76
|
+
# Auto-detect most recent .ndjson file and show only warnings and above
|
77
|
+
logviewer --level warning
|
58
78
|
|
59
|
-
# Show only errors and fatal logs
|
79
|
+
# Show only errors and fatal logs from specific file
|
60
80
|
logviewer -l error system.ndjson
|
61
81
|
|
62
82
|
# Show version
|
@@ -67,25 +87,33 @@ logviewer --version
|
|
67
87
|
|
68
88
|
The tool expects NDJSON (newline-delimited JSON) files where each line contains a JSON object with these fields:
|
69
89
|
|
90
|
+
- `timestamp`: ISO 8601 timestamp (e.g., "2025-06-02T18:22:48.855-07:00")
|
70
91
|
- `level`: Log level (trace, debug, info, warning, error, fatal)
|
92
|
+
- `tag`: Category or module tag (e.g., "Play/manager")
|
71
93
|
- `text`: The log message
|
72
|
-
- `file`: Source file
|
94
|
+
- `file`: Source file path (displayed as filename only)
|
95
|
+
- `line`: Line number in the source file
|
73
96
|
- `method`: Function/method name
|
74
97
|
|
75
98
|
Example log entry:
|
76
99
|
```json
|
77
|
-
{"level":"info","text":"User logged in successfully","file":"auth.rb","method":"login"}
|
100
|
+
{"timestamp":"2025-06-02T18:22:48.855-07:00","level":"info","tag":"Auth/manager","text":"User logged in successfully","file":"auth.rb","line":42,"method":"login"}
|
78
101
|
```
|
79
102
|
|
80
103
|
## Output
|
81
104
|
|
82
105
|
The generated HTML file will be saved in `/tmp/` with a timestamp and automatically opened in your browser. The HTML includes:
|
83
106
|
|
84
|
-
- A responsive table layout
|
107
|
+
- A wide, responsive table layout (1800px max width) with timestamp, level, tag, text, file, line, and method columns
|
108
|
+
- Human-readable timestamps (MM/DD HH:MM:SS format)
|
85
109
|
- Color-coded log levels
|
86
110
|
- Sticky header for easy navigation
|
87
111
|
- Hover effects for better readability
|
88
|
-
-
|
112
|
+
- Large fonts (18px base size) for excellent readability
|
113
|
+
- Simplified file display (filename only, not full paths)
|
114
|
+
- Optimized column widths with expanded text area for log messages
|
115
|
+
- Timestamp, file, line, and method names in monospace font
|
116
|
+
- Color-coded tags for easy categorization
|
89
117
|
|
90
118
|
## Development
|
91
119
|
|
data/lib/logviewer/version.rb
CHANGED
data/lib/logviewer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'optparse'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'time'
|
4
5
|
require_relative 'logviewer/version'
|
5
6
|
|
6
7
|
module LogViewer
|
@@ -22,7 +23,7 @@ module LogViewer
|
|
22
23
|
|
23
24
|
def parse_options
|
24
25
|
OptionParser.new do |opts|
|
25
|
-
opts.banner = "Usage: logviewer [options]
|
26
|
+
opts.banner = "Usage: logviewer [options] [ndjson_file]"
|
26
27
|
|
27
28
|
opts.on('-l', '--level LEVEL', 'Minimum log level (trace, debug, info, warning, error, fatal)') do |level|
|
28
29
|
level = level.downcase
|
@@ -47,19 +48,31 @@ module LogViewer
|
|
47
48
|
end.parse!(@args)
|
48
49
|
|
49
50
|
if @args.empty?
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
@input_file = find_most_recent_ndjson_file
|
52
|
+
if @input_file.nil?
|
53
|
+
puts "Error: No .ndjson files found in current directory"
|
54
|
+
puts "Usage: logviewer [options] [ndjson_file]"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
puts "No file specified, using most recent .ndjson file: #{@input_file}"
|
58
|
+
else
|
59
|
+
@input_file = @args[0]
|
53
60
|
end
|
54
61
|
|
55
|
-
@input_file = @args[0]
|
56
|
-
|
57
62
|
unless File.exist?(@input_file)
|
58
63
|
puts "Error: File not found: #{@input_file}"
|
59
64
|
exit 1
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
68
|
+
def find_most_recent_ndjson_file
|
69
|
+
ndjson_files = Dir.glob('*.ndjson')
|
70
|
+
return nil if ndjson_files.empty?
|
71
|
+
|
72
|
+
# Sort by modification time (most recent first) and return the first one
|
73
|
+
ndjson_files.max_by { |file| File.mtime(file) }
|
74
|
+
end
|
75
|
+
|
63
76
|
def should_include_log?(level)
|
64
77
|
return true unless level
|
65
78
|
LOG_LEVELS[level.downcase] >= LOG_LEVELS[@min_level]
|
@@ -74,9 +87,12 @@ module LogViewer
|
|
74
87
|
|
75
88
|
if should_include_log?(log_entry['level'])
|
76
89
|
logs << {
|
90
|
+
timestamp: log_entry['timestamp'] || '',
|
77
91
|
level: log_entry['level'] || 'unknown',
|
92
|
+
tag: log_entry['tag'] || '',
|
78
93
|
text: log_entry['text'] || '',
|
79
94
|
file: log_entry['file'] || '',
|
95
|
+
line: log_entry['line'],
|
80
96
|
method: log_entry['method'] || ''
|
81
97
|
}
|
82
98
|
end
|
@@ -107,6 +123,22 @@ module LogViewer
|
|
107
123
|
end
|
108
124
|
end
|
109
125
|
|
126
|
+
def format_timestamp(timestamp_str)
|
127
|
+
return '' if timestamp_str.nil? || timestamp_str.empty?
|
128
|
+
|
129
|
+
begin
|
130
|
+
time = Time.parse(timestamp_str)
|
131
|
+
time.strftime('%m/%d %H:%M:%S')
|
132
|
+
rescue => e
|
133
|
+
timestamp_str # fallback to original if parsing fails
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def extract_filename(file_path)
|
138
|
+
return '' if file_path.nil? || file_path.empty?
|
139
|
+
File.basename(file_path)
|
140
|
+
end
|
141
|
+
|
110
142
|
def generate_html(logs)
|
111
143
|
html = <<~HTML
|
112
144
|
<!DOCTYPE html>
|
@@ -123,7 +155,7 @@ module LogViewer
|
|
123
155
|
background-color: #f8f9fa;
|
124
156
|
}
|
125
157
|
.container {
|
126
|
-
max-width:
|
158
|
+
max-width: 1800px;
|
127
159
|
margin: 0 auto;
|
128
160
|
background: white;
|
129
161
|
border-radius: 8px;
|
@@ -150,11 +182,11 @@ module LogViewer
|
|
150
182
|
table {
|
151
183
|
width: 100%;
|
152
184
|
border-collapse: collapse;
|
153
|
-
font-size:
|
185
|
+
font-size: 18px;
|
154
186
|
}
|
155
187
|
th {
|
156
188
|
background: #e9ecef;
|
157
|
-
padding:
|
189
|
+
padding: 18px;
|
158
190
|
text-align: left;
|
159
191
|
font-weight: 600;
|
160
192
|
border-bottom: 2px solid #dee2e6;
|
@@ -162,7 +194,7 @@ module LogViewer
|
|
162
194
|
top: 0;
|
163
195
|
}
|
164
196
|
td {
|
165
|
-
padding:
|
197
|
+
padding: 15px 18px;
|
166
198
|
border-bottom: 1px solid #dee2e6;
|
167
199
|
vertical-align: top;
|
168
200
|
}
|
@@ -172,27 +204,45 @@ module LogViewer
|
|
172
204
|
.level {
|
173
205
|
font-weight: bold;
|
174
206
|
text-transform: uppercase;
|
175
|
-
font-size:
|
207
|
+
font-size: 16px;
|
176
208
|
white-space: nowrap;
|
177
209
|
}
|
178
210
|
.text {
|
179
|
-
|
211
|
+
min-width: 600px;
|
180
212
|
word-wrap: break-word;
|
181
213
|
white-space: pre-wrap;
|
182
214
|
}
|
183
215
|
.file {
|
184
216
|
font-family: 'Monaco', 'Menlo', monospace;
|
185
|
-
font-size:
|
217
|
+
font-size: 16px;
|
186
218
|
color: #666;
|
187
219
|
max-width: 200px;
|
188
220
|
word-wrap: break-word;
|
189
221
|
}
|
190
222
|
.method {
|
191
223
|
font-family: 'Monaco', 'Menlo', monospace;
|
192
|
-
font-size:
|
224
|
+
font-size: 16px;
|
193
225
|
color: #333;
|
194
226
|
font-weight: 500;
|
195
227
|
}
|
228
|
+
.timestamp {
|
229
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
230
|
+
font-size: 15px;
|
231
|
+
color: #666;
|
232
|
+
white-space: nowrap;
|
233
|
+
}
|
234
|
+
.tag {
|
235
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
236
|
+
font-size: 16px;
|
237
|
+
color: #007acc;
|
238
|
+
font-weight: 500;
|
239
|
+
}
|
240
|
+
.line {
|
241
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
242
|
+
font-size: 16px;
|
243
|
+
color: #999;
|
244
|
+
text-align: right;
|
245
|
+
}
|
196
246
|
.empty {
|
197
247
|
color: #999;
|
198
248
|
font-style: italic;
|
@@ -209,10 +259,13 @@ module LogViewer
|
|
209
259
|
<table>
|
210
260
|
<thead>
|
211
261
|
<tr>
|
262
|
+
<th style="width: 120px;">Timestamp</th>
|
212
263
|
<th style="width: 80px;">Level</th>
|
264
|
+
<th style="width: 120px;">Tag</th>
|
213
265
|
<th>Text</th>
|
214
|
-
<th style="width:
|
215
|
-
<th style="width:
|
266
|
+
<th style="width: 180px;">File</th>
|
267
|
+
<th style="width: 50px;">Line</th>
|
268
|
+
<th style="width: 100px;">Method</th>
|
216
269
|
</tr>
|
217
270
|
</thead>
|
218
271
|
<tbody>
|
@@ -220,15 +273,23 @@ module LogViewer
|
|
220
273
|
|
221
274
|
logs.each do |log|
|
222
275
|
level_style = "color: #{level_color(log[:level])}"
|
276
|
+
formatted_timestamp = format_timestamp(log[:timestamp])
|
277
|
+
timestamp_content = formatted_timestamp.empty? ? '<span class="empty">-</span>' : formatted_timestamp
|
278
|
+
tag_content = log[:tag].empty? ? '<span class="empty">-</span>' : log[:tag]
|
223
279
|
text_content = log[:text].empty? ? '<span class="empty">-</span>' : log[:text]
|
224
|
-
|
280
|
+
filename = extract_filename(log[:file])
|
281
|
+
file_content = filename.empty? ? '<span class="empty">-</span>' : filename
|
282
|
+
line_content = log[:line].nil? ? '<span class="empty">-</span>' : log[:line]
|
225
283
|
method_content = log[:method].empty? ? '<span class="empty">-</span>' : log[:method]
|
226
284
|
|
227
285
|
html += <<~HTML
|
228
286
|
<tr>
|
287
|
+
<td class="timestamp">#{timestamp_content}</td>
|
229
288
|
<td class="level" style="#{level_style}">#{log[:level]}</td>
|
289
|
+
<td class="tag">#{tag_content}</td>
|
230
290
|
<td class="text">#{text_content}</td>
|
231
291
|
<td class="file">#{file_content}</td>
|
292
|
+
<td class="line">#{line_content}</td>
|
232
293
|
<td class="method">#{method_content}</td>
|
233
294
|
</tr>
|
234
295
|
HTML
|