diff --git a/modules/module-list.nix b/modules/module-list.nix index ee326205973a..0a6507cd5e6d 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -83,6 +83,7 @@ ./services/hardware/upower.nix ./services/logging/klogd.nix ./services/logging/logrotate.nix + ./services/logging/logstash.nix ./services/logging/syslogd.nix ./services/mail/dovecot.nix ./services/mail/dovecot2.nix diff --git a/modules/services/logging/logstash.nix b/modules/services/logging/logstash.nix new file mode 100644 index 000000000000..f8b0d353cad6 --- /dev/null +++ b/modules/services/logging/logstash.nix @@ -0,0 +1,152 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + cfg = config.services.logstash; + + listToConfig = list: "[ " + (concatStringsSep ", " (map exprToConfig list)) + " ]"; + + hashToConfig = attrs: + let + attrNameToConfigList = name: + [ (exprToConfig name) (exprToConfig (getAttr name attrs)) ]; + in + "[ " + + (concatStringsSep ", " (map attrNameToConfigList (attrNames attrs))) + + " ]"; + + valueToConfig = name: value: + if (isAttrs value) && ((!(value ? __type)) || value.__type == "repeated") + then '' + ${name} { + ${exprToConfig value} + } + '' + else "${name} => ${exprToConfig value}"; + + repeatedAttrsToConfig = names: values: + concatStringsSep "\n" (zipListsWith valueToConfig names values); + + attrsToConfig = attrs: + let + attrToConfig = name: valueToConfig name (getAttr name attrs); + in + concatStringsSep "\n" (map attrToConfig (attrNames attrs)); + + exprToConfig = expr: + let + isCustomType = expr: (isAttrs expr) && (expr ? __type); + + isFloat = expr: (isCustomType expr) && (expr.__type == "float"); + + isHash = expr: (isCustomType expr) && (expr.__type == "hash"); + + isRepeatedAttrs = expr: (isCustomType expr) && (expr.__type == "repeated"); + in + if builtins.isBool expr then (if expr then "true" else "false") else + if builtins.isString expr then ''"${expr}"'' else + if builtins.isInt expr then toString expr else + if isFloat expr then expr.value else + if isList expr then listToConfig expr else + if isHash expr then hashToConfig expr.value else + if isRepeatedAttrs expr then repeatedAttrsToConfig expr.names expr.values + else attrsToConfig expr; + + mergeConfigs = configs: + let + op = attrs: newAttrs: + let + isRepeated = newAttrs ? __type && newAttrs.__type == "repeated"; + in { + names = attrs.names ++ + (if isRepeated then newAttrs.names else attrNames newAttrs); + + values = attrs.values ++ + (if isRepeated then newAttrs.values else attrValues newAttrs); + }; + in (foldl op { names = []; values = []; } configs) // + { __type = "repeated"; }; + +in + +{ + ###### interface + + options = { + services.logstash = { + enable = mkOption { + default = false; + description = '' + Enable logstash. + ''; + }; + + inputConfig = mkOption { + default = {}; + description = '' + An attr set representing a logstash configuration's input section. + logstash configs are name-value pairs, where values can be bools, + strings, numbers, arrays, hashes, or other name-value pairs, + and names are strings that can be repeated. name-value pairs with no + repeats are represented by attr sets. name-value pairs with repeats + are represented by an attrset with attr "__type" = "repeated" and + attrs "names" and "values" as matching lists pairing name and value. + bools, strings, ints, and arrays are mapped directly. Floats are + represented as an attrset with attr "__type" = "float" and attr value + set to the string representation of the float. Hashes are represented + with attr "__type" = "hash" and attr value set to an attr set + corresponding to the hash. + ''; + merge = mergeConfigs; + }; + + filterConfig = mkOption { + default = {}; + description = '' + An attr set representing a logstash configuration's filter section. + See inputConfig description for details. + ''; + merge = mergeConfigs; + }; + + outputConfig = mkOption { + default = {}; + description = '' + An attr set representing a logstash configuration's output section. + See inputConfig description for details. + ''; + merge = mergeConfigs; + }; + }; + }; + + + ###### implementation + + config = mkIf cfg.enable { + # Always log to stdout + services.logstash.outputConfig = { stdout = {}; }; + + jobs.logstash = with pkgs; { + description = "Logstash daemon"; + + path = [ jre ]; + + exec = "java -jar ${logstash} agent -f ${writeText "logstash.conf" '' + input { + ${exprToConfig cfg.inputConfig} + } + + filter { + ${exprToConfig cfg.filterConfig} + } + + output { + ${exprToConfig cfg.outputConfig} + } + ''}"; + }; + }; +}