mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-24 06:28:42 +01:00
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
|
||||
"github.com/go-logfmt/logfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -160,9 +158,6 @@ func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var validLogFmtMessage = regexp.MustCompile(`([a-zA-Z0-9_.-]+)=(?:(?:"(.*)")|(?:(?:([^\s]+)[\s])))`)
|
||||
var validLogFmtKey = regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`)
|
||||
|
||||
func createEvent(message string, streamType StdType) *LogEvent {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
@@ -185,27 +180,8 @@ func createEvent(message string, streamType StdType) *LogEvent {
|
||||
} else {
|
||||
logEvent.Message = data
|
||||
}
|
||||
} else if validLogFmtMessage.MatchString(message) {
|
||||
buffer := bufPool.Get().(*bytes.Buffer)
|
||||
buffer.Reset()
|
||||
defer bufPool.Put(buffer)
|
||||
buffer.WriteString(message)
|
||||
decoder := logfmt.NewDecoder(buffer)
|
||||
data := make(map[string]string)
|
||||
decoder.ScanRecord()
|
||||
allValid := true
|
||||
for decoder.ScanKeyval() {
|
||||
key := decoder.Key()
|
||||
value := decoder.Value()
|
||||
if !validLogFmtKey.Match(key) {
|
||||
allValid = false
|
||||
break
|
||||
}
|
||||
data[string(key)] = string(value)
|
||||
}
|
||||
if allValid && len(data) > 1 {
|
||||
logEvent.Message = data
|
||||
}
|
||||
} else if data, err := ParseLogFmt(message); err == nil {
|
||||
logEvent.Message = data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,17 @@ func Test_createEvent(t *testing.T) {
|
||||
Message: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid logfmt message",
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z sample text with=equal sign",
|
||||
},
|
||||
want: &LogEvent{
|
||||
Message: "sample text with=equal sign",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := createEvent(tt.args.message, STDOUT); !reflect.DeepEqual(got.Message, tt.want.Message) {
|
||||
|
||||
@@ -52,6 +52,11 @@ func guessLogLevel(logEvent *LogEvent) string {
|
||||
}
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
if level, ok := value.Get("level"); ok {
|
||||
return strings.ToLower(level)
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if level, ok := value["level"].(string); ok {
|
||||
return strings.ToLower(level)
|
||||
|
||||
@@ -7,10 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestGuessLogLevel(t *testing.T) {
|
||||
ordereddata := orderedmap.New[string, any]()
|
||||
ordereddata.Set("key", "value")
|
||||
ordereddata.Set("level", "info")
|
||||
|
||||
tests := []struct {
|
||||
input any
|
||||
expected string
|
||||
@@ -35,7 +31,18 @@ func TestGuessLogLevel(t *testing.T) {
|
||||
{map[string]interface{}{"level": "INFO"}, "info"},
|
||||
{map[string]string{"level": "info"}, "info"},
|
||||
{map[string]string{"level": "INFO"}, "info"},
|
||||
{ordereddata, "info"},
|
||||
{orderedmap.New[string, string](
|
||||
orderedmap.WithInitialData(
|
||||
orderedmap.Pair[string, string]{Key: "key", Value: "value"},
|
||||
orderedmap.Pair[string, string]{Key: "level", Value: "info"},
|
||||
),
|
||||
), "info"},
|
||||
{orderedmap.New[string, any](
|
||||
orderedmap.WithInitialData(
|
||||
orderedmap.Pair[string, any]{Key: "key", Value: "value"},
|
||||
orderedmap.Pair[string, any]{Key: "level", Value: "info"},
|
||||
),
|
||||
), "info"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
86
internal/docker/logfmt.go
Normal file
86
internal/docker/logfmt.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
// ParseLogFmt parses a log entry in logfmt format and returns a map of key-value pairs.
|
||||
func ParseLogFmt(log string) (*orderedmap.OrderedMap[string, string], error) {
|
||||
result := orderedmap.New[string, string]()
|
||||
var key, value string
|
||||
inQuotes, escaping, isKey := false, false, true
|
||||
start := 0
|
||||
|
||||
for i := 0; i < len(log); i++ {
|
||||
char := log[i]
|
||||
|
||||
if isKey {
|
||||
if char == '=' {
|
||||
if i == start {
|
||||
return nil, errors.New("invalid format: key is empty")
|
||||
}
|
||||
key = log[start:i]
|
||||
isKey = false
|
||||
start = i + 1
|
||||
} else if char == ' ' {
|
||||
if i > start {
|
||||
return nil, errors.New("invalid format: unexpected space in key")
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if inQuotes {
|
||||
if escaping {
|
||||
escaping = false
|
||||
} else if char == '\\' {
|
||||
escaping = true
|
||||
} else if char == '"' {
|
||||
value = log[start:i]
|
||||
result.Set(key, value)
|
||||
inQuotes = false
|
||||
isKey = true
|
||||
start = i + 2
|
||||
}
|
||||
} else {
|
||||
if char == '"' {
|
||||
inQuotes = true
|
||||
start = i + 1
|
||||
} else if char == ' ' {
|
||||
if i == start {
|
||||
return nil, errors.New("invalid format: value is empty")
|
||||
}
|
||||
value = log[start:i]
|
||||
result.Set(key, value)
|
||||
isKey = true
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last key-value pair if there is no trailing space
|
||||
if !isKey && start < len(log) {
|
||||
if inQuotes {
|
||||
return nil, errors.New("invalid format: unclosed quotes")
|
||||
}
|
||||
value = log[start:]
|
||||
result.Set(key, value)
|
||||
} else if isKey && start < len(log) {
|
||||
return nil, errors.New("invalid format: unexpected key without value")
|
||||
}
|
||||
|
||||
if !isKey {
|
||||
if inQuotes {
|
||||
return nil, errors.New("invalid format: unclosed quotes")
|
||||
}
|
||||
if start >= len(log) {
|
||||
return nil, errors.New("invalid format: value is empty")
|
||||
}
|
||||
value = log[start:]
|
||||
result.Set(key, value)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
82
internal/docker/logfmt_test.go
Normal file
82
internal/docker/logfmt_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
func TestParseLog(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
log string
|
||||
want *orderedmap.OrderedMap[string, string]
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid logfmt log",
|
||||
log: `time="2024-06-02T14:30:42Z" level=debug msg="container e23e04da2cb9 started"`,
|
||||
want: orderedmap.New[string, string](
|
||||
orderedmap.WithInitialData(
|
||||
orderedmap.Pair[string, string]{Key: "time", Value: "2024-06-02T14:30:42Z"},
|
||||
orderedmap.Pair[string, string]{Key: "level", Value: "debug"},
|
||||
orderedmap.Pair[string, string]{Key: "msg", Value: "container e23e04da2cb9 started"},
|
||||
),
|
||||
),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Random test with equal sign",
|
||||
log: "foo bar=baz",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid log with key without value",
|
||||
log: "key1=value1 key2=",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Valid log",
|
||||
log: "key1=value1 key2=value2",
|
||||
want: orderedmap.New[string, string](
|
||||
orderedmap.WithInitialData(
|
||||
orderedmap.Pair[string, string]{Key: "key1", Value: "value1"},
|
||||
orderedmap.Pair[string, string]{Key: "key2", Value: "value2"},
|
||||
),
|
||||
),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid log with unclosed quotes",
|
||||
log: "key1=\"value1 key2=value2",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Plain text log",
|
||||
log: "foo bar baz",
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseLogFmt(tt.log)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseLogFmt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
jsonGot, _ := json.MarshalIndent(got, "", " ")
|
||||
jsonWant, _ := json.MarshalIndent(tt.want, "", " ")
|
||||
t.Errorf("ParseLogFmt() = %v, want %v", string(jsonGot), string(jsonWant))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user