Skip to content
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

feat: add new formatter log output for github #59

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion cmd/flag_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"path/filepath"

"github.com/rs/zerolog"

loggerConfig "github.com/coreruleset/crs-toolchain/logger"
)

// The types in this file satisfy the interface of pflag.Value.
Expand Down Expand Up @@ -38,7 +40,10 @@ func (o *outputType) String() string {

func (o *outputType) Set(value string) error {
switch value {
case string(text), string(gitHub):
case string(gitHub):
logger = loggerConfig.SetGithubOutput(os.Stdout)
fallthrough
case string(text):
rootValues.output = outputType(value)
return nil
default:
Expand Down
9 changes: 4 additions & 5 deletions cmd/regex_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ func performCompare(processAll bool, ctx *processors.Context) error {
if err != nil && len(chainOffsetString) > 0 {
return errors.New("failed to match chain offset. Value must not be larger than 255")
}
regex := runAssemble(filePath, ctx)
err = processRegexForCompare(id, uint8(chainOffset), regex, ctx)
rx := runAssemble(filePath, ctx)
err = processRegexForCompare(id, uint8(chainOffset), rx, ctx)
theseion marked this conversation as resolved.
Show resolved Hide resolved
if err != nil && errors.Is(err, &ComparisonError{}) {
failed = true
return nil
Expand All @@ -148,9 +148,8 @@ func performCompare(processAll bool, ctx *processors.Context) error {
if err != nil {
logger.Fatal().Err(err).Msg("Failed to compare expressions")
}
if failed && rootValues.output == gitHub {
fmt.Println("::error::All rules need to be up to date.",
"Please run `crs-toolchain regex update --all`")
if failed {
logger.Error().Msg("All rules need to be up to date. Please run `crs-toolchain regex update --all`")
theseion marked this conversation as resolved.
Show resolved Hide resolved
return &ComparisonError{}
}
} else {
Expand Down
17 changes: 2 additions & 15 deletions cmd/regex_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

const (
regexAssemblyStandardHeader = "##! Please refer to the documentation at\n##! https://coreruleset.org/docs/development/regex_assembly/.\n"
showCharsAround = 20
)

// formatCmd represents the generate command
Expand Down Expand Up @@ -149,18 +148,14 @@ func processAll(ctxt *processors.Context, checkOnly bool) error {
return err
}
if failed {
if rootValues.output == gitHub {
fmt.Println("::error::All assembly files need to be properly formatted.",
"Please run `crs-toolchain regex format --all`")
}
logger.Error().Msg("All assembly files need to be properly formatted. Please run `crs-toolchain regex format --all`")
return &UnformattedFileError{}
}
return nil
}

func processFile(filePath string, ctxt *processors.Context, checkOnly bool) error {
var processFileError error
message := ""
filename := path.Base(filePath)
logger.Info().Msgf("Processing %s", filename)
file, err := os.Open(filePath)
Expand Down Expand Up @@ -213,8 +208,7 @@ func processFile(filePath string, ctxt *processors.Context, checkOnly bool) erro
}
equalContent := bytes.Equal(currentContents, newContents)
if !equalContent || foundUppercase {
message = formatMessage(fmt.Sprintf("File %s not properly formatted", filePath))
fmt.Println(message)
logger.Error().Msgf("File %s not properly formatted", filePath)
processFileError = &UnformattedFileError{filePath: filePath}
}
} else {
Expand Down Expand Up @@ -281,13 +275,6 @@ func processLine(line []byte, indent int) ([]byte, int, error) {
return trimmedLine, nextIndent, nil
}

func formatMessage(message string) string {
if rootValues.output == gitHub {
message = fmt.Sprintf("::warning ::%s\n", message)
}
return message
}

func formatEndOfFile(lines []string) []string {
eof := len(lines) - 1
if eof < 0 {
Expand Down
39 changes: 39 additions & 0 deletions logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package logger

import (
"fmt"
"io"
"os"

"github.com/rs/zerolog"
Expand All @@ -16,3 +18,40 @@ func init() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "03:04:05"}).With().Caller().Logger()
zerolog.SetGlobalLevel(DefaultLogLevel)
}

func SetGithubOutput(w io.Writer) zerolog.Logger {
ghOutput := zerolog.ConsoleWriter{Out: w, TimeFormat: "03:04:05"}
ghOutput.FormatLevel = func(i interface{}) string {
var l string
if ll, ok := i.(string); ok {
switch ll {
case zerolog.LevelTraceValue, zerolog.LevelDebugValue:
l = "debug"
case zerolog.LevelInfoValue:
l = "notice"
case zerolog.LevelWarnValue:
l = "warn"
case zerolog.LevelErrorValue, zerolog.LevelFatalValue, zerolog.LevelPanicValue:
l = "error "
default:
l = "???"
}
} else {
if i == nil {
l = "???"
}
}
return fmt.Sprintf("::%s", l)
}
ghOutput.FormatMessage = func(i interface{}) string {
return fmt.Sprintf("::%s\n", i)
}
ghOutput.PartsExclude = []string{zerolog.TimestampFieldName, zerolog.CallerFieldName}
ghOutput.PartsOrder = []string{
zerolog.LevelFieldName,
zerolog.MessageFieldName,
}
ghOutput.NoColor = true

return log.Output(ghOutput).With().Caller().Logger()
}
124 changes: 124 additions & 0 deletions logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2024 OWASP ModSecurity Core Rule Set Project
// SPDX-License-Identifier: Apache-2.0

package logger

import (
"bytes"
"testing"

"github.com/rs/zerolog"
"github.com/stretchr/testify/suite"
)

type loggerTestSuite struct {
suite.Suite
out *bytes.Buffer
logger zerolog.Logger
}

func TestRunLoggerTestSuite(t *testing.T) {
suite.Run(t, new(loggerTestSuite))
}

var testJsonBase = []struct {
name string
text string
logType zerolog.Level
want string
}{
{
name: "JsonBaseOutput",
text: "hello",
logType: zerolog.InfoLevel,
want: "message\":\"hello\"",
},
}

var testConsoleBase = []struct {
name string
text string
logType zerolog.Level
want string
}{
{
name: "BaseConsoleOutput",
text: "hello",
logType: zerolog.InfoLevel,
want: "INF hello component=parser-test",
},
}

var testGithub = []struct {
name string
text string
logType zerolog.Level
fzipi marked this conversation as resolved.
Show resolved Hide resolved
want string
}{
{
name: "TestGithubInfoOutput",
text: "this is an info message",
logType: zerolog.InfoLevel,
want: "::notice ::this is an info message",
},
{
name: "TestGithubWarningOutput",
text: "this is a warning message",
logType: zerolog.WarnLevel,
want: "::warn ::this is a warning message",
},
{
name: "TestGithubTraceOutput",
text: "this is a trace message that will show as debug",
logType: zerolog.TraceLevel,
want: "::debug ::this is a trace message that will show as debug",
},
{
name: "TestGithubDebugOutput",
text: "this is a debug message",
logType: zerolog.DebugLevel,
want: "::debug ::this is a debug message",
},
}

func (s *loggerTestSuite) SetupTest() {
// reset logger
s.out = &bytes.Buffer{}
s.logger = zerolog.New(s.out).With().Str("component", "parser-test").Logger()
zerolog.SetGlobalLevel(zerolog.TraceLevel)
}

func (s *loggerTestSuite) TestJsonOutput() {
for _, t := range testJsonBase {
s.Run(t.name, func() {
s.logger.WithLevel(t.logType).Msg(t.text)
s.Contains(s.out.String(), t.want)
s.out.Reset()
})
}
}

func (s *loggerTestSuite) TestConsoleOutput() {
s.logger = s.logger.Output(zerolog.ConsoleWriter{Out: s.out, NoColor: true, TimeFormat: "03:04:05"})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of configuring the console output here explicitly for the test, wouldn't it be better to have an additional choice to generate JSON output? Just like you did now for GH.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean console output? JSON is the default. Do you want something like SetConsoleOutput?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zerolog's default is JSON, but our global logger is configured to use the console writer. So for users, currently, there's only the option of either console or GH optimised output. I don't see the point in testing JSON output if we don't offer it to users.

for _, t := range testConsoleBase {
s.Run(t.name, func() {
s.logger.WithLevel(t.logType).Msg(t.text)
s.Contains(s.out.String(), t.want)
s.out.Reset()
})
}
}

//s.log = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "03:04:05"}).With().Caller().Logger()
fzipi marked this conversation as resolved.
Show resolved Hide resolved

func (s *loggerTestSuite) TestSetGithubOutput() {
// send logs to buffer
fzipi marked this conversation as resolved.
Show resolved Hide resolved
logger := SetGithubOutput(s.out)
for _, t := range testGithub {
s.Run(t.name, func() {
logger.WithLevel(t.logType).Msg(t.text)
s.Contains(s.out.String(), t.want)
s.out.Reset()
})
}
}
Loading