stash/pkg/logger/plugin.go
SmallCoccinelle a5ca8fc678
Enable safe linters (#1786)
* Enable safe linters

Enable the linters dogsled, rowserrcheck, and sqlclosecheck.

These report no errors currently in the code base.

Enable misspell.

Misspell finds two spelling mistakes in comments, which are fixed by the
patch as well.

Add and sort linters which are relatively
safe to add over time. Comment them out for now.

* Close the response body

If we can get a HTTP response, it has a body which ought to be closed.

By doing so, we avoid potentially leaking connections.

* Enable the exportloopref linter

There are two places in the code with these warnings. Fix them while
enabling the linter.

* Remove redundant types in tests

If a slice already determines the type, the inner type declaration is
redundant. Remove the inner declarations.

* Mark autotag test cases as parallel

Autotag test cases is by far the outlier when it comes to test time.
While go test runs test cases in parallel,
it doesn't do so inside a given package, unless one marks the test cases
as parallel.

This change provides a significant speedup on a 8-core machine for test
runs.
2021-10-03 11:48:03 +11:00

192 lines
3.5 KiB
Go

package logger
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
type PluginLogLevel struct {
char byte
Name string
}
// Valid Level values.
var (
TraceLevel = PluginLogLevel{
char: 't',
Name: "trace",
}
DebugLevel = PluginLogLevel{
char: 'd',
Name: "debug",
}
InfoLevel = PluginLogLevel{
char: 'i',
Name: "info",
}
WarningLevel = PluginLogLevel{
char: 'w',
Name: "warning",
}
ErrorLevel = PluginLogLevel{
char: 'e',
Name: "error",
}
ProgressLevel = PluginLogLevel{
char: 'p',
Name: "progress",
}
NoneLevel = PluginLogLevel{
Name: "none",
}
)
var validLevels = []PluginLogLevel{
TraceLevel,
DebugLevel,
InfoLevel,
WarningLevel,
ErrorLevel,
ProgressLevel,
NoneLevel,
}
const startLevelChar byte = 1
const endLevelChar byte = 2
func (l PluginLogLevel) prefix() string {
return string([]byte{
startLevelChar,
byte(l.char),
endLevelChar,
})
}
func (l PluginLogLevel) Log(args ...interface{}) {
if l.char == 0 {
return
}
argsToUse := []interface{}{
l.prefix(),
}
argsToUse = append(argsToUse, args...)
fmt.Fprintln(os.Stderr, argsToUse...)
}
func (l PluginLogLevel) Logf(format string, args ...interface{}) {
if l.char == 0 {
return
}
formatToUse := string(l.prefix()) + format + "\n"
fmt.Fprintf(os.Stderr, formatToUse, args...)
}
// PluginLogLevelFromName returns the Level that matches the provided name or nil if
// the name does not match a valid value.
func PluginLogLevelFromName(name string) *PluginLogLevel {
for _, l := range validLevels {
if l.Name == name {
return &l
}
}
return nil
}
// DetectLogLevel returns the Level and the logging string for a provided line
// of plugin output. It parses the string for logging control characters and
// determines the log level, if present. If not present, the plugin output
// is returned unchanged with a nil Level.
func DetectLogLevel(line string) (*PluginLogLevel, string) {
if len(line) < 4 || line[0] != startLevelChar || line[2] != endLevelChar {
return nil, line
}
char := line[1]
var level *PluginLogLevel
for _, l := range validLevels {
if l.char == char {
l := l // Make a copy of the loop variable
level = &l
break
}
}
if level == nil {
return nil, line
}
line = strings.TrimSpace(line[3:])
return level, line
}
type PluginLogger struct {
Prefix string
DefaultLogLevel *PluginLogLevel
ProgressChan chan float64
}
func (log *PluginLogger) HandleStderrLine(line string) {
level, ll := DetectLogLevel(line)
// if no log level, just output to info
if level == nil {
if log.DefaultLogLevel != nil {
level = log.DefaultLogLevel
} else {
level = &InfoLevel
}
}
switch *level {
case TraceLevel:
Trace(log.Prefix, ll)
case DebugLevel:
Debug(log.Prefix, ll)
case InfoLevel:
Info(log.Prefix, ll)
case WarningLevel:
Warn(log.Prefix, ll)
case ErrorLevel:
Error(log.Prefix, ll)
case ProgressLevel:
p, err := strconv.ParseFloat(ll, 64)
if err != nil {
Errorf("Error parsing progress value '%s': %s", ll, err.Error())
} else {
// only pass progress through if channel present
if log.ProgressChan != nil {
// don't block on this
select {
case log.ProgressChan <- p:
default:
}
}
}
}
}
func (log *PluginLogger) HandlePluginStdErr(pluginStdErr io.ReadCloser) {
// pipe plugin stderr to our logging
scanner := bufio.NewScanner(pluginStdErr)
for scanner.Scan() {
str := scanner.Text()
if str != "" {
log.HandleStderrLine(str)
}
}
str := scanner.Text()
if str != "" {
log.HandleStderrLine(str)
}
pluginStdErr.Close()
}