Skip to content

Commit

Permalink
replace -v --verbose with -l --log-level with 3 settings, 0 default, …
Browse files Browse the repository at this point in the history
…1 adds requests, 2 adds request body
  • Loading branch information
tateexon committed Jun 25, 2024
1 parent 8c2e34c commit fcc3cb7
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 61 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ Flags:
-m, --proxy-mode string Proxy mode, the options are all, missing or none (default "none")
-u, --proxy-url string The url where the proxy will redirect to
-s, --secure Run mock server using TLS (https)
--version Version of Killgrave
-v, --version Version of Killgrave
-w, --watcher File watcher will reload the server on each file change
-v, --verbose Print out more detailed logging
-l, --log-level The higher the log level the more detail you get (default 0, 1 adds requests, 2 adds request body)
-d, --dump-requests-path Print requests out to specified file
```

Expand Down Expand Up @@ -184,8 +184,8 @@ cors:
allow_credentials: true
watcher: true
secure: true
verbose: false
dump-requests-path: "/abc/def.log
log_level: 1
dump_requests_path: "/abc/def.log
```
As you can see, you can configure all the options in a very easy way. For the above example, the file tree looks as follows, with the current working directory being `mymock`.
Expand Down
19 changes: 12 additions & 7 deletions internal/app/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
_defaultPort = 3000
_defaultProxyMode = killgrave.ProxyNone
_defaultStrictSlash = true
_defaultLogLevel = 0

_impostersFlag = "imposters"
_configFlag = "config"
Expand All @@ -34,7 +35,7 @@ const (
_secureFlag = "secure"
_proxyModeFlag = "proxy-mode"
_proxyURLFlag = "proxy-url"
_verboseFlag = "verbose"
_logLevel = "log-level"
_dumpRequestsPathFlag = "dump-requests-path"
)

Expand All @@ -43,7 +44,8 @@ var (
errGetDataFromHostFlag = errors.New("error trying to get data from host flag")
errGetDataFromPortFlag = errors.New("error trying to get data from port flag")
errGetDataFromSecureFlag = errors.New("error trying to get data from secure flag")
errGetDataFromVerboseFlag = errors.New("error trying to get data from verbose flag")
errGetDataFromLogLevelFlag = errors.New("error trying to get data from log-level flag")
errGetDataLogLevelInvalid = errors.New("error setting log-level, must be between 0 and 2 inclusive")
errGetDataFromDumpRequestsPathFlag = errors.New("error trying to get data from dump-requests-path flag")
errMandatoryURL = errors.New("the field proxy-url is mandatory if you selected a proxy mode")
)
Expand Down Expand Up @@ -80,7 +82,7 @@ func NewKillgraveCmd() *cobra.Command {
rootCmd.Flags().BoolP(_secureFlag, "s", false, "Run mock server using TLS (https)")
rootCmd.Flags().StringP(_proxyModeFlag, "m", _defaultProxyMode.String(), "Proxy mode, the options are all, missing or none")
rootCmd.Flags().StringP(_proxyURLFlag, "u", "", "The url where the proxy will redirect to")
rootCmd.Flags().BoolP(_verboseFlag, "v", false, "More verbose logging, adds request dumps to logs")
rootCmd.Flags().IntP(_logLevel, "l", _defaultLogLevel, "Log level, the options are 0, 1, 2. Default is 0, 1 adds requests, 2 adds request body")
rootCmd.Flags().StringP(_dumpRequestsPathFlag, "d", "", "Path the requests will be dumped to")

rootCmd.SetVersionTemplate("Killgrave version: {{.Version}}\n")
Expand Down Expand Up @@ -140,7 +142,7 @@ func runServer(cfg killgrave.Config) server.Server {
cfg.Secure,
imposterFs,
server.PrepareAccessControl(cfg.CORS),
cfg.Verbose,
cfg.LogLevel,
cfg.DumpRequestsPath,
)
if err := s.Build(); err != nil {
Expand Down Expand Up @@ -192,17 +194,20 @@ func prepareConfig(cmd *cobra.Command) (killgrave.Config, error) {
return killgrave.Config{}, fmt.Errorf("%v: %w", err, errGetDataFromSecureFlag)
}

verbose, err := cmd.Flags().GetBool(_verboseFlag)
logLevel, err := cmd.Flags().GetInt(_logLevel)
if err != nil {
return killgrave.Config{}, fmt.Errorf("%v: %w", err, errGetDataFromVerboseFlag)
return killgrave.Config{}, fmt.Errorf("%v: %w", err, errGetDataFromLogLevelFlag)
}
if logLevel < 0 || logLevel > 2 {
return killgrave.Config{}, fmt.Errorf("%v: %w", err, errGetDataLogLevelInvalid)
}

dumpRequestsPath, err := cmd.Flags().GetString(_dumpRequestsPathFlag)
if err != nil {
return killgrave.Config{}, fmt.Errorf("%v: %w", err, errGetDataFromDumpRequestsPathFlag)
}

cfg, err := killgrave.NewConfig(impostersPath, host, port, secure, verbose, dumpRequestsPath)
cfg, err := killgrave.NewConfig(impostersPath, host, port, secure, logLevel, dumpRequestsPath)
if err != nil {
return killgrave.Config{}, err
}
Expand Down
6 changes: 3 additions & 3 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Config struct {
Proxy ConfigProxy `yaml:"proxy"`
Secure bool `yaml:"secure"`
Watcher bool `yaml:"watcher"`
Verbose bool `yaml:"verbose"`
LogLevel int `yaml:"log_level"`
DumpRequestsPath string `yaml:"dump_requests_path"`
}

Expand Down Expand Up @@ -113,7 +113,7 @@ func (cfg *Config) ConfigureProxy(proxyMode ProxyMode, proxyURL string) {
type ConfigOpt func(cfg *Config) error

// NewConfig initialize the config
func NewConfig(impostersPath, host string, port int, secure, verbose bool, dumpRequestsPath string) (Config, error) {
func NewConfig(impostersPath, host string, port int, secure bool, logLevel int, dumpRequestsPath string) (Config, error) {
if impostersPath == "" {
return Config{}, errEmptyImpostersPath
}
Expand All @@ -131,7 +131,7 @@ func NewConfig(impostersPath, host string, port int, secure, verbose bool, dumpR
Host: host,
Port: port,
Secure: secure,
Verbose: verbose,
LogLevel: logLevel,
DumpRequestsPath: dumpRequestsPath,
}

Expand Down
4 changes: 2 additions & 2 deletions internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func TestNewConfig(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewConfig(tt.args.impostersPath, tt.args.host, tt.args.port, false, false, "")
got, err := NewConfig(tt.args.impostersPath, tt.args.host, tt.args.port, false, 0, "")
assert.Equal(t, tt.err, err)
assert.Equal(t, tt.want, got)
})
Expand All @@ -221,7 +221,7 @@ func TestConfig_ConfigureProxy(t *testing.T) {
},
}

got, err := NewConfig("imposters", "localhost", 80, false, false, "")
got, err := NewConfig("imposters", "localhost", 80, false, 0, "")
assert.NoError(t, err)

got.ConfigureProxy(ProxyAll, "https://friendsofgo.tech")
Expand Down
52 changes: 31 additions & 21 deletions internal/server/http/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,40 +185,50 @@ func isBinaryContent(r *http.Request) bool {
return false
}

func shouldRecordRequest(s *Server) bool {
return len(s.dumpRequestsPath) > 0 && s.dumpCh != nil
}

func getBody(r *http.Request, s *Server) string {
if s.logLevel == 0 && !shouldRecordRequest(s) {
return ""
}
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body: %v\n", err)
return ""
}
r.Body = io.NopCloser(bytes.NewReader(bodyBytes)) // Reset the body
body := string(bodyBytes)
// if content is binary, encode it to base64
if isBinaryContent(r) {
body = base64.StdEncoding.EncodeToString(bodyBytes)
}
return body
}

// CustomLoggingHandler provides a way to supply a custom log formatter
// while taking advantage of the mechanisms in this package
func CustomLoggingHandler(out io.Writer, h http.Handler, s *Server) http.Handler {
return handlers.CustomLoggingHandler(out, h, func(writer io.Writer, params handlers.LogFormatterParams) {
bodyBytes, err := io.ReadAll(params.Request.Body)
if err != nil {
log.Printf("Error reading request body: %v\n", err)
return
}
params.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) // Reset the body
body := string(bodyBytes)
// if content is binary, encode it to base64
if isBinaryContent(params.Request) {
body = base64.StdEncoding.EncodeToString(bodyBytes)
}
verbose := s.verbose
body := getBody(params.Request, s)

// record request, if error set verbose to true to log current request since
// it didn't make it into a full channel
err = recordRequest(params.Request, s, body)
if err != nil {
verbose = true
var err error
if shouldRecordRequest(s) {
err = recordRequest(params.Request, s, body)
}

if verbose {
// log the request based on the log level
// if err is set, log the request, but only add the body if the log level is 2 or higher
if s.logLevel >= 2 {
writeLog(writer, params, body)
} else if err != nil || s.logLevel > 0 {
writeLog(writer, params, "")
}
})
}

func recordRequest(r *http.Request, s *Server, body string) error {
if len(s.dumpRequestsPath) < 1 || s.dumpCh == nil {
return nil
}
rd := getRequestData(r, body)
select {
case s.dumpCh <- rd:
Expand Down
20 changes: 10 additions & 10 deletions internal/server/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ type Server struct {
secure bool
imposterFs ImposterFs
corsOptions []handlers.CORSOption
verbose bool
logLevel int
dumpRequestsPath string
dumpCh chan *RequestData
}

// NewServer initialize the mock server
func NewServer(r *mux.Router, httpServer *http.Server, proxyServer *Proxy, secure bool, fs ImposterFs, CORSOptions []handlers.CORSOption, verbose bool, dumpRequestsPath string) Server {
func NewServer(r *mux.Router, httpServer *http.Server, proxyServer *Proxy, secure bool, fs ImposterFs, CORSOptions []handlers.CORSOption, logLevel int, dumpRequestsPath string) Server {
return Server{
router: r,
httpServer: httpServer,
proxy: proxyServer,
secure: secure,
imposterFs: fs,
corsOptions: CORSOptions,
verbose: verbose,
logLevel: logLevel,
dumpRequestsPath: dumpRequestsPath,
}
}
Expand Down Expand Up @@ -92,22 +92,22 @@ func (s *Server) Build() error {
return nil
}

// only intantiate the request dump if we need it
if s.dumpCh == nil && len(s.dumpRequestsPath) > 0 {
s.dumpCh = make(chan *RequestData, 1000)
go RequestWriter(s.dumpRequestsPath, s.dumpCh)
}

// setup the logging handler
var handler http.Handler = s.router
if s.verbose || (s.dumpCh != nil && len(s.dumpRequestsPath) > 0) {
if s.logLevel > 0 || shouldRecordRequest(s) {
handler = CustomLoggingHandler(log.Writer(), handler, s)
}
s.httpServer.Handler = handlers.CORS(s.corsOptions...)(handler)

var impostersCh = make(chan []Imposter)
var done = make(chan struct{})

// only intantiate the request dump if we need it
if s.dumpCh == nil && len(s.dumpRequestsPath) > 0 {
s.dumpCh = make(chan *RequestData, 1000)
go RequestWriter(s.dumpRequestsPath, s.dumpCh)
}

go func() {
s.imposterFs.FindImposters(impostersCh)
done <- struct{}{}
Expand Down
Loading

0 comments on commit fcc3cb7

Please sign in to comment.