journalwatch & journalwatch service: init at 1.1.0

Changed files
+293
nixos
modules
pkgs
tools
system
journalwatch
top-level
+1
nixos/modules/module-list.nix
···
./services/logging/graylog.nix
./services/logging/heartbeat.nix
./services/logging/journalbeat.nix
+
./services/logging/journalwatch.nix
./services/logging/klogd.nix
./services/logging/logcheck.nix
./services/logging/logrotate.nix
+246
nixos/modules/services/logging/journalwatch.nix
···
+
{ config, lib, pkgs, services, ... }:
+
with lib;
+
+
let
+
cfg = config.services.journalwatch;
+
user = "journalwatch";
+
dataDir = "/var/lib/${user}";
+
+
journalwatchConfig = pkgs.writeText "config" (''
+
# (File Generated by NixOS journalwatch module.)
+
[DEFAULT]
+
mail_binary = ${cfg.mailBinary}
+
priority = ${toString cfg.priority}
+
mail_from = ${cfg.mailFrom}
+
''
+
+ optionalString (cfg.mailTo != null) ''
+
mail_to = ${cfg.mailTo}
+
''
+
+ cfg.extraConfig);
+
+
journalwatchPatterns = pkgs.writeText "patterns" ''
+
# (File Generated by NixOS journalwatch module.)
+
+
${mkPatterns cfg.filterBlocks}
+
'';
+
+
# empty line at the end needed to to separate the blocks
+
mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: ''
+
${block.match}
+
${block.filters}
+
+
'') filterBlocks);
+
+
+
in {
+
options = {
+
services.journalwatch = {
+
enable = mkOption {
+
type = types.bool;
+
default = false;
+
description = ''
+
If enabled, periodically check the journal with journalwatch and report the results by mail.
+
'';
+
};
+
+
priority = mkOption {
+
type = types.int;
+
default = 6;
+
description = ''
+
Lowest priority of message to be considered.
+
A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info").
+
If you don't care about anything with "info" priority, you can reduce
+
this to e.g. 5 ("notice") to considerably reduce the amount of
+
messages without needing many <option>filterBlocks</option>.
+
'';
+
};
+
+
# HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if
+
# there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and
+
# then return something right-ish in the direction of /etc/hostname. Just bypass it completely.
+
mailFrom = mkOption {
+
type = types.str;
+
default = "journalwatch@${config.networking.hostName}";
+
description = ''
+
Mail address to send journalwatch reports from.
+
'';
+
};
+
+
mailTo = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = ''
+
Mail address to send journalwatch reports to.
+
'';
+
};
+
+
mailBinary = mkOption {
+
type = types.path;
+
default = "/run/wrappers/bin/sendmail";
+
description = ''
+
Sendmail-compatible binary to be used to send the messages.
+
'';
+
};
+
+
extraConfig = mkOption {
+
type = types.str;
+
default = "";
+
description = ''
+
Extra lines to be added verbatim to the journalwatch/config configuration file.
+
You can add any commandline argument to the config, without the '--'.
+
See <literal>journalwatch --help</literal> for all arguments and their description.
+
'';
+
};
+
+
filterBlocks = mkOption {
+
type = types.listOf (types.submodule {
+
options = {
+
match = mkOption {
+
type = types.str;
+
example = "SYSLOG_IDENTIFIER = systemd";
+
description = ''
+
Syntax: <literal>field = value</literal>
+
Specifies the log entry <literal>field</literal> this block should apply to.
+
If the <literal>field</literal> of a message matches this <literal>value</literal>,
+
this patternBlock's <option>filters</option> are applied.
+
If <literal>value</literal> starts and ends with a slash, it is interpreted as
+
an extended python regular expression, if not, it's an exact match.
+
The journal fields are explained in systemd.journal-fields(7).
+
'';
+
};
+
+
filters = mkOption {
+
type = types.str;
+
example = ''
+
(Stopped|Stopping|Starting|Started) .*
+
(Reached target|Stopped target) .*
+
'';
+
description = ''
+
The filters to apply on all messages which satisfy <option>match</option>.
+
Any of those messages that match any specified filter will be removed from journalwatch's output.
+
Each filter is an extended Python regular expression.
+
You can specify multiple filters and separate them by newlines.
+
Lines starting with '#' are comments. Inline-comments are not permitted.
+
'';
+
};
+
};
+
});
+
+
example = [
+
# examples taken from upstream
+
{
+
match = "_SYSTEMD_UNIT = systemd-logind.service";
+
filters = ''
+
New session [a-z]?\d+ of user \w+\.
+
Removed session [a-z]?\d+\.
+
'';
+
}
+
+
{
+
match = "SYSLOG_IDENTIFIER = /(CROND|crond)/";
+
filters = ''
+
pam_unix\(crond:session\): session (opened|closed) for user \w+
+
\(\w+\) CMD .*
+
'';
+
}
+
];
+
+
# another example from upstream.
+
# very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all.
+
default = [
+
{
+
match = "SYSLOG_IDENTIFIER = systemd";
+
filters = ''
+
(Stopped|Stopping|Starting|Started) .*
+
(Created slice|Removed slice) user-\d*\.slice\.
+
Received SIGRTMIN\+24 from PID .*
+
(Reached target|Stopped target) .*
+
Startup finished in \d*ms\.
+
'';
+
}
+
];
+
+
+
description = ''
+
filterBlocks can be defined to blacklist journal messages which are not errors.
+
Each block matches on a log entry field, and the filters in that block then are matched
+
against all messages with a matching log entry field.
+
+
All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch.
+
If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default.
+
+
All regular expressions are extended Python regular expressions, for details
+
see: http://doc.pyschools.com/html/regex.html
+
'';
+
};
+
+
interval = mkOption {
+
type = types.str;
+
default = "hourly";
+
description = ''
+
How often to run journalwatch.
+
+
The format is described in systemd.time(7).
+
'';
+
};
+
accuracy = mkOption {
+
type = types.str;
+
default = "10min";
+
description = ''
+
The time window around the interval in which the journalwatch run will be scheduled.
+
+
The format is described in systemd.time(7).
+
'';
+
};
+
};
+
};
+
+
config = mkIf cfg.enable {
+
+
users.extraUsers.${user} = {
+
isSystemUser = true;
+
createHome = true;
+
home = dataDir;
+
# for journal access
+
group = "systemd-journal";
+
};
+
+
systemd.services.journalwatch = {
+
environment = {
+
XDG_DATA_HOME = "${dataDir}/share";
+
XDG_CONFIG_HOME = "${dataDir}/config";
+
};
+
serviceConfig = {
+
User = user;
+
Type = "oneshot";
+
PermissionsStartOnly = true;
+
ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
+
# lowest CPU and IO priority, but both still in best-effort class to prevent starvation
+
Nice=19;
+
IOSchedulingPriority=7;
+
};
+
preStart = ''
+
chown -R ${user}:systemd-journal ${dataDir}
+
chmod -R u+rwX,go-w ${dataDir}
+
mkdir -p ${dataDir}/config/journalwatch
+
ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config
+
ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns
+
'';
+
};
+
+
systemd.timers.journalwatch = {
+
description = "Periodic journalwatch run";
+
wantedBy = [ "timers.target" ];
+
timerConfig = {
+
OnCalendar = cfg.interval;
+
AccuracySec = cfg.accuracy;
+
Persistent = true;
+
};
+
};
+
+
};
+
+
meta = {
+
maintainers = with stdenv.lib.maintainers; [ florianjacob ];
+
};
+
}
+43
pkgs/tools/system/journalwatch/default.nix
···
+
{ stdenv, buildPythonPackage, fetchurl, fetchgit, pythonOlder, systemd, pytest }:
+
+
buildPythonPackage rec {
+
pname = "journalwatch";
+
name = "${pname}-${version}";
+
version = "1.1.0";
+
disabled = pythonOlder "3.3";
+
+
+
src = fetchurl {
+
url = "https://github.com/The-Compiler/${pname}/archive/v${version}.tar.gz";
+
sha512 = "3hvbgx95hjfivz9iv0hbhj720wvm32z86vj4a60lji2zdfpbqgr2b428lvg2cpvf71l2xn6ca5v0hzyz57qylgwqzgfrx7hqhl5g38s";
+
};
+
+
# can be removed post 1.1.0
+
postPatch = ''
+
substituteInPlace test_journalwatch.py \
+
--replace "U Thu Jan 1 00:00:00 1970 prio foo [1337]" "U Thu Jan 1 00:00:00 1970 pprio foo [1337]"
+
'';
+
+
+
doCheck = true;
+
+
checkPhase = ''
+
pytest test_journalwatch.py
+
'';
+
+
buildInputs = [
+
pytest
+
];
+
+
propagatedBuildInputs = [
+
systemd
+
];
+
+
+
meta = with stdenv.lib; {
+
description = "journalwatch is a tool to find error messages in the systemd journal.";
+
homepage = "https://github.com/The-Compiler/journalwatch";
+
license = licenses.gpl3Plus;
+
maintainers = with maintainers; [ florianjacob ];
+
};
+
}
+3
pkgs/top-level/python-packages.nix
···
+
journalwatch = callPackage ../tools/system/journalwatch {
+
inherit (self) systemd pytest;
+
};
jrnl = buildPythonPackage rec {
name = "jrnl-1.9.7";