Skip to content

build proxy with conditional logging and jsonselect encoders #4864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions components/proxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020 Gitpod GmbH. All rights reserved.
# 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.

Expand All @@ -9,10 +9,6 @@ RUN curl -fsSL https://github.com/caddyserver/xcaddy/releases/download/v0.1.9/xc

WORKDIR /plugins

COPY plugins/go.* /plugins/

RUN go mod download

COPY plugins /plugins

# the fork contains two changes:
Expand All @@ -25,7 +21,11 @@ RUN git clone -b http2 https://github.com/aledbf/caddy caddy-fork
RUN xcaddy build \
--output /caddy \
--with github.com/caddyserver/caddy/v2=$PWD/caddy-fork \
--with github.com/gitpod-io/gitpod/proxy/plugins=/plugins
--with github.com/gitpod-io/gitpod/proxy/plugins/corsorigin=/plugins/corsorigin \
--with github.com/gitpod-io/gitpod/proxy/plugins/secwebsocketkey=/plugins/secwebsocketkey \
--with github.com/gitpod-io/gitpod/proxy/plugins/workspacedownload=/plugins/workspacedownload \
--with github.com/gitpod-io/gitpod/proxy/plugins/logif=/plugins/logif \
--with github.com/gitpod-io/gitpod/proxy/plugins/jsonselect=/plugins/jsonselect

FROM alpine:3.14

Expand Down
13 changes: 3 additions & 10 deletions components/proxy/conf/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,9 @@
(enable_log) {
log {
output stdout
format filter {
wrap json
fields {
logger delete
msg delete
size delete
status delete
resp_headers delete
request delete
}
format if "status != 200 && status != 0 && status != 304" jsonselect "{severity:level} {timestamp:ts} {logName:logger} {httpRequest>requestMethod:request>method} {httpRequest>protocol:request>proto} {httpRequest>status:status} {httpRequest>responseSize:size} {httpRequest>userAgent:request>headers>User-Agent>[0]} {httpRequest>requestUrl:request>uri}" {
level_format "upper"
time_format "rfc3339_nano"
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions components/proxy/plugins/corsorigin/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/gitpod-io/gitpod/proxy/plugins/corsorigin

go 1.16

require (
github.com/caddyserver/caddy/v2 v2.4.3
github.com/rs/cors v1.8.0
)
1,132 changes: 1,132 additions & 0 deletions components/proxy/plugins/corsorigin/go.sum

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions components/proxy/plugins/go.mod

This file was deleted.

53 changes: 53 additions & 0 deletions components/proxy/plugins/jsonselect/caddyfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 (
"strings"

"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)

func (e *JSONSelectEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
args := d.RemainingArgs()
switch len(args) {
case 0:
return d.Errf("%s (%T) requires an argument", moduleID, e)
default:
e.Selector = strings.Join(args, " ")
}

for n := d.Nesting(); d.NextBlock(n); {
subdir := d.Val()
var arg string
if !d.AllArgs(&arg) {
return d.ArgErr()
}
switch subdir {
case "message_key":
e.MessageKey = &arg
case "level_key":
e.LevelKey = &arg
case "time_key":
e.TimeKey = &arg
case "name_key":
e.NameKey = &arg
case "caller_key":
e.CallerKey = &arg
case "stacktrace_key":
e.StacktraceKey = &arg
case "line_ending":
e.LineEnding = &arg
case "time_format":
e.TimeFormat = arg
case "level_format":
e.LevelFormat = arg
default:
return d.Errf("unrecognized subdirective %s", subdir)
}
}
}
return nil
}
9 changes: 9 additions & 0 deletions components/proxy/plugins/jsonselect/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/gitpod-io/gitpod/proxy/plugins/jsonselect

go 1.16

require (
github.com/buger/jsonparser v1.1.1
github.com/caddyserver/caddy/v2 v2.4.3
go.uber.org/zap v1.18.1
)

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions components/proxy/plugins/jsonselect/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// 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 != 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)
)
61 changes: 61 additions & 0 deletions components/proxy/plugins/logif/caddyfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 logif

import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"go.uber.org/zap/zapcore"
)

// UnmarshalCaddyfile sets up the module form Caddyfile tokens.
//
// Syntax:
// if {
// "<expression>"
// } [<encoder>]
//
// The <expression> must be on a single line.
// Refer to `lang.Lang` for its syntax.
//
// The <encoder> can be one of `json`, `jsonselector`, `console`.
// In case no <encoder> is specified, one between `json` and `console` is set up depending
// on the current environment.
func (ce *ConditionalEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if d.Next() {
if d.Val() != moduleName {
return d.Errf("expecting %s (%T) subdirective", moduleID, ce)
}
var expression string
if !d.Args(&expression) {
return d.Errf("%s (%T) requires an expression", moduleID, ce)
}

ce.Expr = expression
}

if !d.Next() {
return nil
}

// Delegate the parsing of the encoder to the encoder itself
nextDispenser := d.NewFromNextSegment()
if nextDispenser.Next() {
moduleName := nextDispenser.Val()
moduleID := "caddy.logging.encoders." + moduleName
mod, err := caddyfile.UnmarshalModule(nextDispenser, moduleID)
if err != nil {
return err
}
enc, ok := mod.(zapcore.Encoder)
if !ok {
return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, mod)
}
ce.EncRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)
ce.Formatter = moduleName
}

return nil
}
14 changes: 14 additions & 0 deletions components/proxy/plugins/logif/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/gitpod-io/gitpod/proxy/plugins/logif

go 1.16

replace github.com/gitpod-io/gitpod/proxy/plugins/jsonselect => ../jsonselect

require (
github.com/PaesslerAG/gval v1.1.1
github.com/buger/jsonparser v1.1.1
github.com/caddyserver/caddy/v2 v2.4.3
github.com/gitpod-io/gitpod/proxy/plugins/jsonselect v0.0.0-00010101000000-000000000000
go.uber.org/zap v1.18.1
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
)
Loading