onlylogs 0.10.0 → 0.12.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +270 -69
  3. data/app/assets/images/onlylogs/favicon/apple-touch-icon.png +0 -0
  4. data/app/assets/images/onlylogs/favicon/favicon-96x96.png +0 -0
  5. data/app/assets/images/onlylogs/favicon/favicon.ico +0 -0
  6. data/app/assets/images/onlylogs/favicon/favicon.svg +1 -3
  7. data/app/assets/images/onlylogs/favicon/web-app-manifest-192x192.png +0 -0
  8. data/app/assets/images/onlylogs/favicon/web-app-manifest-512x512.png +0 -0
  9. data/app/assets/images/onlylogs/logo.png +0 -0
  10. data/app/assets/javascripts/onlylogs/clusterize.js +26 -1
  11. data/app/channels/onlylogs/logs_channel.rb +22 -9
  12. data/app/controllers/concerns/onlylogs/authentication.rb +26 -0
  13. data/app/controllers/onlylogs/application_controller.rb +1 -18
  14. data/app/controllers/onlylogs/queries_controller.rb +70 -0
  15. data/app/controllers/onlylogs/viewer_controller.rb +7 -0
  16. data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +131 -21
  17. data/app/javascript/onlylogs/controllers/log_cursor_controller.js +286 -0
  18. data/app/javascript/onlylogs/controllers/log_streamer_controller.js +52 -6
  19. data/app/javascript/onlylogs/controllers/match_highlight_controller.js +82 -0
  20. data/app/javascript/onlylogs/controllers/queries_controller.js +470 -0
  21. data/app/javascript/onlylogs/controllers/range_slider_controller.js +5 -4
  22. data/app/javascript/onlylogs/controllers/text_selection_controller.js +136 -15
  23. data/app/models/onlylogs/file.rb +27 -7
  24. data/app/models/onlylogs/grep.rb +13 -6
  25. data/app/models/onlylogs/queries_record.rb +28 -0
  26. data/app/models/onlylogs/query.rb +29 -0
  27. data/app/views/onlylogs/logs/index.html.erb +1 -1
  28. data/app/views/onlylogs/shared/_log_container.html.erb +179 -30
  29. data/app/views/onlylogs/shared/_log_container_styles.html.erb +731 -45
  30. data/app/views/onlylogs/shared/_shortcuts_dialog.html.erb +62 -0
  31. data/bin/super_grep +7 -0
  32. data/bin/super_ripgrep +5 -0
  33. data/config/routes.rb +1 -0
  34. data/lib/onlylogs/configuration.rb +14 -1
  35. data/lib/onlylogs/http_device.rb +28 -13
  36. data/lib/onlylogs/version.rb +1 -1
  37. data/lib/onlylogs.rb +4 -0
  38. metadata +38 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9303c9ba4c727343d48702332baef85d112068f0639a148ff42afc8693b1f7af
4
- data.tar.gz: 82fe3f9d70cfc3b2d4a6f661a296974037aca9fd8bd8e97317476bb091d3c320
3
+ metadata.gz: fae4a8382ece2fa6c8e6c302c0aedda949910921850215d9bcc7f3e86a628fec
4
+ data.tar.gz: c94139e2bc001f2a2bbe7488aabd5394858442105159fb790d42c86d688c3a75
5
5
  SHA512:
6
- metadata.gz: d7bf3497e6013d367776e50e716d113781cdde78b79d302b01a126778092aa36fb0f025c93162301b4f9444d9be83bd30ebd2b832d3e3e5da29d6ed721bd6138
7
- data.tar.gz: 879705e6eb4dafe29e1bf1698495cfb0731ccefc13f3127547a45173e4fdc6beda4ea53c13b4406ae6c3d1ba10ffe36f3b2f21bf1a7e3ff17adf195b5161170f
6
+ metadata.gz: 45f50c071249d629b837bd02f1e785974da7698dcedd6ecd65a55093f853959f24a01290d4b6e1f52d199d09604bf114dcf591daf9ebb8d2d8da34705211a8af
7
+ data.tar.gz: cae201c83159e1b5476979c19b98fed07185eb221e600275a780ac4855f4f4cdf76a81805769c151edcf9a5551ce4eb93b62f5adfbfdde5d73f719bbee11b56c
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  <img alt="w:100px" src="app/assets/images/onlylogs/logo.png" width="400px"/>
2
2
 
3
- We believe logs are enough.
3
+ We believe logs are enough.
4
4
 
5
5
  We believe logs in human-readable format are enough.
6
6
 
7
7
  Stop streaming your logs to very expensive external services: just store your logs on disk.
8
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.
9
+ When your application grows and you don't want to self-host your log files anymore, you can stream them
10
+ to https://onlylogs.io and continue enjoying the same features.
11
11
 
12
12
  > [!IMPORTANT]
13
13
  > https://onlylogs.io is still in beta. Send us an email to a@renuo.ch if you want access to the platform.
@@ -18,7 +18,11 @@ If you already have a disk, you can just keep there also your log files (as well
18
18
 
19
19
  This section explains how to setup onlylogs to self host your logs and access them directly from your Rails app.
20
20
 
21
- If instead you want to stream your logs to https://onlylogs.io, head to [the onlylogs.io instructions page](https://onlylogs.io/instructions).
21
+ If instead you want to stream your logs to https://onlylogs.io, head
22
+ to [the onlylogs.io instructions page](https://onlylogs.io/instructions).
23
+
24
+ Onlylogs needs Rails 8 or newer. It works with Importmap, Sprockets and Propshaft (see
25
+ [INTEGRATION.md](INTEGRATION.md) if you have to register the Stimulus controller by hand).
22
26
 
23
27
  Add this line to your application's Gemfile:
24
28
 
@@ -38,6 +42,7 @@ mount the engine in your `routes.rb`
38
42
  Rails.application.routes.draw do
39
43
  # ...
40
44
  mount Onlylogs::Engine, at: "/onlylogs"
45
+ end
41
46
  ```
42
47
 
43
48
  Finally, you **must secure the engine**. Read the section dedicated to the [Authentication](#authentication).
@@ -51,18 +56,155 @@ Finally, you **must secure the engine**. Read the section dedicated to the [Auth
51
56
 
52
57
  Head to `/onlylogs` and enjoy your logs streamed right into your face!
53
58
 
54
- Here you can grep your logs with regular expressions.
59
+ The viewer has two modes:
60
+
61
+ - **Live**: the end of the file is streamed to you as new lines are written.
62
+ - **Search**: you look for something in the whole file.
63
+
64
+ Next to the query field you find two checkboxes:
65
+
66
+ - **Regexp**: the query is a regular expression. Without it, the query is searched as plain text.
67
+ - **Ignore case**: `error` also finds `ERROR`.
68
+
69
+ What the query matched is highlighted on every line. On any line you can also ask for the lines around it
70
+ (press `Enter` on it), and you can download the whole file.
55
71
 
56
72
  > [!TIP]
57
- > Onlylogs automatically detects and uses [ripgrep (rg)](https://github.com/BurntSushi/ripgrep) if available, which provides significantly faster search experience.
58
- > If ripgrep is not installed, onlylogs falls back to `grep`.
73
+ > Onlylogs automatically detects and uses [ripgrep (rg)](https://github.com/BurntSushi/ripgrep) if available, which
74
+ provides significantly faster search experience.
75
+ > If ripgrep is not installed, onlylogs falls back to `grep`.
59
76
  > A warning icon (⚠️) will be displayed in the toolbar when using `grep` to indicate slower search performance.
60
77
 
78
+ ### Saved searches
79
+
80
+ Click the star in the query field to save the current query. The list next to it shows all saved searches: pick one to
81
+ apply it, rename it or remove it. A saved search is shared by everybody who opens the same log file.
82
+
83
+ Saved searches are stored in a small SQLite database, in a hidden `.onlylogs` folder next to the log file
84
+ (for example `log/.onlylogs/queries.sqlite3`). The folder must be writable. In Docker, keep it on a volume, like the
85
+ logs (see [Notes about Docker](#notes-about-docker)). You can change the folder name with `config.database_dir`.
86
+
87
+ ### Keyboard shortcuts
88
+
89
+ Hold **⌃ Control** on macOS or **Alt** on Windows and Linux: a letter appears on every control of the toolbar, and
90
+ modifier + letter triggers it from anywhere, also while typing a query. Press `?` (or the ⌨ button) for the full list.
91
+
92
+ Inside the log lines, `↑` `↓` (or `j` `k`) move a line cursor, `←` `→` select words of that line (`Shift` grows the
93
+ selection) and `Enter` searches the selection, or shows the lines around the current one. `/` jumps to the query,
94
+ `Enter` or `Esc` in the query jumps back into the lines.
95
+
96
+ A host app can give its own controls a letter with the same attribute, e.g.
97
+ `<button data-onlylogs-shortcut="f">Fullscreen</button>`.
98
+
99
+ ## Logging in production
100
+
101
+ Rails apps log to `STDOUT` in production and let the platform collect the stream. onlylogs keeps that default:
102
+ `Onlylogs::HttpLogger.new` writes every line to `$stdout` and, when
103
+ `ONLYLOGS_DRAIN_URL` is set, ships it to https://onlylogs.io as well. Nothing is written to disk.
104
+
105
+ ```ruby
106
+ # config/environments/production.rb
107
+ config.logger = Onlylogs::HttpLogger.new
108
+ ```
109
+
110
+ Shipping is fire and forget. Lines are batched in memory and sent in the background; when onlylogs.io is down or does
111
+ not answer within the timeouts, the sender backs off for a while and the lines of that period are lost. Your `$stdout`
112
+ stream still has them. If you would rather keep them, set `ONLYLOGS_SPOOL_ENABLED=true` (`1`, `yes` and `on` work
113
+ too): batches that could not be delivered are then written to `ONLYLOGS_SPOOL_DIR` (default `tmp/onlylogs/spool`, up
114
+ to `ONLYLOGS_SPOOL_MAX_BYTES`, 128 MB) and replayed once the drain is back, also after a restart when the directory is
115
+ on a persistent disk.
116
+
117
+ Keep this default on an **ephemeral filesystem** (Heroku, Deploio, Docker, Kubernetes). A log file there is lost on
118
+ every deploy and, while it grows, its page cache counts towards the container's memory limit: it shows up as a memory
119
+ leak that resets on each release.
120
+
121
+ If you have a **persistent disk** and want the files, for instance to browse them with the engine, pass a rotating log
122
+ device as the local fallback. Rails rotates at 100 MB in development, which is a good size here too:
123
+
124
+ ```ruby
125
+ # config/environments/production.rb
126
+ log_file = Logger::LogDevice.new(Rails.root.join("log", "production.log"), shift_age: 5, shift_size: 100.megabytes)
127
+ config.logger = Onlylogs::HttpLogger.new(local_fallback: log_file)
128
+ ```
129
+
130
+ Any IO or log device works and is used as is. `Onlylogs::SocketLogger` accepts the same argument.
131
+
132
+ ### Settings of `HttpLogger`
133
+
134
+ `HttpLogger` is configured with environment variables. Only `ONLYLOGS_DRAIN_URL` is needed. The others have sensible
135
+ defaults.
136
+
137
+ | Variable | Default | What it does |
138
+ |-------------------------------|--------------------------|-----------------------------------------------------------|
139
+ | `ONLYLOGS_DRAIN_URL` | none | Where the logs are sent. Without it, logs stay local. |
140
+ | `ONLYLOGS_BATCH_SIZE` | `100` | Lines sent together in one request. |
141
+ | `ONLYLOGS_FLUSH_INTERVAL` | `0.5` | Seconds to wait before sending an incomplete batch. |
142
+ | `ONLYLOGS_MAX_QUEUE_SIZE` | `10000` | Lines kept in memory while waiting to be sent. |
143
+ | `ONLYLOGS_MAX_BATCH_BYTES` | `1048576` (1 MB) | Maximum size of a batch. Longer lines are truncated. |
144
+ | `ONLYLOGS_OPEN_TIMEOUT` | `0.5` | Seconds to wait when connecting to the drain. |
145
+ | `ONLYLOGS_READ_TIMEOUT` | `0.5` | Seconds to wait for the answer of the drain. |
146
+ | `ONLYLOGS_KEEP_ALIVE_TIMEOUT` | `30` | Seconds an idle connection is kept open for reuse. |
147
+ | `ONLYLOGS_CIRCUIT_COOLDOWN` | `30` | Seconds to stop sending after repeated failures. |
148
+ | `ONLYLOGS_SPOOL_DIR` | `tmp/onlylogs/spool` | Folder where undelivered batches are stored on disk. |
149
+ | `ONLYLOGS_SPOOL_MAX_BYTES` | `134217728` (128 MB) | Maximum size of that folder. |
150
+
151
+ A wrong value (for example a negative number) is ignored with a warning and the default is used. A wrong
152
+ `ONLYLOGS_DRAIN_URL` never stops your app from booting: it just logs locally.
153
+
154
+ ### What happens when the drain is down
155
+
156
+ Your app must never become slow or crash because of logging. `HttpLogger` sends the lines from a background thread,
157
+ never from the request thread, and it protects your app like this:
158
+
159
+ 1. Lines wait in a queue in memory and are sent in batches.
160
+ 2. After a few failed requests, a **circuit breaker** stops trying for a while, so your app does not wait on a dead
161
+ server.
162
+ 3. Batches that could not be delivered are saved in the **spool** folder on disk. They are sent again when the drain
163
+ is back, even after a restart. Because of this, a line can very rarely arrive twice, but it is not lost.
164
+ 4. If the queue is full, new lines are dropped. If the spool is full, its oldest batches are deleted. Your app keeps
165
+ running.
166
+
167
+ To see this in action, run `bin/fake_drain` and `bin/fake_app` (see [Development](#development--contributing)).
168
+
169
+ ### Sidecar (alternative to `HttpLogger`)
170
+
171
+ Instead of sending logs from inside the app, you can let a small separate process do it. The app writes the lines to
172
+ a Unix socket with `Onlylogs::SocketLogger`, and the sidecar sends them to the drain.
173
+
174
+ ```ruby
175
+ # config/environments/production.rb
176
+ config.logger = Onlylogs::SocketLogger.new
177
+ ```
178
+
179
+ ```ruby
180
+ # config/puma.rb
181
+ plugin :onlylogs_sidecar # starts and stops the sidecar together with Puma
182
+ ```
183
+
184
+ You can also start it yourself with `bin/onlylogs_sidecar`. It reads `ONLYLOGS_DRAIN_URL` and, optionally,
185
+ `ONLYLOGS_SIDECAR_SOCKET`, `ONLYLOGS_BATCH_SIZE` and `ONLYLOGS_FLUSH_INTERVAL`.
186
+
187
+ Prefer `HttpLogger`: it works in every process (Puma, Sidekiq, rake tasks) and has the spool. Use the sidecar only if
188
+ you want the sending to happen outside of your app process.
189
+
190
+ ### Filtering Log Lines with a Denylist
191
+
192
+ The `Onlylogs::Formatter` supports a denylist: an array of regular expressions that prevents matching lines from being
193
+ logged. This is useful for filtering out noisy or irrelevant entries like health checks or asset requests.
194
+
195
+ ```ruby
196
+ # config/environments/production.rb
197
+ config.logger = Onlylogs::HttpLogger.new
198
+ config.logger.formatter.denylist = [/health_check/, /ping/, /\.css\z/]
199
+ ```
200
+
201
+ Any log message matching at least one pattern in the denylist will be silently dropped.
202
+
61
203
  ## Authentication
62
204
 
63
205
  Yes, we should do this right away, because this engine gives access to your log files, so you want to be sure.
64
206
 
65
- The engine has one Controller and one ActionCable channel that **must be protected**.
207
+ The engine has controllers and one ActionCable channel that **must be protected**.
66
208
 
67
209
  Please be sure to secure them properly.
68
210
 
@@ -76,15 +218,17 @@ If your app is running in a Docker container, for example with Kamal, remember t
76
218
  ```yaml
77
219
  # config/deploy.yml
78
220
  volumes:
79
- - "storage:/rails/storage"
80
- - "cache:/rails/tmp/cache"
81
- - "logs:/rails/log"
221
+ - "storage:/rails/storage"
222
+ - "cache:/rails/tmp/cache"
223
+ - "logs:/rails/log"
82
224
  ```
83
225
 
226
+ The saved searches live in `log/.onlylogs`, so they are kept too.
227
+
84
228
  ### Basic Authentication Setup
85
229
 
86
- Credentials can be configured using environment variables, Rails credentials, or programmatically.
87
- Environment variables take precedence over Rails credentials.
230
+ Credentials can be configured using environment variables, Rails credentials, or programmatically. Environment variables
231
+ take precedence over Rails credentials.
88
232
 
89
233
  ```bash
90
234
  # env variables
@@ -107,11 +251,10 @@ Onlylogs.configure do |config|
107
251
  end
108
252
  ```
109
253
 
110
-
111
254
  ### Custom Authentication
112
255
 
113
- When you need custom authentication logic beyond basic auth,
114
- you can override the default authentication by configuring a parent controller that defines the `authenticate_onlylogs_user!` method.
256
+ When you need custom authentication logic beyond basic auth, you can override the default authentication by configuring
257
+ a parent controller that defines the `authenticate_onlylogs_user!` method.
115
258
 
116
259
  Configure a custom parent controller in your initializer:
117
260
 
@@ -131,7 +274,7 @@ class ApplicationController < ActionController::Base
131
274
  private
132
275
 
133
276
  def authenticate_onlylogs_user!
134
- raise unless current_user.can_access_logs?
277
+ raise unless current_user.can_access_logs?
135
278
  end
136
279
  end
137
280
  ```
@@ -147,25 +290,36 @@ Onlylogs.configure do |config|
147
290
  end
148
291
  ```
149
292
 
150
- ### WebSocket Authentication
293
+ ### Allowing only some users to see some files
151
294
 
152
- Logs are streamed through a WebSocket connection, the Websocket is not protected, but in order to stream a file,
153
- the file path must be white-listed (see section below) and the file path encrypted using `Onlylogs::SecureFilePath.encrypt`
295
+ By default everybody who gets through the authentication can open every file you allowed (see
296
+ [File Access Security](#file-access-security)). To decide per user and per file, set `authorize_log_file`. It runs
297
+ inside the controller, so you can use your own `current_user`. Return something falsy to refuse the file.
154
298
 
299
+ ```ruby
300
+ # config/initializers/onlylogs.rb
301
+ Onlylogs.configure do |config|
302
+ config.authorize_log_file = ->(file_path) { current_user.admin? || file_path.to_s.end_with?("public.log") }
303
+ end
304
+ ```
155
305
 
156
- ## Customization
306
+ If you show the log viewer in your own pages to more people than the rest of the engine, you can give its endpoints a
307
+ different parent controller with `config.viewer_parent_controller`.
157
308
 
158
- Onlylogs provides two ways to customize the appearance of the log viewer: CSS Variables and a complete style override.
159
- Check the file [_log_container_styles.html.erb](app/views/onlylogs/shared/_log_container_styles.html.erb) for the complete list of CSS variables.
309
+ ### WebSocket Authentication
160
310
 
161
- ## Configuration
311
+ Logs are streamed through a WebSocket. To stream a file, two things must be true:
312
+
313
+ - the file path is allowed (see [File Access Security](#file-access-security));
314
+ - the file path is sent encrypted, with `Onlylogs::SecureFilePath.encrypt`. The viewer does this for you. Nobody can
315
+ ask for a path they were not given.
162
316
 
163
- Check `configuration.rb` to see a list of all possible configuration.
317
+ An empty or unknown path is refused.
164
318
 
165
319
  ### File Access Security
166
320
 
167
- Onlylogs includes a secure file access system that prevents unauthorized access to files on your server.
168
- By default, onlylogs can access your Rails environment-specific log files (e.g., `log/development.log`, `log/production.log`).
321
+ Onlylogs includes a secure file access system that prevents unauthorized access to files on your server. By default,
322
+ onlylogs can access your Rails environment-specific log files (e.g., `log/development.log`, `log/production.log`).
169
323
 
170
324
  #### Configuring Allowed Files
171
325
 
@@ -179,15 +333,15 @@ Onlylogs.configure do |config|
179
333
  Rails.root.join("log/development.log"),
180
334
  Rails.root.join("log/production.log"),
181
335
  Rails.root.join("log/test.log"),
182
-
336
+
183
337
  # Custom log files
184
338
  Rails.root.join("log/custom.log"),
185
339
  Rails.root.join("log/api.log"),
186
-
340
+
187
341
  # Application-specific logs
188
342
  Rails.root.join("log/background_jobs.log"),
189
343
  Rails.root.join("log/imports.log"),
190
-
344
+
191
345
  # Allow all .log files in a directory using glob patterns
192
346
  Rails.root.join("log/*.log"),
193
347
  Rails.root.join("tmp/logs/*.log")
@@ -196,6 +350,7 @@ end
196
350
  ```
197
351
 
198
352
  **Default Behavior:**
353
+
199
354
  - If not configured, onlylogs defaults to `Rails.root.join("log/#{Rails.env}.log").to_s`
200
355
  - This means it will use `log/development.log` in development, `log/production.log` in production, etc.
201
356
 
@@ -209,11 +364,11 @@ Onlylogs.configure do |config|
209
364
  config.log_file_patterns = [
210
365
  # Allow all .log files in the log directory
211
366
  Rails.root.join("log/*.log"),
212
-
367
+
213
368
  # Allow specific pattern matches
214
369
  Rails.root.join("log/*production*.log"),
215
370
  Rails.root.join("log/*development*.log"),
216
-
371
+
217
372
  # Allow files in subdirectories
218
373
  Rails.root.join("log/**/*.log"),
219
374
  Rails.root.join("tmp/**/*.log")
@@ -222,20 +377,43 @@ end
222
377
  ```
223
378
 
224
379
  **Supported Glob Patterns:**
380
+
225
381
  - `*.log` - Matches all files ending with `.log` in the specified directory
226
382
  - `*production*.log` - Matches files containing "production" and ending with `.log`
227
383
  - `**/*.log` - Matches all `.log` files in the directory and all subdirectories
228
384
 
229
385
  **Important Notes:**
386
+
230
387
  - Patterns are directory-specific - `log/*.log` only matches files in the `log/` directory
231
388
  - Multiple patterns can be combined in the same configuration
232
389
 
390
+ ## Configuration
391
+
392
+ All options go into `Onlylogs.configure`. The most useful ones:
393
+
394
+ | Option | Default | What it does |
395
+ |--------------------------------|-------------------------------|-----------------------------------------------------|
396
+ | `log_file_patterns` | `log/<Rails.env>.log` | Files (or globs) the viewer may open. |
397
+ | `basic_auth_user`, `basic_auth_password` | env or credentials | Credentials of the basic authentication. |
398
+ | `disable_basic_authentication` | `false` | Turn off basic auth (use it with your own auth). |
399
+ | `parent_controller` | none | Controller the engine inherits from. |
400
+ | `viewer_parent_controller` | `parent_controller` | Same, but only for the viewer endpoints. |
401
+ | `authorize_log_file` | none | Per-user, per-file permission check. |
402
+ | `ripgrep_enabled` | `true` if `rg` exists | Use ripgrep instead of grep. |
403
+ | `editor` | from env | Editor used for file path links. |
404
+ | `max_line_matches` | `100000` | Maximum lines returned by a search. |
405
+ | `search_timeout` | `120` | Seconds after which a search is stopped. |
406
+ | `database_dir` | `".onlylogs"` | Folder (next to the logs) with the saved searche. |
407
+
408
+ See `lib/onlylogs/configuration.rb` for the details.
233
409
 
234
410
  ### Configuring Code Editor for File Path Links
235
411
 
236
- Onlylogs automatically detects file paths in log messages and converts them into clickable links that open in your preferred code editor.
412
+ Onlylogs automatically detects file paths in log messages and converts them into clickable links that open in your
413
+ preferred code editor.
237
414
 
238
- For a complete list of supported editors, see [app/models/onlylogs/file_path_parser.rb](app/models/onlylogs/file_path_parser.rb).
415
+ For a complete list of supported editors,
416
+ see [app/models/onlylogs/file_path_parser.rb](app/models/onlylogs/file_path_parser.rb).
239
417
 
240
418
  ```bash
241
419
  # env variables
@@ -257,25 +435,26 @@ Onlylogs.configure do |config|
257
435
  end
258
436
  ```
259
437
 
260
- #### Configuring Maximum Search Results
438
+ ### Configuring Maximum Search Results
261
439
 
262
- 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:
440
+ By default, onlylogs limits search results to 100,000 lines to prevent memory issues and ensure responsive performance.
441
+ You can configure this limit based on your needs:
263
442
 
264
443
  ```ruby
265
444
  # config/initializers/onlylogs.rb
266
445
  Onlylogs.configure do |config|
267
446
  # Set a custom limit (e.g., 50,000 lines)
268
447
  config.max_line_matches = 50_000
269
-
448
+
270
449
  # Or remove the limit entirely (use with caution)
271
450
  config.max_line_matches = nil
272
451
  end
273
452
  ```
274
453
 
275
- #### Bounding How Long a Viewer Search Can Run
454
+ ### Bounding How Long a Viewer Search Can Run
276
455
 
277
- Searches started from the log viewer stop after `search_timeout` seconds, keeping whatever they
278
- found and reporting that they did not reach the end of the file. The default is 120 seconds:
456
+ Searches started from the log viewer stop after `search_timeout` seconds, keeping whatever they found and reporting that
457
+ they did not reach the end of the file. The default is 120 seconds:
279
458
 
280
459
  ```ruby
281
460
  # config/initializers/onlylogs.rb
@@ -288,21 +467,30 @@ Onlylogs.configure do |config|
288
467
  end
289
468
  ```
290
469
 
291
- ### Filtering Log Lines with a Denylist
470
+ ## Customization
292
471
 
293
- The `Onlylogs::Formatter` supports a denylist: an array of regular expressions that prevents matching lines from being logged. This is useful for filtering out noisy or irrelevant entries like health checks or asset requests.
472
+ Onlylogs provides two ways to customize the appearance of the log viewer: CSS Variables and a complete style override.
473
+ Check the file [_log_container_styles.html.erb](app/views/onlylogs/shared/_log_container_styles.html.erb) for the
474
+ complete list of CSS variables.
475
+
476
+ ## Instrumentation
477
+
478
+ Every search sends an `ActiveSupport::Notifications` event called `search.onlylogs`. You can use it to measure how long
479
+ searches take, for example:
294
480
 
295
481
  ```ruby
296
- # config/environments/production.rb
297
- config.logger = Onlylogs::Logger.new(Rails.root.join("log", "production.log"))
298
- config.logger.formatter.denylist = [/health_check/, /ping/, /\.css\z/]
482
+ ActiveSupport::Notifications.subscribe("search.onlylogs") do |event|
483
+ Rails.logger.info "Search #{event.payload[:query]} took #{event.duration.round}ms, " \
484
+ "#{event.payload[:matches]} matches, timed out: #{event.payload[:timed_out]}"
485
+ end
299
486
  ```
300
487
 
301
- Any log message matching at least one pattern in the denylist will be silently dropped.
488
+ The payload contains `file_path`, `query`, `regexp`, `case_sensitive`, `start_position`, `end_position`,
489
+ `max_matches`, `matches` and `timed_out`.
302
490
 
303
491
  ## Development & Contributing
304
492
 
305
- You are more than welcome to help and contribute to this package.
493
+ You are more than welcome to help and contribute to this package.
306
494
 
307
495
  The app uses minitest and includes a dummy app, so getting started should be straightforward.
308
496
 
@@ -321,11 +509,13 @@ cd test/dummy
321
509
  bundle exec rails s
322
510
  ```
323
511
 
324
- The dummy app will be available at `http://localhost:3000` and onlylogs at `http://localhost:3000/onlylogs`.
512
+ The dummy app will be available at `http://onlylogs.localhost:3000` and onlylogs at
513
+ `http://onlylogs.localhost:3000/onlylogs`.
325
514
 
326
515
  ### Generating Test Logs
327
516
 
328
- To test onlylogs with live log data, use the continuous log writer script. This is especially useful for testing real-time log streaming and UI behavior.
517
+ To test onlylogs with live log data, use the continuous log writer script. This is especially useful for testing
518
+ real-time log streaming and UI behavior.
329
519
 
330
520
  ```bash
331
521
  # Generate 1 log entry every 2 seconds (default)
@@ -338,9 +528,12 @@ bin/continuous_log_writer 5 1
338
528
  bin/continuous_log_writer 10 3
339
529
  ```
340
530
 
341
- The script will write logs to `test/dummy/log/development.log`, which will appear in real-time in the onlylogs UI at `http://localhost:3000/onlylogs`.
531
+ The script will write logs to `test/dummy/log/development.log`, which will appear in real-time in the onlylogs UI at
532
+ `http://onlylogs.localhost:3000/onlylogs`.
342
533
 
343
- The lines are generated by `Onlylogs::ContinuousLogWriter`, in the same format a Rails app logging through `Onlylogs::Logger` produces. The script is a thin wrapper, so applications embedding onlylogs can reuse the writer against any file of their own:
534
+ The lines are generated by `Onlylogs::ContinuousLogWriter`, in the same format a Rails app logging through
535
+ `Onlylogs::Logger` produces. The script is a thin wrapper, so applications embedding onlylogs can reuse the writer
536
+ against any file of their own:
344
537
 
345
538
  ```ruby
346
539
  require "onlylogs/continuous_log_writer"
@@ -348,7 +541,8 @@ require "onlylogs/continuous_log_writer"
348
541
  Onlylogs::ContinuousLogWriter.new(some_log_path, logs_per_batch: 3, interval: 1).call
349
542
  ```
350
543
 
351
- It is plain Ruby (it does not need Rails to be booted) and is not loaded by `require "onlylogs"` — require it explicitly where you need it.
544
+ It is plain Ruby (it does not need Rails to be booted) and is not loaded by `require "onlylogs"` — require it explicitly
545
+ where you need it.
352
546
 
353
547
  **Example workflow:**
354
548
 
@@ -360,15 +554,18 @@ cd test/dummy && bundle exec rails s
360
554
  bin/continuous_log_writer 3 1
361
555
  ```
362
556
 
363
- Open `http://localhost:3000/onlylogs` in your browser and watch logs appear as the script writes them.
557
+ Open `http://onlylogs.localhost:3000/onlylogs` in your browser and watch logs appear as the script writes them.
364
558
 
365
559
  ### Latency Simulation
366
560
 
367
- 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.
561
+ For testing how onlylogs behaves under production-like network conditions, you can simulate latency for HTTP requests
562
+ and WebSocket connections using the included latency simulation tool.
368
563
 
369
564
  **Parameters:**
565
+
370
566
  - **Latency** (default: 120ms): The base network delay added to all traffic
371
- - **Jitter** (default: 30ms): Random variation (±) applied to the latency (changes every 2 seconds to simulate real network conditions)
567
+ - **Jitter** (default: 30ms): Random variation (±) applied to the latency (changes every 2 seconds to simulate real
568
+ network conditions)
372
569
  - **Port** (default: 3000): The port to apply latency simulation to
373
570
 
374
571
  **Common scenarios:**
@@ -387,7 +584,6 @@ bin/simulate_latency enable 300/100
387
584
  bin/simulate_latency enable -p 8080
388
585
  ```
389
586
 
390
-
391
587
  **Testing and monitoring:**
392
588
 
393
589
  ```bash
@@ -401,11 +597,25 @@ bin/simulate_latency status
401
597
  bin/simulate_latency disable
402
598
  ```
403
599
 
404
- The `test` command will run 10 HTTP requests and 10 WebSocket connections, showing you the actual round-trip times and helping you verify the latency is working as expected.
600
+ The `test` command will run 10 HTTP requests and 10 WebSocket connections, showing you the actual round-trip times and
601
+ helping you verify the latency is working as expected.
602
+
603
+ ### Simulating a drain outage
604
+
605
+ To see how `HttpLogger` behaves when the drain is slow or broken, run a fake drain and a fake app in two terminals:
606
+
607
+ ```bash
608
+ bin/fake_drain # press a key to change its state: 2=200 5=500 3=503 4=413 h=hang s=slow r=reset d=down
609
+ bin/fake_app # 5 "Puma threads" logging like Rails requests through HttpLogger
610
+ ```
611
+
612
+ The dashboard shows request latency, CPU, memory, the queue, the circuit breaker, the spool and the drain counters.
613
+ A healthy app has a few % of CPU and no HTTP calls on request threads.
405
614
 
406
615
  ### Performance Testing
407
616
 
408
- Performance tests require large log files that are not included in the repository. You can download them using the provided script:
617
+ Performance tests require large log files that are not included in the repository. You can download them using the
618
+ provided script:
409
619
 
410
620
  ```bash
411
621
  bin/download_performance_fixtures
@@ -422,17 +632,8 @@ bin/rails test test/models/onlylogs/grep_performance_test.rb
422
632
 
423
633
  ### Plans for the future
424
634
 
425
- We believe that by simply analysing your logs you can also have a fancy errors report.
426
- Yes, correct. You don't need Sentry either.
427
-
428
- And you know what? You can get also performance reports.
429
-
430
- All of a sudden you are 100% free from external services for three more things:
431
-
432
- * logs
433
- * errors
434
- * performance
435
-
635
+ We believe that by simply analysing your logs you can also get error reports and performance reports. The goal is to
636
+ need no external service for logs, errors and performance.
436
637
 
437
638
  ## License
438
639