diff --git a/README.md b/README.md
index ac031bc..6b4005f 100644
--- a/README.md
+++ b/README.md
@@ -46,9 +46,16 @@ message, host and app. Words are combined with AND.
`error AND host:web-01`, `app:~"ssh|nginx"`, or `* | stats by (host) count()`.
The live view is disabled in this mode.
-Each row shows, from left to right: the **reception time** (server clock, stored in the
-`received` field), the timestamp found in the message itself, severity, host, app and message.
-Logs stored before the `received` field existed show `—` in the first column.
+Each row shows, from left to right: the **reception time** (server clock), the timestamp
+found in the message itself (`msg_time`), severity, host, app and message. Click a host or an
+app to filter on it.
+
+Logs are indexed, searched and sorted by **reception time**: devices with a wrong clock
+(e.g. access points whose NTP fails) still show up in the right time range. Logs stored by
+versions before this change are indexed by their message timestamp; purge them from
+Settings to start clean.
+
+Severity badges `err`/`crit` and `warning` use the colors of the `error` and `warning` tags.
Shortcuts: `/` focuses the search box, `Esc` clears it. Clicking a row shows all its fields.
@@ -80,7 +87,8 @@ are only known by your router or a local DNS (Pi-hole, AdGuard, Unbound…), set
- **Color tags**: each tag has a keyword, a background color (the text automatically
switches to black or white to stay readable) and options: whole word, match case,
regular expression, active. Tags are stored in `/data/tags.json` (`logstream-data` volume).
- Default tags: `warning` (orange), `error` (red), `ok` (green).
+ Default tags (pastel): `warning` (orange), `error` (red), `ok` (green). Default tags
+ still using the colors of earlier versions are switched to the pastel ones automatically.
## Configuration
diff --git a/syslog.go b/syslog.go
index dbe572b..72ba7c4 100644
--- a/syslog.go
+++ b/syslog.go
@@ -38,10 +38,15 @@ type Entry struct {
}
// Record returns the entry in the shape stored in VictoriaLogs and returned by the API.
+// The time axis (_time) is the reception time: device clocks can be wrong, and
+// logs indexed in the past would escape time ranges. The device timestamp is
+// kept in msg_time.
func (e *Entry) Record() map[string]string {
+ rcv := e.Received.UTC().Format(time.RFC3339Nano)
r := map[string]string{
- "_time": e.Time.UTC().Format(time.RFC3339Nano),
- "received": e.Received.UTC().Format(time.RFC3339Nano),
+ "_time": rcv,
+ "received": rcv,
+ "msg_time": e.Time.UTC().Format(time.RFC3339Nano),
"_msg": e.Message,
"host": e.Host,
"app": e.App,
diff --git a/tags.go b/tags.go
index d5dc81a..1b59c62 100644
--- a/tags.go
+++ b/tags.go
@@ -26,12 +26,31 @@ type Tag struct {
func defaultTags() []Tag {
return []Tag{
- {ID: "warning", Pattern: "warning", Color: "#f59e0b", WholeWord: true, Enabled: true},
- {ID: "error", Pattern: "error", Color: "#ef4444", WholeWord: true, Enabled: true},
- {ID: "ok", Pattern: "ok", Color: "#22c55e", WholeWord: true, Enabled: true},
+ {ID: "warning", Pattern: "warning", Color: "#fdba74", WholeWord: true, Enabled: true},
+ {ID: "error", Pattern: "error", Color: "#fca5a5", WholeWord: true, Enabled: true},
+ {ID: "ok", Pattern: "ok", Color: "#86efac", WholeWord: true, Enabled: true},
}
}
+// Colors of the default tags in earlier versions: still unchanged, they are
+// switched to the new pastel defaults when the file is loaded.
+var oldDefaultColors = map[string]string{"warning": "#f59e0b", "error": "#ef4444", "ok": "#22c55e"}
+
+func migrateDefaultColors(tags []Tag) bool {
+ newColors := map[string]string{}
+ for _, t := range defaultTags() {
+ newColors[t.ID] = t.Color
+ }
+ changed := false
+ for i := range tags {
+ if old, ok := oldDefaultColors[tags[i].ID]; ok && strings.EqualFold(tags[i].Color, old) {
+ tags[i].Color = newColors[tags[i].ID]
+ changed = true
+ }
+ }
+ return changed
+}
+
// codedError carries a stable code the UI can translate.
type codedError struct {
code string
@@ -87,6 +106,11 @@ func LoadTagStore(path string) (*TagStore, error) {
if err := json.Unmarshal(b, &s.tags); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
+ if migrateDefaultColors(s.tags) {
+ if err := s.save(); err != nil {
+ return nil, err
+ }
+ }
return s, nil
}
diff --git a/web/app.js b/web/app.js
index 282dcaa..c0a4aec 100644
--- a/web/app.js
+++ b/web/app.js
@@ -65,6 +65,7 @@ const I18N = {
rcvTitle: 'Received by the server',
msgTimeTitle: 'Timestamp from the message',
fHostName: 'host name (DNS)', fHostIp: 'host IP',
+ clickHost: 'Click to filter on this host', clickApp: 'Click to filter on this app',
dangerZone: 'Danger zone',
purgeHelp: 'Permanently delete every stored log to start from scratch. Color tags and settings are kept.',
purgeBtn: 'Delete all logs…',
@@ -139,6 +140,7 @@ const I18N = {
rcvTitle: 'Reçu par le serveur',
msgTimeTitle: 'Horodatage contenu dans le message',
fHostName: 'nom d\'hôte (DNS)', fHostIp: 'IP de l\'hôte',
+ clickHost: 'Cliquer pour filtrer sur cet hôte', clickApp: 'Cliquer pour filtrer sur cette appli',
dangerZone: 'Zone de danger',
purgeHelp: 'Supprime définitivement tous les logs stockés pour repartir de zéro. Les tags de couleur et les réglages sont conservés.',
purgeBtn: 'Supprimer tous les logs…',
@@ -393,7 +395,23 @@ function textOn(hex) {
const tagStyle = (color) => `background:${color};color:${textOn(color)}`;
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+// Severity badges share the colors of the "error" and "warning" tags.
+function applySeverityColors() {
+ const style = document.documentElement.style;
+ for (const name of ['error', 'warning']) {
+ const tag = state.tags.find((x) => String(x.pattern).toLowerCase() === name && /^#[0-9a-f]{6}$/i.test(x.color));
+ if (tag) {
+ style.setProperty(`--tag-${name}`, tag.color);
+ style.setProperty(`--tag-${name}-text`, textOn(tag.color));
+ } else {
+ style.removeProperty(`--tag-${name}`);
+ style.removeProperty(`--tag-${name}-text`);
+ }
+ }
+}
+
function compileMatchers() {
+ applySeverityColors();
const out = [];
for (const tag of state.tags) {
if (!tag.enabled || !tag.pattern) continue;
@@ -457,10 +475,14 @@ function rowHTML(r, isNew) {
const cls = SEV_CLASS[sev] || 'info';
return `