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 (
levelOff string = "OFF" // log level for logging switched off
levelError string = "ERROR" // error log level
levelWarn string = "WARN" // warn log level
levelInfo string = "INFO" // info log level
levelDebug string = "DEBUG" // debug log level
levelTrace 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 levelOff, levelError, levelWarn, levelInfo, levelDebug, levelTrace:
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 {
testName string
fileName string
fileContents string
expectedLogLevel string
expectedLogPath string
}{
{
testName: "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",
},
{
testName: "TestWithLogLevelLowerCase",
fileName: "config_2.json",
fileContents: `{
"common": {
"log_level" : "info",
"log_path" : "/some-path/some-directory"
}
}`,
expectedLogLevel: "info",
expectedLogPath: "/some-path/some-directory",
},
{
testName: "TestWithMissingValues",
fileName: "config_3.json",
fileContents: `{
"common": {}
}`,
expectedLogLevel: "",
expectedLogPath: "",
},
}
for _, tc := range testCases {
t.Run(tc.testName, 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 {
testName string
fileName string
FileContents string
expectedErrorMessageToContain string
}{
{
testName: "TestWithWrongLogLevel",
fileName: "config_1.json",
FileContents: `{
"common": {
"log_level" : "something weird",
"log_path" : "/some-path/some-directory"
}
}`,
expectedErrorMessageToContain: "unknown log level",
},
{
testName: "TestWithWrongTypeOfLogLevel",
fileName: "config_2.json",
FileContents: `{
"common": {
"log_level" : 15,
"log_path" : "/some-path/some-directory"
}
}`,
expectedErrorMessageToContain: "ClientConfigCommonProps.common.log_level",
},
{
testName: "TestWithWrongTypeOfLogPath",
fileName: "config_3.json",
FileContents: `{
"common": {
"log_level" : "INFO",
"log_path" : true
}
}`,
expectedErrorMessageToContain: "ClientConfigCommonProps.common.log_path",
},
{
testName: "TestWithoutCommon",
fileName: "config_4.json",
FileContents: "{}",
expectedErrorMessageToContain: "common section in client config not found",
},
}
for _, tc := range testCases {
t.Run(tc.testName, 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 {
fullFileName := path.Join(directory, fileName)
err := os.WriteFile(fullFileName, []byte(fileContents), 0644)
if err != nil {
t.Fatal("Could not create file")
}
return fullFileName
}
Loading