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 config root
type ClientConfig struct {
Common *ClientConfigCommonProps `json:"common"`
}

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

func parseClientConfiguration(filePath string) (*ClientConfig, error) {
if filePath == "" {
return nil, nil
}

Check warning on line 37 in client_configuration.go

View check run for this annotation

Codecov / codecov/patch

client_configuration.go#L36-L37

Added lines #L36 - L37 were not covered by tests
fileContents, err := os.ReadFile(filePath)
if err != nil {
return nil, parsingClientConfigError(err)
}

Check warning on line 41 in client_configuration.go

View check run for this annotation

Codecov / codecov/patch

client_configuration.go#L40-L41

Added lines #L40 - L41 were not covered by tests
var clientConfig ClientConfig
err = json.Unmarshal(fileContents, &clientConfig)
if err != nil {
return nil, parsingClientConfigError(err)
}
err = validateClientConfiguration(&clientConfig)
if err != nil {
return nil, parsingClientConfigError(err)
}
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")
}

Check warning on line 61 in client_configuration.go

View check run for this annotation

Codecov / codecov/patch

client_configuration.go#L60-L61

Added lines #L60 - L61 were not covered by tests
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 != "" {
_, 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 "", errors.New("unknown log level: " + logLevelString)
}
}
175 changes: 175 additions & 0 deletions client_configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright (c) 2023 Snowflake Computing Inc. All rights reserved.

package gosnowflake

import (
"fmt"
"os"
"path"
"strings"
"testing"
)

func TestParseConfiguration(t *testing.T) {
dir := t.TempDir()
testCases := []struct {
Name string
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
FileName string
sfc-gh-knozderko marked this conversation as resolved.
Show resolved Hide resolved
FileContents string
ExpectedLogLevel string
ExpectedLogPath string
}{
{
Name: "TestWithLogLevelUpperCase",
FileName: "config_1.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: "INFO",
ExpectedLogPath: "/some-path/some-directory",
},
{
Name: "TestWithLogLevelLowerCase",
FileName: "config_2.json",
FileContents: `{
"common": {
"log_level" : "info",
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedLogLevel: "info",
ExpectedLogPath: "/some-path/some-directory",
},
{
Name: "TestWithMissingValues",
FileName: "config_3.json",
FileContents: `{
"common": {}
}`,
ExpectedLogLevel: "",
ExpectedLogPath: "",
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
fileName := CreateFile(t, tc.FileName, tc.FileContents, dir)

config, err := parseClientConfiguration(fileName)

if err != nil {
t.Fatalf("Error should be nil but was %s", err)
}
if config.Common.LogLevel != tc.ExpectedLogLevel {
t.Errorf("Log level should be %s but was %s", tc.ExpectedLogLevel, config.Common.LogLevel)
}
if config.Common.LogPath != tc.ExpectedLogPath {
t.Errorf("Log path should be %s but was %s", tc.ExpectedLogPath, config.Common.LogPath)
}
})
}
}

func TestParseAllLogLevels(t *testing.T) {
dir := t.TempDir()
for _, logLevel := range []string{"OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"} {
t.Run(logLevel, func(t *testing.T) {
fileContents := fmt.Sprintf(`{
"common": {
"log_level" : "%s",
"log_path" : "/some-path/some-directory"
}
}`, logLevel)
fileName := CreateFile(t, fmt.Sprintf("config_%s.json", logLevel), fileContents, dir)

config, err := parseClientConfiguration(fileName)

if err != nil {
t.Fatalf("Error should be nil but was: %s", err)
}
if config.Common.LogLevel != logLevel {
t.Errorf("Log level should be %s but was %s", logLevel, config.Common.LogLevel)
}
})
}
}

func TestParseConfigurationFails(t *testing.T) {
dir := t.TempDir()
testCases := []struct {
Name string
FileName string
FileContents string
ExpectedErrorMessageToContain string
}{
{
Name: "TestWithWrongLogLevel",
FileName: "config_1.json",
FileContents: `{
"common": {
"log_level" : "something weird",
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedErrorMessageToContain: "unknown log level",
},
{
Name: "TestWithWrongTypeOfLogLevel",
FileName: "config_2.json",
FileContents: `{
"common": {
"log_level" : 15,
"log_path" : "/some-path/some-directory"
}
}`,
ExpectedErrorMessageToContain: "ClientConfigCommonProps.common.log_level",
},
{
Name: "TestWithWrongTypeOfLogPath",
FileName: "config_3.json",
FileContents: `{
"common": {
"log_level" : "INFO",
"log_path" : true
}
}`,
ExpectedErrorMessageToContain: "ClientConfigCommonProps.common.log_path",
},
{
Name: "TestWithoutCommon",
FileName: "config_4.json",
FileContents: "{}",
ExpectedErrorMessageToContain: "common section in client config not found",
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
fileName := CreateFile(t, tc.FileName, tc.FileContents, dir)

_, err := parseClientConfiguration(fileName)

if err == nil {
t.Fatal("Error should not be nil but was nil")
}
errMessage := fmt.Sprint(err)
expectedPrefix := "parsing client config failed"
if !strings.HasPrefix(errMessage, expectedPrefix) {
t.Errorf("Error message: \"%s\" should start with prefix: \"%s\"", errMessage, expectedPrefix)
}
if !strings.Contains(errMessage, tc.ExpectedErrorMessageToContain) {
t.Errorf("Error message: \"%s\" should contain given phrase: \"%s\"", errMessage, tc.ExpectedErrorMessageToContain)
}
})
}
}

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.Fatal("Could not create file")
}
return fullFileName
}
Loading