Skip to content

Commit 9f53a8e

Browse files
committed
feat: ✨ Add option to unescape strings inside log string
1 parent f627fc4 commit 9f53a8e

File tree

7 files changed

+176
-17
lines changed

7 files changed

+176
-17
lines changed

README.md

+1
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 <bool>
3637
# other fields accepted by JSON encoder
3738
}
3839
}

caddyfile.go

+22-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package transformencoder
1717
import (
1818
"strings"
1919

20+
"strconv"
21+
2022
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
2123
)
2224

@@ -30,34 +32,45 @@ import (
3032
// See the godoc on the LogEncoderConfig type for the syntax of
3133
// subdirectives that are common to most/all encoders.
3234
func (se *TransformEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
33-
foundTemplate := 0
34-
outerloop:
3535
for d.Next() {
3636
args := d.RemainingArgs()
3737
switch len(args) {
3838
case 0:
3939
se.Template = commonLogFormat
4040
default:
41-
foundTemplate = len(args)
4241
se.Template = strings.Join(args, " ")
4342
}
4443

4544
for nesting := d.Nesting(); d.NextBlock(nesting); {
46-
if d.Val() == "placeholder" {
45+
switch d.Val() {
46+
case "placeholder":
4747
d.AllArgs(&se.Placeholder)
4848
// delete the `placeholder` token and the value, and reset the cursor
4949
d.Delete()
5050
d.Delete()
51-
break outerloop
51+
case "unescape_strings":
52+
// require an argument
53+
if !d.NextArg() {
54+
return d.ArgErr()
55+
}
56+
b, err := strconv.ParseBool(d.Val())
57+
if nil == err {
58+
se.UnescapeStrings = b
59+
}
60+
if d.NextArg() {
61+
return d.ArgErr()
62+
}
63+
d.Delete()
64+
d.Delete()
65+
default:
66+
d.RemainingArgs() //consume line without getting values
5267
}
5368
}
5469
}
5570

5671
d.Reset()
5772
// consume the directive and the template
58-
d.Next()
59-
for ; foundTemplate > 0; foundTemplate-- {
60-
d.Next()
61-
}
73+
d.RemainingArgs()
74+
6275
return (&se.LogEncoderConfig).UnmarshalCaddyfile(d)
6376
}

caddyfile_test.go

+51-3
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 true
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 true
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 true
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

+13-4
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,14 @@ 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+
} else {
162+
str, _ := jsonparser.ParseString(value)
163+
return str, true
164+
}
165+
case jsonparser.Array, jsonparser.Boolean, jsonparser.Null, jsonparser.Number, jsonparser.Object, jsonparser.Unknown:
157166
// if a value exists, return it as is. A byte is a byte is a byte. The replacer handles them just fine.
158167
return value, true
159168
default:

formatencoder_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
"github.com/stretchr/testify/assert"
23+
"go.uber.org/zap"
24+
"go.uber.org/zap/zapcore"
25+
)
26+
27+
func TestEncodeEntry(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
se TransformEncoder
31+
entry zapcore.Entry
32+
fields []zapcore.Field
33+
expectedLogString string
34+
}{
35+
{
36+
name: "transform: no args",
37+
se: TransformEncoder{
38+
Encoder: new(logging.JSONEncoder),
39+
Template: "{msg} {username}",
40+
},
41+
entry: zapcore.Entry{
42+
Message: "lob\nlaw",
43+
},
44+
fields: []zapcore.Field{
45+
zap.String("username", "john\ndoe"),
46+
},
47+
expectedLogString: "lob\\nlaw john\\ndoe\n",
48+
},
49+
{
50+
name: "transform: no args",
51+
se: TransformEncoder{
52+
Encoder: new(logging.JSONEncoder),
53+
Template: "{msg} {username}",
54+
UnescapeStrings: true,
55+
},
56+
entry: zapcore.Entry{
57+
Message: "lob\nlaw",
58+
},
59+
fields: []zapcore.Field{
60+
zap.String("username", "john\ndoe"),
61+
},
62+
expectedLogString: "lob\nlaw john\ndoe\n",
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
69+
err := tt.se.Provision(caddy.Context{})
70+
assert.NoError(t, err)
71+
72+
buf, err := tt.se.EncodeEntry(tt.entry, tt.fields)
73+
74+
if err != nil {
75+
t.Errorf("TransformEncoder.EncodeEntry() error = %v", err)
76+
}
77+
78+
assert.Equal(t, tt.expectedLogString, buf.String())
79+
80+
})
81+
}
82+
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.18
55
require (
66
github.com/buger/jsonparser v1.1.1
77
github.com/caddyserver/caddy/v2 v2.6.2
8+
github.com/stretchr/testify v1.10.0
89
go.uber.org/zap v1.23.0
910
)
1011

@@ -22,6 +23,7 @@ require (
2223
github.com/cespare/xxhash/v2 v2.2.0 // indirect
2324
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
2425
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
26+
github.com/davecgh/go-spew v1.1.1 // indirect
2527
github.com/dgraph-io/badger v1.6.2 // indirect
2628
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
2729
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect
@@ -69,6 +71,7 @@ require (
6971
github.com/nxadm/tail v1.4.8 // indirect
7072
github.com/onsi/ginkgo v1.16.4 // indirect
7173
github.com/pkg/errors v0.9.1 // indirect
74+
github.com/pmezard/go-difflib v1.0.0 // indirect
7275
github.com/prometheus/client_golang v1.12.2 // indirect
7376
github.com/prometheus/client_model v0.2.0 // indirect
7477
github.com/prometheus/common v0.32.1 // indirect
@@ -111,5 +114,6 @@ require (
111114
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
112115
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
113116
gopkg.in/yaml.v2 v2.4.0 // indirect
117+
gopkg.in/yaml.v3 v3.0.1 // indirect
114118
howett.net/plist v1.0.0 // indirect
115119
)

go.sum

+3-1
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
621621
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
622622
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
623623
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
624-
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
624+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
625+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
625626
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 h1:xwMw7LFhV9dbvot9A7NLClP9udqbjrQlIwWMH8e7uiQ=
626627
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
627628
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -1070,6 +1071,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
10701071
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10711072
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10721073
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1074+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10731075
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
10741076
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
10751077
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

0 commit comments

Comments
 (0)