logwhisperer 0.2.2__tar.gz
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.
- logwhisperer-0.2.2/LICENSE +21 -0
- logwhisperer-0.2.2/PKG-INFO +647 -0
- logwhisperer-0.2.2/README.md +614 -0
- logwhisperer-0.2.2/man/log-whisperer.1 +581 -0
- logwhisperer-0.2.2/pyproject.toml +51 -0
- logwhisperer-0.2.2/setup.cfg +4 -0
- logwhisperer-0.2.2/src/logwhisperer/__init__.py +3 -0
- logwhisperer-0.2.2/src/logwhisperer/cli.py +130 -0
- logwhisperer-0.2.2/src/logwhisperer/core.py +199 -0
- logwhisperer-0.2.2/src/logwhisperer/normalize.py +59 -0
- logwhisperer-0.2.2/src/logwhisperer/notify/__init__.py +1 -0
- logwhisperer-0.2.2/src/logwhisperer/notify/dispatch.py +67 -0
- logwhisperer-0.2.2/src/logwhisperer/notify/email_smtp.py +36 -0
- logwhisperer-0.2.2/src/logwhisperer/notify/ntfy.py +17 -0
- logwhisperer-0.2.2/src/logwhisperer/notify/telegram.py +13 -0
- logwhisperer-0.2.2/src/logwhisperer/paths.py +19 -0
- logwhisperer-0.2.2/src/logwhisperer/report.py +72 -0
- logwhisperer-0.2.2/src/logwhisperer/severity.py +42 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/__init__.py +39 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/_subprocess.py +25 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/compose.py +19 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/docker.py +13 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/file.py +18 -0
- logwhisperer-0.2.2/src/logwhisperer/sources/journal.py +16 -0
- logwhisperer-0.2.2/src/logwhisperer/state.py +188 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/PKG-INFO +647 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/SOURCES.txt +38 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/dependency_links.txt +1 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/entry_points.txt +2 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/requires.txt +4 -0
- logwhisperer-0.2.2/src/logwhisperer.egg-info/top_level.txt +1 -0
- logwhisperer-0.2.2/tests/test_cli.py +143 -0
- logwhisperer-0.2.2/tests/test_core.py +227 -0
- logwhisperer-0.2.2/tests/test_dispatch.py +94 -0
- logwhisperer-0.2.2/tests/test_normalize.py +119 -0
- logwhisperer-0.2.2/tests/test_report.py +111 -0
- logwhisperer-0.2.2/tests/test_severity.py +53 -0
- logwhisperer-0.2.2/tests/test_sources.py +121 -0
- logwhisperer-0.2.2/tests/test_state.py +239 -0
- logwhisperer-0.2.2/tests/test_subprocess.py +38 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nao Intelligence GmbH
|
|
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.
|
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: logwhisperer
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: CLI log pattern analysis and anomaly detection tool
|
|
5
|
+
Author-email: Nao Intelligence GmbH <pranto@naointelligence.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/naointelligence/logwhisperer
|
|
8
|
+
Project-URL: Repository, https://github.com/naointelligence/logwhisperer
|
|
9
|
+
Project-URL: Issues, https://github.com/naointelligence/logwhisperer/issues
|
|
10
|
+
Keywords: logging,log-analysis,pattern-detection,anomaly-detection,cli,devops,monitoring
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: System :: Logging
|
|
24
|
+
Classifier: Topic :: System :: Monitoring
|
|
25
|
+
Classifier: Topic :: System :: Systems Administration
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: requests
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
<h1 align="center">
|
|
35
|
+
<br>
|
|
36
|
+
LogWhisperer
|
|
37
|
+
<br>
|
|
38
|
+
</h1>
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<b>CLI log pattern analysis & anomaly detection tool</b><br>
|
|
42
|
+
<a href="https://pypi.org/project/logwhisperer/"><img src="https://img.shields.io/pypi/v/logwhisperer" alt="PyPI"></a>
|
|
43
|
+
<a href="https://pypi.org/project/logwhisperer/"><img src="https://img.shields.io/pypi/pyversions/logwhisperer" alt="Python versions"></a>
|
|
44
|
+
<a href="https://github.com/naointelligence/logwhisperer/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/logwhisperer" alt="License"></a>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<a href="#installation">Installation</a> •
|
|
49
|
+
<a href="#quick-start">Quick Start</a> •
|
|
50
|
+
<a href="#sources">Sources</a> •
|
|
51
|
+
<a href="#pattern-engine">Pattern Engine</a> •
|
|
52
|
+
<a href="#baseline-learning">Baseline Learning</a> •
|
|
53
|
+
<a href="#notifications">Notifications</a> •
|
|
54
|
+
<a href="#recipes">Recipes</a>
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## What Is LogWhisperer?
|
|
60
|
+
|
|
61
|
+
LogWhisperer reads logs from **multiple sources** (Docker containers, Compose
|
|
62
|
+
services, systemd journals, plain files), **normalizes** variable data out of
|
|
63
|
+
each line (IPs, UUIDs, timestamps, paths, numbers), and **clusters** them into
|
|
64
|
+
patterns. It remembers every pattern it has ever seen and tells you which ones
|
|
65
|
+
are **new**.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
┌───────────┐
|
|
69
|
+
docker logs ──>│ │ normalize cluster diff
|
|
70
|
+
compose ──>│ Sources │──> "ERROR <IP>" ──> pattern A ──> [NEW]
|
|
71
|
+
journalctl ──>│ │ "INFO <UUID>" pattern B [seen]
|
|
72
|
+
plain file ──>└───────────┘ pattern C [NEW]
|
|
73
|
+
│
|
|
74
|
+
┌───────────────┼───────────────┐
|
|
75
|
+
▼ ▼ ▼
|
|
76
|
+
patterns.db text/JSON alerts
|
|
77
|
+
(JSON-lines) report (ntfy/tg/email)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
### From PyPI (recommended)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install logwhisperer
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Or with [pipx](https://pipx.pypa.io/) for an isolated install:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pipx install logwhisperer
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### From source (development)
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git clone https://github.com/naointelligence/logwhisperer.git
|
|
100
|
+
cd logwhisperer
|
|
101
|
+
python3 -m venv .venv
|
|
102
|
+
source .venv/bin/activate
|
|
103
|
+
pip install -e ".[dev]"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Verify
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
log-whisperer --help
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Quick Start
|
|
115
|
+
|
|
116
|
+
**1. Analyse a log file**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
log-whisperer --file /var/log/syslog --show-samples
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**2. See only never-before-seen patterns**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
log-whisperer --file /var/log/syslog --show-new
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**3. First run shows `[NEW]`, second run shows `[seen]`**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
$ log-whisperer --file app.log
|
|
132
|
+
[NEW][ERROR] x12 total=12 ERROR something failed with code <N>
|
|
133
|
+
[NEW][INFO] x841 total=841 INFO request from <IP> processed in <N>ms
|
|
134
|
+
|
|
135
|
+
$ log-whisperer --file app.log
|
|
136
|
+
[seen][ERROR] x12 total=24 ERROR something failed with code <N>
|
|
137
|
+
[seen][INFO] x841 total=1682 INFO request from <IP> processed in <N>ms
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
log-whisperer [SOURCE] [OPTIONS]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Source Flags
|
|
149
|
+
|
|
150
|
+
> Exactly **one** source must be specified (unless using `--reset`).
|
|
151
|
+
|
|
152
|
+
| Flag | Description | Example |
|
|
153
|
+
|:-----|:------------|:--------|
|
|
154
|
+
| `--file PATH` | Read from a plain text file | `--file /var/log/app.log` |
|
|
155
|
+
| `--docker NAME` | Read from a Docker container | `--docker my-api` |
|
|
156
|
+
| `--compose SERVICE` | Read from a Compose service | `--compose web` |
|
|
157
|
+
| `--compose-all` | Read from **all** Compose services | `--compose-all` |
|
|
158
|
+
| `--service UNIT` | Read from a systemd journal unit | `--service nginx` |
|
|
159
|
+
|
|
160
|
+
### Display Options
|
|
161
|
+
|
|
162
|
+
| Flag | Default | Description |
|
|
163
|
+
|:-----|:--------|:------------|
|
|
164
|
+
| `--show-samples` | off | Print one raw sample line per pattern |
|
|
165
|
+
| `--show-new` | off | Only display patterns never seen before |
|
|
166
|
+
| `--min-severity` | `INFO` | Filter output: `INFO`, `WARN`, or `ERROR` |
|
|
167
|
+
| `--json` | off | Output the full report as JSON |
|
|
168
|
+
|
|
169
|
+
### Window Options
|
|
170
|
+
|
|
171
|
+
| Flag | Default | Description |
|
|
172
|
+
|:-----|:--------|:------------|
|
|
173
|
+
| `--since` | `1h` | Time window passed to docker/journalctl (e.g. `10m`, `1h`, `today`) |
|
|
174
|
+
| `--lines` | `5000` | Maximum number of lines to process |
|
|
175
|
+
|
|
176
|
+
### State Management
|
|
177
|
+
|
|
178
|
+
| Flag | Default | Description |
|
|
179
|
+
|:-----|:--------|:------------|
|
|
180
|
+
| `--state-db PATH` | `~/.local/state/logwhisperer/patterns.db` | Pattern database file |
|
|
181
|
+
| `--baseline-state PATH` | `~/.local/state/logwhisperer/baseline.json` | Baseline state file |
|
|
182
|
+
| `--baseline-learn DURATION` | — | Enter baseline learning mode (e.g. `24h`, `30m`) |
|
|
183
|
+
| `--reset` | off | Delete the pattern DB and baseline state |
|
|
184
|
+
|
|
185
|
+
### Notification Flags
|
|
186
|
+
|
|
187
|
+
| Flag | Env Variable | Description |
|
|
188
|
+
|:-----|:-------------|:------------|
|
|
189
|
+
| `--notify-ntfy-topic` | `LOGWHISPERER_NTFY_TOPIC` | ntfy topic name |
|
|
190
|
+
| `--notify-ntfy-server` | `LOGWHISPERER_NTFY_SERVER` | ntfy server URL (default: `https://ntfy.sh`) |
|
|
191
|
+
| `--notify-telegram-token` | `LOGWHISPERER_TELEGRAM_TOKEN` | Telegram bot token |
|
|
192
|
+
| `--notify-telegram-chat-id` | `LOGWHISPERER_TELEGRAM_CHAT_ID` | Telegram chat ID |
|
|
193
|
+
| `--notify-email-host` | `LOGWHISPERER_SMTP_HOST` | SMTP server hostname |
|
|
194
|
+
| `--notify-email-port` | `LOGWHISPERER_SMTP_PORT` | SMTP port (default: `587`) |
|
|
195
|
+
| `--notify-email-user` | `LOGWHISPERER_SMTP_USER` | SMTP username |
|
|
196
|
+
| `--notify-email-pass` | `LOGWHISPERER_SMTP_PASS` | SMTP password |
|
|
197
|
+
| `--notify-email-from` | `LOGWHISPERER_EMAIL_FROM` | Sender email address |
|
|
198
|
+
| `--notify-email-to` | `LOGWHISPERER_EMAIL_TO` | Recipient email address |
|
|
199
|
+
| `--notify-email-no-tls` | — | Disable STARTTLS |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Sources
|
|
204
|
+
|
|
205
|
+
### Plain File
|
|
206
|
+
|
|
207
|
+
Reads the last `--lines` lines from any text file. No external tools required.
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
log-whisperer --file /var/log/nginx/error.log --since 1h
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
> **Note:** `--since` is passed to docker/journalctl only. For files, all
|
|
214
|
+
> lines are read and the last `--lines` are analysed.
|
|
215
|
+
|
|
216
|
+
### Docker Container
|
|
217
|
+
|
|
218
|
+
Fetches logs from a running (or stopped) container via `docker logs`.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
log-whisperer --docker my-api --since 30m --show-new
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Docker Compose
|
|
225
|
+
|
|
226
|
+
Fetches logs from one service or all services in the current project.
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Single service
|
|
230
|
+
log-whisperer --compose web --since 1h
|
|
231
|
+
|
|
232
|
+
# All services
|
|
233
|
+
log-whisperer --compose-all --since 1h
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### systemd Journal
|
|
237
|
+
|
|
238
|
+
Reads from `journalctl` for a specific unit, with bare message output (`-o cat`).
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
log-whisperer --service nginx --since today
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Pattern Engine
|
|
247
|
+
|
|
248
|
+
### How Normalisation Works
|
|
249
|
+
|
|
250
|
+
LogWhisperer transforms each raw log line into a **pattern** by replacing
|
|
251
|
+
variable data with fixed placeholders:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Raw: 2024-03-15T10:23:45 ERROR Connection to 192.168.1.42 failed (attempt 3)
|
|
255
|
+
Pattern: ERROR Connection to <IP> failed (attempt <N>)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The following substitutions are applied **in order**:
|
|
259
|
+
|
|
260
|
+
| Data Type | Example | Placeholder |
|
|
261
|
+
|:----------|:--------|:------------|
|
|
262
|
+
| Timestamp prefix | `2024-03-15T10:23:45` | *(stripped)* |
|
|
263
|
+
| Syslog prefix | `Mar 15 10:23:45 host app:` | *(stripped)* |
|
|
264
|
+
| UUID | `550e8400-e29b-41d4-a716-446655440000` | `<UUID>` |
|
|
265
|
+
| Hash (32-64 hex chars) | `d41d8cd98f00b204e9800998ecf8427e` | `<HASH>` |
|
|
266
|
+
| IPv4 address | `192.168.1.42` | `<IP>` |
|
|
267
|
+
| MAC address | `aa:bb:cc:dd:ee:ff` | `<MAC>` |
|
|
268
|
+
| Hex literal | `0x1A2F` | `<HEX>` |
|
|
269
|
+
| File path | `/usr/local/bin/app` | `<PATH>` |
|
|
270
|
+
| Number | `42`, `3000` | `<N>` |
|
|
271
|
+
|
|
272
|
+
> **Order matters.** UUIDs and long hashes are matched *before* the generic
|
|
273
|
+
> number pattern to prevent partial replacement (e.g. a UUID being half-replaced
|
|
274
|
+
> with `<N>`).
|
|
275
|
+
|
|
276
|
+
### Pattern Hashing
|
|
277
|
+
|
|
278
|
+
Each normalized pattern is hashed with **SHA-1** to produce a stable identifier.
|
|
279
|
+
Two log lines that normalize to the same string will always share the same hash
|
|
280
|
+
and be counted together.
|
|
281
|
+
|
|
282
|
+
### Severity Classification
|
|
283
|
+
|
|
284
|
+
Patterns are classified by scanning for keywords (case-insensitive):
|
|
285
|
+
|
|
286
|
+
| Severity | Keywords |
|
|
287
|
+
|:---------|:---------|
|
|
288
|
+
| **ERROR** | `error`, `fatal`, `exception`, `traceback`, `panic`, `segfault`, `failed`, `failure`, `critical` |
|
|
289
|
+
| **WARN** | `warn`, `warning`, `timeout`, `timed out`, `retry`, `throttle`, `rate limit`, `deprecated`, `slow`, `unavailable` |
|
|
290
|
+
| **INFO** | Everything else |
|
|
291
|
+
|
|
292
|
+
Use `--min-severity` to filter the report output:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Only show warnings and errors
|
|
296
|
+
log-whisperer --file app.log --min-severity WARN
|
|
297
|
+
|
|
298
|
+
# Only show errors
|
|
299
|
+
log-whisperer --file app.log --min-severity ERROR
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
> **Note:** Patterns below the severity threshold are still recorded in the
|
|
303
|
+
> database — they just don't appear in the report.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Baseline Learning
|
|
308
|
+
|
|
309
|
+
When you first deploy LogWhisperer against a service, *every* pattern is new.
|
|
310
|
+
**Baseline learning** lets the tool silently learn existing patterns for a
|
|
311
|
+
set period before it starts alerting.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Learn patterns for the next 24 hours — no alerts during this time
|
|
315
|
+
log-whisperer --file app.log --baseline-learn 24h
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
Baseline learning enabled until 2024-03-16 10:30:00. (No alerts during this period)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
During baseline learning:
|
|
323
|
+
- All patterns are recorded in the database as usual.
|
|
324
|
+
- The report still shows `[NEW]` / `[seen]` tags.
|
|
325
|
+
- **No alert notifications are dispatched.**
|
|
326
|
+
|
|
327
|
+
Once the learning window expires, any genuinely new pattern will trigger alerts.
|
|
328
|
+
|
|
329
|
+
### Duration Format
|
|
330
|
+
|
|
331
|
+
| Example | Meaning |
|
|
332
|
+
|:--------|:--------|
|
|
333
|
+
| `30s` | 30 seconds |
|
|
334
|
+
| `10m` | 10 minutes |
|
|
335
|
+
| `2h` | 2 hours |
|
|
336
|
+
| `1d` | 1 day |
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## State & Database
|
|
341
|
+
|
|
342
|
+
### Pattern Database
|
|
343
|
+
|
|
344
|
+
The pattern DB is a **JSON-lines** file (one JSON object per line):
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
$ cat ~/.local/state/logwhisperer/patterns.db
|
|
348
|
+
{"h":"d72cb82a...","first_seen":1710500000,"last_seen":1710503600,"total_seen":47,"severity":"ERROR","pattern":"ERROR something failed","sample":"2024-03-15 ERROR something failed"}
|
|
349
|
+
{"h":"8c39b05a...","first_seen":1710500000,"last_seen":1710503600,"total_seen":1203,"severity":"INFO","pattern":"INFO all good","sample":"2024-03-15 INFO all good"}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
You can inspect it with standard tools:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Pretty-print all entries
|
|
356
|
+
cat ~/.local/state/logwhisperer/patterns.db | jq .
|
|
357
|
+
|
|
358
|
+
# Count total patterns
|
|
359
|
+
wc -l ~/.local/state/logwhisperer/patterns.db
|
|
360
|
+
|
|
361
|
+
# Find ERROR patterns
|
|
362
|
+
grep '"severity":"ERROR"' ~/.local/state/logwhisperer/patterns.db | jq .
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Concurrency Safety
|
|
366
|
+
|
|
367
|
+
The database uses **file-level locking** (`fcntl.flock`):
|
|
368
|
+
|
|
369
|
+
- **Readers** acquire a shared lock — multiple concurrent reads are safe.
|
|
370
|
+
- **Writers** acquire an exclusive lock — writes are serialized.
|
|
371
|
+
|
|
372
|
+
This prevents corruption when two cron jobs overlap.
|
|
373
|
+
|
|
374
|
+
### Reset
|
|
375
|
+
|
|
376
|
+
To clear all learned patterns and start fresh:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
log-whisperer --reset --file /dev/null
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Custom State Location
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
log-whisperer --file app.log \
|
|
386
|
+
--state-db /opt/logwhisperer/myapp.db \
|
|
387
|
+
--baseline-state /opt/logwhisperer/myapp-baseline.json
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Notifications
|
|
393
|
+
|
|
394
|
+
LogWhisperer can alert you when **new patterns appear** (outside baseline
|
|
395
|
+
learning mode). Configure one or more channels:
|
|
396
|
+
|
|
397
|
+
### ntfy
|
|
398
|
+
|
|
399
|
+
[ntfy](https://ntfy.sh) is a simple pub/sub notification service.
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
log-whisperer --file app.log \
|
|
403
|
+
--notify-ntfy-topic my-alerts \
|
|
404
|
+
--notify-ntfy-server https://ntfy.sh
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Or via environment variables:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
export LOGWHISPERER_NTFY_TOPIC=my-alerts
|
|
411
|
+
log-whisperer --file app.log
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Telegram
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
log-whisperer --file app.log \
|
|
418
|
+
--notify-telegram-token "123456:ABC-DEF..." \
|
|
419
|
+
--notify-telegram-chat-id "-100123456789"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Or via environment:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
export LOGWHISPERER_TELEGRAM_TOKEN="123456:ABC-DEF..."
|
|
426
|
+
export LOGWHISPERER_TELEGRAM_CHAT_ID="-100123456789"
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Email (SMTP)
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
log-whisperer --file app.log \
|
|
433
|
+
--notify-email-host smtp.gmail.com \
|
|
434
|
+
--notify-email-port 587 \
|
|
435
|
+
--notify-email-user me@gmail.com \
|
|
436
|
+
--notify-email-pass "app-password" \
|
|
437
|
+
--notify-email-from me@gmail.com \
|
|
438
|
+
--notify-email-to ops-team@company.com
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Use `--notify-email-no-tls` to disable STARTTLS for local/internal relays.
|
|
442
|
+
|
|
443
|
+
### Alert Format
|
|
444
|
+
|
|
445
|
+
When new patterns are detected, all configured channels receive a message like:
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
Log Whisperer ALERT (2 new patterns)
|
|
449
|
+
Source: file:/var/log/app.log | since=1h
|
|
450
|
+
|
|
451
|
+
[NEW][ERROR] x5 ERROR connection to <IP> refused
|
|
452
|
+
sample: 2024-03-15 10:23:45 ERROR connection to 10.0.0.3 refused
|
|
453
|
+
|
|
454
|
+
[NEW][WARN] x12 WARN request latency <N>ms exceeds threshold
|
|
455
|
+
sample: 2024-03-15 10:24:01 WARN request latency 3502ms exceeds threshold
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
> **Tip:** Multiple channels can be configured simultaneously. A failure in
|
|
459
|
+
> one channel does not block delivery to the others.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## JSON Output
|
|
464
|
+
|
|
465
|
+
Use `--json` for machine-readable output (piping, dashboards, further processing):
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
log-whisperer --file app.log --json | python3 -m json.tool
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
```json
|
|
472
|
+
{
|
|
473
|
+
"source": "file:app.log",
|
|
474
|
+
"since": "1h",
|
|
475
|
+
"lines_limit": 5000,
|
|
476
|
+
"state_db": "/home/user/.local/state/logwhisperer/patterns.db",
|
|
477
|
+
"baseline_active": false,
|
|
478
|
+
"baseline_until": 0,
|
|
479
|
+
"generated_at": 1710503600,
|
|
480
|
+
"items": [
|
|
481
|
+
{
|
|
482
|
+
"tag": "NEW",
|
|
483
|
+
"count_window": 5,
|
|
484
|
+
"total_seen": 5,
|
|
485
|
+
"severity": "ERROR",
|
|
486
|
+
"pattern": "ERROR connection to <IP> refused",
|
|
487
|
+
"sample": "2024-03-15 10:23:45 ERROR connection to 10.0.0.3 refused",
|
|
488
|
+
"hash": "a1b2c3d4..."
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Useful jq Queries
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# Count of new patterns only
|
|
498
|
+
log-whisperer --file app.log --json | jq '[.items[] | select(.tag == "NEW")] | length'
|
|
499
|
+
|
|
500
|
+
# List ERROR patterns with their counts
|
|
501
|
+
log-whisperer --file app.log --json | jq '.items[] | select(.severity == "ERROR") | {pattern, count_window}'
|
|
502
|
+
|
|
503
|
+
# Exit non-zero if any new ERROR patterns found
|
|
504
|
+
log-whisperer --file app.log --json | jq -e '[.items[] | select(.tag == "NEW" and .severity == "ERROR")] | length > 0'
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Recipes
|
|
510
|
+
|
|
511
|
+
### Cron Job — Check Every 5 Minutes, Alert on New Patterns
|
|
512
|
+
|
|
513
|
+
```cron
|
|
514
|
+
*/5 * * * * /usr/local/bin/log-whisperer --file /var/log/app.log --since 5m --notify-ntfy-topic my-alerts 2>>/var/log/logwhisperer-errors.log
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### First-Time Setup with Baseline Learning
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
# Step 1: Learn existing patterns for 24 hours
|
|
521
|
+
log-whisperer --file /var/log/app.log --baseline-learn 24h
|
|
522
|
+
|
|
523
|
+
# Step 2: Set up the cron job (alerts will start after 24h)
|
|
524
|
+
crontab -e
|
|
525
|
+
# */5 * * * * log-whisperer --file /var/log/app.log --since 5m --notify-ntfy-topic my-alerts
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Monitor Multiple Services
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
#!/bin/bash
|
|
532
|
+
# monitor-all.sh — run from cron
|
|
533
|
+
|
|
534
|
+
log-whisperer --docker api-server --since 5m --state-db /var/lib/lw/api.db
|
|
535
|
+
log-whisperer --docker worker --since 5m --state-db /var/lib/lw/worker.db
|
|
536
|
+
log-whisperer --service nginx --since 5m --state-db /var/lib/lw/nginx.db
|
|
537
|
+
log-whisperer --file /var/log/app.log --since 5m --state-db /var/lib/lw/app.db
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
> Use separate `--state-db` paths to keep pattern histories isolated per service.
|
|
541
|
+
|
|
542
|
+
### Errors-Only Daily Digest
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
log-whisperer --file /var/log/app.log \
|
|
546
|
+
--since 24h \
|
|
547
|
+
--min-severity ERROR \
|
|
548
|
+
--show-samples \
|
|
549
|
+
--notify-email-host smtp.company.com \
|
|
550
|
+
--notify-email-from lw@company.com \
|
|
551
|
+
--notify-email-to oncall@company.com
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### CI / Smoke Test — Fail If New Error Patterns Appear
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
log-whisperer --file test-output.log --json \
|
|
558
|
+
| jq -e '[.items[] | select(.tag == "NEW" and .severity == "ERROR")] | length == 0' \
|
|
559
|
+
|| { echo "New error patterns detected!"; exit 1; }
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Pipe Logs Directly (via process substitution)
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
log-whisperer --file <(kubectl logs deploy/my-app --since=1h) --show-new
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Environment Variables
|
|
571
|
+
|
|
572
|
+
All notification settings can be set via environment variables so they don't
|
|
573
|
+
appear in process listings or shell history:
|
|
574
|
+
|
|
575
|
+
| Variable | Maps To |
|
|
576
|
+
|:---------|:--------|
|
|
577
|
+
| `LOGWHISPERER_NTFY_TOPIC` | `--notify-ntfy-topic` |
|
|
578
|
+
| `LOGWHISPERER_NTFY_SERVER` | `--notify-ntfy-server` |
|
|
579
|
+
| `LOGWHISPERER_TELEGRAM_TOKEN` | `--notify-telegram-token` |
|
|
580
|
+
| `LOGWHISPERER_TELEGRAM_CHAT_ID` | `--notify-telegram-chat-id` |
|
|
581
|
+
| `LOGWHISPERER_SMTP_HOST` | `--notify-email-host` |
|
|
582
|
+
| `LOGWHISPERER_SMTP_PORT` | `--notify-email-port` |
|
|
583
|
+
| `LOGWHISPERER_SMTP_USER` | `--notify-email-user` |
|
|
584
|
+
| `LOGWHISPERER_SMTP_PASS` | `--notify-email-pass` |
|
|
585
|
+
| `LOGWHISPERER_EMAIL_FROM` | `--notify-email-from` |
|
|
586
|
+
| `LOGWHISPERER_EMAIL_TO` | `--notify-email-to` |
|
|
587
|
+
| `XDG_STATE_HOME` | Base directory for state files (default: `~/.local/state`) |
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Troubleshooting
|
|
592
|
+
|
|
593
|
+
### "Command not found: docker"
|
|
594
|
+
|
|
595
|
+
The Docker or journalctl binary isn't on `$PATH`. Make sure the relevant tool
|
|
596
|
+
is installed and accessible to the user running LogWhisperer.
|
|
597
|
+
|
|
598
|
+
### "Choose exactly one source"
|
|
599
|
+
|
|
600
|
+
You must specify exactly one of `--file`, `--docker`, `--compose`, `--compose-all`,
|
|
601
|
+
or `--service`. You cannot combine sources in a single run.
|
|
602
|
+
|
|
603
|
+
### Every run shows `[NEW]`
|
|
604
|
+
|
|
605
|
+
Your state DB is being reset or isn't persisting. Check:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
ls -la ~/.local/state/logwhisperer/patterns.db
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
If the file doesn't exist between runs, verify that `--state-db` points to a
|
|
612
|
+
writable, persistent location.
|
|
613
|
+
|
|
614
|
+
### No alerts firing
|
|
615
|
+
|
|
616
|
+
- Check that baseline learning has expired: look for `Baseline: ACTIVE` in the
|
|
617
|
+
text report output.
|
|
618
|
+
- Alerts only fire for `[NEW]` patterns — if all patterns are `[seen]`, no alert
|
|
619
|
+
is sent.
|
|
620
|
+
- Verify your notification credentials are correct by checking stderr for
|
|
621
|
+
`"Notification failures:"` messages.
|
|
622
|
+
|
|
623
|
+
### Database looks corrupted
|
|
624
|
+
|
|
625
|
+
The DB is JSON-lines. Validate it:
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
python3 -c "
|
|
629
|
+
import json, sys
|
|
630
|
+
for i, line in enumerate(open(sys.argv[1]), 1):
|
|
631
|
+
try: json.loads(line)
|
|
632
|
+
except: print(f'Bad line {i}: {line.rstrip()}')
|
|
633
|
+
" ~/.local/state/logwhisperer/patterns.db
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
If corruption occurred, reset and re-learn:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
log-whisperer --reset --file /dev/null
|
|
640
|
+
log-whisperer --file /var/log/app.log --baseline-learn 1h
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
<p align="center">
|
|
646
|
+
<sub>LogWhisperer — Built with Python 3.9+</sub>
|
|
647
|
+
</p>
|