nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: rs/zerolog
Pull request #272 (2020-11-29 05:21:27)

Allow to exclude parts from console output


Patch:
diff --git a/console.go b/console.go
index 2d80a15..ecd9258 100644
--- a/console.go
+++ b/console.go
@@ -57,6 +57,9 @@ type ConsoleWriter struct {
 	// PartsOrder defines the order of parts in output.
 	PartsOrder []string
 
+	// PartsExclude defines parts to not display in output.
+	PartsExclude []string
+
 	FormatTimestamp     Formatter
 	FormatLevel         Formatter
 	FormatCaller        Formatter
@@ -208,6 +211,14 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
 func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
 	var f Formatter
 
+	if w.PartsExclude != nil && len(w.PartsExclude) > 0 {
+		for _, exclude := range w.PartsExclude {
+			if exclude == p {
+				return
+			}
+		}
+	}
+
 	switch p {
 	case LevelFieldName:
 		if w.FormatLevel == nil {


Test patch:
diff --git a/console_test.go b/console_test.go
index 751cdcd..508a544 100644
--- a/console_test.go
+++ b/console_test.go
@@ -336,6 +336,24 @@ func TestConsoleWriterConfiguration(t *testing.T) {
 			t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
 		}
 	})
+
+	t.Run("Sets PartsExclude", func(t *testing.T) {
+		buf := &bytes.Buffer{}
+		w := zerolog.ConsoleWriter{Out: buf, NoColor: true, PartsExclude: []string{"time"}}
+
+		d := time.Unix(0, 0).UTC().Format(time.RFC3339)
+		evt := `{"time": "` + d + `", "level": "info", "message": "Foobar"}`
+		_, err := w.Write([]byte(evt))
+		if err != nil {
+			t.Errorf("Unexpected error when writing output: %s", err)
+		}
+
+		expectedOutput := "INF Foobar\n"
+		actualOutput := buf.String()
+		if actualOutput != expectedOutput {
+			t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
+		}
+	})
 }
 
 func BenchmarkConsoleWriter(b *testing.B) {
Repository: rs/zerolog
Pull request #296 (2021-03-01 23:15:26)

Allow setting context using idKey. Assist for issue #293.
This is in reference to Issue #293. I actually have a separate PR ready as well, because it wasn't as simple as I thought it would be after digging into the code and testing. `RequestIDHandler` is tightly coupled to xid, so I did not want to change that. 

If @artefactop or others wants to be able to set the RequestID to the context prior to calling `RequestIDHandler`, it is possible with this new function. The only caveat is that the ID being set must be an `xid.ID`.

I have a whole separate PR with a CustomIDHandler and associated type, etc. to allow setting an arbitrary string as the Request ID. I did not want to do as one PR since you may not like the second one.

Let me know if you're ok with this as is.

Thanks!

rs: Please remove go 7/8 from travis config so the test pass

Patch:
diff --git a/.travis.yml b/.travis.yml
index 061f265..a5d00e2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,5 @@
 language: go
 go:
-- "1.7"
-- "1.8"
 - "1.9"
 - "1.10"
 - "1.11"
@@ -9,6 +7,7 @@ go:
 - "1.13"
 - "1.14"
 - "1.15"
+- "1.16"
 - "master"
 matrix:
   allow_failures:
diff --git a/hlog/hlog.go b/hlog/hlog.go
index 8748b25..d9a6095 100644
--- a/hlog/hlog.go
+++ b/hlog/hlog.go
@@ -138,6 +138,11 @@ func IDFromCtx(ctx context.Context) (id xid.ID, ok bool) {
 	return
 }
 
+// CtxWithID adds the given xid.ID to the context
+func CtxWithID(ctx context.Context, id xid.ID) context.Context {
+	return context.WithValue(ctx, idKey{}, id)
+}
+
 // RequestIDHandler returns a handler setting a unique id to the request which can
 // be gathered using IDFromRequest(req). This generated id is added as a field to the
 // logger using the passed fieldKey as field name. The id is also added as a response
@@ -154,7 +159,7 @@ func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.
 			id, ok := IDFromRequest(r)
 			if !ok {
 				id = xid.New()
-				ctx = context.WithValue(ctx, idKey{}, id)
+				ctx = CtxWithID(ctx, id)
 				r = r.WithContext(ctx)
 			}
 			if fieldKey != "" {


Test patch:
diff --git a/hlog/hlog_test.go b/hlog/hlog_test.go
index 2967eb2..b1c24fe 100644
--- a/hlog/hlog_test.go
+++ b/hlog/hlog_test.go
@@ -4,16 +4,16 @@ package hlog
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/http/httptest"
 	"net/url"
-	"testing"
-
 	"reflect"
+	"testing"
 
-	"net/http/httptest"
-
+	"github.com/rs/xid"
 	"github.com/rs/zerolog"
 	"github.com/rs/zerolog/internal/cbor"
 )
@@ -262,3 +262,16 @@ func BenchmarkDataRace(b *testing.B) {
 		}
 	})
 }
+
+func TestCtxWithID(t *testing.T) {
+	ctx := context.Background()
+
+	id, _ := xid.FromString(`c0umremcie6smuu506pg`)
+
+	want := context.Background()
+	want = context.WithValue(want, idKey{}, id)
+
+	if got := CtxWithID(ctx, id); !reflect.DeepEqual(got, want) {
+		t.Errorf("CtxWithID() = %v, want %v", got, want)
+	}
+}
Repository: rs/zerolog
Pull request #307 (2021-04-12 20:07:55)

add Disabled to String and ParseLevel; add tests
hi @rs - I've been working with ParseLevel recently and was wondering about `Disabled` vs. `NoLevel` - there wasn't any tests for the `Level.String` method and the `ParseLevel` function to help me understand the behavior, so I created some. I added Disabled as well to the `String()` switch statement and the `ParseLevel` case statement. I'm not sure if it was intentionally skipped for some reason? 

Thanks as always for your help!

Dan


Patch:
diff --git a/log.go b/log.go
index 7927b2a..2da3e68 100644
--- a/log.go
+++ b/log.go
@@ -147,6 +147,8 @@ func (l Level) String() string {
 		return "fatal"
 	case PanicLevel:
 		return "panic"
+	case Disabled:
+		return "disabled"
 	case NoLevel:
 		return ""
 	}
@@ -171,6 +173,8 @@ func ParseLevel(levelStr string) (Level, error) {
 		return FatalLevel, nil
 	case LevelFieldMarshalFunc(PanicLevel):
 		return PanicLevel, nil
+	case LevelFieldMarshalFunc(Disabled):
+		return Disabled, nil
 	case LevelFieldMarshalFunc(NoLevel):
 		return NoLevel, nil
 	}


Test patch:
diff --git a/log_test.go b/log_test.go
index 83e7db1..7899c78 100644
--- a/log_test.go
+++ b/log_test.go
@@ -788,3 +788,62 @@ func TestUpdateEmptyContext(t *testing.T) {
 		t.Errorf("invalid log output:\ngot:  %q\nwant: %q", got, want)
 	}
 }
+
+func TestLevel_String(t *testing.T) {
+	tests := []struct {
+		name string
+		l    Level
+		want string
+	}{
+		{"trace", TraceLevel, "trace"},
+		{"debug", DebugLevel, "debug"},
+		{"info", InfoLevel, "info"},
+		{"warn", WarnLevel, "warn"},
+		{"error", ErrorLevel, "error"},
+		{"fatal", FatalLevel, "fatal"},
+		{"panic", PanicLevel, "panic"},
+		{"disabled", Disabled, "disabled"},
+		{"nolevel", NoLevel, ""},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.l.String(); got != tt.want {
+				t.Errorf("String() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestParseLevel(t *testing.T) {
+	type args struct {
+		levelStr string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    Level
+		wantErr bool
+	}{
+		{"trace", args{"trace"}, TraceLevel, false},
+		{"debug", args{"debug"}, DebugLevel, false},
+		{"info", args{"info"}, InfoLevel, false},
+		{"warn", args{"warn"}, WarnLevel, false},
+		{"error", args{"error"}, ErrorLevel, false},
+		{"fatal", args{"fatal"}, FatalLevel, false},
+		{"panic", args{"panic"}, PanicLevel, false},
+		{"disabled", args{"disabled"}, Disabled, false},
+		{"nolevel", args{""}, NoLevel, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ParseLevel(tt.args.levelStr)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ParseLevel() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("ParseLevel() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
Repository: rs/zerolog
Pull request #313 (2021-05-05 10:37:50)

If LevelFieldName is empty don't log level
This is in reference to our conversation in #312 


Patch:
diff --git a/log.go b/log.go
index 2da3e68..4779e8c 100644
--- a/log.go
+++ b/log.go
@@ -424,7 +424,7 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
 	e := newEvent(l.w, level)
 	e.done = done
 	e.ch = l.hooks
-	if level != NoLevel {
+	if level != NoLevel && LevelFieldName != "" {
 		e.Str(LevelFieldName, LevelFieldMarshalFunc(level))
 	}
 	if l.context != nil && len(l.context) > 1 {


Test patch:
diff --git a/log_test.go b/log_test.go
index 7899c78..82bcf8b 100644
--- a/log_test.go
+++ b/log_test.go
@@ -77,6 +77,24 @@ func TestInfo(t *testing.T) {
 	})
 }
 
+func TestEmptyLevelFieldName(t *testing.T) {
+	fieldName := LevelFieldName
+	LevelFieldName = ""
+
+	t.Run("empty setting", func(t *testing.T) {
+		out := &bytes.Buffer{}
+		log := New(out)
+		log.Info().
+			Str("foo", "bar").
+			Int("n", 123).
+			Msg("")
+		if got, want := decodeIfBinaryToString(out.Bytes()), `{"foo":"bar","n":123}`+"\n"; got != want {
+			t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
+		}
+	})
+	LevelFieldName = fieldName
+}
+
 func TestWith(t *testing.T) {
 	out := &bytes.Buffer{}
 	ctx := New(out).With().
Repository: rs/zerolog
Pull request #338 (2021-08-01 07:35:36)

Panic of Event.Object() and Event.EmbedObject() with nil
This patch supports `Event.Object` and `Event.EmbedObject` can get nil without panic.


Patch:
diff --git a/event.go b/event.go
index 6f8341c..5a6d2a3 100644
--- a/event.go
+++ b/event.go
@@ -207,6 +207,12 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
 		return e
 	}
 	e.buf = enc.AppendKey(e.buf, key)
+	if obj == nil {
+		e.buf = enc.AppendNil(e.buf)
+
+		return e
+	}
+
 	e.appendObject(obj)
 	return e
 }
@@ -224,6 +230,9 @@ func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event {
 	if e == nil {
 		return e
 	}
+	if obj == nil {
+		return e
+	}
 	obj.MarshalZerologObject(e)
 	return e
 }


Test patch:
diff --git a/event_test.go b/event_test.go
index 879eca1..98041b3 100644
--- a/event_test.go
+++ b/event_test.go
@@ -37,3 +37,29 @@ func TestEvent_AnErr(t *testing.T) {
 		})
 	}
 }
+
+func TestEvent_ObjectWithNil(t *testing.T) {
+	var buf bytes.Buffer
+	e := newEvent(levelWriterAdapter{&buf}, DebugLevel)
+	_ = e.Object("obj", nil)
+	_ = e.write()
+
+	want := `{"obj":null}`
+	got := strings.TrimSpace(buf.String())
+	if got != want {
+		t.Errorf("Event.Object() = %q, want %q", got, want)
+	}
+}
+
+func TestEvent_EmbedObjectWithNil(t *testing.T) {
+	var buf bytes.Buffer
+	e := newEvent(levelWriterAdapter{&buf}, DebugLevel)
+	_ = e.EmbedObject(nil)
+	_ = e.write()
+
+	want := "{}"
+	got := strings.TrimSpace(buf.String())
+	if got != want {
+		t.Errorf("Event.EmbedObject() = %q, want %q", got, want)
+	}
+}
Repository: rs/zerolog
Pull request #343 (2021-08-11 23:12:50)

Allow user configuration of default logger to return from Ctx()
If a user is trying to fetch a logger from their context, they probably want to log something.
A likely use case is the desire to fetch the logger from the context, but fall back to using the global logger if none is associated.  This could facilitate migrating code towards using context-based loggers incrementally.

In order to keep backward compatibility with existing users of the library, we can add a configuration setting, which if left unchanged, causes the backwards compatible behavior.  I chose to follow the existing pattern of configuration via global variables.

I hope that by making the configuration var a `*Logger` we offer maximum flexibility to users who want this feature, while keeping the existing behaviors for users who don't.   Added a unit test to cover the new case.


Patch:
diff --git a/ctx.go b/ctx.go
index ce18a32..966d17e 100644
--- a/ctx.go
+++ b/ctx.go
@@ -39,10 +39,13 @@ func (l *Logger) WithContext(ctx context.Context) context.Context {
 }
 
 // Ctx returns the Logger associated with the ctx. If no logger
-// is associated, a disabled logger is returned.
+// is associated, DefaultContextLogger is returned, unless DefaultContextLogger
+// is nil, in which case a disabled logger is returned.
 func Ctx(ctx context.Context) *Logger {
 	if l, ok := ctx.Value(ctxKey{}).(*Logger); ok {
 		return l
+	} else if l = DefaultContextLogger; l != nil {
+		return l
 	}
 	return disabledLogger
 }
diff --git a/globals.go b/globals.go
index 98c67ed..e561d8f 100644
--- a/globals.go
+++ b/globals.go
@@ -100,6 +100,10 @@ var (
 	// output. If not set, an error is printed on the stderr. This handler must
 	// be thread safe and non-blocking.
 	ErrorHandler func(err error)
+
+	// DefaultContextLogger is returned from Ctx() if there is no logger associated
+	// with the context.
+	DefaultContextLogger *Logger
 )
 
 var (


Test patch:
diff --git a/ctx_test.go b/ctx_test.go
index 646cd0f..2355893 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -27,6 +27,13 @@ func TestCtx(t *testing.T) {
 	if log2 != disabledLogger {
 		t.Error("Ctx did not return the expected logger")
 	}
+
+	DefaultContextLogger = &log
+	t.Cleanup(func() { DefaultContextLogger = nil })
+	log2 = Ctx(context.Background())
+	if log2 != &log {
+		t.Error("Ctx did not return the expected logger")
+	}
 }
 
 func TestCtxDisabled(t *testing.T) {
Repository: rs/zerolog
Pull request #347 (2021-08-19 15:59:44)

allow RemoteAddrHandler to include addresses which have no port
Many HTTP servers end up being placed behind reverse proxies. As a result, many devs use middleware to map reverse-proxy headers to the request RemoteAddr. Many of these headers don't include ports, causing an otherwise valid RemoteAddrHandler to not set the field.﻿


rs: We should probably not remove the port anyway. I would accept a PR with plain remote addr copied to the field.
Vivalldi: Just for clarification, are you suggesting

A. Update `RemoteAddrHandler` to only use the `r.RemoteAddr` in it's pure state (unless empty, then don't add)
```
if r.RemoteAddr != "" {
    // Add to log
}

B. Create a new Handler that does the above, but leaves `RemoteAddrHandler` intact
rs: I mean A. This is a change of behavior, but thinking about it, current behavior is misleading.
Vivalldi: Updated! Would you like to keep or remove the additional tests? (They don't really assert anything special anymore due to the change in logic)
rs: You can remove the non relevant ones.
Vivalldi: Done! Thanks for the feedback

Patch:
diff --git a/hlog/hlog.go b/hlog/hlog.go
index d9a6095..4f19a08 100644
--- a/hlog/hlog.go
+++ b/hlog/hlog.go
@@ -3,7 +3,6 @@ package hlog
 
 import (
 	"context"
-	"net"
 	"net/http"
 	"time"
 
@@ -79,10 +78,10 @@ func RequestHandler(fieldKey string) func(next http.Handler) http.Handler {
 func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
+			if r.RemoteAddr != "" {
 				log := zerolog.Ctx(r.Context())
 				log.UpdateContext(func(c zerolog.Context) zerolog.Context {
-					return c.Str(fieldKey, host)
+					return c.Str(fieldKey, r.RemoteAddr)
 				})
 			}
 			next.ServeHTTP(w, r)


Test patch:
diff --git a/hlog/hlog_test.go b/hlog/hlog_test.go
index b1c24fe..481525b 100644
--- a/hlog/hlog_test.go
+++ b/hlog/hlog_test.go
@@ -100,7 +100,7 @@ func TestRemoteAddrHandler(t *testing.T) {
 	}))
 	h = NewHandler(zerolog.New(out))(h)
 	h.ServeHTTP(nil, r)
-	if want, got := `{"ip":"1.2.3.4"}`+"\n", decodeIfBinary(out); want != got {
+	if want, got := `{"ip":"1.2.3.4:1234"}`+"\n", decodeIfBinary(out); want != got {
 		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
 	}
 }
@@ -116,7 +116,7 @@ func TestRemoteAddrHandlerIPv6(t *testing.T) {
 	}))
 	h = NewHandler(zerolog.New(out))(h)
 	h.ServeHTTP(nil, r)
-	if want, got := `{"ip":"2001:db8:a0b:12f0::1"}`+"\n", decodeIfBinary(out); want != got {
+	if want, got := `{"ip":"[2001:db8:a0b:12f0::1]:1234"}`+"\n", decodeIfBinary(out); want != got {
 		t.Errorf("Invalid log output, got: %s, want: %s", got, want)
 	}
 }
Repository: rs/zerolog
Pull request #418 (2022-02-27 17:00:09)

#411 Add FieldsExclude parameter to console writer
First pass at a PR for #411 


Patch:
diff --git a/console.go b/console.go
index a3fb542..25c178b 100644
--- a/console.go
+++ b/console.go
@@ -63,6 +63,9 @@ type ConsoleWriter struct {
 	// PartsExclude defines parts to not display in output.
 	PartsExclude []string
 
+	// FieldsExclude defines contextual fields to not display in output.
+	FieldsExclude []string
+
 	FormatTimestamp     Formatter
 	FormatLevel         Formatter
 	FormatCaller        Formatter
@@ -137,6 +140,17 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
 func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
 	var fields = make([]string, 0, len(evt))
 	for field := range evt {
+		var isExcluded bool
+		for _, excluded := range w.FieldsExclude {
+			if field == excluded {
+				isExcluded = true
+				break
+			}
+		}
+		if isExcluded {
+			continue
+		}
+
 		switch field {
 		case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName:
 			continue


Test patch:
diff --git a/console_test.go b/console_test.go
index 2b9ccb7..bd79584 100644
--- a/console_test.go
+++ b/console_test.go
@@ -354,6 +354,23 @@ func TestConsoleWriterConfiguration(t *testing.T) {
 			t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
 		}
 	})
+
+	t.Run("Sets FieldsExclude", func(t *testing.T) {
+		buf := &bytes.Buffer{}
+		w := zerolog.ConsoleWriter{Out: buf, NoColor: true, FieldsExclude: []string{"foo"}}
+
+		evt := `{"level": "info", "message": "Foobar", "foo":"bar", "baz":"quux"}`
+		_, err := w.Write([]byte(evt))
+		if err != nil {
+			t.Errorf("Unexpected error when writing output: %s", err)
+		}
+
+		expectedOutput := "<nil> INF Foobar baz=quux\n"
+		actualOutput := buf.String()
+		if actualOutput != expectedOutput {
+			t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
+		}
+	})
 }
 
 func BenchmarkConsoleWriter(b *testing.B) {
Repository: rs/zerolog
Pull request #552 (2023-05-14 04:35:14)

Fix error chain from pkgerrors
pkgerrors may be wrapped to new error, it should be unwrap in wrapping chain.


Patch:
diff --git a/pkgerrors/stacktrace.go b/pkgerrors/stacktrace.go
index 01420e6..2f62ffd 100644
--- a/pkgerrors/stacktrace.go
+++ b/pkgerrors/stacktrace.go
@@ -42,15 +42,32 @@ func frameField(f errors.Frame, s *state, c rune) string {
 
 // MarshalStack implements pkg/errors stack trace marshaling.
 //
-//   zerolog.ErrorStackMarshaler = MarshalStack
+// zerolog.ErrorStackMarshaler = MarshalStack
 func MarshalStack(err error) interface{} {
 	type stackTracer interface {
 		StackTrace() errors.StackTrace
 	}
-	sterr, ok := err.(stackTracer)
-	if !ok {
+	var sterr stackTracer
+	var ok bool
+	for err != nil {
+		sterr, ok = err.(stackTracer)
+		if ok {
+			break
+		}
+
+		u, ok := err.(interface {
+			Unwrap() error
+		})
+		if !ok {
+			return nil
+		}
+
+		err = u.Unwrap()
+	}
+	if sterr == nil {
 		return nil
 	}
+
 	st := sterr.StackTrace()
 	s := &state{}
 	out := make([]map[string]string, 0, len(st))


Test patch:
diff --git a/pkgerrors/stacktrace_test.go b/pkgerrors/stacktrace_test.go
index 4f9838c..5a13832 100644
--- a/pkgerrors/stacktrace_test.go
+++ b/pkgerrors/stacktrace_test.go
@@ -4,6 +4,7 @@ package pkgerrors
 
 import (
 	"bytes"
+	"fmt"
 	"regexp"
 	"testing"
 
@@ -17,11 +18,11 @@ func TestLogStack(t *testing.T) {
 	out := &bytes.Buffer{}
 	log := zerolog.New(out)
 
-	err := errors.Wrap(errors.New("error message"), "from error")
+	err := fmt.Errorf("from error: %w", errors.New("error message"))
 	log.Log().Stack().Err(err).Msg("")
 
 	got := out.String()
-	want := `\{"stack":\[\{"func":"TestLogStack","line":"20","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
+	want := `\{"stack":\[\{"func":"TestLogStack","line":"21","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
 	if ok, _ := regexp.MatchString(want, got); !ok {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
@@ -33,11 +34,11 @@ func TestLogStackFromContext(t *testing.T) {
 	out := &bytes.Buffer{}
 	log := zerolog.New(out).With().Stack().Logger() // calling Stack() on log context instead of event
 
-	err := errors.Wrap(errors.New("error message"), "from error")
+	err := fmt.Errorf("from error: %w", errors.New("error message"))
 	log.Log().Err(err).Msg("") // not explicitly calling Stack()
 
 	got := out.String()
-	want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"36","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
+	want := `\{"stack":\[\{"func":"TestLogStackFromContext","line":"37","source":"stacktrace_test.go"\},.*\],"error":"from error: error message"\}\n`
 	if ok, _ := regexp.MatchString(want, got); !ok {
 		t.Errorf("invalid log output:\ngot:  %v\nwant: %v", got, want)
 	}
Repository: rs/zerolog
Pull request #595 (2023-10-12 20:40:23)

Add FormatPrepare to ConsoleWriter
I realized that most of the recent additions like `PartsExclude` and `FieldsExclude` could be done just be some preprocessing before formatting starts.

In my particular case, I realized that I cannot use `FormatMessage` because it has access only to the message and not other fields: but I want to make it bold only if the level is info or above.

So `FormatPrepare` allows all such changes: removing fields, adding fields, changing fields.


Patch:
diff --git a/console.go b/console.go
index 2827988..02104f4 100644
--- a/console.go
+++ b/console.go
@@ -76,6 +76,8 @@ type ConsoleWriter struct {
 	FormatErrFieldValue Formatter
 
 	FormatExtra func(map[string]interface{}, *bytes.Buffer) error
+
+	FormatPrepare func(map[string]interface{}) error
 }
 
 // NewConsoleWriter creates and initializes a new ConsoleWriter.
@@ -124,6 +126,13 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
 		return n, fmt.Errorf("cannot decode event: %s", err)
 	}
 
+	if w.FormatPrepare != nil {
+		err = w.FormatPrepare(evt)
+		if err != nil {
+			return n, err
+		}
+	}
+
 	for _, p := range w.PartsOrder {
 		w.writePart(buf, evt, p)
 	}


Test patch:
diff --git a/console_test.go b/console_test.go
index 0265672..bdb70a0 100644
--- a/console_test.go
+++ b/console_test.go
@@ -418,6 +418,29 @@ func TestConsoleWriterConfiguration(t *testing.T) {
 		}
 	})
 
+	t.Run("Sets FormatPrepare", func(t *testing.T) {
+		buf := &bytes.Buffer{}
+		w := zerolog.ConsoleWriter{
+			Out: buf, NoColor: true, PartsOrder: []string{"level", "message"},
+			FormatPrepare: func(evt map[string]interface{}) error {
+				evt["message"] = fmt.Sprintf("msg=%s", evt["message"])
+				return nil
+			},
+		}
+
+		evt := `{"level": "info", "message": "Foobar"}`
+		_, err := w.Write([]byte(evt))
+		if err != nil {
+			t.Errorf("Unexpected error when writing output: %s", err)
+		}
+
+		expectedOutput := "INF msg=Foobar\n"
+		actualOutput := buf.String()
+		if actualOutput != expectedOutput {
+			t.Errorf("Unexpected output %q, want: %q", actualOutput, expectedOutput)
+		}
+	})
+
 	t.Run("Uses local time for console writer without time zone", func(t *testing.T) {
 		// Regression test for issue #483 (check there for more details)