Repository: rs/zerolog
Pull request #625 (2023-12-11 06:54:33)

Bump actions/setup-go from 4 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-go/releases">actions/setup-go's releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p>In scope of this release, we change Nodejs runtime from node16 to node20 (<a href="https://redirect.github.com/actions/setup-go/pull/421">actions/setup-go#421</a>). Moreover, we update some dependencies to the latest versions (<a href="https://redirect.github.com/actions/setup-go/pull/445">actions/setup-go#445</a>).</p>
<p>Besides, this release contains such changes as:</p>
<ul>
<li>Fix hosted tool cache usage on windows by <a href="https://github.com/galargh"><code>@​galargh</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/411">actions/setup-go#411</a></li>
<li>Improve documentation regarding dependencies caching by <a href="https://github.com/artemgavrilov"><code>@​artemgavrilov</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/417">actions/setup-go#417</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/galargh"><code>@​galargh</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/411">actions/setup-go#411</a></li>
<li><a href="https://github.com/artemgavrilov"><code>@​artemgavrilov</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/417">actions/setup-go#417</a></li>
<li><a href="https://github.com/chenrui333"><code>@​chenrui333</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/421">actions/setup-go#421</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v4...v5.0.0">https://github.com/actions/setup-go/compare/v4...v5.0.0</a></p>
<h2>v4.1.0</h2>
<h2>What's Changed</h2>
<p>In scope of this release, slow installation on Windows was fixed by <a href="https://github.com/dsame"><code>@​dsame</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/393">actions/setup-go#393</a> and OS version was added to <code>primaryKey</code> for Ubuntu runners to avoid conflicts (<a href="https://redirect.github.com/actions/setup-go/pull/383">actions/setup-go#383</a>)</p>
<p>This release also includes the following changes:</p>
<ul>
<li>Remove implicit dependencies by <a href="https://github.com/nikolai-laevskii"><code>@​nikolai-laevskii</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/378">actions/setup-go#378</a></li>
<li>Update action.yml by <a href="https://github.com/mkelly"><code>@​mkelly</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/379">actions/setup-go#379</a></li>
<li>Added a description that go-version should be specified as a string type by <a href="https://github.com/n3xem"><code>@​n3xem</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/367">actions/setup-go#367</a></li>
<li>Add note about YAML parsing versions by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/382">actions/setup-go#382</a></li>
<li>Automatic update of configuration files from 05/23/2023 by <a href="https://github.com/github-actions"><code>@​github-actions</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/377">actions/setup-go#377</a></li>
<li>Bump tough-cookie and <code>@​azure/ms-rest-js</code> by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/392">actions/setup-go#392</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/397">actions/setup-go#397</a></li>
<li>Bump semver from 6.3.0 to 6.3.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/396">actions/setup-go#396</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/mkelly"><code>@​mkelly</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/379">actions/setup-go#379</a></li>
<li><a href="https://github.com/n3xem"><code>@​n3xem</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/367">actions/setup-go#367</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v4...v4.1.0">https://github.com/actions/setup-go/compare/v4...v4.1.0</a></p>
<h2>v4.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update documentation for <code>v4</code> by <a href="https://github.com/dsame"><code>@​dsame</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/354">actions/setup-go#354</a></li>
<li>Fix glob bug in the package.json scripts section by <a href="https://github.com/IvanZosimov"><code>@​IvanZosimov</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/359">actions/setup-go#359</a></li>
<li>Bump <code>xml2js</code> dependency by <a href="https://github.com/dmitry-shibanov"><code>@​dmitry-shibanov</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/370">actions/setup-go#370</a></li>
<li>Bump <code>@actions/cache</code> dependency to v3.2.1 by <a href="https://github.com/nikolai-laevskii"><code>@​nikolai-laevskii</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/374">actions/setup-go#374</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/nikolai-laevskii"><code>@​nikolai-laevskii</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/374">actions/setup-go#374</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v4...v4.0.1">https://github.com/actions/setup-go/compare/v4...v4.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-go/commit/0c52d547c9bc32b1aa3301fd7a9cb496313a4491"><code>0c52d54</code></a> Update dependencies for node20 (<a href="https://redirect.github.com/actions/setup-go/issues/445">#445</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/bfd2fb341f32be7281829126376a12a780ca79fc"><code>bfd2fb3</code></a> Merge pull request <a href="https://redirect.github.com/actions/setup-go/issues/421">#421</a> from chenrui333/node20-runtime</li>
<li><a href="https://github.com/actions/setup-go/commit/3d65fa57fcbfe4a359b6b71a6c65e6eec12984eb"><code>3d65fa5</code></a> feat: bump to use actions/checkout@v4</li>
<li><a href="https://github.com/actions/setup-go/commit/8a505c9cf2e2726eda7f3268d6992e386a12da52"><code>8a505c9</code></a> feat: bump to use node20 runtime</li>
<li><a href="https://github.com/actions/setup-go/commit/883490dfd06f396ebe0b738bc313a53cf9d851e5"><code>883490d</code></a> Merge pull request <a href="https://redirect.github.com/actions/setup-go/issues/417">#417</a> from artemgavrilov/main</li>
<li><a href="https://github.com/actions/setup-go/commit/d45ebba0ce181dc5604aaf69ce5a0bdcbd3b1807"><code>d45ebba</code></a> Rephrase sentence</li>
<li><a href="https://github.com/actions/setup-go/commit/317c6617fa9e4e67f1e5e20ad8bc98bf298a0f8f"><code>317c661</code></a> Replace <code>wildcards</code> term with <code>globs</code>.</li>
<li><a href="https://github.com/actions/setup-go/commit/f90673ad641a19d0689fba58b5c79adc54be5d81"><code>f90673a</code></a> Merge pull request <a href="https://redirect.github.com/actions/setup-go/issues/1">#1</a> from artemgavrilov/caching-docs-improvement</li>
<li><a href="https://github.com/actions/setup-go/commit/801823434715e45aa48743a38182d33b33675d02"><code>8018234</code></a> Improve documentation regarding dependencies cachin</li>
<li><a href="https://github.com/actions/setup-go/commit/d085b4fe57b6e17262cbebc67b4d2d341d8938c2"><code>d085b4f</code></a> Merge pull request <a href="https://redirect.github.com/actions/setup-go/issues/411">#411</a> from galargh/fix/windows-hostedtoolcache</li>
<li>Additional commits viewable in <a href="https://github.com/actions/setup-go/compare/v4...v5">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-go&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.



Patch:
diff --git a/cmd/prettylog/prettylog.go b/cmd/prettylog/prettylog.go
index 9b8b43c..10d7799 100644
--- a/cmd/prettylog/prettylog.go
+++ b/cmd/prettylog/prettylog.go
@@ -3,9 +3,11 @@ package main
 import (
 	"bufio"
 	"errors"
+	"flag"
 	"fmt"
 	"io"
 	"os"
+	"time"
 
 	"github.com/rs/zerolog"
 )
@@ -15,31 +17,57 @@ func isInputFromPipe() bool {
 	return fileInfo.Mode()&os.ModeCharDevice == 0
 }
 
+func processInput(reader io.Reader, writer io.Writer) error {
+	scanner := bufio.NewScanner(reader)
+	for scanner.Scan() {
+		bytesToWrite := scanner.Bytes()
+		_, err := writer.Write(bytesToWrite)
+		if err != nil {
+			if errors.Is(err, io.EOF) {
+				break
+			}
+
+			fmt.Printf("%s\n", bytesToWrite)
+		}
+	}
+
+	return scanner.Err()
+}
+
 func main() {
+	timeFormats := map[string]string{
+		"default": time.Kitchen,
+		"full":    time.RFC1123,
+	}
+
+	timeFormatFlag := flag.String(
+		"time-format",
+		"default",
+		"Time format, either 'default' or 'full'",
+	)
+
+	flag.Parse()
+
+	timeFormat, ok := timeFormats[*timeFormatFlag]
+	if !ok {
+		panic("Invalid time-format provided")
+	}
+
 	writer := zerolog.NewConsoleWriter()
+	writer.TimeFormat = timeFormat
 
 	if isInputFromPipe() {
-		_, _ = io.Copy(writer, os.Stdin)
-	} else if len(os.Args) > 1 {
-		for _, filename := range os.Args[1:] {
+		_ = processInput(os.Stdin, writer)
+	} else if flag.NArg() >= 1 {
+		for _, filename := range flag.Args() {
 			// Scan each line from filename and write it into writer
-			r, err := os.Open(filename)
+			reader, err := os.Open(filename)
 			if err != nil {
 				fmt.Printf("%s open: %v", filename, err)
 				os.Exit(1)
 			}
-			scanner := bufio.NewScanner(r)
-			for scanner.Scan() {
-				_, err = writer.Write(scanner.Bytes())
-				if err != nil {
-					if errors.Is(err, io.EOF) {
-						break
-					}
-					fmt.Printf("%s write: %v", filename, err)
-					os.Exit(1)
-				}
-			}
-			if err := scanner.Err(); err != nil {
+
+			if err := processInput(reader, writer); err != nil {
 				fmt.Printf("%s scan: %v", filename, err)
 				os.Exit(1)
 			}
diff --git a/console.go b/console.go
index e8eeaa3..cc6d623 100644
--- a/console.go
+++ b/console.go
@@ -155,6 +155,15 @@ func (w ConsoleWriter) Write(p []byte) (n int, err error) {
 	return len(p), err
 }
 
+// Call the underlying writer's Close method if it is an io.Closer. Otherwise
+// does nothing.
+func (w ConsoleWriter) Close() error {
+	if closer, ok := w.Out.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
 // writeFields appends formatted key-value pairs to buf.
 func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) {
 	var fields = make([]string, 0, len(evt))
diff --git a/context.go b/context.go
index 48c1d5d..df352eb 100644
--- a/context.go
+++ b/context.go
@@ -163,6 +163,22 @@ func (c Context) Errs(key string, errs []error) Context {
 
 // Err adds the field "error" with serialized err to the logger context.
 func (c Context) Err(err error) Context {
+	if c.l.stack && ErrorStackMarshaler != nil {
+		switch m := ErrorStackMarshaler(err).(type) {
+		case nil:
+		case LogObjectMarshaler:
+			c = c.Object(ErrorStackFieldName, m)
+		case error:
+			if m != nil && !isNilValue(m) {
+				c = c.Str(ErrorStackFieldName, m.Error())
+			}
+		case string:
+			c = c.Str(ErrorStackFieldName, m)
+		default:
+			c = c.Interface(ErrorStackFieldName, m)
+		}
+	}
+
 	return c.AnErr(ErrorFieldName, err)
 }
 
@@ -375,6 +391,9 @@ func (c Context) Durs(key string, d []time.Duration) Context {
 
 // Interface adds the field key with obj marshaled using reflection.
 func (c Context) Interface(key string, i interface{}) Context {
+	if obj, ok := i.(LogObjectMarshaler); ok {
+		return c.Object(key, obj)
+	}
 	c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i)
 	return c
 }
diff --git a/log.go b/log.go
index caa1845..9fec7cc 100644
--- a/log.go
+++ b/log.go
@@ -325,10 +325,13 @@ func (l Logger) Sample(s Sampler) Logger {
 }
 
 // Hook returns a logger with the h Hook.
-func (l Logger) Hook(h Hook) Logger {
-	newHooks := make([]Hook, len(l.hooks), len(l.hooks)+1)
+func (l Logger) Hook(hooks ...Hook) Logger {
+	if len(hooks) == 0 {
+		return l
+	}
+	newHooks := make([]Hook, len(l.hooks), len(l.hooks)+len(hooks))
 	copy(newHooks, l.hooks)
-	l.hooks = append(newHooks, h)
+	l.hooks = append(newHooks, hooks...)
 	return l
 }
 
@@ -384,7 +387,14 @@ func (l *Logger) Err(err error) *Event {
 //
 // You must call Msg on the returned event in order to send the event.
 func (l *Logger) Fatal() *Event {
-	return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) })
+	return l.newEvent(FatalLevel, func(msg string) {
+		if closer, ok := l.w.(io.Closer); ok {
+			// Close the writer to flush any buffered message. Otherwise the message
+			// will be lost as os.Exit() terminates the program immediately.
+			closer.Close()
+		}
+		os.Exit(1)
+	})
 }
 
 // Panic starts a new message with panic level. The panic() function
diff --git a/syslog.go b/syslog.go
index c408283..a2b7285 100644
--- a/syslog.go
+++ b/syslog.go
@@ -78,3 +78,12 @@ func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
 	n = len(p)
 	return
 }
+
+// Call the underlying writer's Close method if it is an io.Closer. Otherwise
+// does nothing.
+func (sw syslogWriter) Close() error {
+	if c, ok := sw.w.(io.Closer); ok {
+		return c.Close()
+	}
+	return nil
+}
diff --git a/writer.go b/writer.go
index 50d7653..41b394d 100644
--- a/writer.go
+++ b/writer.go
@@ -27,6 +27,15 @@ func (lw LevelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) {
 	return lw.Write(p)
 }
 
+// Call the underlying writer's Close method if it is an io.Closer. Otherwise
+// does nothing.
+func (lw LevelWriterAdapter) Close() error {
+	if closer, ok := lw.Writer.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
 type syncWriter struct {
 	mu sync.Mutex
 	lw LevelWriter
@@ -57,6 +66,15 @@ func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) {
 	return s.lw.WriteLevel(l, p)
 }
 
+func (s *syncWriter) Close() error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if closer, ok := s.lw.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
 type multiLevelWriter struct {
 	writers []LevelWriter
 }
@@ -89,6 +107,20 @@ func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
 	return n, err
 }
 
+// Calls close on all the underlying writers that are io.Closers. If any of the
+// Close methods return an error, the remainder of the closers are not closed
+// and the error is returned.
+func (t multiLevelWriter) Close() error {
+	for _, w := range t.writers {
+		if closer, ok := w.(io.Closer); ok {
+			if err := closer.Close(); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
 // MultiLevelWriter creates a writer that duplicates its writes to all the
 // provided writers, similar to the Unix tee(1) command. If some writers
 // implement LevelWriter, their WriteLevel method will be used instead of Write.


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 93c0756..a9725b3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -9,12 +9,12 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
     - name: Install Go
-      uses: actions/setup-go@v4
+      uses: actions/setup-go@v5
       with:
         go-version: ${{ matrix.go-version }}
     - name: Checkout code
       uses: actions/checkout@v4
-    - uses: actions/cache@v3
+    - uses: actions/cache@v4
       with:
         path: ~/go/pkg/mod
         key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
diff --git a/ctx_test.go b/ctx_test.go
index d024d54..6315cf7 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -1,10 +1,13 @@
 package zerolog
 
 import (
+	"bytes"
 	"context"
 	"io"
 	"reflect"
 	"testing"
+
+	"github.com/rs/zerolog/internal/cbor"
 )
 
 func TestCtx(t *testing.T) {
@@ -68,3 +71,31 @@ func TestCtxDisabled(t *testing.T) {
 		t.Error("WithContext did not override logger with a disabled logger")
 	}
 }
+
+type logObjectMarshalerImpl struct {
+	name string
+	age  int
+}
+
+func (t logObjectMarshalerImpl) MarshalZerologObject(e *Event) {
+	e.Str("name", "custom_value").Int("age", t.age)
+}
+
+func Test_InterfaceLogObjectMarshaler(t *testing.T) {
+	var buf bytes.Buffer
+	log := New(&buf)
+	ctx := log.WithContext(context.Background())
+
+	log2 := Ctx(ctx)
+
+	withLog := log2.With().Interface("obj", &logObjectMarshalerImpl{
+		name: "foo",
+		age:  29,
+	}).Logger()
+
+	withLog.Info().Msg("test")
+
+	if got, want := cbor.DecodeIfBinaryToString(buf.Bytes()), `{"level":"info","obj":{"name":"custom_value","age":29},"message":"test"}`+"\n"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
diff --git a/diode/diode_test.go b/diode/diode_test.go
index 87f62bc..ab6b55e 100644
--- a/diode/diode_test.go
+++ b/diode/diode_test.go
@@ -3,8 +3,10 @@ package diode_test
 import (
 	"bytes"
 	"fmt"
+	"io"
 	"log"
 	"os"
+	"os/exec"
 	"testing"
 	"time"
 
@@ -37,6 +39,43 @@ func TestClose(t *testing.T) {
 	w.Close()
 }
 
+func TestFatal(t *testing.T) {
+	if os.Getenv("TEST_FATAL") == "1" {
+		w := diode.NewWriter(os.Stderr, 1000, 0, func(missed int) {
+			fmt.Printf("Dropped %d messages\n", missed)
+		})
+		defer w.Close()
+		log := zerolog.New(w)
+		log.Fatal().Msg("test")
+		return
+	}
+
+	cmd := exec.Command(os.Args[0], "-test.run=TestFatal")
+	cmd.Env = append(os.Environ(), "TEST_FATAL=1")
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = cmd.Start()
+	if err != nil {
+		t.Fatal(err)
+	}
+	slurp, err := io.ReadAll(stderr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = cmd.Wait()
+	if err == nil {
+		t.Error("Expected log.Fatal to exit with non-zero status")
+	}
+
+	want := "{\"level\":\"fatal\",\"message\":\"test\"}\n"
+	got := cbor.DecodeIfBinaryToString(slurp)
+	if got != want {
+		t.Errorf("Diode Fatal Test failed. got:%s, want:%s!", got, want)
+	}
+}
+
 func Benchmark(b *testing.B) {
 	log.SetOutput(io.Discard)
 	defer log.SetOutput(os.Stderr)
diff --git a/hlog/hlog_test.go b/hlog/hlog_test.go
index 0d6b31e..445d6b6 100644
--- a/hlog/hlog_test.go
+++ b/hlog/hlog_test.go
@@ -7,6 +7,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
diff --git a/log_example_test.go b/log_example_test.go
index 81e2c20..c48a1e3 100644
--- a/log_example_test.go
+++ b/log_example_test.go
@@ -72,7 +72,7 @@ func ExampleLogger_Hook() {
 	var levelNameHook LevelNameHook
 	var messageHook MessageHook = "The message"
 
-	log := zerolog.New(os.Stdout).Hook(levelNameHook).Hook(messageHook)
+	log := zerolog.New(os.Stdout).Hook(levelNameHook, messageHook)
 
 	log.Info().Msg("hello world")
 
diff --git a/pkgerrors/stacktrace_test.go b/pkgerrors/stacktrace_test.go
index a09b986..787a096 100644
--- a/pkgerrors/stacktrace_test.go
+++ b/pkgerrors/stacktrace_test.go
@@ -60,6 +60,22 @@ func TestLogStackFromContext(t *testing.T) {
 	}
 }
 
+func TestLogStackFromContextWith(t *testing.T) {
+	zerolog.ErrorStackMarshaler = MarshalStack
+
+	err := fmt.Errorf("from error: %w", errors.New("error message"))
+	out := &bytes.Buffer{}
+	log := zerolog.New(out).With().Stack().Err(err).Logger() // calling Stack() on log context instead of event
+
+	log.Error().Msg("")
+
+	got := out.String()
+	want := `\{"level":"error","stack":\[\{"func":"TestLogStackFromContextWith","line":"66","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)
+	}
+}
+
 func BenchmarkLogStack(b *testing.B) {
 	zerolog.ErrorStackMarshaler = MarshalStack
 	out := &bytes.Buffer{}