Path: blob/main/components/proxy/plugins/jsonselect/plugin.go
2500 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package jsonselect56import (7"bytes"8"fmt"9"strings"1011"github.com/buger/jsonparser"12"github.com/caddyserver/caddy/v2"13"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"14"github.com/caddyserver/caddy/v2/modules/logging"15"go.uber.org/zap/buffer"16"go.uber.org/zap/zapcore"17)1819const (20moduleName = "jsonselect"21moduleID = "caddy.logging.encoders." + moduleName22)2324func init() {25caddy.RegisterModule(JSONSelectEncoder{})26}2728type JSONSelectEncoder struct {29logging.LogEncoderConfig30zapcore.Encoder `json:"-"`31Selector string `json:"selector,omitempty"`3233getters [][]string34setters [][]string35}3637func (JSONSelectEncoder) CaddyModule() caddy.ModuleInfo {38return caddy.ModuleInfo{39ID: moduleID,40New: func() caddy.Module {41return &JSONSelectEncoder{42Encoder: new(logging.JSONEncoder),43}44},45}46}4748func (e *JSONSelectEncoder) Provision(ctx caddy.Context) error {49if e.Selector == "" {50return fmt.Errorf("selector is mandatory")51}5253e.setters = [][]string{}54e.getters = [][]string{}55r := caddy.NewReplacer()56r.Map(func(sel string) (interface{}, bool) {57var set, get string5859parts := strings.Split(sel, ":")60if len(parts) == 1 {61set = parts[0]62get = set63} else if len(parts) == 2 {64set = parts[0]65get = parts[1]66} else {67// todo > error out - how?68return nil, false69}7071e.setters = append(e.setters, strings.Split(set, ">"))72e.getters = append(e.getters, strings.Split(get, ">"))73return nil, false74})75r.ReplaceAll(e.Selector, "")7677if len(e.setters) != len(e.getters) {78return fmt.Errorf("selector must have the same number of setters and getters")79}8081e.Encoder = zapcore.NewJSONEncoder(e.ZapcoreEncoderConfig())82return nil83}8485func (e JSONSelectEncoder) Clone() zapcore.Encoder {86return JSONSelectEncoder{87LogEncoderConfig: e.LogEncoderConfig,88Encoder: e.Encoder.Clone(),89Selector: e.Selector,90getters: e.getters,91setters: e.setters,92}93}9495func (e JSONSelectEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {96buf, err := e.Encoder.EncodeEntry(entry, fields)97if err != nil {98return buf, err99}100101res := []byte{'{', '}'}102// Temporary workaround the bug https://github.com/buger/jsonparser/issues/232103// TODO(leo): switch back to EachKey (see git history) for perf reasons when fixed104for idx, paths := range e.getters {105val, typ, _, err := jsonparser.Get(buf.Bytes(), paths...)106if err == jsonparser.KeyPathNotFoundError {107// path not found, skip108continue109}110if err != nil {111return nil, err112}113switch typ {114case jsonparser.NotExist:115// path not found, skip116case jsonparser.String:117res, _ = jsonparser.Set(res, append(append([]byte{'"'}, val...), '"'), e.setters[idx]...)118default:119res, _ = jsonparser.Set(res, val, e.setters[idx]...)120}121}122123// Reset the buffer to output our own content124buf.Reset()125// Insert the new content126nl := []byte("\n")127if !bytes.HasSuffix(res, nl) {128res = append(res, nl...)129}130buf.Write(res)131132return buf, err133}134135// Interface guards136var (137_ zapcore.Encoder = (*JSONSelectEncoder)(nil)138_ caddy.Provisioner = (*JSONSelectEncoder)(nil)139_ caddyfile.Unmarshaler = (*JSONSelectEncoder)(nil)140)141142143