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.
- checksums.yaml +7 -0
- data/README.md +311 -0
- data/Rakefile +8 -0
- data/app/assets/config/onlylogs_manifest.js +2 -0
- data/app/assets/images/onlylogs/favicon/apple-touch-icon.png +0 -0
- data/app/assets/images/onlylogs/favicon/favicon-96x96.png +0 -0
- data/app/assets/images/onlylogs/favicon/favicon.ico +0 -0
- data/app/assets/images/onlylogs/favicon/favicon.svg +3 -0
- data/app/assets/images/onlylogs/favicon/site.webmanifest.erb +21 -0
- data/app/assets/images/onlylogs/favicon/web-app-manifest-192x192.png +0 -0
- data/app/assets/images/onlylogs/favicon/web-app-manifest-512x512.png +0 -0
- data/app/assets/images/onlylogs/logo.png +0 -0
- data/app/channels/onlylogs/application_cable/channel.rb +11 -0
- data/app/channels/onlylogs/logs_channel.rb +181 -0
- data/app/controllers/onlylogs/application_controller.rb +22 -0
- data/app/controllers/onlylogs/logs_controller.rb +23 -0
- data/app/helpers/onlylogs/application_helper.rb +4 -0
- data/app/javascript/onlylogs/application.js +1 -0
- data/app/javascript/onlylogs/controllers/application.js +9 -0
- data/app/javascript/onlylogs/controllers/index.js +11 -0
- data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +46 -0
- data/app/javascript/onlylogs/controllers/log_streamer_controller.js +432 -0
- data/app/javascript/onlylogs/controllers/text_selection_controller.js +90 -0
- data/app/jobs/onlylogs/application_job.rb +4 -0
- data/app/models/onlylogs/ansi_color_parser.rb +78 -0
- data/app/models/onlylogs/application_record.rb +5 -0
- data/app/models/onlylogs/batch_sender.rb +61 -0
- data/app/models/onlylogs/file.rb +151 -0
- data/app/models/onlylogs/file_path_parser.rb +118 -0
- data/app/models/onlylogs/grep.rb +54 -0
- data/app/models/onlylogs/log_line.rb +24 -0
- data/app/models/onlylogs/secure_file_path.rb +31 -0
- data/app/views/home/show.html.erb +10 -0
- data/app/views/layouts/onlylogs/application.html.erb +27 -0
- data/app/views/onlylogs/logs/index.html.erb +49 -0
- data/app/views/onlylogs/shared/_log_container.html.erb +106 -0
- data/app/views/onlylogs/shared/_log_container_styles.html.erb +228 -0
- data/config/importmap.rb +6 -0
- data/config/puma_plugins/vector.rb +94 -0
- data/config/routes.rb +4 -0
- data/config/udp_logger.rb +40 -0
- data/config/vector.toml +32 -0
- data/db/migrate/20250902112548_create_books.rb +9 -0
- data/lib/onlylogs/configuration.rb +133 -0
- data/lib/onlylogs/engine.rb +39 -0
- data/lib/onlylogs/formatter.rb +14 -0
- data/lib/onlylogs/log_silencer_middleware.rb +26 -0
- data/lib/onlylogs/logger.rb +10 -0
- data/lib/onlylogs/socket_logger.rb +71 -0
- data/lib/onlylogs/version.rb +3 -0
- data/lib/onlylogs.rb +17 -0
- data/lib/puma/plugin/onlylogs_sidecar.rb +113 -0
- data/lib/tasks/onlylogs_tasks.rake +4 -0
- 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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|