mirror of
https://github.com/dokku/dokku.git
synced 2025-12-29 00:25:08 +01:00
Merge pull request #4291 from dokku/2268-vector-logging
Add log aggregation support via Vector
This commit is contained in:
@@ -53,4 +53,4 @@ RUN \
|
||||
&& rm -f /etc/nginx/sites-enabled/default /usr/share/nginx/html/index.html /etc/my_init.d/10_syslog-ng.init \
|
||||
&& rm -f /usr/local/openresty/nginx/conf/sites-enabled/default /usr/share/openresty/html/index.html \
|
||||
&& sed -i '/imklog/d' /etc/rsyslog.conf \
|
||||
&& rm -f /var/log/btmp /var/log/wtmp /var/log/*log /var/log/apt/* /var/log/dokku/* /var/log/nginx/* /var/log/openresty/*
|
||||
&& rm -f /var/log/btmp /var/log/wtmp /var/log/*log /var/log/apt/* /var/log/dokku/*.log /var/log/nginx/* /var/log/openresty/*
|
||||
|
||||
@@ -87,6 +87,7 @@ The following config variables have special meanings and can be set in a variety
|
||||
| `PLUGIN_CORE_AVAILABLE_PATH` | `$PLUGIN_CORE_PATH/available"` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | The directory that stores all available core plugins. |
|
||||
| `PLUGIN_CORE_ENABLED_PATH` | `$PLUGIN_CORE_PATH/enabled"` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | The directory that stores all enabled core plugins. |
|
||||
| `DOKKU_LOGS_DIR` | `/var/log/dokku` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | Where dokku logs should be written to. |
|
||||
| `DOKKU_LOGS_HOST_DIR` | `$DOKKU_LOGS_DIR` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | A path on the host that will be mounted into the vector logging container. |
|
||||
| `DOKKU_EVENTS_LOGFILE` | `$DOKKU_LOGS_DIR/events.log` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | Where the events log file is written to. |
|
||||
| `DOKKU_APP_NAME` | none | `--app APP` flag | Name of application to work on. Respected by core plugins. |
|
||||
| `DOKKU_APPS_FORCE_DELETE` | none | `--force` flag | Whether to force delete an application. Also used by other plugins for destructive actions. |
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
|
||||
```
|
||||
logs <app> [-h] [-t] [-n num] [-q] [-p process] # Display recent log output
|
||||
logs:failed [--parallel count] [--all|<app>] # Shows the last failed deploy logs
|
||||
logs:failed [--all|<app>] # Shows the last failed deploy logs
|
||||
logs:report [<app>] [<flag>] # Displays a logs report for one or more apps
|
||||
logs:set [--global|<app>] <key> <value> # Set or clear a logs property for an app
|
||||
logs:vector-logs # Tail the logs of the vector container
|
||||
logs:vector-start # Start the vector logging container
|
||||
logs:vector-stop # Stop the vector logging container
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -40,7 +45,7 @@ will show logs continually from the web process.
|
||||
|
||||
In some cases, it may be useful to retrieve the logs from a previously failed deploy.
|
||||
|
||||
You can retrieve these logs by using the `logs:failed` command
|
||||
You can retrieve these logs by using the `logs:failed` command.
|
||||
|
||||
```shell
|
||||
dokku logs:failed node-js-app
|
||||
@@ -51,3 +56,94 @@ You may also fetch all failed app logs by using the `--all` flag.
|
||||
```shell
|
||||
dokku logs:failed --all
|
||||
```
|
||||
|
||||
### Vector Logging
|
||||
|
||||
> New as of 0.22.6
|
||||
|
||||
Vector is an open-source, lightweight and ultra-fast tool for building observability pipelines. Dokku integrates with it for shipping container logs for the `docker-local` scheduler. Users may configure log-shipping on a per-app or global basis, neither of which interfere with the `dokku logs` commands.
|
||||
|
||||
#### Starting the Vector container
|
||||
|
||||
> Warning: While the default vector image may be updated over time, this will not impact running vector containers. Users are encouraged to view any Dokku and Vector changelogs to ensure their system will continue running as expected.
|
||||
|
||||
Vector may be started via the `logs:vector-start` command.
|
||||
|
||||
```shell
|
||||
dokku logs:vector-start
|
||||
```
|
||||
|
||||
This will start a new container named `vector` with Dokku's vector config mounted and ready for use. If a running container already exists, this command will do nothing. Additionally, if a container exists but is not running, this command will attempt to start the container.
|
||||
|
||||
While the default vector image is hardcoded, users may specify an alternative via the `--vector-image` flag:
|
||||
|
||||
```shell
|
||||
dokku logs:vector-start --vector-image timberio/vector:latest-debian
|
||||
```
|
||||
|
||||
The `vector` container will be started with the following volume mounts:
|
||||
|
||||
- `/var/lib/dokku/data/logs/vector.json:/etc/vector/vector.json`
|
||||
- `/var/run/docker.sock:/var/run/docker.sock`
|
||||
- `/var/log/dokku/apps:/var/log/dokku/apps`
|
||||
|
||||
The final volume mount - `/var/log/dokku/apps` - may be used for users that wish to ship logs to a file on disk that may be later logrotated. This directory is owned by the `dokku` user and group, with permissions set to `0755`. At this time, log-rotation is not configured for this directory.
|
||||
|
||||
#### Stopping the Vector container
|
||||
|
||||
Vector may be stopped via the `logs:vector-stop` command.
|
||||
|
||||
```shell
|
||||
dokku logs:vector-stop
|
||||
```
|
||||
|
||||
The `vector` container will be stopped and removed from the system. If the container is not running, this command will do nothing.
|
||||
|
||||
#### Configuring a log sink
|
||||
|
||||
Vector uses the concept of log "sinks" to send logs to a given endpoint. Log sinks may be configured globally or on a per-app basis by specifying a `sink` in DSN form with the `logs:set` command. Specifying a sink value will reload any running vector container.
|
||||
|
||||
```shell
|
||||
# setting the sink value in quotes is encouraged to avoid
|
||||
# issues with ampersand encoding in shell commands
|
||||
dokku logs:set node-js-app sink "console://?encoding[codec]=json"
|
||||
```
|
||||
|
||||
A sink may be removed by setting an empty value, which will also reload the running vector container.
|
||||
|
||||
```shell
|
||||
dokku logs:set node-js-app sink
|
||||
```
|
||||
|
||||
Only one sink may be specified on a per-app basis at a given time.
|
||||
|
||||
Log sinks can also be specified globally by specifying the `--global` flag to `logs:set` with no app name specified:
|
||||
|
||||
```shell
|
||||
dokku logs:set --global sink "console://?encoding[codec]=json"
|
||||
```
|
||||
|
||||
As with app-specific sink settings, the global value may also be cleared by setting no value.
|
||||
|
||||
```shell
|
||||
dokku logs:set --global sink
|
||||
```
|
||||
|
||||
##### Log Sink DSN Format
|
||||
|
||||
The DSN form of a sink is as follows:
|
||||
|
||||
```
|
||||
SINK_TYPE://?SINK_OPTIONS
|
||||
```
|
||||
|
||||
Valid values for `SINK_TYPE` include all log vector log sinks, while `SINK_OPTIONS` is a query-string form for the sink's options. The following is a short description on how to set various values:
|
||||
|
||||
- `bool`: form: `key=bool`
|
||||
- `string`: form: `key=string`
|
||||
- `int`: form: `key=int`
|
||||
- `[string]`: form: `key[]=string`
|
||||
- `[int]`: form: `key[]=int`
|
||||
- `table`: form: `option[key]=value
|
||||
|
||||
Please read the [sink documentation](https://vector.dev/docs/reference/sinks/) for your sink of choice to configure the sink as desired.
|
||||
|
||||
1
dokku
1
dokku
@@ -59,6 +59,7 @@ export DOKKU_VALID_EXIT=0
|
||||
export DOKKU_PID="$BASHPID"
|
||||
|
||||
export DOKKU_LOGS_DIR=${DOKKU_LOGS_DIR:="/var/log/dokku"}
|
||||
export DOKKU_LOGS_HOST_DIR=${DOKKU_LOGS_HOST_DIR:=$DOKKU_LOGS_DIR}
|
||||
export DOKKU_EVENTS_LOGFILE=${DOKKU_EVENTS_LOGFILE:="$DOKKU_LOGS_DIR/events.log"}
|
||||
|
||||
export DOKKU_CONTAINER_LABEL=dokku
|
||||
|
||||
@@ -20,6 +20,31 @@ func ContainerIsRunning(containerID string) bool {
|
||||
return strings.TrimSpace(string(b[:])) == "true"
|
||||
}
|
||||
|
||||
// ContainerStart runs 'docker container start' against an existing container
|
||||
// whether that container is running or not
|
||||
func ContainerStart(containerID string) bool {
|
||||
cmd := sh.Command(DockerBin(), "container", "start", containerID)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainerExists checks to see if a container exists
|
||||
func ContainerExists(containerID string) bool {
|
||||
cmd := sh.Command(DockerBin(), "container", "inspect", containerID)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// CopyFromImage copies a file from named image to destination
|
||||
func CopyFromImage(appName string, image string, source string, destination string) error {
|
||||
if !VerifyImage(image) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -93,6 +94,21 @@ func LogVerboseQuietContainerLogs(containerID string) {
|
||||
}
|
||||
}
|
||||
|
||||
// LogVerboseQuietContainerLogsTail is the verbose log formatter for container logs with tail mode enabled
|
||||
func LogVerboseQuietContainerLogsTail(containerID string) {
|
||||
sc := NewShellCmdWithArgs(DockerBin(), "container", "logs", containerID, "--follow", "--tail", "10")
|
||||
stdout, _ := sc.Command.StdoutPipe()
|
||||
sc.Command.Start()
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
m := scanner.Text()
|
||||
fmt.Println(m)
|
||||
}
|
||||
sc.Command.Wait()
|
||||
}
|
||||
|
||||
// LogWarn is the warning log formatter
|
||||
func LogWarn(text string) {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
|
||||
|
||||
@@ -12,9 +12,14 @@ import (
|
||||
)
|
||||
|
||||
// CommandPropertySet is a generic function that will set a property for a given plugin/app combination
|
||||
func CommandPropertySet(pluginName, appName, property, value string, properties map[string]string) {
|
||||
if err := VerifyAppName(appName); err != nil {
|
||||
LogFail(err.Error())
|
||||
func CommandPropertySet(pluginName, appName, property, value string, properties map[string]string, globalProperties map[string]bool) {
|
||||
if appName != "--global" {
|
||||
if err := VerifyAppName(appName); err != nil {
|
||||
LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
if appName == "--global" && !globalProperties[property] {
|
||||
LogFail("Property cannot be specified globally")
|
||||
}
|
||||
if property == "" {
|
||||
LogFail("No property specified")
|
||||
|
||||
5
plugins/logs/.gitignore
vendored
5
plugins/logs/.gitignore
vendored
@@ -1,2 +1,7 @@
|
||||
/commands
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/install
|
||||
/post-*
|
||||
/report
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
SUBCOMMANDS = subcommands/failed
|
||||
BUILD = commands subcommands
|
||||
SUBCOMMANDS = subcommands/failed subcommands/report subcommands/set subcommands/vector-logs subcommands/vector-start subcommands/vector-stop
|
||||
TRIGGERS = triggers/install triggers/post-delete triggers/report
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = logs
|
||||
|
||||
include ../../common.mk
|
||||
|
||||
226
plugins/logs/functions.go
Normal file
226
plugins/logs/functions.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/joncalhoun/qson"
|
||||
)
|
||||
|
||||
type vectorConfig struct {
|
||||
Sources map[string]vectorSource `json:"sources"`
|
||||
Sinks map[string]vectorSink `json:"sinks"`
|
||||
}
|
||||
|
||||
type vectorSource struct {
|
||||
Type string `json:"type"`
|
||||
IncludeLabels []string `json:"include_labels,omitempty"`
|
||||
}
|
||||
|
||||
type vectorSink map[string]interface{}
|
||||
|
||||
const vectorContainerName = "vector"
|
||||
|
||||
func killVectorContainer() error {
|
||||
if !common.ContainerExists(vectorContainerName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := stopVectorContainer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
if err := removeVectorContainer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeVectorContainer() error {
|
||||
if !common.ContainerExists(vectorContainerName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(), "container", "rm", "-f", vectorContainerName}, " "))
|
||||
|
||||
return common.SuppressOutput(func() error {
|
||||
if cmd.Execute() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if common.ContainerExists(vectorContainerName) {
|
||||
return errors.New("Unable to remove vector container")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func startVectorContainer(vectorImage string) error {
|
||||
cmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(),
|
||||
"container",
|
||||
"run", "--detach", "--name", vectorContainerName, common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS"),
|
||||
"--volume", "/var/lib/dokku/data/logs/vector.json:/etc/vector/vector.json",
|
||||
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
||||
"--volume", common.MustGetEnv("DOKKU_LOGS_HOST_DIR") + ":/var/logs/dokku/apps",
|
||||
vectorImage,
|
||||
"--config", "/etc/vector/vector.json", "--watch-config"}, " "))
|
||||
|
||||
if !cmd.Execute() {
|
||||
return errors.New("Unable to start vector container")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopVectorContainer() error {
|
||||
if !common.ContainerExists(vectorContainerName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !common.ContainerIsRunning(vectorContainerName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(), "container", "stop", vectorContainerName}, " "))
|
||||
|
||||
return common.SuppressOutput(func() error {
|
||||
if cmd.Execute() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if common.ContainerIsRunning(vectorContainerName) {
|
||||
return errors.New("Unable to stop vector container")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func valueToConfig(appName string, value string) (vectorSink, error) {
|
||||
var data vectorSink
|
||||
u, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
if u.Query().Get("sinks") != "" {
|
||||
return data, errors.New("Invalid option sinks")
|
||||
}
|
||||
|
||||
t := fmt.Sprintf("type=%s", u.Scheme)
|
||||
i := fmt.Sprintf("inputs[]=docker-source:%s", appName)
|
||||
if appName == "--global" {
|
||||
i = "inputs[]=docker-global-source"
|
||||
}
|
||||
if appName == "--null" {
|
||||
i = "inputs[]=docker-null-source"
|
||||
}
|
||||
|
||||
initialQuery := fmt.Sprintf("%s&%s", t, i)
|
||||
query := u.RawQuery
|
||||
if query == "" {
|
||||
query = initialQuery
|
||||
} else if strings.HasPrefix(query, "&") {
|
||||
query = fmt.Sprintf("%s%s", initialQuery, query)
|
||||
} else {
|
||||
query = fmt.Sprintf("%s&%s", initialQuery, query)
|
||||
}
|
||||
|
||||
b, err := qson.ToJSON(query)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func writeVectorConfig() error {
|
||||
apps, err := common.DokkuApps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := vectorConfig{
|
||||
Sources: map[string]vectorSource{},
|
||||
Sinks: map[string]vectorSink{},
|
||||
}
|
||||
for _, appName := range apps {
|
||||
value := common.PropertyGet("logs", appName, "vector-sink")
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
sink, err := valueToConfig(appName, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.Sources[fmt.Sprintf("docker-source:%s", appName)] = vectorSource{
|
||||
Type: "docker_logs",
|
||||
IncludeLabels: []string{fmt.Sprintf("com.dokku.app-name=%s", appName)},
|
||||
}
|
||||
|
||||
data.Sinks[fmt.Sprintf("docker-sink:%s", appName)] = sink
|
||||
}
|
||||
|
||||
value := common.PropertyGet("logs", "--global", "vector-sink")
|
||||
if value != "" {
|
||||
sink, err := valueToConfig("--global", value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.Sources["docker-global-source"] = vectorSource{
|
||||
Type: "docker_logs",
|
||||
IncludeLabels: []string{"com.dokku.app-name"},
|
||||
}
|
||||
|
||||
data.Sinks["docker-global-sink"] = sink
|
||||
}
|
||||
|
||||
if len(data.Sources) == 0 {
|
||||
// pull from no containers
|
||||
data.Sources["docker-null-source"] = vectorSource{
|
||||
Type: "docker_logs",
|
||||
IncludeLabels: []string{"com.dokku.vector-null"},
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.Sinks) == 0 {
|
||||
// write logs to a blackhole
|
||||
sink, err := valueToConfig("--null", "blackhole://?print_amount=1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.Sinks["docker-null-sink"] = sink
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vectorConfig := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "logs", "vector.json")
|
||||
if err := common.WriteSliceToFile(vectorConfig, []string{string(b)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ go 1.15
|
||||
require (
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27
|
||||
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
|
||||
github.com/joncalhoun/qson v0.0.0-20200422171543-84433dcd3da0
|
||||
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+Bu
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA=
|
||||
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=
|
||||
github.com/joncalhoun/qson v0.0.0-20200422171543-84433dcd3da0 h1:ct2XA1aDw8A07Dr8gtrrZgIgLKcZNAl2o9nn0WRMK4Y=
|
||||
github.com/joncalhoun/qson v0.0.0-20200422171543-84433dcd3da0/go.mod h1:DFXrEwSRX0p/aSvxE21319menCBFeQO0jXpRj7LEZUA=
|
||||
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3 h1:utdYOikI1XjNtTFGCwSM6OmFJblU4ld4gACoJsbadJg=
|
||||
github.com/ryanuber/columnize v1.1.2-0.20190319233515-9e6335e58db3/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
|
||||
@@ -6,6 +6,21 @@ import (
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultProperties is a map of all valid ps properties with corresponding default property values
|
||||
DefaultProperties = map[string]string{
|
||||
"vector-sink": "",
|
||||
}
|
||||
|
||||
// GlobalProperties is a map of all valid global logs properties
|
||||
GlobalProperties = map[string]bool{
|
||||
"vector-sink": true,
|
||||
}
|
||||
)
|
||||
|
||||
// VectorImage contains the default vector image to run
|
||||
const VectorImage = "timberio/vector:0.11.X-debian"
|
||||
|
||||
// GetFailedLogs outputs failed deploy logs for a given app
|
||||
func GetFailedLogs(appName string) error {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("%s failed deploy logs", appName))
|
||||
|
||||
30
plugins/logs/report.go
Normal file
30
plugins/logs/report.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// ReportSingleApp is an internal function that displays the logs report for one or more apps
|
||||
func ReportSingleApp(appName, infoFlag string) error {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags := map[string]common.ReportFunc{
|
||||
"--logs-vector-sink": reportVectorSink,
|
||||
"--logs-global-vector-sink": reportGlobalVectorSink,
|
||||
}
|
||||
|
||||
trimPrefix := false
|
||||
uppercaseFirstCharacter := true
|
||||
infoFlags := common.CollectReport(appName, infoFlag, flags)
|
||||
return common.ReportSingleApp("logs", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter)
|
||||
}
|
||||
|
||||
func reportVectorSink(appName string) string {
|
||||
return common.PropertyGet("logs", appName, "vector-sink")
|
||||
}
|
||||
|
||||
func reportGlobalVectorSink(appName string) string {
|
||||
return common.PropertyGet("logs", "--global", "vector-sink")
|
||||
}
|
||||
@@ -23,7 +23,12 @@ Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
logs [-h] [-t|--tail] [-n|--num num] [-q|--quiet] [-p|--ps process] <app>, Display recent log output
|
||||
logs:failed [<app>], Shows the last failed deploy logs
|
||||
logs:failed [--all|<app>], Shows the last failed deploy logs
|
||||
logs:report [<app>] [<flag>], Displays a logs report for one or more apps
|
||||
logs:set <app> <key> <value>, Set or clear a logs property for an app
|
||||
logs:vector-logs, Tail the logs of the vector container
|
||||
logs:vector-start, Start the vector logging container
|
||||
logs:vector-stop, Stop the vector logging container
|
||||
`
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,40 @@ func main() {
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
err = logs.CommandFailed(appName, *allApps)
|
||||
case "report":
|
||||
args := flag.NewFlagSet("logs:report", flag.ExitOnError)
|
||||
osArgs, infoFlag, err := common.ParseReportArgs("logs", os.Args[2:])
|
||||
if err == nil {
|
||||
args.Parse(osArgs)
|
||||
appName := args.Arg(0)
|
||||
err = logs.CommandReport(appName, infoFlag)
|
||||
}
|
||||
case "set":
|
||||
args := flag.NewFlagSet("logs:set", flag.ExitOnError)
|
||||
global := args.Bool("global", false, "--global: set a global property")
|
||||
args.Parse(os.Args[2:])
|
||||
appName := args.Arg(0)
|
||||
property := args.Arg(1)
|
||||
value := args.Arg(2)
|
||||
if *global {
|
||||
appName = "--global"
|
||||
property = args.Arg(0)
|
||||
value = args.Arg(1)
|
||||
}
|
||||
err = logs.CommandSet(appName, property, value)
|
||||
case "vector-logs":
|
||||
args := flag.NewFlagSet("logs:vector-logs", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
err = logs.CommandVectorLogs()
|
||||
case "vector-start":
|
||||
args := flag.NewFlagSet("logs:vector-start", flag.ExitOnError)
|
||||
vectorImage := args.String("vector-image", logs.VectorImage, "--vector-image: the name of the docker image to run for vector")
|
||||
args.Parse(os.Args[2:])
|
||||
err = logs.CommandVectorStart(*vectorImage)
|
||||
case "vector-stop":
|
||||
args := flag.NewFlagSet("logs:vector-stop", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
err = logs.CommandVectorStop()
|
||||
default:
|
||||
common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand))
|
||||
}
|
||||
|
||||
36
plugins/logs/src/triggers/triggers.go
Normal file
36
plugins/logs/src/triggers/triggers.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/logs"
|
||||
)
|
||||
|
||||
// main entrypoint to all triggers
|
||||
func main() {
|
||||
parts := strings.Split(os.Args[0], "/")
|
||||
trigger := parts[len(parts)-1]
|
||||
flag.Parse()
|
||||
|
||||
var err error
|
||||
switch trigger {
|
||||
case "install":
|
||||
err = logs.TriggerInstall()
|
||||
case "post-delete":
|
||||
appName := flag.Arg(0)
|
||||
err = logs.TriggerPostDelete(appName)
|
||||
case "report":
|
||||
appName := flag.Arg(0)
|
||||
err = logs.ReportSingleApp(appName, "")
|
||||
default:
|
||||
common.LogFail(fmt.Sprintf("Invalid plugin trigger call: %s", trigger))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
@@ -45,3 +47,85 @@ func CommandFailed(appName string, allApps bool) error {
|
||||
|
||||
return GetFailedLogs(appName)
|
||||
}
|
||||
|
||||
// CommandReport displays a logs report for one or more apps
|
||||
func CommandReport(appName string, infoFlag string) error {
|
||||
if len(appName) == 0 {
|
||||
apps, err := common.DokkuApps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, appName := range apps {
|
||||
if err := ReportSingleApp(appName, infoFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return ReportSingleApp(appName, infoFlag)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
common.CommandPropertySet("logs", appName, property, value, DefaultProperties, GlobalProperties)
|
||||
if property == "vector-sink" {
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Writing updated vector config to %s", filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "logs", "vector.json")))
|
||||
return writeVectorConfig()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandVectorLogs tails the log output for the vector container
|
||||
func CommandVectorLogs() error {
|
||||
if !common.ContainerExists(vectorContainerName) {
|
||||
return errors.New("Vector container does not exist")
|
||||
}
|
||||
|
||||
if !common.ContainerIsRunning(vectorContainerName) {
|
||||
return errors.New("Vector container is not running")
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet("Tailing vector container logs")
|
||||
common.LogVerboseQuietContainerLogsTail(vectorContainerName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandVectorStart starts a new vector container
|
||||
// or starts an existing one if it already exists
|
||||
func CommandVectorStart(vectorImage string) error {
|
||||
common.LogInfo2("Starting vector container")
|
||||
common.LogVerbose("Ensuring vector configuration exists")
|
||||
if err := writeVectorConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.ContainerExists(vectorContainerName) {
|
||||
return startVectorContainer(vectorImage)
|
||||
}
|
||||
|
||||
if common.ContainerIsRunning(vectorContainerName) {
|
||||
common.LogVerbose("Container already running")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !common.ContainerStart(vectorContainerName) {
|
||||
return errors.New("Unable to start vector container")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandVectorStop stops and removes an existing vector container
|
||||
func CommandVectorStop() error {
|
||||
common.LogInfo2Quiet("Stopping and removing vector container")
|
||||
return killVectorContainer()
|
||||
}
|
||||
|
||||
41
plugins/logs/triggers.go
Normal file
41
plugins/logs/triggers.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// TriggerInstall initializes app restart policies
|
||||
func TriggerInstall() error {
|
||||
if err := common.PropertySetup("logs"); err != nil {
|
||||
return fmt.Errorf("Unable to install the logs plugin: %s", err.Error())
|
||||
}
|
||||
|
||||
directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "logs")
|
||||
if err := os.MkdirAll(directory, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := common.SetPermissions(directory, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logDirectory := filepath.Join(common.MustGetEnv("DOKKU_LOGS_DIR"), "apps")
|
||||
if err := os.MkdirAll(logDirectory, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := common.SetPermissions(logDirectory, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerPostDelete destroys the network property for a given app container
|
||||
func TriggerPostDelete(appName string) error {
|
||||
return common.PropertyDestroy("logs", appName)
|
||||
}
|
||||
@@ -22,6 +22,9 @@ var (
|
||||
"attach-post-deploy": "",
|
||||
"tld": "",
|
||||
}
|
||||
|
||||
// GlobalProperties is a map of all valid global network properties
|
||||
GlobalProperties = map[string]bool{}
|
||||
)
|
||||
|
||||
// BuildConfig builds network config files
|
||||
|
||||
@@ -169,6 +169,6 @@ func CommandSet(appName string, property string, value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
common.CommandPropertySet("network", appName, property, value, DefaultProperties)
|
||||
common.CommandPropertySet("network", appName, property, value, DefaultProperties, GlobalProperties)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ var (
|
||||
DefaultProperties = map[string]string{
|
||||
"restart-policy": "on-failure:10",
|
||||
}
|
||||
|
||||
// GlobalProperties is a map of all valid global ps properties
|
||||
GlobalProperties = map[string]bool{}
|
||||
)
|
||||
|
||||
// Rebuild rebuilds app from base image
|
||||
|
||||
@@ -163,7 +163,7 @@ func CommandSet(appName string, property string, value string) error {
|
||||
return dockeroptions.SetDockerOptionForPhases(appName, []string{"deploy"}, "restart", value)
|
||||
}
|
||||
|
||||
common.CommandPropertySet("ps", appName, property, value, DefaultProperties)
|
||||
common.CommandPropertySet("ps", appName, property, value, DefaultProperties, GlobalProperties)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user