Files
dokku/plugins/common/log.go
2025-11-13 01:52:08 -05:00

250 lines
6.0 KiB
Go

package common
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
"github.com/hashicorp/go-multierror"
)
// ErrWithExitCode wraps error and exposes an ExitCode method
type ErrWithExitCode interface {
ExitCode() int
}
type writer struct {
mu *sync.Mutex
source string
}
// Write prints the data to either stdout or stderr using the log helper functions
func (w *writer) Write(bytes []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if w.source == "stdout" {
for _, line := range strings.Split(string(bytes), "\n") {
if line == "" {
continue
}
LogVerboseQuiet(line)
}
} else {
for _, line := range strings.Split(string(bytes), "\n") {
if line == "" {
continue
}
LogVerboseStderrQuiet(line)
}
}
return len(bytes), nil
}
// PrefixingWriter is a writer that prefixes all writes with a given prefix
type PrefixingWriter struct {
Prefix []byte
Writer io.Writer
}
// Write writes the given bytes to the writer with the prefix
func (pw *PrefixingWriter) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
// Perform an "atomic" write of a prefix and p to make sure that it doesn't interleave
// sub-line when used concurrently with io.PipeWrite.
n, err := pw.Writer.Write(append(pw.Prefix, p...))
if n > len(p) {
// To comply with the io.Writer interface requirements we must
// return a number of bytes written from p (0 <= n <= len(p)),
// so we are ignoring the length of the prefix here.
return len(p), err
}
return n, err
}
// LogFail is the failure log formatter
// prints text to stderr and exits with status 1
func LogFail(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
os.Exit(1)
}
// LogFailWithError is the failure log formatter
// prints text to stderr and exits with the specified exit code
func LogFailWithError(err error) {
if err == nil {
return
}
if merr, ok := err.(*multierror.Error); ok {
for _, e := range merr.Errors {
fmt.Fprintf(os.Stderr, " ! %s\n", e.Error())
}
} else {
if err.Error() != "" {
fmt.Fprintf(os.Stderr, " ! %s\n", err.Error())
}
}
if errExit, ok := err.(ErrWithExitCode); ok {
os.Exit(errExit.ExitCode())
}
os.Exit(1)
}
// LogFailWithErrorQuiet is the failure log formatter (with quiet option)
// prints text to stderr and exits with the specified exit code
// The error message is not printed if DOKKU_QUIET_OUTPUT has any value
func LogFailWithErrorQuiet(err error) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", err.Error()))
}
if errExit, ok := err.(ErrWithExitCode); ok {
os.Exit(errExit.ExitCode())
}
os.Exit(1)
}
// LogFailQuiet is the failure log formatter (with quiet option)
// prints text to stderr and exits with status 1
func LogFailQuiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
}
os.Exit(1)
}
// Log is the log formatter
func Log(text string) {
fmt.Println(text)
}
// LogQuiet is the log formatter (with quiet option)
func LogQuiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
fmt.Println(text)
}
}
// LogInfo1 is the info1 header formatter
func LogInfo1(text string) {
fmt.Println(fmt.Sprintf("-----> %s", text))
}
// LogInfo1Quiet is the info1 header formatter (with quiet option)
func LogInfo1Quiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
LogInfo1(text)
}
}
// LogInfo2 is the info2 header formatter
func LogInfo2(text string) {
fmt.Println(fmt.Sprintf("=====> %s", text))
}
// LogInfo2Quiet is the info2 header formatter (with quiet option)
func LogInfo2Quiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
LogInfo2(text)
}
}
// LogVerbose is the verbose log formatter
// prints indented text to stdout
func LogVerbose(text string) {
fmt.Println(fmt.Sprintf(" %s", text))
}
// LogVerboseStderr is the verbose log formatter
// prints indented text to stderr
func LogVerboseStderr(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
}
// LogVerboseQuiet is the verbose log formatter
// prints indented text to stdout (with quiet option)
func LogVerboseQuiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
LogVerbose(text)
}
}
// LogVerboseStderrQuiet is the verbose log formatter
// prints indented text to stderr (with quiet option)
func LogVerboseStderrQuiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
LogVerboseStderr(text)
}
}
// LogVerboseQuietContainerLogs is the verbose log formatter for container logs
func LogVerboseQuietContainerLogs(containerID string) {
LogVerboseQuietContainerLogsTail(containerID, 0, false)
}
// LogVerboseQuietContainerLogsTail is the verbose log formatter for container logs with tail mode enabled
func LogVerboseQuietContainerLogsTail(containerID string, lines int, tail bool) {
args := []string{"container", "logs", containerID}
if lines > 0 {
args = append(args, "--tail", strconv.Itoa(lines))
}
if tail {
args = append(args, "--follow")
}
var mu sync.Mutex
result, err := CallExecCommand(ExecCommandInput{
Command: DockerBin(),
Args: args,
DisableStdioBuffer: true,
StdoutWriter: &writer{
mu: &mu,
source: "stdout",
},
StderrWriter: &writer{
mu: &mu,
source: "stderr",
},
})
if err != nil {
LogExclaim(fmt.Sprintf("Failed to fetch container logs: %s", containerID))
return
}
if !tail && result.ExitCode != 0 {
LogExclaim(fmt.Sprintf("Failed to fetch container logs: %s", containerID))
}
}
// LogWarn is the warning log formatter
func LogWarn(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
}
// LogExclaim is the log exclaim formatter
func LogExclaim(text string) {
fmt.Println(fmt.Sprintf(" ! %s", text))
}
// LogStderr is the stderr log formatter
func LogStderr(text string) {
fmt.Fprintln(os.Stderr, text)
}
// LogDebug is the debug log formatter
func LogDebug(text string) {
if os.Getenv("DOKKU_TRACE") == "1" {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ? %s", strings.TrimPrefix(text, " ? ")))
}
}