From f3d53fbba4fbbf61a7211e4dfc144f51f5badf00 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 17 Jan 2021 23:35:31 -0500 Subject: [PATCH 1/7] feat: add logrotation to container log files Closes #4023 --- plugins/scheduler-docker-local/install | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/scheduler-docker-local/install b/plugins/scheduler-docker-local/install index 780159608..78d883b5e 100755 --- a/plugins/scheduler-docker-local/install +++ b/plugins/scheduler-docker-local/install @@ -8,6 +8,7 @@ trigger-scheduler-docker-local-install() { declare desc="scheduler-docker-local install plugin trigger" declare trigger="install" local DOKKU_PATH + DOKKU_LOGROTATE_FILE=/etc/logrotate.d/docker mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" @@ -16,6 +17,20 @@ trigger-scheduler-docker-local-install() { DOKKU_PATH="$(which dokku)" + if [[ ! -f "$DOKKU_LOGROTATE_FILE" ]]; then + cat >"$DOKKU_LOGROTATE_FILE" </dev/null) =~ -\.mount ]]; then cat </etc/systemd/system/dokku-retire.service [Unit] From 6eb0bd8c870e97f16bd3412ab4b9367edf0d3eaa Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 22 Jan 2021 00:08:40 -0500 Subject: [PATCH 2/7] chore: sort set properties --- plugins/common/properties.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/common/properties.go b/plugins/common/properties.go index 89d561017..7cacffb18 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "reflect" + "sort" "strings" ) @@ -32,6 +33,7 @@ func CommandPropertySet(pluginName, appName, property, value string, properties validPropertyList[i] = properties[i].String() } + sort.Strings(validPropertyList) LogFail(fmt.Sprintf("Invalid property specified, valid properties include: %s", strings.Join(validPropertyList, ", "))) } From afe1e8b33c781f766d3f54efda05280dd1eaf805 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 22 Jan 2021 00:39:08 -0500 Subject: [PATCH 3/7] refactor: use log:set for logs and add global support --- plugins/logs/.gitignore | 1 + plugins/logs/Makefile | 2 +- plugins/logs/logs.go | 4 + plugins/logs/report.go | 26 +++++- plugins/logs/set.go | 58 +++++++++++++ plugins/logs/src/subcommands/subcommands.go | 6 ++ plugins/logs/src/triggers/triggers.go | 3 + plugins/logs/subcommands.go | 7 +- plugins/logs/triggers.go | 21 +++++ plugins/scheduler-docker-local/install | 15 ---- tests/unit/logs.bats | 95 ++++++++++++++++++++- 11 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 plugins/logs/set.go diff --git a/plugins/logs/.gitignore b/plugins/logs/.gitignore index 291036bd4..ce3e77e5c 100644 --- a/plugins/logs/.gitignore +++ b/plugins/logs/.gitignore @@ -2,6 +2,7 @@ /subcommands/* /triggers/* /triggers +/docker-args-process-deploy /install /post-* /report diff --git a/plugins/logs/Makefile b/plugins/logs/Makefile index bcd96e90f..213c56394 100644 --- a/plugins/logs/Makefile +++ b/plugins/logs/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/failed subcommands/report subcommands/set subcommands/vector-logs subcommands/vector-start subcommands/vector-stop -TRIGGERS = triggers/install triggers/post-delete triggers/report +TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = logs diff --git a/plugins/logs/logs.go b/plugins/logs/logs.go index 7af0000af..c735f18d4 100644 --- a/plugins/logs/logs.go +++ b/plugins/logs/logs.go @@ -6,14 +6,18 @@ import ( "github.com/dokku/dokku/plugins/common" ) +const MaxSize = "10m" + var ( // DefaultProperties is a map of all valid ps properties with corresponding default property values DefaultProperties = map[string]string{ + "max-size": "", "vector-sink": "", } // GlobalProperties is a map of all valid global logs properties GlobalProperties = map[string]bool{ + "max-size": true, "vector-sink": true, } ) diff --git a/plugins/logs/report.go b/plugins/logs/report.go index 3e8a95aa9..c88ac5047 100644 --- a/plugins/logs/report.go +++ b/plugins/logs/report.go @@ -11,8 +11,11 @@ func ReportSingleApp(appName, infoFlag string) error { } flags := map[string]common.ReportFunc{ - "--logs-vector-sink": reportVectorSink, + "--logs-computed-max-size": reportComputedMaxSize, + "--logs-global-max-size": reportGlobalMaxSize, "--logs-global-vector-sink": reportGlobalVectorSink, + "--logs-max-size": reportMaxSize, + "--logs-vector-sink": reportVectorSink, } flagKeys := []string{} @@ -26,10 +29,27 @@ func ReportSingleApp(appName, infoFlag string) error { return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, flagKeys, trimPrefix, uppercaseFirstCharacter) } -func reportVectorSink(appName string) string { - return common.PropertyGet("logs", appName, "vector-sink") +func reportComputedMaxSize(appName string) string { + value := reportMaxSize(appName) + if value == "" { + value = reportGlobalMaxSize(appName) + } + + return value +} + +func reportGlobalMaxSize(appName string) string { + return common.PropertyGetDefault("logs", "--global", "max-size", MaxSize) } func reportGlobalVectorSink(appName string) string { return common.PropertyGet("logs", "--global", "vector-sink") } + +func reportMaxSize(appName string) string { + return common.PropertyGet("logs", appName, "max-size") +} + +func reportVectorSink(appName string) string { + return common.PropertyGet("logs", appName, "vector-sink") +} diff --git a/plugins/logs/set.go b/plugins/logs/set.go new file mode 100644 index 000000000..5878958f9 --- /dev/null +++ b/plugins/logs/set.go @@ -0,0 +1,58 @@ +package logs + +import ( + "errors" + "fmt" + "strconv" +) + +func validateSetValue(appName string, key string, value string) error { + if key == "max-size" { + return validateMaxSize(appName, value) + } + + if key == "vector-sink" { + return validateVectorSink(appName, value) + } + + return nil +} + +func validateMaxSize(appName string, value string) error { + if value == "" { + return nil + } + + if value == "unlimited" { + return nil + } + + last := value[len(value)-1:] + if last != "k" && last != "m" && last != "g" { + return errors.New("Invalid max-size unit measure, value must end in any of [k, m, g]") + } + + if len(value) < 2 { + return errors.New("Invalid max-size value, must be a number followed by a unit of measure [k, m, d]") + } + + number := value[:len(value)-1] + if _, err := strconv.Atoi(number); err != nil { + return fmt.Errorf("Invalid max-size value, unable to convert number to int: %s", err.Error()) + } + + return nil +} + +func validateVectorSink(appName string, value string) error { + if value == "" { + return nil + } + + _, err := valueToConfig(appName, value) + if err != nil { + return err + } + + return nil +} diff --git a/plugins/logs/src/subcommands/subcommands.go b/plugins/logs/src/subcommands/subcommands.go index 875dc2d67..f40ec834b 100644 --- a/plugins/logs/src/subcommands/subcommands.go +++ b/plugins/logs/src/subcommands/subcommands.go @@ -35,6 +35,7 @@ func main() { case "set": args := flag.NewFlagSet("logs:set", flag.ExitOnError) global := args.Bool("global", false, "--global: set a global property") + valueOverride := args.Bool("1", false, "-1: negative value (for max-size)") args.Parse(os.Args[2:]) appName := args.Arg(0) property := args.Arg(1) @@ -44,6 +45,11 @@ func main() { property = args.Arg(0) value = args.Arg(1) } + + if *valueOverride { + value = "-1" + } + err = logs.CommandSet(appName, property, value) case "vector-logs": args := flag.NewFlagSet("logs:vector-logs", flag.ExitOnError) diff --git a/plugins/logs/src/triggers/triggers.go b/plugins/logs/src/triggers/triggers.go index d09fa5c80..0fd62af87 100644 --- a/plugins/logs/src/triggers/triggers.go +++ b/plugins/logs/src/triggers/triggers.go @@ -18,6 +18,9 @@ func main() { var err error switch trigger { + case "docker-args-process-deploy": + appName := flag.Arg(0) + err = logs.TriggerDockerArgsProcessDeploy(appName) case "install": err = logs.TriggerInstall() case "post-delete": diff --git a/plugins/logs/subcommands.go b/plugins/logs/subcommands.go index d9c8ce572..e8a9b4a25 100644 --- a/plugins/logs/subcommands.go +++ b/plugins/logs/subcommands.go @@ -69,11 +69,8 @@ func CommandReport(appName string, infoFlag string) error { // CommandSet sets or clears a logs property for an app func CommandSet(appName string, property string, value string) error { - if property == "vector-sink" && value != "" { - _, err := valueToConfig(appName, value) - if err != nil { - return err - } + if err := validateSetValue(appName, property, value); err != nil { + return err } common.CommandPropertySet("logs", appName, property, value, DefaultProperties, GlobalProperties) diff --git a/plugins/logs/triggers.go b/plugins/logs/triggers.go index a43c9c884..3058ce9d1 100644 --- a/plugins/logs/triggers.go +++ b/plugins/logs/triggers.go @@ -2,12 +2,33 @@ package logs import ( "fmt" + "io/ioutil" "os" "path/filepath" "github.com/dokku/dokku/plugins/common" ) +// TriggerDockerArgsProcessDeploy outputs the logs plugin docker options for an app +func TriggerDockerArgsProcessDeploy(appName string) error { + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + + maxSize := common.PropertyGet("logs", appName, "max-size") + if maxSize == "" { + maxSize = common.PropertyGetDefault("logs", "--global", "max-size", MaxSize) + } + + if maxSize != "unlimited" { + fmt.Printf(" --log-opt max-size=%s ", maxSize) + } + + fmt.Print(string(stdin)) + return nil +} + // TriggerInstall initializes app restart policies func TriggerInstall() error { if err := common.PropertySetup("logs"); err != nil { diff --git a/plugins/scheduler-docker-local/install b/plugins/scheduler-docker-local/install index 78d883b5e..780159608 100755 --- a/plugins/scheduler-docker-local/install +++ b/plugins/scheduler-docker-local/install @@ -8,7 +8,6 @@ trigger-scheduler-docker-local-install() { declare desc="scheduler-docker-local install plugin trigger" declare trigger="install" local DOKKU_PATH - DOKKU_LOGROTATE_FILE=/etc/logrotate.d/docker mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-docker-local" @@ -17,20 +16,6 @@ trigger-scheduler-docker-local-install() { DOKKU_PATH="$(which dokku)" - if [[ ! -f "$DOKKU_LOGROTATE_FILE" ]]; then - cat >"$DOKKU_LOGROTATE_FILE" </dev/null) =~ -\.mount ]]; then cat </etc/systemd/system/dokku-retire.service [Unit] diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 0e86f5f3a..3b2dc9d8c 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -98,13 +98,13 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: vector-sink" + assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" run /bin/bash -c "dokku logs:set $TEST_APP invalid value" 2>&1 echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: vector-sink" + assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" } @test "(logs) logs:set app" { @@ -144,7 +144,50 @@ teardown() { echo "status: $status" assert_success assert_output "console://?encoding[codec]=json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set $TEST_APP max-size 20m" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "20m" + + run /bin/bash -c "dokku logs:set $TEST_APP max-size unlimited" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "unlimited" } + @test "(logs) logs:set global" { run create_app echo "output: $output" @@ -189,6 +232,54 @@ teardown() { assert_success assert_output_contains "Unsetting vector-sink" assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:set --global max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10m" + + run /bin/bash -c "dokku logs:set --global max-size 20m" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "20m" + + run /bin/bash -c "dokku logs:set --global max-size unlimited" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting max-size" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "unlimited" + + run /bin/bash -c "dokku logs:set --global max-size" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting max-size" } @test "(logs) logs:vector" { From 8c81d520f6a5c0a0aa9b44262ef7122dcef91b0d Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 22 Jan 2021 00:45:00 -0500 Subject: [PATCH 4/7] fix: correct stickler --- plugins/logs/logs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/logs/logs.go b/plugins/logs/logs.go index c735f18d4..780802f47 100644 --- a/plugins/logs/logs.go +++ b/plugins/logs/logs.go @@ -6,6 +6,7 @@ import ( "github.com/dokku/dokku/plugins/common" ) +// MaxSize is the default max retention size for docker logs const MaxSize = "10m" var ( From 76b36aa3f6b271ae61e750e6603ccbfa468448f2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 22 Jan 2021 01:14:41 -0500 Subject: [PATCH 5/7] docs: document log retention --- docs/deployment/logs.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index 2e242c689..52588eabd 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -57,6 +57,34 @@ You may also fetch all failed app logs by using the `--all` flag. dokku logs:failed --all ``` +### Docker Log Retention + +Docker log retention can be specified via the `logs:set` command by specifying a value for `max-size`. Log retention is set via injected docker options for all applications, but is also available via a trigger for alternative schedulers. + +```shell +dokku logs:set node-js-app max-size 20m +``` + +The default value may be set by passing an empty value for the option: + +```shell +dokku logs:set node-js-app max-size +``` + +Valid values include any integer number followed by a unit of measure (`k`, `m`, or `g`) or the string `unlimited`. Setting to `unlimited` will result in Dokku omitting the log option. + +The `max-size` property can also be set globally. The global default is `10m`, and the global value is used when no app-specific value is set. + +```shell +dokku logs:set --global max-size 20m +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku logs:set --global max-size +``` + ### Vector Logging Shipping > New as of 0.22.6 From ccbe2c719a089b2ea5c506a455281b8775884d2f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 23 Jan 2021 13:02:37 -0500 Subject: [PATCH 6/7] fix: correct test check --- tests/unit/logs.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 3b2dc9d8c..23547e9ed 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -53,7 +53,7 @@ teardown() { echo "status: $status" assert_failure assert_output_contains "$TEST_APP logs information" 0 - assert_output_contains "Invalid flag passed, valid flags: --logs-global-vector-sink, --logs-vector-sink" + assert_output_contains "Invalid flag passed, valid flags: --logs-computed-max-size, --logs-global-max-size, --logs-global-vector-sink, --logs-max-size, --logs-vector-sink" run /bin/bash -c "dokku logs:report $TEST_APP --logs-vector-sink 2>&1" echo "output: $output" From 815b839eeb61c06dda6dd9731a329a96cf4c75c5 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 23 Jan 2021 13:39:29 -0500 Subject: [PATCH 7/7] feat: add trigger for fetching logs properties --- docs/deployment/logs.md | 2 +- docs/development/plugin-triggers.md | 16 ++++++++++++++++ plugins/logs/Makefile | 2 +- plugins/logs/src/triggers/triggers.go | 4 ++++ plugins/logs/triggers.go | 16 ++++++++++++++++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index 52588eabd..ef63fd47d 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -59,7 +59,7 @@ dokku logs:failed --all ### Docker Log Retention -Docker log retention can be specified via the `logs:set` command by specifying a value for `max-size`. Log retention is set via injected docker options for all applications, but is also available via a trigger for alternative schedulers. +Docker log retention can be specified via the `logs:set` command by specifying a value for `max-size`. Log retention is set via injected docker options for all applications, but is also available via the `logs-get-property` trigger for alternative schedulers. ```shell dokku logs:set node-js-app max-size 20m diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 0924d087e..571ddaf14 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -699,6 +699,22 @@ if [[ ! -f "$DOKKU_ROOT/HOSTNAME" ]]; then fi ``` +### `logs-get-property` + +- Description: Fetches a given logs property value +- Invoked by: +- Arguments: `$APP` `$PROPERTY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + ### `network-build-config` - Description: Rebuilds network configuration diff --git a/plugins/logs/Makefile b/plugins/logs/Makefile index 213c56394..eab1d71e3 100644 --- a/plugins/logs/Makefile +++ b/plugins/logs/Makefile @@ -1,5 +1,5 @@ SUBCOMMANDS = subcommands/failed subcommands/report subcommands/set subcommands/vector-logs subcommands/vector-start subcommands/vector-stop -TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/post-delete triggers/report +TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/logs-get-property triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = logs diff --git a/plugins/logs/src/triggers/triggers.go b/plugins/logs/src/triggers/triggers.go index 0fd62af87..58e951171 100644 --- a/plugins/logs/src/triggers/triggers.go +++ b/plugins/logs/src/triggers/triggers.go @@ -23,6 +23,10 @@ func main() { err = logs.TriggerDockerArgsProcessDeploy(appName) case "install": err = logs.TriggerInstall() + case "logs-get-property": + appName := flag.Arg(0) + property := flag.Arg(1) + err = logs.TriggerLogsGetProperty(appName, property) case "post-delete": appName := flag.Arg(0) err = logs.TriggerPostDelete(appName) diff --git a/plugins/logs/triggers.go b/plugins/logs/triggers.go index 3058ce9d1..175eb0c6e 100644 --- a/plugins/logs/triggers.go +++ b/plugins/logs/triggers.go @@ -1,6 +1,7 @@ package logs import ( + "errors" "fmt" "io/ioutil" "os" @@ -56,6 +57,21 @@ func TriggerInstall() error { return nil } +// TriggerLogsGetProperty writes the logs key to stdout for a given app container +func TriggerLogsGetProperty(appName string, key string) error { + if key != "max-size" { + return errors.New("Invalid logs property specified") + } + + value := common.PropertyGet("logs", appName, "max-size") + if value == "" { + value = common.PropertyGetDefault("logs", "--global", "max-size", MaxSize) + } + + fmt.Println(value) + return nil +} + // TriggerPostDelete destroys the network property for a given app container func TriggerPostDelete(appName string) error { return common.PropertyDestroy("logs", appName)