- add Formatter.JSONEscapeHTML and SetJSONEscapeHTML to let JSON writers preserve HTML-sensitive characters when needed while keeping escaping enabled by default. - switch JSON output to json.Encoder, preserve existing Print/Println newline semantics, and cover the new escaping behavior in tests.
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
# v2.3.0
|
||||
|
||||
This release adds JSON HTML-escaping control while preserving the existing
|
||||
JSON writer defaults and newline semantics.
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Formatter.SetJSONEscapeHTML(bool)` and `Formatter.JSONEscapeHTML` to control whether JSON writers escape HTML-sensitive characters such as `<`, `>`, and `&`.
|
||||
|
||||
### Changed
|
||||
|
||||
- JSON writers now use `json.Encoder` internally so formatter-level HTML escaping can be configured.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Preserved `Print` and `Println` newline behavior for JSON output while switching to `json.Encoder`.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- No breaking API changes.
|
||||
- HTML escaping remains enabled by default, matching the previous `encoding/json` behavior.
|
||||
- `Print` still emits JSON without an added newline, and `Println` emits exactly one trailing newline.
|
||||
|
||||
# v2.2.0
|
||||
|
||||
This release expands the public LogLevel API, adds optional threshold-based
|
||||
|
||||
@@ -32,6 +32,7 @@ func main() {
|
||||
jsonFormatter := sneklog.NewFormatter().
|
||||
SetFormat("[%L] [%N] %m").
|
||||
SetColorOutput(false).
|
||||
SetJSONEscapeHTML(false).
|
||||
SetTimeStampFormat(sneklog.Kitchen)
|
||||
|
||||
textStdout.SetFormatter(formatter)
|
||||
@@ -62,6 +63,7 @@ func main() {
|
||||
externalJSON,
|
||||
)
|
||||
|
||||
logger.Infoln("Тест <>&")
|
||||
logger.Infoln("service started")
|
||||
logger.Warnln("cache miss")
|
||||
logger.Errorln("request failed")
|
||||
|
||||
+18
-3
@@ -32,9 +32,10 @@ type Formatter struct {
|
||||
Format string
|
||||
MessageSeparator string
|
||||
|
||||
// JSONTraceBackPath
|
||||
// If true, in traceback will be path to file, otherwise only filename.
|
||||
JSONTraceBackPath bool
|
||||
// JSONTraceBackPath enables full file paths in formatted traceback fields.
|
||||
JSONTraceBackPath bool
|
||||
// JSONEscapeHTML controls whether JSON output escapes HTML-sensitive characters.
|
||||
JSONEscapeHTML bool
|
||||
TimeStampFormat string
|
||||
TraceBackFormat string
|
||||
TraceBackSeparator string
|
||||
@@ -57,6 +58,7 @@ var DefaultTextFormatter = &Formatter{
|
||||
// DefaultJsonFormatter is the default formatter used by JSON writers.
|
||||
var DefaultJsonFormatter = &Formatter{
|
||||
Format: "%m",
|
||||
JSONEscapeHTML: true,
|
||||
MessageSeparator: " ",
|
||||
TimeStampFormat: RFC3339,
|
||||
ColorOutput: false,
|
||||
@@ -67,6 +69,7 @@ func NewFormatter() *Formatter {
|
||||
return &Formatter{
|
||||
Format: DefaultTextFormatter.Format,
|
||||
MessageSeparator: DefaultTextFormatter.MessageSeparator,
|
||||
JSONEscapeHTML: true,
|
||||
TimeStampFormat: DefaultTextFormatter.TimeStampFormat,
|
||||
TraceBackFormat: DefaultTextFormatter.TraceBackFormat,
|
||||
TraceBackSeparator: DefaultTextFormatter.TraceBackSeparator,
|
||||
@@ -87,6 +90,18 @@ func (f *Formatter) SetMessageSeparator(separator string) *Formatter {
|
||||
return f
|
||||
}
|
||||
|
||||
// SetJSONTraceBackPath enables or disables full file paths in formatted traceback fields.
|
||||
func (f *Formatter) SetJSONTraceBackPath(set bool) *Formatter {
|
||||
f.JSONTraceBackPath = set
|
||||
return f
|
||||
}
|
||||
|
||||
// SetJSONEscapeHTML enables or disables HTML escaping in JSON output.
|
||||
func (f *Formatter) SetJSONEscapeHTML(escape bool) *Formatter {
|
||||
f.JSONEscapeHTML = escape
|
||||
return f
|
||||
}
|
||||
|
||||
// SetTimeStampFormat sets the strftime-like format used for timestamps.
|
||||
func (f *Formatter) SetTimeStampFormat(format string) *Formatter {
|
||||
f.TimeStampFormat = format
|
||||
|
||||
@@ -342,6 +342,42 @@ func TestJsonWriterPrintAllowsEmptyMessages(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonWriterPrintDoesNotAppendNewline(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := CreateJsonWriter(&buf, false)
|
||||
|
||||
if err := writer.Print(INFO, "TEST", nil, "hello"); err != nil {
|
||||
t.Fatalf("Print() error = %v", err)
|
||||
}
|
||||
|
||||
if bytes.HasSuffix(buf.Bytes(), []byte("\n")) {
|
||||
t.Fatalf("Print() should not append newline, got %q", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonWriterSetJSONEscapeHTML(t *testing.T) {
|
||||
var escaped bytes.Buffer
|
||||
escapedWriter := CreateJsonWriter(&escaped, false)
|
||||
|
||||
if err := escapedWriter.Print(INFO, "TEST", nil, "<>&"); err != nil {
|
||||
t.Fatalf("Print() with default formatter error = %v", err)
|
||||
}
|
||||
if strings.Contains(escaped.String(), "<>&") {
|
||||
t.Fatalf("default JSON output should escape HTML-sensitive characters, got %q", escaped.String())
|
||||
}
|
||||
|
||||
var raw bytes.Buffer
|
||||
rawWriter := CreateJsonWriter(&raw, false).
|
||||
SetFormatter(NewFormatter().SetFormat("%m").SetJSONEscapeHTML(false))
|
||||
|
||||
if err := rawWriter.Print(INFO, "TEST", nil, "<>&"); err != nil {
|
||||
t.Fatalf("Print() with SetJSONEscapeHTML(false) error = %v", err)
|
||||
}
|
||||
if !strings.Contains(raw.String(), "<>&") {
|
||||
t.Fatalf("SetJSONEscapeHTML(false) should preserve HTML-sensitive characters, got %q", raw.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonWriterPrintPreservesTrailingNewlineSemantic(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := CreateJsonWriter(&buf, false)
|
||||
@@ -354,6 +390,9 @@ func TestJsonWriterPrintPreservesTrailingNewlineSemantic(t *testing.T) {
|
||||
if !bytes.HasSuffix(data, []byte("\n")) {
|
||||
t.Fatalf("JSON output should end with newline, got %q", data)
|
||||
}
|
||||
if bytes.HasSuffix(bytes.TrimSuffix(data, []byte("\n")), []byte("\n")) {
|
||||
t.Fatalf("JSON output should end with exactly one newline, got %q", data)
|
||||
}
|
||||
|
||||
var message LoggerJsonMessage
|
||||
if err := json.Unmarshal(bytes.TrimSuffix(data, []byte("\n")), &message); err != nil {
|
||||
|
||||
+16
-9
@@ -1,6 +1,7 @@
|
||||
package sneklog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -145,21 +146,27 @@ func (w *LoggerJsonWriter) Print(level LogLevel, prefix string, traceback []*Met
|
||||
Message: s,
|
||||
Traceback: traceback,
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(f.JSONEscapeHTML)
|
||||
if w.pretty {
|
||||
data, err = json.MarshalIndent(m, "", " ")
|
||||
} else {
|
||||
data, err = json.Marshal(m)
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
|
||||
err := enc.Encode(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newline {
|
||||
data = append(data, '\n')
|
||||
data := buf.Bytes()
|
||||
if !newline {
|
||||
data = bytes.TrimSuffix(data, []byte("\n"))
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return err
|
||||
_, err = w.writer.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the owned writer, if any.
|
||||
|
||||
Reference in New Issue
Block a user