{ lib, pkgs, config, ... }: let cfg = config.services.amazon-cloudwatch-agent; tomlFormat = pkgs.formats.toml { }; jsonFormat = pkgs.formats.json { }; # See https://docs.aws.amazon.com/prescriptive-guidance/latest/implementing-logging-monitoring-cloudwatch/create-store-cloudwatch-configurations.html#store-cloudwatch-configuration-s3. # # We don't use the multiple JSON configuration files feature, # but "config-translator" will log a benign error if the "-input-dir" option is omitted or is a non-existent directory. # # Create an empty directory to hide this benign error log. This prevents false-positives if users filter for "error" in the agent logs. configurationDirectory = pkgs.runCommand "amazon-cloudwatch-agent.d" { } "mkdir $out"; in { options.services.amazon-cloudwatch-agent = { enable = lib.mkEnableOption "Amazon CloudWatch Agent"; package = lib.mkPackageOption pkgs "amazon-cloudwatch-agent" { }; commonConfigurationFile = lib.mkOption { type = lib.types.path; default = tomlFormat.generate "common-config.toml" cfg.commonConfiguration; defaultText = lib.literalExpression ''tomlFormat.generate "common-config.toml" cfg.commonConfiguration''; description = '' Amazon CloudWatch Agent common configuration. See for supported values. {option}`commonConfigurationFile` takes precedence over {option}`commonConfiguration`. Note: Restricted evaluation blocks access to paths outside the Nix store. This means detecting content changes for mutable paths (i.e. not input or content-addressed) can't be done. As a result, `nixos-rebuild` won't reload/restart the systemd unit when mutable path contents change. `systemctl restart amazon-cloudwatch-agent.service` must be used instead. ''; example = "/etc/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json"; }; commonConfiguration = lib.mkOption { type = tomlFormat.type; default = { }; description = '' See {option}`commonConfigurationFile`. {option}`commonConfigurationFile` takes precedence over {option}`commonConfiguration`. ''; example = { credentials = { shared_credential_profile = "profile_name"; shared_credential_file = "/path/to/credentials"; }; proxy = { http_proxy = "http_url"; https_proxy = "https_url"; no_proxy = "domain"; }; }; }; configurationFile = lib.mkOption { type = lib.types.path; default = jsonFormat.generate "amazon-cloudwatch-agent.json" cfg.configuration; defaultText = lib.literalExpression ''jsonFormat.generate "amazon-cloudwatch-agent.json" cfg.configuration''; description = '' Amazon CloudWatch Agent configuration file. See for supported values. The following options aren't supported: * `agent.run_as_user` * Use {option}`user` instead. {option}`configurationFile` takes precedence over {option}`configuration`. Note: Restricted evaluation blocks access to paths outside the Nix store. This means detecting content changes for mutable paths (i.e. not input or content-addressed) can't be done. As a result, `nixos-rebuild` won't reload/restart the systemd unit when mutable path contents change. `systemctl restart amazon-cloudwatch-agent.service` must be used instead. ''; example = "/etc/amazon-cloudwatch-agent/amazon-cloudwatch-agent.json"; }; configuration = lib.mkOption { type = jsonFormat.type; default = { }; description = '' See {option}`configurationFile`. {option}`configurationFile` takes precedence over {option}`configuration`. ''; # Subset of "CloudWatch agent configuration file: Complete examples" and "CloudWatch agent configuration file: Traces section" in the description link. # # Log file path changed from "/opt/aws/amazon-cloudwatch-agent/logs" to "/var/log/amazon-cloudwatch-agent" to follow the FHS. example = { agent = { metrics_collection_interval = 10; logfile = "/var/log/amazon-cloudwatch-agent/amazon-cloudwatch-agent.log"; }; metrics = { namespace = "MyCustomNamespace"; metrics_collected = { cpu = { resource = [ "*" ]; measurement = [ { name = "cpu_usage_idle"; rename = "CPU_USAGE_IDLE"; unit = "Percent"; } { name = "cpu_usage_nice"; unit = "Percent"; } "cpu_usage_guest" ]; totalcpu = false; metrics_collection_interval = 10; append_dimensions = { customized_dimension_key_1 = "customized_dimension_value_1"; customized_dimension_key_2 = "customized_dimension_value_2"; }; }; }; }; logs = { logs_collected = { files = { collect_list = [ { file_path = "/var/log/amazon-cloudwatch-agent/amazon-cloudwatch-agent.log"; log_group_name = "amazon-cloudwatch-agent.log"; log_stream_name = "{instance_id}"; timezone = "UTC"; } ]; }; }; log_stream_name = "log_stream_name"; force_flush_interval = 15; }; traces = { traces_collected = { xray = { }; oltp = { }; }; }; }; }; # Replaces "agent.run_as_user" from the configuration file. user = lib.mkOption { type = lib.types.str; default = "root"; description = '' The user that runs the Amazon CloudWatch Agent. ''; example = "amazon-cloudwatch-agent"; }; mode = lib.mkOption { type = lib.types.str; default = "auto"; description = '' Amazon CloudWatch Agent mode. Indicates whether the agent is running in EC2 ("ec2"), on-premises ("onPremise"), or if it should guess based on metadata endpoints like IMDS or the ECS task metadata endpoint ("auto"). ''; example = "onPremise"; }; }; config = lib.mkIf cfg.enable { # See https://github.com/aws/amazon-cloudwatch-agent/blob/v1.300049.1/packaging/dependencies/amazon-cloudwatch-agent.service. systemd.services.amazon-cloudwatch-agent = { description = "Amazon CloudWatch Agent"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; # "start-amazon-cloudwatch-agent" assumes the package is installed at "/opt/aws/amazon-cloudwatch-agent" so we can't use it. # # See https://github.com/aws/amazon-cloudwatch-agent/issues/1319. # # This program: # 1. Switches to a non-root user if configured. # 2. Runs "config-translator" to translate the input JSON configuration files into separate TOML (for CloudWatch Logs + Metrics), # YAML (for X-Ray + OpenTelemetry), and JSON (for environment variables) configuration files. # 3. Runs "amazon-cloudwatch-agent" with the paths to these generated files. # # Re-implementing with systemd options. User = cfg.user; RuntimeDirectory = "amazon-cloudwatch-agent"; LogsDirectory = "amazon-cloudwatch-agent"; ExecStartPre = builtins.concatStringsSep " " [ "${cfg.package}/bin/config-translator" "-config ${cfg.commonConfigurationFile}" "-input ${cfg.configurationFile}" "-input-dir ${configurationDirectory}" "-mode ${cfg.mode}" "-output \${RUNTIME_DIRECTORY}/amazon-cloudwatch-agent.toml" ]; ExecStart = builtins.concatStringsSep " " [ "${cfg.package}/bin/amazon-cloudwatch-agent" "-config \${RUNTIME_DIRECTORY}/amazon-cloudwatch-agent.toml" "-envconfig \${RUNTIME_DIRECTORY}/env-config.json" "-otelconfig \${RUNTIME_DIRECTORY}/amazon-cloudwatch-agent.yaml" "-pidfile \${RUNTIME_DIRECTORY}/amazon-cloudwatch-agent.pid" ]; KillMode = "process"; Restart = "on-failure"; RestartSec = 60; }; }; }; }