Files
opencloud/vendor/github.com/olekukonko/ll/lh/json.go
dependabot[bot] 70fc6eb40b build(deps): bump github.com/olekukonko/tablewriter from 0.0.5 to 1.0.6
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.5 to 1.0.6.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.5...v1.0.6)

---
updated-dependencies:
- dependency-name: github.com/olekukonko/tablewriter
  dependency-version: 1.0.6
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 15:04:23 +00:00

171 lines
6.6 KiB
Go

package lh
import (
"encoding/json"
"fmt"
"github.com/olekukonko/ll/lx"
"io"
"os"
"strings"
"sync"
"time"
)
// JSONHandler is a handler that outputs log entries as JSON objects.
// It formats log entries with timestamp, level, message, namespace, fields, and optional
// stack traces or dump segments, writing the result to the provided writer.
// Thread-safe with a mutex to protect concurrent writes.
type JSONHandler struct {
writer io.Writer // Destination for JSON output
timeFmt string // Format for timestamp (default: RFC3339Nano)
pretty bool // Enable pretty printing with indentation if true
fieldMap map[string]string // Optional mapping for field names (not used in provided code)
mu sync.Mutex // Protects concurrent access to writer
}
// JsonOutput represents the JSON structure for a log entry.
// It includes all relevant log data, such as timestamp, level, message, and optional
// stack trace or dump segments, serialized as a JSON object.
type JsonOutput struct {
Time string `json:"ts"` // Timestamp in specified format
Level string `json:"lvl"` // Log level (e.g., "INFO")
Class string `json:"class"` // Entry class (e.g., "Text", "Dump")
Msg string `json:"msg"` // Log message
Namespace string `json:"ns"` // Namespace path
Stack []byte `json:"stack"` // Stack trace (if present)
Dump []dumpSegment `json:"dump"` // Hex/ASCII dump segments (for ClassDump)
Fields map[string]interface{} `json:"fields"` // Custom fields
}
// dumpSegment represents a single segment of a hex/ASCII dump.
// Used for ClassDump entries to structure position, hex values, and ASCII representation.
type dumpSegment struct {
Offset int `json:"offset"` // Starting byte offset of the segment
Hex []string `json:"hex"` // Hexadecimal values of bytes
ASCII string `json:"ascii"` // ASCII representation of bytes
}
// NewJSONHandler creates a new JSONHandler writing to the specified writer.
// It initializes the handler with a default timestamp format (RFC3339Nano) and optional
// configuration functions to customize settings like pretty printing.
// Example:
//
// handler := NewJSONHandler(os.Stdout)
// logger := ll.New("app").Enable().Handler(handler)
// logger.Info("Test") // Output: {"ts":"...","lvl":"INFO","class":"Text","msg":"Test","ns":"app","stack":null,"dump":null,"fields":null}
func NewJSONHandler(w io.Writer, opts ...func(*JSONHandler)) *JSONHandler {
h := &JSONHandler{
writer: w, // Set output writer
timeFmt: time.RFC3339Nano, // Default timestamp format
}
// Apply configuration options
for _, opt := range opts {
opt(h)
}
return h
}
// Handle processes a log entry and writes it as JSON.
// It delegates to specialized methods based on the entry's class (Dump or regular),
// ensuring thread-safety with a mutex.
// Returns an error if JSON encoding or writing fails.
// Example:
//
// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes JSON object
func (h *JSONHandler) Handle(e *lx.Entry) error {
h.mu.Lock()
defer h.mu.Unlock()
// Handle dump entries separately
if e.Class == lx.ClassDump {
return h.handleDump(e)
}
// Handle standard log entries
return h.handleRegular(e)
}
// handleRegular handles standard log entries (non-dump).
// It converts the entry to a JsonOutput struct and encodes it as JSON,
// applying pretty printing if enabled. Logs encoding errors to stderr for debugging.
// Returns an error if encoding or writing fails.
// Example (internal usage):
//
// h.handleRegular(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes JSON object
func (h *JSONHandler) handleRegular(e *lx.Entry) error {
// Create JSON output structure
entry := JsonOutput{
Time: e.Timestamp.Format(h.timeFmt), // Format timestamp
Level: e.Level.String(), // Convert level to string
Class: e.Class.String(), // Convert class to string
Msg: e.Message, // Set message
Namespace: e.Namespace, // Set namespace
Dump: nil, // No dump for regular entries
Fields: e.Fields, // Copy fields
Stack: e.Stack, // Include stack trace if present
}
// Create JSON encoder
enc := json.NewEncoder(h.writer)
if h.pretty {
// Enable indentation for pretty printing
enc.SetIndent("", " ")
}
// Log encoding attempt for debugging
fmt.Fprintf(os.Stderr, "Encoding JSON entry: %v\n", e.Message)
// Encode and write JSON
err := enc.Encode(entry)
if err != nil {
// Log encoding error for debugging
fmt.Fprintf(os.Stderr, "JSON encode error: %v\n", err)
}
return err
}
// handleDump processes ClassDump entries, converting hex dump output to JSON segments.
// It parses the dump message into structured segments with offset, hex, and ASCII data,
// encoding them as a JsonOutput struct.
// Returns an error if parsing or encoding fails.
// Example (internal usage):
//
// h.handleDump(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61 62 'ab'"}) // Writes JSON with dump segments
func (h *JSONHandler) handleDump(e *lx.Entry) error {
var segments []dumpSegment
lines := strings.Split(e.Message, "\n")
// Parse each line of the dump message
for _, line := range lines {
if !strings.HasPrefix(line, "pos") {
continue // Skip non-dump lines
}
parts := strings.SplitN(line, "hex:", 2)
if len(parts) != 2 {
continue // Skip invalid lines
}
// Parse position
var offset int
fmt.Sscanf(parts[0], "pos %d", &offset)
// Parse hex and ASCII
hexAscii := strings.SplitN(parts[1], "'", 2)
hexStr := strings.Fields(strings.TrimSpace(hexAscii[0]))
// Create dump segment
segments = append(segments, dumpSegment{
Offset: offset, // Set byte offset
Hex: hexStr, // Set hex values
ASCII: strings.Trim(hexAscii[1], "'"), // Set ASCII representation
})
}
// Encode JSON output with dump segments
return json.NewEncoder(h.writer).Encode(JsonOutput{
Time: e.Timestamp.Format(h.timeFmt), // Format timestamp
Level: e.Level.String(), // Convert level to string
Class: e.Class.String(), // Convert class to string
Msg: "dumping segments", // Fixed message for dumps
Namespace: e.Namespace, // Set namespace
Dump: segments, // Include parsed segments
Fields: e.Fields, // Copy fields
Stack: e.Stack, // Include stack trace if present
})
}