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

SNOW-856228 easy logging parser #924

Merged
merged 11 commits into from
Oct 13, 2023
87 changes: 87 additions & 0 deletions client_configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.

package gosnowflake

import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"
)

// log levels for easy logging
const (
Off string = "OFF" // log level for logging switched off
sfc-gh-pfus marked this conversation as resolved.
Show resolved Hide resolved
sfc-gh-pfus marked this conversation as resolved.
Show resolved Hide resolved
Error string = "ERROR" // error log level
Warn string = "WARN" // warn log level
Info string = "INFO" // info log level
Debug string = "DEBUG" // debug log level
Trace string = "TRACE" // trace log level
)

// ClientConfig properties root
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
type ClientConfig struct {
Common *ClientConfigCommonProps `json:"common"`
}

// ClientConfigCommonProps properties from "common" section
type ClientConfigCommonProps struct {
LogLevel *string `json:"log_level"`
LogPath *string `json:"log_path"`
}

func parseClientConfiguration(filePath string) (*ClientConfig, error) {
if filePath == "" {
return nil, nil
}
fileContents, readError := os.ReadFile(filePath)
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
if readError != nil {
return nil, parsingClientConfigError(readError)
}
var clientConfig ClientConfig
parseError := json.Unmarshal(fileContents, &clientConfig)
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
if parseError != nil {
return nil, parsingClientConfigError(parseError)
}
validateError := validateClientConfiguration(&clientConfig)
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
if validateError != nil {
return nil, parsingClientConfigError(validateError)
}
return &clientConfig, nil
}

func parsingClientConfigError(err error) error {
return fmt.Errorf("parsing client config failed: %w", err)
}

func validateClientConfiguration(clientConfig *ClientConfig) error {
if clientConfig == nil {
return errors.New("client config not found")
}
if clientConfig.Common == nil {
return errors.New("common section in client config not found")
}
return validateLogLevel(clientConfig)
}

func validateLogLevel(clientConfig *ClientConfig) error {
var logLevel = clientConfig.Common.LogLevel
if logLevel != nil && *logLevel != "" {
_, error := toLogLevel(*logLevel)
if error != nil {
return error
}
}
return nil
}

func toLogLevel(logLevelString string) (*string, error) {
var logLevel = strings.ToUpper(logLevelString)
switch logLevel {
case Off, Error, Warn, Info, Debug, Trace:
return &logLevel, nil
default:
return nil, errors.New("unknown log level: " + logLevelString)
}
}
164 changes: 164 additions & 0 deletions client_configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.

package gosnowflake

import (
"fmt"
"github.com/stretchr/testify/assert"
"os"
"path"
"strings"
"testing"
)

type PositiveTestCase struct {
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
Name string
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
FileName string
FileContents string
ExpectedLogLevel *string
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
ExpectedLogPath *string
}

type NegativeTestCase struct {
Name string
FileName string
FileContents string
ExpectedErrorMessageToContain string
}

func TestThatParsesConfiguration(t *testing.T) {
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
// given
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
dir := CreateTempDirectory(t, "conf_parse_tests_")
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
testCases := []PositiveTestCase{
{
Name: "TestWithLogLevelUpperCase",
FileName: "config.json",
FileContents: `{
"common": {
"log_level" : "INFO",
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedLogLevel: toStringPointer("INFO"),
ExpectedLogPath: toStringPointer("/some-path/some-directory"),
},
{
Name: "TestWithLogLevelLowerCase",
FileName: "config.json",
FileContents: `{
"common": {
"log_level" : "info",
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedLogLevel: toStringPointer("info"),
ExpectedLogPath: toStringPointer("/some-path/some-directory"),
},
{
Name: "TestWithMissingValues",
FileName: "config.json",
FileContents: `{
"common": {}
}`,
ExpectedLogLevel: nil,
ExpectedLogPath: nil,
},
}
for _, testCase := range testCases {
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
t.Run(testCase.Name, func(t *testing.T) {
fileName := CreateFile(t, testCase.FileName, testCase.FileContents, dir)

// when
config, err := parseClientConfiguration(fileName)

// then
assert := assert.New(t)
assert.Equal(nil, err, "Error should be nil")
assert.Equal(testCase.ExpectedLogLevel, config.Common.LogLevel, "Log level should be as expected")
assert.Equal(testCase.ExpectedLogPath, config.Common.LogPath, "Log path should be as expected")
})
}
}

func TestThatFailsToParse(t *testing.T) {
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
// given
dir := CreateTempDirectory(t, "conf_negative_parse_tests_")
testCases := []NegativeTestCase{
{
Name: "TestWithWrongLogLevel",
FileName: "config.json",
FileContents: `{
"common": {
"log_level" : "something weird",
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedErrorMessageToContain: "unknown log level",
},
{
Name: "TestWithWrongTypeOfLogLevel",
FileName: "config.json",
FileContents: `{
"common": {
"log_level" : 15,
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedErrorMessageToContain: "ClientConfigCommonProps.Common.log_level",
},
{
Name: "TestWithWrongTypeOfLogPath",
FileName: "config.json",
FileContents: `{
"common": {
"log_level" : "INFO",
"log_path" : true
}
}`,
ExpectedErrorMessageToContain: "ClientConfigCommonProps.Common.log_path",
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
fileName := CreateFile(t, testCase.FileName, testCase.FileContents, dir)

// when
_, err := parseClientConfiguration(fileName)

// then
assert := assert.New(t)
assert.Equal(err != nil, true, "Error should not be nil")
errMessage := fmt.Sprint(err)
expectedPrefix := "parsing client config failed"
assert.Equal(strings.HasPrefix(errMessage, expectedPrefix), true,
fmt.Sprintf("Error message: \"%s\" should start with prefix: \"%s\"", errMessage, expectedPrefix))
assert.Equal(strings.Contains(errMessage, testCase.ExpectedErrorMessageToContain), true,
fmt.Sprintf("Error message: \"%s\" should contain given phrase: \"%s\"", errMessage, testCase.ExpectedErrorMessageToContain))
})
}
}

func toStringPointer(value string) *string {
var copyOfValue = value
return &copyOfValue
}

func CreateFile(t *testing.T, fileName string, fileContents string, directory string) string {
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
fullFileName := path.Join(directory, fileName)
writeErr := os.WriteFile(fullFileName, []byte(fileContents), 0644)
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
if writeErr != nil {
t.Error("Could not create file")
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
}
return fullFileName
}

func CreateTempDirectory(t *testing.T, dirPattern string) string {
dir, dirErr := os.MkdirTemp(os.TempDir(), dirPattern)
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
if dirErr != nil {
t.Error("Failed to create test directory")
}
t.Cleanup(func() {
os.RemoveAll(dir)
})
return dir
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.2
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
golang.org/x/crypto v0.7.0
)

Expand All @@ -34,6 +35,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
Expand All @@ -50,7 +52,7 @@ require (
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/mod v0.8.0 // indirect
Expand All @@ -61,4 +63,5 @@ require (
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading