Skip to content

Commit fc0a1cd

Browse files
committed
log: restore WithOutput/WithFormat compatibility
Reintroduce the legacy single-output options on top of WithOutputs and keep write behavior consistent while adding a multi-output fanout test.
1 parent 1cebfa9 commit fc0a1cd

File tree

5 files changed

+88
-21
lines changed

5 files changed

+88
-21
lines changed

log/README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ By default `log` writes log messages to `os.Stdout`. The following example shows
177177
how to change the log output:
178178

179179
```go
180-
ctx := log.Context(context.Background(), log.WithOutputs(log.Output{Writer: os.Stderr, Format: log.FormatTerminal}))
180+
ctx := log.Context(context.Background(), log.WithOutput(os.Stderr))
181181
log.Printf(ctx, "hello world")
182182
```
183183

@@ -187,8 +187,17 @@ The example above logs the following message to stderr:
187187
INFO[0000] msg="hello world"
188188
```
189189

190-
Each `log.Output` specifies both the destination writer and the formatter used
191-
to turn log entries into bytes.
190+
The `WithOutput` function accepts any type that implements the `io.Writer`
191+
interface.
192+
193+
For multiple outputs with independent formats, use `WithOutputs`:
194+
195+
```go
196+
ctx := log.Context(context.Background(), log.WithOutputs(
197+
log.Output{Writer: os.Stdout, Format: log.FormatTerminal},
198+
log.Output{Writer: logfile, Format: log.FormatJSON},
199+
))
200+
```
192201

193202
## Log Format
194203

@@ -205,7 +214,7 @@ The text format is the default format used when the application is not running
205214
in a terminal.
206215

207216
```go
208-
ctx := log.Context(context.Background(), log.WithOutputs(log.Output{Writer: os.Stdout, Format: log.FormatText}))
217+
ctx := log.Context(context.Background(), log.WithFormat(log.FormatText))
209218
log.Printf(ctx, "hello world")
210219
```
211220

@@ -223,7 +232,7 @@ The terminal format is the default format used when the application is running
223232
in a terminal.
224233

225234
```go
226-
ctx := log.Context(context.Background(), log.WithOutputs(log.Output{Writer: os.Stdout, Format: log.FormatTerminal}))
235+
ctx := log.Context(context.Background(), log.WithFormat(log.FormatTerminal))
227236
log.Printf(ctx, "hello world")
228237
```
229238

@@ -242,7 +251,7 @@ blue for info entries and red for errors).
242251
The JSON format prints entries in JSON.
243252

244253
```go
245-
ctx := log.Context(context.Background(), log.WithOutputs(log.Output{Writer: os.Stdout, Format: log.FormatJSON}))
254+
ctx := log.Context(context.Background(), log.WithFormat(log.FormatJSON))
246255
log.Printf(ctx, "hello world")
247256
```
248257

@@ -263,7 +272,7 @@ func formatFunc(entry *log.Entry) []byte {
263272
return []byte(fmt.Sprintf("%s: %s", entry.Severity, entry.KeyVals[0].V))
264273
}
265274

266-
ctx := log.Context(context.Background(), log.WithOutputs(log.Output{Writer: os.Stdout, Format: formatFunc}))
275+
ctx := log.Context(context.Background(), log.WithFormat(formatFunc))
267276
log.Printf(ctx, "hello world")
268277
```
269278

log/log.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,8 @@ func FlushAndDisableBuffering(ctx context.Context) {
177177
}
178178

179179
func (l *logger) writeEntry(e *Entry) {
180-
for i, out := range l.options.outputs {
181-
b := out.Format(e)
182-
n, err := out.Writer.Write(b)
183-
if err != nil {
184-
panic(fmt.Errorf("log: write failed (output=%d): %w", i, err))
185-
}
186-
if n != len(b) {
187-
panic(fmt.Errorf("log: short write (output=%d): %w", i, io.ErrShortWrite))
188-
}
180+
for _, out := range l.options.outputs {
181+
out.Writer.Write(out.Format(e)) // nolint: errcheck
189182
}
190183
}
191184

@@ -337,13 +330,11 @@ func newLimitWriter(w io.Writer, max int) io.Writer {
337330
func (lw *limitWriter) Write(b []byte) (int, error) {
338331
newLen := lw.n + len(b)
339332
if newLen > lw.max {
333+
oldN := lw.n
340334
b = b[:lw.max-lw.n]
341-
n, err := lw.Writer.Write(b)
342-
if err != nil {
343-
return n, err
344-
}
335+
lw.Writer.Write(b) // nolint: errcheck
345336
lw.n = lw.max
346-
return n, errTruncated
337+
return lw.max - oldN, errTruncated
347338
}
348339
lw.n = newLen
349340
return lw.Writer.Write(b)

log/log_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ func TestBufferingWithError(t *testing.T) {
154154
assert.Equal(t, expected+printed, buf.String())
155155
}
156156

157+
func TestMultipleOutputs(t *testing.T) {
158+
var (
159+
b1 bytes.Buffer
160+
b2 bytes.Buffer
161+
)
162+
format1 := func(*Entry) []byte {
163+
return []byte("one")
164+
}
165+
format2 := func(*Entry) []byte {
166+
return []byte("two")
167+
}
168+
ctx := Context(context.Background(), WithOutputs(
169+
Output{Writer: &b1, Format: format1},
170+
Output{Writer: &b2, Format: format2},
171+
))
172+
173+
Printf(ctx, "ignored")
174+
175+
assert.Equal(t, "one", b1.String())
176+
assert.Equal(t, "two", b2.String())
177+
}
178+
157179
type ctxTestKey int
158180

159181
const disableBufferingKey ctxTestKey = iota + 1

log/options.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ func WithNoDebug() LogOption {
8080
}
8181
}
8282

83+
// WithOutput sets the log output.
84+
//
85+
// This option exists for backward compatibility. When used in conjunction with
86+
// WithOutputs, it updates the first output writer.
87+
func WithOutput(w io.Writer) LogOption {
88+
return func(o *options) {
89+
if len(o.outputs) == 0 {
90+
o.outputs = []Output{{Writer: w, Format: FormatText}}
91+
return
92+
}
93+
o.outputs[0].Writer = w
94+
}
95+
}
96+
97+
// WithFormat sets the log format.
98+
//
99+
// This option exists for backward compatibility. When used in conjunction with
100+
// WithOutputs, it updates the first output format.
101+
func WithFormat(fn FormatFunc) LogOption {
102+
return func(o *options) {
103+
if len(o.outputs) == 0 {
104+
o.outputs = []Output{{Writer: os.Stdout, Format: fn}}
105+
return
106+
}
107+
o.outputs[0].Format = fn
108+
}
109+
}
110+
83111
// WithOutputs sets the log outputs.
84112
//
85113
// Each output formats the entry then writes it to its writer. This makes it

log/options_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ func TestWithNoDebug(t *testing.T) {
4444
assert.False(t, opts.debug)
4545
}
4646

47+
func TestWithOutput(t *testing.T) {
48+
opts := defaultOptions()
49+
w := io.Discard
50+
WithOutput(w)(opts)
51+
if assert.Len(t, opts.outputs, 1) {
52+
assert.Equal(t, w, opts.outputs[0].Writer)
53+
}
54+
}
55+
56+
func TestWithFormat(t *testing.T) {
57+
opts := defaultOptions()
58+
WithFormat(FormatJSON)(opts)
59+
if assert.Len(t, opts.outputs, 1) {
60+
assert.Equal(t, fmt.Sprintf("%p", opts.outputs[0].Format), fmt.Sprintf("%p", FormatJSON))
61+
}
62+
}
63+
4764
func TestWithOutputs(t *testing.T) {
4865
opts := defaultOptions()
4966
w := io.Discard

0 commit comments

Comments
 (0)