e1.9.0 Structured JSON log method added, refactored.
This commit is contained in:
@ -48,7 +48,7 @@ func WithLevelDetector(detector func(string) int64) FastHTTPOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Printf implements fasthttp's Logger interface
|
// Printf implements fasthttp's Logger interface
|
||||||
func (a *FastHTTPAdapter) Printf(format string, args ...interface{}) {
|
func (a *FastHTTPAdapter) Printf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
|
||||||
// Detect log level from message content
|
// Detect log level from message content
|
||||||
|
|||||||
@ -42,31 +42,31 @@ func WithFatalHandler(handler func(string)) GnetOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debugf logs at debug level with printf-style formatting
|
// Debugf logs at debug level with printf-style formatting
|
||||||
func (a *GnetAdapter) Debugf(format string, args ...interface{}) {
|
func (a *GnetAdapter) Debugf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
a.logger.Debug("msg", msg, "source", "gnet")
|
a.logger.Debug("msg", msg, "source", "gnet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof logs at info level with printf-style formatting
|
// Infof logs at info level with printf-style formatting
|
||||||
func (a *GnetAdapter) Infof(format string, args ...interface{}) {
|
func (a *GnetAdapter) Infof(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
a.logger.Info("msg", msg, "source", "gnet")
|
a.logger.Info("msg", msg, "source", "gnet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnf logs at warn level with printf-style formatting
|
// Warnf logs at warn level with printf-style formatting
|
||||||
func (a *GnetAdapter) Warnf(format string, args ...interface{}) {
|
func (a *GnetAdapter) Warnf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
a.logger.Warn("msg", msg, "source", "gnet")
|
a.logger.Warn("msg", msg, "source", "gnet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs at error level with printf-style formatting
|
// Errorf logs at error level with printf-style formatting
|
||||||
func (a *GnetAdapter) Errorf(format string, args ...interface{}) {
|
func (a *GnetAdapter) Errorf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
a.logger.Error("msg", msg, "source", "gnet")
|
a.logger.Error("msg", msg, "source", "gnet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf logs at error level and triggers fatal handler
|
// Fatalf logs at error level and triggers fatal handler
|
||||||
func (a *GnetAdapter) Fatalf(format string, args ...interface{}) {
|
func (a *GnetAdapter) Fatalf(format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
a.logger.Error("msg", msg, "source", "gnet", "fatal", true)
|
a.logger.Error("msg", msg, "source", "gnet", "fatal", true)
|
||||||
|
|
||||||
|
|||||||
@ -11,18 +11,18 @@ import (
|
|||||||
|
|
||||||
// parseFormat attempts to extract structured fields from printf-style format strings
|
// parseFormat attempts to extract structured fields from printf-style format strings
|
||||||
// This is useful for preserving structured logging semantics
|
// This is useful for preserving structured logging semantics
|
||||||
func parseFormat(format string, args []interface{}) []interface{} {
|
func parseFormat(format string, args []any) []any {
|
||||||
// Pattern to detect common structured patterns like "key=%v" or "key: %v"
|
// Pattern to detect common structured patterns like "key=%v" or "key: %v"
|
||||||
keyValuePattern := regexp.MustCompile(`(\w+)\s*[:=]\s*%[vsdqxXeEfFgGpbcU]`)
|
keyValuePattern := regexp.MustCompile(`(\w+)\s*[:=]\s*%[vsdqxXeEfFgGpbcU]`)
|
||||||
|
|
||||||
matches := keyValuePattern.FindAllStringSubmatchIndex(format, -1)
|
matches := keyValuePattern.FindAllStringSubmatchIndex(format, -1)
|
||||||
if len(matches) == 0 || len(matches) > len(args) {
|
if len(matches) == 0 || len(matches) > len(args) {
|
||||||
// Fallback to simple message if pattern doesn't match
|
// Fallback to simple message if pattern doesn't match
|
||||||
return []interface{}{"msg", fmt.Sprintf(format, args...)}
|
return []any{"msg", fmt.Sprintf(format, args...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build structured fields
|
// Build structured fields
|
||||||
fields := make([]interface{}, 0, len(matches)*2+2)
|
fields := make([]any, 0, len(matches)*2+2)
|
||||||
lastEnd := 0
|
lastEnd := 0
|
||||||
argIndex := 0
|
argIndex := 0
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ func NewStructuredGnetAdapter(logger *log.Logger, opts ...GnetOption) *Structure
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debugf logs with structured field extraction
|
// Debugf logs with structured field extraction
|
||||||
func (a *StructuredGnetAdapter) Debugf(format string, args ...interface{}) {
|
func (a *StructuredGnetAdapter) Debugf(format string, args ...any) {
|
||||||
if a.extractFields {
|
if a.extractFields {
|
||||||
fields := parseFormat(format, args)
|
fields := parseFormat(format, args)
|
||||||
a.logger.Debug(append(fields, "source", "gnet")...)
|
a.logger.Debug(append(fields, "source", "gnet")...)
|
||||||
@ -101,7 +101,7 @@ func (a *StructuredGnetAdapter) Debugf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Infof logs with structured field extraction
|
// Infof logs with structured field extraction
|
||||||
func (a *StructuredGnetAdapter) Infof(format string, args ...interface{}) {
|
func (a *StructuredGnetAdapter) Infof(format string, args ...any) {
|
||||||
if a.extractFields {
|
if a.extractFields {
|
||||||
fields := parseFormat(format, args)
|
fields := parseFormat(format, args)
|
||||||
a.logger.Info(append(fields, "source", "gnet")...)
|
a.logger.Info(append(fields, "source", "gnet")...)
|
||||||
@ -111,7 +111,7 @@ func (a *StructuredGnetAdapter) Infof(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Warnf logs with structured field extraction
|
// Warnf logs with structured field extraction
|
||||||
func (a *StructuredGnetAdapter) Warnf(format string, args ...interface{}) {
|
func (a *StructuredGnetAdapter) Warnf(format string, args ...any) {
|
||||||
if a.extractFields {
|
if a.extractFields {
|
||||||
fields := parseFormat(format, args)
|
fields := parseFormat(format, args)
|
||||||
a.logger.Warn(append(fields, "source", "gnet")...)
|
a.logger.Warn(append(fields, "source", "gnet")...)
|
||||||
@ -121,7 +121,7 @@ func (a *StructuredGnetAdapter) Warnf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs with structured field extraction
|
// Errorf logs with structured field extraction
|
||||||
func (a *StructuredGnetAdapter) Errorf(format string, args ...interface{}) {
|
func (a *StructuredGnetAdapter) Errorf(format string, args ...any) {
|
||||||
if a.extractFields {
|
if a.extractFields {
|
||||||
fields := parseFormat(format, args)
|
fields := parseFormat(format, args)
|
||||||
a.logger.Error(append(fields, "source", "gnet")...)
|
a.logger.Error(append(fields, "source", "gnet")...)
|
||||||
|
|||||||
@ -63,11 +63,11 @@ type GnetAdapter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Methods implemented:
|
// Methods implemented:
|
||||||
// - Debugf(format string, args ...interface{})
|
// - Debugf(format string, args ...any)
|
||||||
// - Infof(format string, args ...interface{})
|
// - Infof(format string, args ...any)
|
||||||
// - Warnf(format string, args ...interface{})
|
// - Warnf(format string, args ...any)
|
||||||
// - Errorf(format string, args ...interface{})
|
// - Errorf(format string, args ...any)
|
||||||
// - Fatalf(format string, args ...interface{})
|
// - Fatalf(format string, args ...any)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom Fatal Behavior
|
### Custom Fatal Behavior
|
||||||
|
|||||||
174
format.go
174
format.go
@ -4,8 +4,8 @@ package log
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -41,7 +41,12 @@ func (s *serializer) serialize(format string, flags int64, timestamp time.Time,
|
|||||||
return s.serializeRaw(args)
|
return s.serializeRaw(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Handle the instance-wide configuration setting
|
// 2. Check for structured JSON flag
|
||||||
|
if flags&FlagStructuredJSON != 0 && format == "json" {
|
||||||
|
return s.serializeStructuredJSON(flags, timestamp, level, trace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Handle the instance-wide configuration setting
|
||||||
if format == "raw" {
|
if format == "raw" {
|
||||||
return s.serializeRaw(args)
|
return s.serializeRaw(args)
|
||||||
}
|
}
|
||||||
@ -122,86 +127,6 @@ func (s *serializer) writeRawValue(v any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the safe, dependency-free replacement for fmt.Sprintf.
|
|
||||||
func (s *serializer) reflectValue(v reflect.Value) {
|
|
||||||
// Safely handle invalid, nil pointer, or nil interface values.
|
|
||||||
if !v.IsValid() {
|
|
||||||
s.buf = append(s.buf, "nil"...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Dereference pointers and interfaces to get the concrete value.
|
|
||||||
// Recurse to handle multiple levels of pointers.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Ptr || kind == reflect.Interface {
|
|
||||||
if v.IsNil() {
|
|
||||||
s.buf = append(s.buf, "nil"...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.reflectValue(v.Elem())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.String:
|
|
||||||
s.buf = append(s.buf, v.String()...)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
s.buf = strconv.AppendInt(s.buf, v.Int(), 10)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
s.buf = strconv.AppendUint(s.buf, v.Uint(), 10)
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
s.buf = strconv.AppendFloat(s.buf, v.Float(), 'f', -1, 64)
|
|
||||||
case reflect.Bool:
|
|
||||||
s.buf = strconv.AppendBool(s.buf, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
// Check if it's a byte slice ([]uint8) and hex-encode it for safety.
|
|
||||||
if v.Type().Elem().Kind() == reflect.Uint8 {
|
|
||||||
s.buf = append(s.buf, "0x"...)
|
|
||||||
s.buf = hex.AppendEncode(s.buf, v.Bytes())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.buf = append(s.buf, '[')
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
s.buf = append(s.buf, ' ')
|
|
||||||
}
|
|
||||||
s.reflectValue(v.Index(i))
|
|
||||||
}
|
|
||||||
s.buf = append(s.buf, ']')
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
s.buf = append(s.buf, '{')
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
if !v.Type().Field(i).IsExported() {
|
|
||||||
continue // Skip unexported fields
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
s.buf = append(s.buf, ' ')
|
|
||||||
}
|
|
||||||
s.buf = append(s.buf, v.Type().Field(i).Name...)
|
|
||||||
s.buf = append(s.buf, ':')
|
|
||||||
s.reflectValue(v.Field(i))
|
|
||||||
}
|
|
||||||
s.buf = append(s.buf, '}')
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
s.buf = append(s.buf, '{')
|
|
||||||
for i, key := range v.MapKeys() {
|
|
||||||
if i > 0 {
|
|
||||||
s.buf = append(s.buf, ' ')
|
|
||||||
}
|
|
||||||
s.reflectValue(key)
|
|
||||||
s.buf = append(s.buf, ':')
|
|
||||||
s.reflectValue(v.MapIndex(key))
|
|
||||||
}
|
|
||||||
s.buf = append(s.buf, '}')
|
|
||||||
|
|
||||||
default:
|
|
||||||
// As a final fallback, use fmt, but this should rarely be hit.
|
|
||||||
s.buf = append(s.buf, fmt.Sprint(v.Interface())...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// serializeJSON formats log entries as JSON (time, level, trace, fields).
|
// serializeJSON formats log entries as JSON (time, level, trace, fields).
|
||||||
func (s *serializer) serializeJSON(flags int64, timestamp time.Time, level int64, trace string, args []any) []byte {
|
func (s *serializer) serializeJSON(flags int64, timestamp time.Time, level int64, trace string, args []any) []byte {
|
||||||
s.buf = append(s.buf, '{')
|
s.buf = append(s.buf, '{')
|
||||||
@ -390,6 +315,91 @@ func (s *serializer) writeJSONValue(v any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serializeStructuredJSON formats log entries as structured JSON with proper field marshaling
|
||||||
|
func (s *serializer) serializeStructuredJSON(flags int64, timestamp time.Time, level int64, trace string, args []any) []byte {
|
||||||
|
// Validate args structure
|
||||||
|
if len(args) < 2 {
|
||||||
|
// Fallback to regular JSON if args are malformed
|
||||||
|
return s.serializeJSON(flags, timestamp, level, trace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
message, ok := args[0].(string)
|
||||||
|
if !ok {
|
||||||
|
// Fallback if message is not a string
|
||||||
|
return s.serializeJSON(flags, timestamp, level, trace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields, ok := args[1].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
// Fallback if fields is not a map
|
||||||
|
return s.serializeJSON(flags, timestamp, level, trace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.buf = append(s.buf, '{')
|
||||||
|
needsComma := false
|
||||||
|
|
||||||
|
// Add timestamp
|
||||||
|
if flags&FlagShowTimestamp != 0 {
|
||||||
|
s.buf = append(s.buf, `"time":"`...)
|
||||||
|
s.buf = timestamp.AppendFormat(s.buf, s.timestampFormat)
|
||||||
|
s.buf = append(s.buf, '"')
|
||||||
|
needsComma = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add level
|
||||||
|
if flags&FlagShowLevel != 0 {
|
||||||
|
if needsComma {
|
||||||
|
s.buf = append(s.buf, ',')
|
||||||
|
}
|
||||||
|
s.buf = append(s.buf, `"level":"`...)
|
||||||
|
s.buf = append(s.buf, levelToString(level)...)
|
||||||
|
s.buf = append(s.buf, '"')
|
||||||
|
needsComma = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add message
|
||||||
|
if needsComma {
|
||||||
|
s.buf = append(s.buf, ',')
|
||||||
|
}
|
||||||
|
s.buf = append(s.buf, `"message":"`...)
|
||||||
|
s.writeString(message)
|
||||||
|
s.buf = append(s.buf, '"')
|
||||||
|
needsComma = true
|
||||||
|
|
||||||
|
// // Add trace if present
|
||||||
|
// if trace != "" {
|
||||||
|
// if needsComma {
|
||||||
|
// s.buf = append(s.buf, ',')
|
||||||
|
// }
|
||||||
|
// s.buf = append(s.buf, `"trace":"`...)
|
||||||
|
// s.writeString(trace)
|
||||||
|
// s.buf = append(s.buf, '"')
|
||||||
|
// needsComma = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Marshal fields using encoding/json
|
||||||
|
if len(fields) > 0 {
|
||||||
|
if needsComma {
|
||||||
|
s.buf = append(s.buf, ',')
|
||||||
|
}
|
||||||
|
s.buf = append(s.buf, `"fields":`...)
|
||||||
|
|
||||||
|
// Use json.Marshal for proper encoding
|
||||||
|
marshaledFields, err := json.Marshal(fields)
|
||||||
|
if err != nil {
|
||||||
|
// SECURITY: Log marshaling error as a string to prevent log injection
|
||||||
|
s.buf = append(s.buf, `{"_marshal_error":"`...)
|
||||||
|
s.writeString(err.Error())
|
||||||
|
s.buf = append(s.buf, `"}`...)
|
||||||
|
} else {
|
||||||
|
s.buf = append(s.buf, marshaledFields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.buf = append(s.buf, '}', '\n')
|
||||||
|
return s.buf
|
||||||
|
}
|
||||||
|
|
||||||
// Update the levelToString function to include the new heartbeat levels
|
// Update the levelToString function to include the new heartbeat levels
|
||||||
func levelToString(level int64) string {
|
func levelToString(level int64) string {
|
||||||
switch level {
|
switch level {
|
||||||
|
|||||||
14
interface.go
14
interface.go
@ -22,10 +22,11 @@ const (
|
|||||||
|
|
||||||
// Record flags for controlling output structure
|
// Record flags for controlling output structure
|
||||||
const (
|
const (
|
||||||
FlagShowTimestamp int64 = 0b001
|
FlagShowTimestamp int64 = 0b0001
|
||||||
FlagShowLevel int64 = 0b010
|
FlagShowLevel int64 = 0b0010
|
||||||
FlagRaw int64 = 0b100
|
FlagRaw int64 = 0b0100
|
||||||
FlagDefault = FlagShowTimestamp | FlagShowLevel
|
FlagStructuredJSON int64 = 0b1000
|
||||||
|
FlagDefault = FlagShowTimestamp | FlagShowLevel
|
||||||
)
|
)
|
||||||
|
|
||||||
// logRecord represents a single log entry.
|
// logRecord represents a single log entry.
|
||||||
@ -107,6 +108,11 @@ func (l *Logger) LogTrace(depth int, args ...any) {
|
|||||||
l.log(FlagShowTimestamp, LevelInfo, int64(depth), args...)
|
l.log(FlagShowTimestamp, LevelInfo, int64(depth), args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogStructured logs a message with structured fields as proper JSON
|
||||||
|
func (l *Logger) LogStructured(level int64, message string, fields map[string]any) {
|
||||||
|
l.log(l.getFlags()|FlagStructuredJSON, level, 0, []any{message, fields})
|
||||||
|
}
|
||||||
|
|
||||||
// Write outputs raw, unformatted data regardless of configured format.
|
// Write outputs raw, unformatted data regardless of configured format.
|
||||||
// This method bypasses all formatting (timestamps, levels, JSON structure)
|
// This method bypasses all formatting (timestamps, levels, JSON structure)
|
||||||
// and writes args as space-separated strings without a trailing newline.
|
// and writes args as space-separated strings without a trailing newline.
|
||||||
|
|||||||
@ -77,7 +77,7 @@ func (l *Logger) LoadConfig(path string, args []string) error {
|
|||||||
|
|
||||||
l.initMu.Lock()
|
l.initMu.Lock()
|
||||||
defer l.initMu.Unlock()
|
defer l.initMu.Unlock()
|
||||||
return l.applyAndReconfigureLocked()
|
return l.applyConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfig saves the current logger configuration to a file
|
// SaveConfig saves the current logger configuration to a file
|
||||||
@ -143,9 +143,9 @@ func (l *Logger) updateConfigFromExternal(extCfg *config.Config, basePath string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAndReconfigureLocked applies the configuration and reconfigures logger components
|
// applyConfig applies the configuration and reconfigures logger components
|
||||||
// Assumes initMu is held
|
// Assumes initMu is held
|
||||||
func (l *Logger) applyAndReconfigureLocked() error {
|
func (l *Logger) applyConfig() error {
|
||||||
// Check parameter relationship issues
|
// Check parameter relationship issues
|
||||||
minInterval, _ := l.config.Int64("log.min_check_interval_ms")
|
minInterval, _ := l.config.Int64("log.min_check_interval_ms")
|
||||||
maxInterval, _ := l.config.Int64("log.max_check_interval_ms")
|
maxInterval, _ := l.config.Int64("log.max_check_interval_ms")
|
||||||
@ -331,7 +331,7 @@ func (l *Logger) getFlags() int64 {
|
|||||||
|
|
||||||
// log handles the core logging logic
|
// log handles the core logging logic
|
||||||
func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
|
func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
|
||||||
if l.state.LoggerDisabled.Load() || !l.state.IsInitialized.Load() {
|
if !l.state.IsInitialized.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
processor.go
59
processor.go
@ -462,59 +462,22 @@ func (l *Logger) logSysHeartbeat() {
|
|||||||
l.writeHeartbeatRecord(LevelSys, sysArgs)
|
l.writeHeartbeatRecord(LevelSys, sysArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHeartbeatRecord handles common logic for writing a heartbeat record
|
// writeHeartbeatRecord creates and sends a heartbeat log record through the main processing channel
|
||||||
func (l *Logger) writeHeartbeatRecord(level int64, args []any) {
|
func (l *Logger) writeHeartbeatRecord(level int64, args []any) {
|
||||||
if l.state.LoggerDisabled.Load() || l.state.ShutdownCalled.Load() {
|
if l.state.LoggerDisabled.Load() || l.state.ShutdownCalled.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize heartbeat data
|
// Create heartbeat record with appropriate flags
|
||||||
format, _ := l.config.String("log.format")
|
record := logRecord{
|
||||||
hbData := l.serializer.serialize(format, FlagDefault|FlagShowLevel, time.Now(), level, "", args)
|
Flags: FlagDefault | FlagShowLevel,
|
||||||
|
TimeStamp: time.Now(),
|
||||||
// Mirror to stdout if enabled
|
Level: level,
|
||||||
enableStdout, _ := l.config.Bool("log.enable_stdout")
|
Trace: "",
|
||||||
if enableStdout {
|
Args: args,
|
||||||
if s := l.state.StdoutWriter.Load(); s != nil {
|
unreportedDrops: 0,
|
||||||
// Assert to concrete type: *sink
|
|
||||||
if sinkWrapper, ok := s.(*sink); ok && sinkWrapper != nil {
|
|
||||||
// Use the wrapped writer (sinkWrapper.w)
|
|
||||||
_, _ = sinkWrapper.w.Write(hbData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
// Send through the main processing channel
|
||||||
if disableFile || !l.state.DiskStatusOK.Load() {
|
l.sendLogRecord(record)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to file
|
|
||||||
cfPtr := l.state.CurrentFile.Load()
|
|
||||||
if cfPtr == nil {
|
|
||||||
l.internalLog("error - current file handle is nil during heartbeat\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLogFile, isFile := cfPtr.(*os.File)
|
|
||||||
if !isFile || currentLogFile == nil {
|
|
||||||
l.internalLog("error - invalid file handle type during heartbeat\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := currentLogFile.Write(hbData)
|
|
||||||
if err != nil {
|
|
||||||
l.internalLog("failed to write heartbeat: %v\n", err)
|
|
||||||
l.performDiskCheck(true) // Force disk check on write failure
|
|
||||||
|
|
||||||
// One retry after disk check
|
|
||||||
n, err = currentLogFile.Write(hbData)
|
|
||||||
if err != nil {
|
|
||||||
l.internalLog("failed to write heartbeat on retry: %v\n", err)
|
|
||||||
} else {
|
|
||||||
l.state.CurrentSize.Add(int64(n))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
l.state.CurrentSize.Add(int64(n))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
6
state.go
6
state.go
@ -64,7 +64,7 @@ func (l *Logger) Init(cfg *config.Config, basePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.applyAndReconfigureLocked()
|
return l.applyConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
||||||
@ -94,7 +94,7 @@ func (l *Logger) InitWithDefaults(overrides ...string) error {
|
|||||||
return fmtErrorf("failed to get current value for '%s'", key)
|
return fmtErrorf("failed to get current value for '%s'", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedValue interface{}
|
var parsedValue any
|
||||||
var parseErr error
|
var parseErr error
|
||||||
|
|
||||||
switch currentVal.(type) {
|
switch currentVal.(type) {
|
||||||
@ -124,7 +124,7 @@ func (l *Logger) InitWithDefaults(overrides ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.applyAndReconfigureLocked()
|
return l.applyConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully closes the logger, attempting to flush pending records
|
// Shutdown gracefully closes the logger, attempting to flush pending records
|
||||||
|
|||||||
Reference in New Issue
Block a user