2022-12-08 13:05:19 -03:00

142 lines
3.4 KiB
Go

// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package jsonselect
import (
"bytes"
"fmt"
"strings"
"github.com/buger/jsonparser"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/logging"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
const (
moduleName = "jsonselect"
moduleID = "caddy.logging.encoders." + moduleName
)
func init() {
caddy.RegisterModule(JSONSelectEncoder{})
}
type JSONSelectEncoder struct {
logging.LogEncoderConfig
zapcore.Encoder `json:"-"`
Selector string `json:"selector,omitempty"`
getters [][]string
setters [][]string
}
func (JSONSelectEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: moduleID,
New: func() caddy.Module {
return &JSONSelectEncoder{
Encoder: new(logging.JSONEncoder),
}
},
}
}
func (e *JSONSelectEncoder) Provision(ctx caddy.Context) error {
if e.Selector == "" {
return fmt.Errorf("selector is mandatory")
}
e.setters = [][]string{}
e.getters = [][]string{}
r := caddy.NewReplacer()
r.Map(func(sel string) (interface{}, bool) {
var set, get string
parts := strings.Split(sel, ":")
if len(parts) == 1 {
set = parts[0]
get = set
} else if len(parts) == 2 {
set = parts[0]
get = parts[1]
} else {
// todo > error out - how?
return nil, false
}
e.setters = append(e.setters, strings.Split(set, ">"))
e.getters = append(e.getters, strings.Split(get, ">"))
return nil, false
})
r.ReplaceAll(e.Selector, "")
if len(e.setters) != len(e.getters) {
return fmt.Errorf("selector must have the same number of setters and getters")
}
e.Encoder = zapcore.NewJSONEncoder(e.ZapcoreEncoderConfig())
return nil
}
func (e JSONSelectEncoder) Clone() zapcore.Encoder {
return JSONSelectEncoder{
LogEncoderConfig: e.LogEncoderConfig,
Encoder: e.Encoder.Clone(),
Selector: e.Selector,
getters: e.getters,
setters: e.setters,
}
}
func (e JSONSelectEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
buf, err := e.Encoder.EncodeEntry(entry, fields)
if err != nil {
return buf, err
}
res := []byte{'{', '}'}
// Temporary workaround the bug https://github.com/buger/jsonparser/issues/232
// TODO(leo): switch back to EachKey (see git history) for perf reasons when fixed
for idx, paths := range e.getters {
val, typ, _, err := jsonparser.Get(buf.Bytes(), paths...)
if err == jsonparser.KeyPathNotFoundError {
// path not found, skip
continue
}
if err != nil {
return nil, err
}
switch typ {
case jsonparser.NotExist:
// path not found, skip
case jsonparser.String:
res, _ = jsonparser.Set(res, append(append([]byte{'"'}, val...), '"'), e.setters[idx]...)
default:
res, _ = jsonparser.Set(res, val, e.setters[idx]...)
}
}
// Reset the buffer to output our own content
buf.Reset()
// Insert the new content
nl := []byte("\n")
if !bytes.HasSuffix(res, nl) {
res = append(res, nl...)
}
buf.Write(res)
return buf, err
}
// Interface guards
var (
_ zapcore.Encoder = (*JSONSelectEncoder)(nil)
_ caddy.Provisioner = (*JSONSelectEncoder)(nil)
_ caddyfile.Unmarshaler = (*JSONSelectEncoder)(nil)
)