mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-14 18:31:10 +02:00
This allows us to keep our current zap configuration, keep logs human-readable and still extract metadata fields from them. Signed-off-by: Alexey Palazhchenko <alexey.palazhchenko@talos-systems.com>
125 lines
2.3 KiB
Go
125 lines
2.3 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package logging
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
|
)
|
|
|
|
var maxEpochTS = float64(time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC).Unix())
|
|
|
|
//nolint:gocyclo
|
|
func parseLogLine(l []byte, now time.Time) *runtime.LogEvent {
|
|
msg, m := parseJSONLogLine(l)
|
|
e := &runtime.LogEvent{
|
|
Msg: msg,
|
|
Time: now,
|
|
Level: zapcore.InfoLevel,
|
|
}
|
|
|
|
if m == nil {
|
|
return e
|
|
}
|
|
|
|
for _, k := range []string{"time", "ts"} {
|
|
var t time.Time
|
|
switch ts := m[k].(type) {
|
|
case string:
|
|
t, _ = time.Parse(time.RFC3339Nano, ts) //nolint:errcheck
|
|
case float64:
|
|
// seconds or milliseconds since epoch
|
|
sec, fsec := math.Modf(ts)
|
|
if sec > maxEpochTS {
|
|
sec, fsec = math.Modf(ts / 1000)
|
|
}
|
|
|
|
t = time.Unix(int64(sec), int64(fsec*float64(time.Second)))
|
|
}
|
|
|
|
if !t.IsZero() {
|
|
e.Time = t.UTC()
|
|
|
|
delete(m, k)
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if levelS, ok := m["level"].(string); ok {
|
|
levelS = strings.ToLower(levelS)
|
|
|
|
// convert containerd's logrus' level to zap's level
|
|
if levelS == "warning" {
|
|
levelS = "warn"
|
|
}
|
|
|
|
var level zapcore.Level
|
|
if err := level.UnmarshalText([]byte(levelS)); err == nil {
|
|
e.Level = level
|
|
|
|
delete(m, "level")
|
|
}
|
|
}
|
|
|
|
if msgS, ok := m["msg"].(string); ok {
|
|
// in case we have both message before JSON and "msg" JSON field
|
|
if e.Msg != "" {
|
|
e.Msg += " "
|
|
}
|
|
|
|
e.Msg += strings.TrimSpace(msgS)
|
|
|
|
delete(m, "msg")
|
|
}
|
|
|
|
if errS, ok := m["err"].(string); ok {
|
|
if e.Level < zap.WarnLevel {
|
|
e.Level = zap.WarnLevel
|
|
}
|
|
|
|
if e.Msg != "" {
|
|
e.Msg += ": "
|
|
}
|
|
|
|
e.Msg += strings.TrimSpace(errS)
|
|
|
|
delete(m, "err")
|
|
}
|
|
|
|
e.Fields = m
|
|
|
|
return e
|
|
}
|
|
|
|
func parseJSONLogLine(l []byte) (msg string, m map[string]interface{}) {
|
|
// the whole line is valid JSON
|
|
if err := json.Unmarshal(l, &m); err == nil {
|
|
return
|
|
}
|
|
|
|
// the line is a message followed by JSON
|
|
if i := bytes.Index(l, []byte("{")); i != -1 {
|
|
if err := json.Unmarshal(l[i:], &m); err == nil {
|
|
msg = string(bytes.TrimSpace(l[:i]))
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
// no JSON found
|
|
msg = string(l)
|
|
|
|
return
|
|
}
|