Skip to content

Commit 47f376e

Browse files
authored
feat: ✨ Add option to unescape strings inside log string (#48)
* feat: ✨ Add option to unescape strings inside log string * fix: 🎨 Minor changes * fix: arrow_down: Remove testify dependency
1 parent f627fc4 commit 47f376e

File tree

5 files changed

+161
-16
lines changed

5 files changed

+161
-16
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Apache Common Log Format. The body of the block accepts the custom `placeholder`
3333
log {
3434
format transform [<template>] {
3535
placeholder <string>
36+
unescape_strings
3637
# other fields accepted by JSON encoder
3738
}
3839
}

caddyfile.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,37 @@ import (
3030
// See the godoc on the LogEncoderConfig type for the syntax of
3131
// subdirectives that are common to most/all encoders.
3232
func (se *TransformEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
33-
foundTemplate := 0
34-
outerloop:
3533
for d.Next() {
3634
args := d.RemainingArgs()
3735
switch len(args) {
3836
case 0:
3937
se.Template = commonLogFormat
4038
default:
41-
foundTemplate = len(args)
4239
se.Template = strings.Join(args, " ")
4340
}
4441

4542
for nesting := d.Nesting(); d.NextBlock(nesting); {
46-
if d.Val() == "placeholder" {
43+
switch d.Val() {
44+
case "placeholder":
4745
d.AllArgs(&se.Placeholder)
4846
// delete the `placeholder` token and the value, and reset the cursor
4947
d.Delete()
5048
d.Delete()
51-
break outerloop
49+
case "unescape_strings":
50+
if d.NextArg() {
51+
return d.ArgErr()
52+
}
53+
d.Delete()
54+
se.UnescapeStrings = true
55+
default:
56+
d.RemainingArgs() //consume line without getting values
5257
}
5358
}
5459
}
5560

5661
d.Reset()
5762
// consume the directive and the template
58-
d.Next()
59-
for ; foundTemplate > 0; foundTemplate-- {
60-
d.Next()
61-
}
63+
d.RemainingArgs()
64+
6265
return (&se.LogEncoderConfig).UnmarshalCaddyfile(d)
6366
}

caddyfile_test.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func TestUnmarshalCaddyfile(t *testing.T) {
2929
Encoder zapcore.Encoder
3030
Template string
3131
Placeholder string
32+
UnescapeStrings bool
3233
}
3334
type args struct {
3435
d *caddyfile.Dispenser
@@ -295,17 +296,64 @@ func TestUnmarshalCaddyfile(t *testing.T) {
295296
},
296297
wantErr: false,
297298
},
299+
{
300+
name: "transform: not template but given unescape_strings",
301+
fields: fields{
302+
Template: commonLogFormat,
303+
UnescapeStrings: true,
304+
},
305+
args: args{
306+
d: caddyfile.NewTestDispenser(`transform {
307+
unescape_strings
308+
}`),
309+
},
310+
wantErr: false,
311+
},
312+
{
313+
name: "transform: given template and given unescape_strings",
314+
fields: fields{
315+
Template: "{obj1>obj2>[0]}",
316+
UnescapeStrings: true,
317+
},
318+
args: args{
319+
d: caddyfile.NewTestDispenser(`transform "{obj1>obj2>[0]}" {
320+
unescape_strings
321+
}`),
322+
},
323+
wantErr: false,
324+
},
325+
{
326+
name: "transform: `placeholder` `unescape_strings` and property sitting between other properties",
327+
fields: fields{
328+
Template: "{obj1>obj2>[0]}",
329+
Placeholder: "-",
330+
UnescapeStrings: true,
331+
LogEncoderConfig: logging.LogEncoderConfig{
332+
TimeLocal: true,
333+
TimeFormat: "iso8601",
334+
},
335+
},
336+
args: args{
337+
d: caddyfile.NewTestDispenser(`transform "{obj1>obj2>[0]}" {
338+
time_local
339+
placeholder -
340+
unescape_strings
341+
time_format iso8601
342+
}`),
343+
},
344+
wantErr: false,
345+
},
298346
}
299347
for _, tt := range tests {
300348
t.Run(tt.name, func(t *testing.T) {
301349
se := &TransformEncoder{
302350
Encoder: new(logging.JSONEncoder),
303351
}
304352
if err := se.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
305-
t.Errorf("TransformEncoder.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
353+
t.Fatalf("TransformEncoder.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
306354
}
307-
if se.Template != tt.fields.Template || se.Placeholder != tt.fields.Placeholder || !reflect.DeepEqual(se.LogEncoderConfig, tt.fields.LogEncoderConfig) {
308-
t.Errorf("Unexpected marshalling error: expected = %+v, received: %+v", tt.fields, *se)
355+
if se.Template != tt.fields.Template || se.Placeholder != tt.fields.Placeholder || se.UnescapeStrings != tt.fields.UnescapeStrings || !reflect.DeepEqual(se.LogEncoderConfig, tt.fields.LogEncoderConfig) {
356+
t.Fatalf("Unexpected marshalling error: expected = %+v, received: %+v", tt.fields, *se)
309357
}
310358
})
311359
}

formatencoder.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type TransformEncoder struct {
6464
zapcore.Encoder `json:"-"`
6565
Template string `json:"template,omitempty"`
6666
Placeholder string `json:"placeholder,omitempty"`
67+
UnescapeStrings bool `json:"unescape_strings,omitempty"`
6768
}
6869

6970
func (TransformEncoder) CaddyModule() caddy.ModuleInfo {
@@ -106,6 +107,7 @@ func (se TransformEncoder) Clone() zapcore.Encoder {
106107
Encoder: se.Encoder.Clone(),
107108
Template: se.Template,
108109
Placeholder: se.Placeholder,
110+
UnescapeStrings: se.UnescapeStrings,
109111
}
110112
}
111113

@@ -119,7 +121,7 @@ func (se TransformEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field
119121
repl.Map(func(key string) (interface{}, bool) {
120122
if strings.Contains(key, ":") {
121123
for _, slice := range strings.Split(key, ":") {
122-
val, found := getValue(buf, slice)
124+
val, found := getValue(buf, slice, se.UnescapeStrings)
123125
if found {
124126
return val, found
125127
}
@@ -128,7 +130,7 @@ func (se TransformEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field
128130
return nil, false
129131
}
130132

131-
return getValue(buf, key)
133+
return getValue(buf, key, se.UnescapeStrings)
132134
})
133135

134136
out := repl.ReplaceAll(se.Template, se.Placeholder)
@@ -144,7 +146,7 @@ func (se TransformEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field
144146
return buf, err
145147
}
146148

147-
func getValue(buf *buffer.Buffer, key string) (interface{}, bool) {
149+
func getValue(buf *buffer.Buffer, key string, unescapeStrings bool) (interface{}, bool) {
148150
path := strings.Split(key, ">")
149151
value, dataType, _, err := jsonparser.Get(buf.Bytes(), path...)
150152
if err != nil {
@@ -153,7 +155,13 @@ func getValue(buf *buffer.Buffer, key string) (interface{}, bool) {
153155
switch dataType {
154156
case jsonparser.NotExist:
155157
return nil, false
156-
case jsonparser.Array, jsonparser.Boolean, jsonparser.Null, jsonparser.Number, jsonparser.Object, jsonparser.String, jsonparser.Unknown:
158+
case jsonparser.String:
159+
if !unescapeStrings {
160+
return value, true
161+
}
162+
str, _ := jsonparser.ParseString(value)
163+
return str, true
164+
case jsonparser.Array, jsonparser.Boolean, jsonparser.Null, jsonparser.Number, jsonparser.Object, jsonparser.Unknown:
157165
// if a value exists, return it as is. A byte is a byte is a byte. The replacer handles them just fine.
158166
return value, true
159167
default:

formatencoder_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2015 Matthew Holt and The Caddy Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package transformencoder
16+
17+
import (
18+
"testing"
19+
20+
"github.com/caddyserver/caddy/v2"
21+
"github.com/caddyserver/caddy/v2/modules/logging"
22+
"go.uber.org/zap"
23+
"go.uber.org/zap/zapcore"
24+
)
25+
26+
func TestEncodeEntry(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
se TransformEncoder
30+
entry zapcore.Entry
31+
fields []zapcore.Field
32+
expectedLogString string
33+
}{
34+
{
35+
name: "encode entry: no unescape field",
36+
se: TransformEncoder{
37+
Encoder: new(logging.JSONEncoder),
38+
Template: "{msg} {username}",
39+
},
40+
entry: zapcore.Entry{
41+
Message: "lob\nlaw",
42+
},
43+
fields: []zapcore.Field{
44+
zap.String("username", "john\ndoe"),
45+
},
46+
expectedLogString: "lob\\nlaw john\\ndoe\n",
47+
},
48+
{
49+
name: "encode entry: unescape field",
50+
se: TransformEncoder{
51+
Encoder: new(logging.JSONEncoder),
52+
Template: "{msg} {username}",
53+
UnescapeStrings: true,
54+
},
55+
entry: zapcore.Entry{
56+
Message: "lob\nlaw",
57+
},
58+
fields: []zapcore.Field{
59+
zap.String("username", "john\ndoe"),
60+
},
61+
expectedLogString: "lob\nlaw john\ndoe\n",
62+
},
63+
}
64+
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
68+
err := tt.se.Provision(caddy.Context{})
69+
if err != nil {
70+
t.Fatalf("TransformEncoder.Provision() error = %v", err)
71+
}
72+
73+
buf, err := tt.se.EncodeEntry(tt.entry, tt.fields)
74+
75+
if err != nil {
76+
t.Fatalf("TransformEncoder.EncodeEntry() error = %v", err)
77+
}
78+
79+
if tt.expectedLogString != buf.String() {
80+
t.Fatalf("Unexpected encoding error: expected = %+v, received: %+v", tt.expectedLogString, buf)
81+
}
82+
83+
})
84+
}
85+
}

0 commit comments

Comments
 (0)