onlylogs 0.1.2

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +311 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/onlylogs_manifest.js +2 -0
  5. data/app/assets/images/onlylogs/favicon/apple-touch-icon.png +0 -0
  6. data/app/assets/images/onlylogs/favicon/favicon-96x96.png +0 -0
  7. data/app/assets/images/onlylogs/favicon/favicon.ico +0 -0
  8. data/app/assets/images/onlylogs/favicon/favicon.svg +3 -0
  9. data/app/assets/images/onlylogs/favicon/site.webmanifest.erb +21 -0
  10. data/app/assets/images/onlylogs/favicon/web-app-manifest-192x192.png +0 -0
  11. data/app/assets/images/onlylogs/favicon/web-app-manifest-512x512.png +0 -0
  12. data/app/assets/images/onlylogs/logo.png +0 -0
  13. data/app/channels/onlylogs/application_cable/channel.rb +11 -0
  14. data/app/channels/onlylogs/logs_channel.rb +181 -0
  15. data/app/controllers/onlylogs/application_controller.rb +22 -0
  16. data/app/controllers/onlylogs/logs_controller.rb +23 -0
  17. data/app/helpers/onlylogs/application_helper.rb +4 -0
  18. data/app/javascript/onlylogs/application.js +1 -0
  19. data/app/javascript/onlylogs/controllers/application.js +9 -0
  20. data/app/javascript/onlylogs/controllers/index.js +11 -0
  21. data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +46 -0
  22. data/app/javascript/onlylogs/controllers/log_streamer_controller.js +432 -0
  23. data/app/javascript/onlylogs/controllers/text_selection_controller.js +90 -0
  24. data/app/jobs/onlylogs/application_job.rb +4 -0
  25. data/app/models/onlylogs/ansi_color_parser.rb +78 -0
  26. data/app/models/onlylogs/application_record.rb +5 -0
  27. data/app/models/onlylogs/batch_sender.rb +61 -0
  28. data/app/models/onlylogs/file.rb +151 -0
  29. data/app/models/onlylogs/file_path_parser.rb +118 -0
  30. data/app/models/onlylogs/grep.rb +54 -0
  31. data/app/models/onlylogs/log_line.rb +24 -0
  32. data/app/models/onlylogs/secure_file_path.rb +31 -0
  33. data/app/views/home/show.html.erb +10 -0
  34. data/app/views/layouts/onlylogs/application.html.erb +27 -0
  35. data/app/views/onlylogs/logs/index.html.erb +49 -0
  36. data/app/views/onlylogs/shared/_log_container.html.erb +106 -0
  37. data/app/views/onlylogs/shared/_log_container_styles.html.erb +228 -0
  38. data/config/importmap.rb +6 -0
  39. data/config/puma_plugins/vector.rb +94 -0
  40. data/config/routes.rb +4 -0
  41. data/config/udp_logger.rb +40 -0
  42. data/config/vector.toml +32 -0
  43. data/db/migrate/20250902112548_create_books.rb +9 -0
  44. data/lib/onlylogs/configuration.rb +133 -0
  45. data/lib/onlylogs/engine.rb +39 -0
  46. data/lib/onlylogs/formatter.rb +14 -0
  47. data/lib/onlylogs/log_silencer_middleware.rb +26 -0
  48. data/lib/onlylogs/logger.rb +10 -0
  49. data/lib/onlylogs/socket_logger.rb +71 -0
  50. data/lib/onlylogs/version.rb +3 -0
  51. data/lib/onlylogs.rb +17 -0
  52. data/lib/puma/plugin/onlylogs_sidecar.rb +113 -0
  53. data/lib/tasks/onlylogs_tasks.rake +4 -0
  54. metadata +110 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f2f05cff2b5f48634427295d3704774b1431c85f5bbba89a5adc9d3a6769c79
4
+ data.tar.gz: 16fc0ca2ec651f9523e266ec883a796f2dc94bb46b952b5b6aaf0eb527fd8871
5
+ SHA512:
6
+ metadata.gz: 7625fcd6b6b275a4008e88d7ec9a085b8277854f759472a7eea0acb98d2b8d81c14023ddb8eae313f74bf95d82b9ce8d134fdc0b52af019d84d560a2e62fb7e8
7
+ data.tar.gz: 104a06ec0c73c0885a89cce09dd25597d4c013c2cac9ab9b88f32a77d785aba323885f6eb6cc7a28bfe87c0e0792777d1aae16e6188ffb7bcc589961b8e7a4f6
data/README.md ADDED
@@ -0,0 +1,311 @@
1
+ <img alt="w:100px" src="app/assets/images/onlylogs/logo.png" width="400px"/>
2
+
3
+ We believe logs are enough.
4
+
5
+ We believe logs in human-readable format are enough.
6
+
7
+ Stop streaming your logs to very expensive external services: just store your logs on disk.
8
+
9
+ When your application grows and you don't want to self-host your log files anymore, you can
10
+ stream them to https://onlylogs.io and continue enjoying the same features.
11
+
12
+ > [!IMPORTANT]
13
+ > https://onlylogs.io is still in beta. Send us an email to a@renuo.ch if you want access to the platform.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem "onlylogs"
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```bash
26
+ $ bundle
27
+ ```
28
+
29
+ mount the engine in your `routes.rb`
30
+
31
+ ```ruby
32
+ Rails.application.routes.draw do
33
+ # ...
34
+ mount Onlylogs::Engine, at: "/onlylogs"
35
+ ```
36
+
37
+ Finally, you **must secure the engine**. Read the section dedicated to the [Authentication](#authentication).
38
+
39
+ > [!TIP]
40
+ > **Install ripgrep for Better Performance**.
41
+ > For optimal search performance, we recommend installing [ripgrep](https://github.com/BurntSushi/ripgrep).
42
+ > Onlylogs will automatically detect and use ripgrep if available.
43
+
44
+ ## Usage
45
+
46
+ Head to `/onlylogs` and enjoy your logs streamed right into your face!
47
+
48
+ Here you can grep your logs with regular expressions.
49
+
50
+ > [!TIP]
51
+ > Onlylogs automatically detects and uses [ripgrep (rg)](https://github.com/BurntSushi/ripgrep) if available, which provides significantly faster search experience.
52
+ > If ripgrep is not installed, onlylogs falls back to `grep`.
53
+ > A warning icon (⚠️) will be displayed in the toolbar when using `grep` to indicate slower search performance.
54
+
55
+ ## Authentication
56
+
57
+ Yes, we should do this right away, because this engine gives access to your log files, so you want to be sure.
58
+
59
+ The engine has one Controller and one ActionCable channel that **must be protected**.
60
+
61
+ Please be sure to secure them properly.
62
+
63
+ > [!IMPORTANT]
64
+ > By default, onlylogs endpoints are completely inaccessible until basic auth credentials are configured.
65
+
66
+ ### Basic Authentication Setup
67
+
68
+ Credentials can be configured using environment variables, Rails credentials, or programmatically.
69
+ Environment variables take precedence over Rails credentials.
70
+
71
+ ```bash
72
+ # env variables
73
+ export ONLYLOGS_BASIC_AUTH_USER="your_username"
74
+ export ONLYLOGS_BASIC_AUTH_PASSWORD="your_password"
75
+ ```
76
+
77
+ ```yml
78
+ # config/credentials.yml.enc
79
+ onlylogs:
80
+ basic_auth_user: your_username
81
+ basic_auth_password: your_password
82
+ ```
83
+
84
+ ```ruby
85
+ # config/initializers/onlylogs.rb
86
+ Onlylogs.configure do |config|
87
+ config.basic_auth_user = "your_username"
88
+ config.basic_auth_password = "your_password"
89
+ end
90
+ ```
91
+
92
+
93
+ ### Custom Authentication
94
+
95
+ When you need custom authentication logic beyond basic auth,
96
+ you can override the default authentication by configuring a parent controller that defines the `authenticate_onlylogs_user!` method.
97
+
98
+ Configure a custom parent controller in your initializer:
99
+
100
+ ```ruby
101
+ # config/initializers/onlylogs.rb
102
+ Onlylogs.configure do |config|
103
+ config.disable_basic_authentication = true
104
+ config.parent_controller = "ApplicationController" # or any other controller
105
+ end
106
+ ```
107
+
108
+ In your parent controller, define the `authenticate_onlylogs_user!` method:
109
+
110
+ ```ruby
111
+ # app/controllers/application_controller.rb
112
+ class ApplicationController < ActionController::Base
113
+ private
114
+
115
+ def authenticate_onlylogs_user!
116
+ raise unless current_user.can_access_logs?
117
+ end
118
+ end
119
+ ```
120
+
121
+ #### Disabling Authentication
122
+
123
+ For development you can disable basic authentication entirely:
124
+
125
+ ```ruby
126
+ # config/environments/development.rb
127
+ Onlylogs.configure do |config|
128
+ config.disable_basic_authentication = true
129
+ end
130
+ ```
131
+
132
+ ### WebSocket Authentication
133
+
134
+ Logs are streamed through a WebSocket connection, the Websocket is not protected, but in order to stream a file,
135
+ the file path must be white-listed (see section below) and the file path encrypted using `Onlylogs::SecureFilePath.encrypt`
136
+
137
+
138
+ ## Customization
139
+
140
+ Onlylogs provides two ways to customize the appearance of the log viewer: CSS Variables and a complete style override.
141
+ Check the file [_log_container_styles.html.erb](app/views/onlylogs/shared/_log_container_styles.html.erb) for the complete list of CSS variables.
142
+
143
+ ## Configuration
144
+
145
+ Check `configuration.rb` to see a list of all possible configuration.
146
+
147
+ ### File Access Security
148
+
149
+ Onlylogs includes a secure file access system that prevents unauthorized access to files on your server.
150
+ By default, onlylogs can access your Rails environment-specific log files (e.g., `log/development.log`, `log/production.log`).
151
+
152
+ #### Configuring Allowed Files
153
+
154
+ You can configure which files onlylogs is allowed to access by creating a configuration initializer:
155
+
156
+ ```ruby
157
+ # config/initializers/onlylogs.rb
158
+ Onlylogs.configure do |config|
159
+ config.allowed_files = [
160
+ # Default Rails log files
161
+ Rails.root.join("log/development.log"),
162
+ Rails.root.join("log/production.log"),
163
+ Rails.root.join("log/test.log"),
164
+
165
+ # Custom log files
166
+ Rails.root.join("log/custom.log"),
167
+ Rails.root.join("log/api.log"),
168
+
169
+ # Application-specific logs
170
+ Rails.root.join("log/background_jobs.log"),
171
+ Rails.root.join("log/imports.log"),
172
+
173
+ # Allow all .log files in a directory using glob patterns
174
+ Rails.root.join("log/*.log"),
175
+ Rails.root.join("tmp/logs/*.log")
176
+ ]
177
+ end
178
+ ```
179
+
180
+ **Default Behavior:**
181
+ - If not configured, onlylogs defaults to `Rails.root.join("log/#{Rails.env}.log").to_s`
182
+ - This means it will use `log/development.log` in development, `log/production.log` in production, etc.
183
+
184
+ #### Glob Pattern Support
185
+
186
+ Onlylogs supports glob patterns to allow multiple files at once:
187
+
188
+ ```ruby
189
+ # config/initializers/onlylogs.rb
190
+ Onlylogs.configure do |config|
191
+ config.allowed_files = [
192
+ # Allow all .log files in the log directory
193
+ Rails.root.join("log/*.log"),
194
+
195
+ # Allow specific pattern matches
196
+ Rails.root.join("log/*production*.log"),
197
+ Rails.root.join("log/*development*.log"),
198
+
199
+ # Allow files in subdirectories
200
+ Rails.root.join("log/**/*.log"),
201
+ Rails.root.join("tmp/**/*.log")
202
+ ]
203
+ end
204
+ ```
205
+
206
+ **Supported Glob Patterns:**
207
+ - `*.log` - Matches all files ending with `.log` in the specified directory
208
+ - `*production*.log` - Matches files containing "production" and ending with `.log`
209
+ - `**/*.log` - Matches all `.log` files in the directory and all subdirectories
210
+
211
+ **Important Notes:**
212
+ - Patterns are directory-specific - `log/*.log` only matches files in the `log/` directory
213
+ - Multiple patterns can be combined in the same configuration
214
+
215
+
216
+ ### Configuring Code Editor for File Path Links
217
+
218
+ Onlylogs automatically detects file paths in log messages and converts them into clickable links that open in your preferred code editor.
219
+
220
+ For a complete list of supported editors, see [lib/onlylogs/editor_detector.rb](lib/onlylogs/editor_detector.rb).
221
+
222
+ ```bash
223
+ # env variables
224
+ export EDITOR="vscode"
225
+ export RAILS_EDITOR="vscode"
226
+ export ONLYLOGS_EDITOR="vscode" # highest precedence
227
+ ```
228
+
229
+ ```yml
230
+ # config/credentials.yml.enc
231
+ onlylogs:
232
+ editor: vscode
233
+ ```
234
+
235
+ ```ruby
236
+ # config/initializers/onlylogs.rb
237
+ Onlylogs.configure do |config|
238
+ config.editor = :vscode
239
+ end
240
+ ```
241
+
242
+ #### Configuring Maximum Search Results
243
+
244
+ By default, onlylogs limits search results to 100,000 lines to prevent memory issues and ensure responsive performance. You can configure this limit based on your needs:
245
+
246
+ ```ruby
247
+ # config/initializers/onlylogs.rb
248
+ Onlylogs.configure do |config|
249
+ # Set a custom limit (e.g., 50,000 lines)
250
+ config.max_line_matches = 50_000
251
+
252
+ # Or remove the limit entirely (use with caution)
253
+ config.max_line_matches = nil
254
+ end
255
+ ```
256
+
257
+ ## Development & Contributing
258
+
259
+ You are more than welcome to help and contribute to this package.
260
+
261
+ The app uses minitest and includes a dummy app, so getting started should be straightforward.
262
+
263
+ ### Latency Simulation
264
+
265
+ For testing how onlylogs behaves under production-like network conditions, you can simulate latency for HTTP requests and WebSocket connections using the included latency simulation tool.
266
+
267
+ ### Usage
268
+
269
+ ```bash
270
+ # Enable latency simulation (120±30ms jitter on port 3000)
271
+ ./bin/simulate_latency enable
272
+
273
+ # Enable custom latency simulation (150±30ms jitter on port 3000)
274
+ ./bin/simulate_latency enable 150
275
+
276
+ # Enable custom latency and jitter (200±50ms jitter on port 3000)
277
+ ./bin/simulate_latency enable 200/50
278
+
279
+ # Enable latency simulation on custom port (120±30ms jitter on port 8080)
280
+ ./bin/simulate_latency enable -p 8080
281
+
282
+ # Enable custom latency and jitter on custom port (150±50ms jitter on port 8080)
283
+ ./bin/simulate_latency enable 150/50 -p 8080
284
+
285
+ # Test the latency
286
+ ./bin/simulate_latency test
287
+
288
+ # Check current status
289
+ ./bin/simulate_latency status
290
+
291
+ # Disable and clean up
292
+ ./bin/simulate_latency disable
293
+ ```
294
+
295
+ ### Plans for the future
296
+
297
+ We believe that by simply analysing your logs you can also have a fancy errors report.
298
+ Yes, correct. You don't need Sentry either.
299
+
300
+ And you know what? You can get also performance reports.
301
+
302
+ All of a sudden you are 100% free from external services for three more things:
303
+
304
+ * logs
305
+ * errors
306
+ * performance
307
+
308
+
309
+ ## License
310
+
311
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ //= link_directory ../../javascript/onlylogs .js
2
+ //= link_directory ../../javascript/onlylogs/controllers .js
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="286" height="233" viewBox="0 0 286 233"><image width="286" height="233" xlink:href="data:image/png;base64,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"></image><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
2
+ @media (prefers-color-scheme: dark) { :root { filter: none; } }
3
+ </style></svg>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "Onlylogs",
3
+ "short_name": "Onlylogs",
4
+ "icons": [
5
+ {
6
+ "src": "<%= asset_path 'favicon/web-app-manifest-192x192.png' %>",
7
+ "sizes": "192x192",
8
+ "type": "image/png",
9
+ "purpose": "maskable"
10
+ },
11
+ {
12
+ "src": "<%= asset_path 'favicon/web-app-manifest-512x512.png' %>",
13
+ "sizes": "512x512",
14
+ "type": "image/png",
15
+ "purpose": "maskable"
16
+ }
17
+ ],
18
+ "theme_color": "#ffffff",
19
+ "background_color": "#ffffff",
20
+ "display": "standalone"
21
+ }
Binary file
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onlylogs
4
+ module ApplicationCable
5
+ class Channel < ActionCable::Channel::Base
6
+ def subscribed
7
+ stream_from "onlylogs:stream"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onlylogs
4
+ class LogsChannel < ActionCable::Channel::Base
5
+ def subscribed
6
+ # Rails.logger.info "Client subscribed to Onlylogs::LogsChannel"
7
+ # Wait for the client to send the cursor position
8
+ # start_log_watcher will be called from the initialize_watcher method
9
+ end
10
+
11
+ def initialize_watcher(data)
12
+ cleanup_existing_operations
13
+
14
+ # Decrypt and verify the file path
15
+ begin
16
+ encrypted_file_path = data["file_path"]
17
+ if encrypted_file_path.present?
18
+ file_path = Onlylogs::SecureFilePath.decrypt(encrypted_file_path)
19
+
20
+ # Verify the decrypted path is still allowed
21
+ unless Onlylogs.allowed_file_path?(file_path)
22
+ Rails.logger.error "Onlylogs: Attempted to access non-allowed file: #{file_path}"
23
+ transmit({ action: "error", content: "Access denied" })
24
+ return
25
+ end
26
+ else
27
+ # Fallback to default if no encrypted path provided
28
+ file_path = Onlylogs.default_log_file_path
29
+ end
30
+ rescue Onlylogs::SecureFilePath::SecurityError => e
31
+ Rails.logger.error "Onlylogs: Security violation - #{e.message}"
32
+ transmit({ action: "error", content: "Access denied" })
33
+ return
34
+ end
35
+
36
+ # Check if the file is a text file
37
+ unless Onlylogs::File.text_file?(file_path)
38
+ transmit({ action: "error", content: "Cannot read file: File is not a text file" })
39
+ return
40
+ end
41
+
42
+ cursor_position = data["cursor_position"] || 0
43
+ filter = data["filter"].presence
44
+ mode = data["mode"] || "live"
45
+ regexp_mode = data["regexp_mode"] == true || data["regexp_mode"] == "true"
46
+ start_position = data["start_position"]&.to_i || 0
47
+ end_position = data["end_position"]&.to_i
48
+
49
+ if mode == "search"
50
+ # For search mode, read the entire file with filter and send all matching lines
51
+ read_entire_file_with_filter(file_path, filter, regexp_mode, start_position, end_position)
52
+ else
53
+ # For live mode, start the watcher
54
+ start_log_watcher(file_path, cursor_position, filter, regexp_mode)
55
+ end
56
+ end
57
+
58
+ def stop_watcher
59
+ cleanup_existing_operations
60
+ transmit({ action: "finish", content: "Search stopped." })
61
+ end
62
+
63
+ def unsubscribed
64
+ cleanup_existing_operations
65
+ end
66
+
67
+ private
68
+
69
+ def cleanup_existing_operations
70
+ if @batch_sender
71
+ @batch_sender.stop
72
+ @batch_sender = nil
73
+ end
74
+
75
+ stop_log_watcher
76
+ end
77
+
78
+ def start_log_watcher(file_path, cursor_position, filter = nil, regexp_mode = false)
79
+ return if @log_watcher_running
80
+
81
+ @log_watcher_running = true
82
+ @filter = filter
83
+ @regexp_mode = regexp_mode
84
+
85
+ transmit({ action: "message", content: "Reading file. Please wait..." })
86
+
87
+ @log_file = Onlylogs::File.new(file_path, last_position: cursor_position)
88
+
89
+ transmit({ action: "message", content: "" })
90
+
91
+ @log_watcher_thread = Thread.new do
92
+ Rails.logger.silence(Logger::ERROR) do
93
+ @log_file.watch do |new_lines|
94
+ break unless @log_watcher_running
95
+
96
+ # Collect all filtered lines from this batch
97
+ lines_to_send = []
98
+
99
+ new_lines.each do |log_line|
100
+ # Filters in live mode are not yet implemented
101
+ # if @filter.present? && !Onlylogs::Grep.match_line?(log_line.text, @filter, regexp_mode: @regexp_mode)
102
+ # next
103
+ # end
104
+
105
+ lines_to_send << {
106
+ line_number: log_line.number,
107
+ html: render_log_line(log_line)
108
+ }
109
+ end
110
+
111
+ if lines_to_send.any?
112
+ transmit({
113
+ action: "append_logs",
114
+ lines: lines_to_send
115
+ })
116
+ end
117
+ end
118
+ end
119
+ rescue StandardError => e
120
+ Rails.logger.error "Log watcher error: #{e.message}"
121
+ Rails.logger.error e.backtrace.join("\n")
122
+ ensure
123
+ @log_watcher_running = false
124
+ end
125
+ end
126
+
127
+ def stop_log_watcher
128
+ return unless @log_watcher_running
129
+
130
+ @log_watcher_running = false
131
+
132
+ return unless @log_watcher_thread&.alive?
133
+
134
+ @log_watcher_thread.kill
135
+ @log_watcher_thread.join(1)
136
+ end
137
+
138
+ def read_entire_file_with_filter(file_path, filter = nil, regexp_mode = false, start_position = 0, end_position = nil)
139
+ @log_watcher_running = true
140
+ @log_file = Onlylogs::File.new(file_path, last_position: 0)
141
+
142
+ transmit({ action: "message", content: "Searching..." })
143
+
144
+ @batch_sender = BatchSender.new(self)
145
+ @batch_sender.start
146
+
147
+ line_count = 0
148
+
149
+ Rails.logger.silence(Logger::ERROR) do
150
+ @log_file.grep(filter, regexp_mode: regexp_mode, start_position: start_position, end_position: end_position) do |log_line|
151
+ return if @batch_sender.nil?
152
+
153
+ # Add to batch buffer (sender thread will handle sending)
154
+ @batch_sender.add_line({
155
+ line_number: log_line.number,
156
+ html: render_log_line(log_line)
157
+ })
158
+
159
+ line_count += 1
160
+ end
161
+ end
162
+
163
+ # Stop batch sender and flush any remaining lines
164
+ @batch_sender.stop
165
+
166
+ # Send completion message
167
+ if line_count >= Onlylogs.max_line_matches
168
+ transmit({ action: "finish", content: "Search finished. Search results limit reached." })
169
+ else
170
+ transmit({ action: "finish", content: "Search finished." })
171
+ end
172
+
173
+ @log_watcher_running = false
174
+ end
175
+
176
+ def render_log_line(log_line)
177
+ "<pre data-line-number=\"#{log_line.number}\">" \
178
+ "<span class=\"line-number\">#{log_line.parsed_number}</span>#{log_line.parsed_text}</pre>"
179
+ end
180
+ end
181
+ end