REPOSITORY / ScuroNeko/SNekLog

Compare commits

DIFF REPOSITORY
2 Commits
Author SHA1 Message Date
ScuroNeko dd3c7286d2 (new): JSON HTML escaping control
Golang lint / lint (push) Successful in 45s
- 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.
2026-04-28 14:36:03 +03:00
ScuroNeko a54a854a77 (fix): RELEASE_NOTES.md
Golang lint / lint (push) Successful in 1m44s
2026-04-28 09:46:18 +03:00
5 changed files with 99 additions and 13 deletions
+24 -1
View File
@@ -1,4 +1,27 @@
# Unreleased (planned v2.2.0)
# 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
filtering alongside the legacy severity ordering, and improves documentation
+2
View File
@@ -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")
+17 -2
View File
@@ -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 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
+39
View File
@@ -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 {
+15 -8
View File
@@ -1,6 +1,7 @@
package sneklog
import (
"bytes"
"encoding/json"
"fmt"
"io"
@@ -145,22 +146,28 @@ 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)
_, err = w.writer.Write(data)
if err != nil {
return err
}
return nil
}
// Close closes the owned writer, if any.
func (w *LoggerJsonWriter) Close() error {