Merge pull request #27410 from florianjacob/journalwatch

journalwatch & journalwatch service: init at 1.1.0

authored by Jörg Thalheim and committed by GitHub 26f85e42 f59e71e9

+293
+1
nixos/modules/module-list.nix
··· 243 243 ./services/logging/graylog.nix 244 244 ./services/logging/heartbeat.nix 245 245 ./services/logging/journalbeat.nix 246 + ./services/logging/journalwatch.nix 246 247 ./services/logging/klogd.nix 247 248 ./services/logging/logcheck.nix 248 249 ./services/logging/logrotate.nix
+246
nixos/modules/services/logging/journalwatch.nix
··· 1 + { config, lib, pkgs, services, ... }: 2 + with lib; 3 + 4 + let 5 + cfg = config.services.journalwatch; 6 + user = "journalwatch"; 7 + dataDir = "/var/lib/${user}"; 8 + 9 + journalwatchConfig = pkgs.writeText "config" ('' 10 + # (File Generated by NixOS journalwatch module.) 11 + [DEFAULT] 12 + mail_binary = ${cfg.mailBinary} 13 + priority = ${toString cfg.priority} 14 + mail_from = ${cfg.mailFrom} 15 + '' 16 + + optionalString (cfg.mailTo != null) '' 17 + mail_to = ${cfg.mailTo} 18 + '' 19 + + cfg.extraConfig); 20 + 21 + journalwatchPatterns = pkgs.writeText "patterns" '' 22 + # (File Generated by NixOS journalwatch module.) 23 + 24 + ${mkPatterns cfg.filterBlocks} 25 + ''; 26 + 27 + # empty line at the end needed to to separate the blocks 28 + mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: '' 29 + ${block.match} 30 + ${block.filters} 31 + 32 + '') filterBlocks); 33 + 34 + 35 + in { 36 + options = { 37 + services.journalwatch = { 38 + enable = mkOption { 39 + type = types.bool; 40 + default = false; 41 + description = '' 42 + If enabled, periodically check the journal with journalwatch and report the results by mail. 43 + ''; 44 + }; 45 + 46 + priority = mkOption { 47 + type = types.int; 48 + default = 6; 49 + description = '' 50 + Lowest priority of message to be considered. 51 + A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info"). 52 + If you don't care about anything with "info" priority, you can reduce 53 + this to e.g. 5 ("notice") to considerably reduce the amount of 54 + messages without needing many <option>filterBlocks</option>. 55 + ''; 56 + }; 57 + 58 + # HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if 59 + # there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and 60 + # then return something right-ish in the direction of /etc/hostname. Just bypass it completely. 61 + mailFrom = mkOption { 62 + type = types.str; 63 + default = "journalwatch@${config.networking.hostName}"; 64 + description = '' 65 + Mail address to send journalwatch reports from. 66 + ''; 67 + }; 68 + 69 + mailTo = mkOption { 70 + type = types.nullOr types.str; 71 + default = null; 72 + description = '' 73 + Mail address to send journalwatch reports to. 74 + ''; 75 + }; 76 + 77 + mailBinary = mkOption { 78 + type = types.path; 79 + default = "/run/wrappers/bin/sendmail"; 80 + description = '' 81 + Sendmail-compatible binary to be used to send the messages. 82 + ''; 83 + }; 84 + 85 + extraConfig = mkOption { 86 + type = types.str; 87 + default = ""; 88 + description = '' 89 + Extra lines to be added verbatim to the journalwatch/config configuration file. 90 + You can add any commandline argument to the config, without the '--'. 91 + See <literal>journalwatch --help</literal> for all arguments and their description. 92 + ''; 93 + }; 94 + 95 + filterBlocks = mkOption { 96 + type = types.listOf (types.submodule { 97 + options = { 98 + match = mkOption { 99 + type = types.str; 100 + example = "SYSLOG_IDENTIFIER = systemd"; 101 + description = '' 102 + Syntax: <literal>field = value</literal> 103 + Specifies the log entry <literal>field</literal> this block should apply to. 104 + If the <literal>field</literal> of a message matches this <literal>value</literal>, 105 + this patternBlock's <option>filters</option> are applied. 106 + If <literal>value</literal> starts and ends with a slash, it is interpreted as 107 + an extended python regular expression, if not, it's an exact match. 108 + The journal fields are explained in systemd.journal-fields(7). 109 + ''; 110 + }; 111 + 112 + filters = mkOption { 113 + type = types.str; 114 + example = '' 115 + (Stopped|Stopping|Starting|Started) .* 116 + (Reached target|Stopped target) .* 117 + ''; 118 + description = '' 119 + The filters to apply on all messages which satisfy <option>match</option>. 120 + Any of those messages that match any specified filter will be removed from journalwatch's output. 121 + Each filter is an extended Python regular expression. 122 + You can specify multiple filters and separate them by newlines. 123 + Lines starting with '#' are comments. Inline-comments are not permitted. 124 + ''; 125 + }; 126 + }; 127 + }); 128 + 129 + example = [ 130 + # examples taken from upstream 131 + { 132 + match = "_SYSTEMD_UNIT = systemd-logind.service"; 133 + filters = '' 134 + New session [a-z]?\d+ of user \w+\. 135 + Removed session [a-z]?\d+\. 136 + ''; 137 + } 138 + 139 + { 140 + match = "SYSLOG_IDENTIFIER = /(CROND|crond)/"; 141 + filters = '' 142 + pam_unix\(crond:session\): session (opened|closed) for user \w+ 143 + \(\w+\) CMD .* 144 + ''; 145 + } 146 + ]; 147 + 148 + # another example from upstream. 149 + # very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all. 150 + default = [ 151 + { 152 + match = "SYSLOG_IDENTIFIER = systemd"; 153 + filters = '' 154 + (Stopped|Stopping|Starting|Started) .* 155 + (Created slice|Removed slice) user-\d*\.slice\. 156 + Received SIGRTMIN\+24 from PID .* 157 + (Reached target|Stopped target) .* 158 + Startup finished in \d*ms\. 159 + ''; 160 + } 161 + ]; 162 + 163 + 164 + description = '' 165 + filterBlocks can be defined to blacklist journal messages which are not errors. 166 + Each block matches on a log entry field, and the filters in that block then are matched 167 + against all messages with a matching log entry field. 168 + 169 + All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch. 170 + If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default. 171 + 172 + All regular expressions are extended Python regular expressions, for details 173 + see: http://doc.pyschools.com/html/regex.html 174 + ''; 175 + }; 176 + 177 + interval = mkOption { 178 + type = types.str; 179 + default = "hourly"; 180 + description = '' 181 + How often to run journalwatch. 182 + 183 + The format is described in systemd.time(7). 184 + ''; 185 + }; 186 + accuracy = mkOption { 187 + type = types.str; 188 + default = "10min"; 189 + description = '' 190 + The time window around the interval in which the journalwatch run will be scheduled. 191 + 192 + The format is described in systemd.time(7). 193 + ''; 194 + }; 195 + }; 196 + }; 197 + 198 + config = mkIf cfg.enable { 199 + 200 + users.extraUsers.${user} = { 201 + isSystemUser = true; 202 + createHome = true; 203 + home = dataDir; 204 + # for journal access 205 + group = "systemd-journal"; 206 + }; 207 + 208 + systemd.services.journalwatch = { 209 + environment = { 210 + XDG_DATA_HOME = "${dataDir}/share"; 211 + XDG_CONFIG_HOME = "${dataDir}/config"; 212 + }; 213 + serviceConfig = { 214 + User = user; 215 + Type = "oneshot"; 216 + PermissionsStartOnly = true; 217 + ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail"; 218 + # lowest CPU and IO priority, but both still in best-effort class to prevent starvation 219 + Nice=19; 220 + IOSchedulingPriority=7; 221 + }; 222 + preStart = '' 223 + chown -R ${user}:systemd-journal ${dataDir} 224 + chmod -R u+rwX,go-w ${dataDir} 225 + mkdir -p ${dataDir}/config/journalwatch 226 + ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config 227 + ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns 228 + ''; 229 + }; 230 + 231 + systemd.timers.journalwatch = { 232 + description = "Periodic journalwatch run"; 233 + wantedBy = [ "timers.target" ]; 234 + timerConfig = { 235 + OnCalendar = cfg.interval; 236 + AccuracySec = cfg.accuracy; 237 + Persistent = true; 238 + }; 239 + }; 240 + 241 + }; 242 + 243 + meta = { 244 + maintainers = with stdenv.lib.maintainers; [ florianjacob ]; 245 + }; 246 + }
+43
pkgs/tools/system/journalwatch/default.nix
··· 1 + { stdenv, buildPythonPackage, fetchurl, fetchgit, pythonOlder, systemd, pytest }: 2 + 3 + buildPythonPackage rec { 4 + pname = "journalwatch"; 5 + name = "${pname}-${version}"; 6 + version = "1.1.0"; 7 + disabled = pythonOlder "3.3"; 8 + 9 + 10 + src = fetchurl { 11 + url = "https://github.com/The-Compiler/${pname}/archive/v${version}.tar.gz"; 12 + sha512 = "3hvbgx95hjfivz9iv0hbhj720wvm32z86vj4a60lji2zdfpbqgr2b428lvg2cpvf71l2xn6ca5v0hzyz57qylgwqzgfrx7hqhl5g38s"; 13 + }; 14 + 15 + # can be removed post 1.1.0 16 + postPatch = '' 17 + substituteInPlace test_journalwatch.py \ 18 + --replace "U Thu Jan 1 00:00:00 1970 prio foo [1337]" "U Thu Jan 1 00:00:00 1970 pprio foo [1337]" 19 + ''; 20 + 21 + 22 + doCheck = true; 23 + 24 + checkPhase = '' 25 + pytest test_journalwatch.py 26 + ''; 27 + 28 + buildInputs = [ 29 + pytest 30 + ]; 31 + 32 + propagatedBuildInputs = [ 33 + systemd 34 + ]; 35 + 36 + 37 + meta = with stdenv.lib; { 38 + description = "journalwatch is a tool to find error messages in the systemd journal."; 39 + homepage = "https://github.com/The-Compiler/journalwatch"; 40 + license = licenses.gpl3Plus; 41 + maintainers = with maintainers; [ florianjacob ]; 42 + }; 43 + }
+3
pkgs/top-level/python-packages.nix
··· 12387 12387 }; 12388 12388 }; 12389 12389 12390 + journalwatch = callPackage ../tools/system/journalwatch { 12391 + inherit (self) systemd pytest; 12392 + }; 12390 12393 12391 12394 jrnl = buildPythonPackage rec { 12392 12395 name = "jrnl-1.9.7";