stash/pkg/logger/plugin.go
gitgiggety b83ce29ac4
Scraper log improvements (#1741)
* Fix logs from scraper and plugins not being shown in UI

Using `logger.` in the logger package to write logs is "incorrect". This
as the package contains a variable named `logger` which contains the
logrus instance. So instead of the log line being handled by the custom
log implementation / wrapper which makes sure the lines are shown in the
UI as well, it's written to logrus directly meaning the wrapper is
skipped.

This "issue" is obviously triggered because in any other place
`logger.X` can be used and it will used the custom logger package /
wrapper which works fine.

* Add plugin / scraper name to logging output

Indicate which plugin / scraper wrote a log message by including its
name to the `[Scrape]` prefix.

* Add missing addLogItem call
2021-09-19 10:06:34 +10:00

191 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 {
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()
}