Skip to content

Commit

Permalink
Add: json log options: pretty print and parent key
Browse files Browse the repository at this point in the history
Change: access and upstream parent keys can be configured with the new global parent key
  • Loading branch information
Marcel Ludwig committed Apr 1, 2021
1 parent b7ba02b commit c8cea80
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 30 deletions.
16 changes: 9 additions & 7 deletions DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,24 @@ docker run --rm -p 8080:8080 -v `pwd`:/htdocs avenga/couper
| Variable | Default | Description |
|--- |--- |--- |
| COUPER_CONFIG_FILE | `couper.hcl` | Path to the configuration file. |
| COUPER_LOG_FORMAT | `common` | Can be set to `json` output which is the _container default_. |
| COUPER_DEFAULT_PORT | `8080` | Sets the default port to the given value and does not override explicit `[host:port]` configurations from file. |
| COUPER_XFH | `false` | Global configurations which uses the `Forwarded-Host` header instead of the request host. |
| COUPER_HEALTH_PATH | `/healthz` | Path for health-check requests for all servers and ports. |
| COUPER_NO_PROXY_FROM_ENV | `false` | Disables the connect hop to configured [proxy via environment](https://godoc.org/golang.org/x/net/http/httpproxy). |
| COUPER_REQUEST_ID_FORMAT | `common` | If set to `uuid4` a rfc4122 uuid is used for `req.id` and related log fields. |
| COUPER_SECURE_COOKIES | `""` | If set to `"strip"`, the `Secure` flag is removed from all `Set-Cookie` HTTP header fields. |
| COUPER_ACCESS_LOG_PARENT_FIELD | `""` | An option for `json` log format to add all log fields as child properties. |
| COUPER_TIMING_IDLE_TIMEOUT | `60s` | The maximum amount of time to wait for the next request on client connections when keep-alives are enabled. |
| COUPER_TIMING_READ_HEADER_TIMEOUT | `10s` | The amount of time allowed to read client request headers. |
| COUPER_TIMING_SHUTDOWN_DELAY | `0` | The amount of time the server is marked as unhealthy until calling server close finally. |
| COUPER_TIMING_SHUTDOWN_TIMEOUT | `0` | The maximum amount of time allowed to close the server with all running connections. |
| | | |
| COUPER_LOG_FORMAT | `common` | Can be set to `json` output which is the _container default_. |
| COUPER_LOG_PRETTY | `false` | Global option for `json` log format which pretty prints with basic key coloring. |
| COUPER_LOG_PARENT_FIELD | `""` | An option for `json` log format to add all log fields as child properties. |
| COUPER_LOG_TYPE_VALUE | `couper_daemon` | Value for the runtime log field `type`. |
| COUPER_ACCESS_LOG_TYPE_VALUE | `couper_access` | Value for the log field `type`. |
| COUPER_ACCESS_LOG_REQUEST_HEADERS | `User-Agent, Accept, Referer` | A comma separated list of header names whose values should be logged. |
| COUPER_ACCESS_LOG_RESPONSE_HEADERS | `Cache-Control, Content-Encoding, Content-Type, Location` | A comma separated list of header names whose values should be logged. |
| COUPER_BACKEND_LOG_PARENT_FIELD | `""` | An option for `json` log format to add all log fields as child properties. |
| COUPER_BACKEND_LOG_TYPE_VALUE | `couper_backend` | Value for the log field `type`. |
| COUPER_BACKEND_LOG_REQUEST_HEADERS | `User-Agent, Accept, Referer` | A comma separated list of header names whose values should be logged. |
| COUPER_BACKEND_LOG_RESPONSE_HEADERS | `Cache-Control, Content-Encoding, Content-Type, Location` | A comma separated list of header names whose values should be logged. |
| COUPER_TIMING_IDLE_TIMEOUT | `60s` | The maximum amount of time to wait for the next request on client connections when keep-alives are enabled. |
| COUPER_TIMING_READ_HEADER_TIMEOUT | `10s` | The amount of time allowed to read client request headers. |
| COUPER_TIMING_SHUTDOWN_DELAY | `0` | The amount of time the server is marked as unhealthy until calling server close finally. |
| COUPER_TIMING_SHUTDOWN_TIMEOUT | `0` | The maximum amount of time allowed to close the server with all running connections. |
1 change: 1 addition & 0 deletions config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Settings struct {
DefaultPort int `hcl:"default_port,optional"`
HealthPath string `hcl:"health_path,optional"`
LogFormat string `hcl:"log_format,optional"`
LogPretty bool `hcl:"log_pretty,optional"`
NoProxyFromEnv bool `hcl:"no_proxy_from_env,optional"`
RequestIDFormat string `hcl:"request_id_format,optional"`
SecureCookies string `hcl:"secure_cookies,optional"`
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ gateway instance.
| `no_proxy_from_env` | Disables the connect hop to configured [proxy via environment](https://godoc.org/golang.org/x/net/http/httpproxy). | `false` |
| `default_port` | Port which will be used if not explicitly specified per host within the [`hosts`](#server-block) list | `8080` |
| `log_format` | Switch for tab/field based colored view or json log lines | `common` |
| `log_pretty` | Global option for `json` log format which pretty prints with basic key coloring | `false` |
| `xfh` | Option to use the `X-Forwarded-Host` header as the request host | `false` |
| `request_id_format` | If set to `uuid4` a rfc4122 uuid is used for `req.id` and related log fields | `common` |
| `secure_cookies` | If set to `"strip"`, the `Secure` flag is removed from all `Set-Cookie` HTTP header fields. | `""` |
Expand Down
7 changes: 1 addition & 6 deletions logging/access_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,7 @@ func (log *AccessLog) ServeHTTP(rw http.ResponseWriter, req *http.Request, nextH
fields["code"] = i
}

var entry *logrus.Entry
if log.conf.ParentFieldKey != "" {
entry = log.logger.WithField(log.conf.ParentFieldKey, fields)
} else {
entry = log.logger.WithFields(logrus.Fields(fields))
}
entry := log.logger.WithFields(logrus.Fields(fields))
entry.Time = startTime

if statusRecorder.status == http.StatusInternalServerError || err != nil {
Expand Down
40 changes: 40 additions & 0 deletions logging/json_color_formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package logging

import (
"regexp"

"github.com/fatih/color"
"github.com/sirupsen/logrus"
)

type JSONColorFormatter struct {
inner *logrus.JSONFormatter
}

func NewJSONColorFormatter(parent string, pretty bool) logrus.Formatter {
return &JSONColorFormatter{
inner: &logrus.JSONFormatter{
DataKey: parent,
FieldMap: logrus.FieldMap{
logrus.FieldKeyTime: "timestamp",
logrus.FieldKeyMsg: "message",
},
PrettyPrint: pretty,
},
}
}

var keyRegex = regexp.MustCompile(`"(\w+)":`)

func (jcf *JSONColorFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b, err := jcf.inner.Format(entry)
if !jcf.inner.PrettyPrint || err != nil {
return b, err
}

result := keyRegex.ReplaceAllFunc(b, func(needle []byte) []byte {
return []byte(color.HiGreenString("%s", string(needle)))
})

return result, err
}
7 changes: 1 addition & 6 deletions logging/upstream_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,7 @@ func (u *UpstreamLog) RoundTrip(req *http.Request) (*http.Response, error) {
fields["timings"] = timingResults
//timings["ttlb"] = roundMS(rtDone.Sub(timeTTFB)) // TODO: depends on stream or buffer

var entry *logrus.Entry
if u.config.ParentFieldKey != "" {
entry = u.log.WithField(u.config.ParentFieldKey, fields)
} else {
entry = u.log.WithFields(logrus.Fields(fields))
}
entry := u.log.WithFields(logrus.Fields(fields))
entry.Time = startTime

if (beresp != nil && beresp.StatusCode == http.StatusInternalServerError) || err != nil {
Expand Down
32 changes: 21 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
"github.com/avenga/couper/config/configload"
"github.com/avenga/couper/config/env"
"github.com/avenga/couper/config/runtime"
"github.com/avenga/couper/logging"
)

var (
fields = logrus.Fields{
"type": "couper_daemon",
"build": runtime.BuildName,
"version": runtime.VersionName,
}
Expand Down Expand Up @@ -46,18 +46,20 @@ func realmain(arguments []string) int {
}

var filePath, logFormat string
var logPretty bool
set := flag.NewFlagSet("global", flag.ContinueOnError)
set.StringVar(&filePath, "f", config.DefaultFilename, "-f ./couper.hcl")
set.StringVar(&logFormat, "log-format", config.DefaultSettings.LogFormat, "-log-format=common")
set.BoolVar(&logPretty, "log-pretty", config.DefaultSettings.LogPretty, "-log-pretty")
err := set.Parse(args.Filter(set))
if err != nil {
newLogger(logFormat).WithFields(fields).Error(err)
newLogger(logFormat, logPretty).WithFields(fields).Error(err)
return 1
}

confFile, err := configload.LoadFile(filePath)
if err != nil {
newLogger(logFormat).WithFields(fields).Error(err)
newLogger(logFormat, logPretty).WithFields(fields).Error(err)
return 1
}

Expand All @@ -66,7 +68,10 @@ func realmain(arguments []string) int {
if logFormat != config.DefaultSettings.LogFormat {
confFile.Settings.LogFormat = logFormat
}
logger := newLogger(confFile.Settings.LogFormat).WithFields(fields)
if logPretty != config.DefaultSettings.LogPretty {
confFile.Settings.LogPretty = logPretty
}
logger := newLogger(confFile.Settings.LogFormat, confFile.Settings.LogPretty).WithFields(fields)

wd, err := os.Getwd()
if err != nil {
Expand All @@ -85,23 +90,28 @@ func realmain(arguments []string) int {
// newLogger creates a log instance with the configured formatter.
// Since the format option may required to be correct in early states
// we parse the env configuration on every call.
func newLogger(format string) logrus.FieldLogger {
func newLogger(format string, pretty bool) logrus.FieldLogger {
logger := logrus.New()
logger.Out = os.Stdout
if hook != nil {
logger.AddHook(hook)
logger.Out = ioutil.Discard
}

settings := &config.Settings{LogFormat: format}
settings := &config.Settings{
LogFormat: format,
LogPretty: pretty,
}
env.Decode(settings)

logConf := &logging.Config{
TypeFieldKey: "couper_daemon",
}
env.Decode(logConf)

if settings.LogFormat == "json" {
logger.Formatter = &logrus.JSONFormatter{FieldMap: logrus.FieldMap{
logrus.FieldKeyTime: "timestamp",
logrus.FieldKeyMsg: "message",
}}
logger.SetFormatter(logging.NewJSONColorFormatter(logConf.ParentFieldKey, settings.LogPretty))
}
logger.Level = logrus.DebugLevel
return logger
return logger.WithField("type", logConf.TypeFieldKey)
}

0 comments on commit c8cea80

Please sign in to comment.