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

Implement Easy Logging using a config file #747

Open
wants to merge 86 commits into
base: master
Choose a base branch
from

Conversation

sfc-gh-ext-simba-nl
Copy link
Collaborator

@sfc-gh-ext-simba-nl sfc-gh-ext-simba-nl commented Oct 2, 2024

For SNOW-981602: Implement Easy Logging using a config file

Add several new global attributes:
SF_GLOBAL_CLIENT_CONFIG_FILE: To set the client config file so libsnowflakeclient knows where to look for the config file
SF_GLOBAL_LOG_LEVEL: To allow the user to get the log level since libsnowflakeclient handles log setup
SF_GLOBAL_LOG_PATH: To allow the user to get the log path

@sfc-gh-ext-simba-nl sfc-gh-ext-simba-nl marked this pull request as ready for review October 11, 2024 16:43
@sfc-gh-ext-simba-nl sfc-gh-ext-simba-nl requested a review from a team as a code owner October 11, 2024 16:43
@@ -25,6 +28,47 @@ void test_log_str_to_level(void **unused) {
}

#ifndef _WIN32
/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a test case to cover the behavior in log_init(), e.g. having a config file in place, after calling snowflake_global_init() check the log settings are as expected.

{
#if defined(WIN32) || defined(_WIN64)
// 4. Try user home dir
std::string homeDir = sf_getenv_s("USERPROFILE", envbuf, sizeof(envbuf));
Copy link
Collaborator

Choose a reason for hiding this comment

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

sf_getenv_s could return NULL and cause runtime exception thrown.


// Private =====================================================================
////////////////////////////////////////////////////////////////////////////////
bool ClientConfigParser::checkFileExists(const std::string& in_filePath)
Copy link
Collaborator

@sfc-gh-ext-simba-hx sfc-gh-ext-simba-hx Oct 11, 2024

Choose a reason for hiding this comment

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

We are on c++17 already maybe we could use std::filesystem::is_regular_file() directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

std::filesystem isn't available on osx10.14 and boost has a debug assertion on 32-bit windows with MT build

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we resolve debug assertion issue with 32-bit windows build and use boost filesystem

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think using boost filesystem was able to fix the issue

lib/client.c Outdated
char client_config_file[MAX_PATH] = {0};
snowflake_global_get_attribute(
SF_GLOBAL_CLIENT_CONFIG_FILE, client_config_file, sizeof(client_config_file));
client_config clientConfig = { .logLevel = "", .logPath = "" };
Copy link
Collaborator

Choose a reason for hiding this comment

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

memory leak currently with the buffer allocated in load_client_config().
When you try to free the buffer, initialize with const string could be a problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think if client config is not initialized .logLevel and .logPath should be null

}

////////////////////////////////////////////////////////////////////////////////
void load_client_config(
Copy link
Collaborator

@sfc-gh-ext-simba-hx sfc-gh-ext-simba-hx Oct 11, 2024

Choose a reason for hiding this comment

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

Seems we are throwing exception from loadClientConfig(). We need to catch that and return error. C interface doesn't expect exception.
Please add error test cases as well this will be caught if we have error test cases.

lib/client.c Outdated
@@ -36,6 +37,8 @@ sf_bool DEBUG;
sf_bool SF_OCSP_CHECK;
char *SF_HEADER_USER_AGENT = NULL;

static char *CLIENT_CONFIG_FILE = NULL;
static char* LOG_LEVEL;
Copy link
Collaborator

@sfc-gh-ext-simba-hx sfc-gh-ext-simba-hx Oct 11, 2024

Choose a reason for hiding this comment

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

why not to set NULL as others? Better not to add LOG_LEVEL as we've had number loglevel maintained with log_set_level() and log_get_level() already. Having another one could cause mismatch.

lib/client.c Outdated
case SF_GLOBAL_CLIENT_CONFIG_FILE:
alloc_buffer_and_copy(&CLIENT_CONFIG_FILE, value);
break;
case SF_GLOBAL_LOG_LEVEL:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would prefer SF_GLOBAL_LOG_PATH and SF_GLOBAL_LOG_LEVEL to be read only attributes since application already have enough ways to do log settings.

SF_GLOBAL_OCSP_CHECK
SF_GLOBAL_OCSP_CHECK,
SF_GLOBAL_CLIENT_CONFIG_FILE,
SF_GLOBAL_LOG_LEVEL,
Copy link
Collaborator

@sfc-gh-ext-simba-hx sfc-gh-ext-simba-hx Oct 11, 2024

Choose a reason for hiding this comment

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

remind me why we need SF_GLOBAL_LOG_LEVEL and SF_GLOBAL_LOG_PATH?
Please having a brief note of the interface and usage in either the ticket or the PR description. That could help understanding the implementation and later for documentation as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated description

cpp/lib/ClientConfigParser.cpp Outdated Show resolved Hide resolved
cpp/lib/ClientConfigParser.cpp Outdated Show resolved Hide resolved
FILE* configFile;
try
{
configFile = sf_fopen(&configFile, in_filePath.c_str(), "r");
Copy link
Contributor

Choose a reason for hiding this comment

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

Use std::fstream and std::filesystem APIs?

Copy link
Collaborator Author

@sfc-gh-ext-simba-nl sfc-gh-ext-simba-nl Oct 17, 2024

Choose a reason for hiding this comment

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

Unfortunately std::filesystem isn't in osx10.14 yet so it can't be used and there seems to be a 32-bit windows debug assertion issue with reading file with fstream when built with MT

Copy link
Contributor

Choose a reason for hiding this comment

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

That means we cannot use std::fstream at all? Can you show me the assertion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not exactly, it only happens for Windows 32-bit built with MT. All other configurations seem to work fine. If you prefer, I can ifdef it to not run when it's windows debug and it should work fine. The assertion says something like this:
Debug Assertion Failed!

File: minkernel\crts\ucrt\src\appcrt\lowio\read.cpp

Expression: _osfile(fh) & FOPEN

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this local issue? Does it appear on CI? I think you might be building the project incorrectly. See: https://stackoverflow.com/questions/5984144/assertion-error-in-crt-calling-osfile-in-vs-2008/6010756#6010756

Copy link
Contributor

Choose a reason for hiding this comment

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

I am using fstream and boost filesystem on my PR without issues

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm still running into this issue with the tests run on jenkins. Basically the test run just hangs after it hits the assertion error. In the mean time, I've added an ifdef to to run easy logging when in 32-bit debug. I've locally tested running on /MDd and it runs fine, but currently I think we build with dynamic runtime set to OFF

cpp/lib/ClientConfigParser.cpp Outdated Show resolved Hide resolved
CXX_LOG_INFO("Using client configuration path from a connection string: %s", in_configFilePath.c_str());
return in_configFilePath;
}
else if (const char* clientConfigEnv = sf_getenv_s(SF_CLIENT_CONFIG_ENV_NAME.c_str(), envbuf, sizeof(envbuf)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Else no longer needed when we do an early return.

CXX_LOG_INFO("Using client configuration path from an environment variable: %s", clientConfigEnv);
return clientConfigEnv;
}
else
Copy link
Contributor

Choose a reason for hiding this comment

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

Else no longer needed when we do an early return.

throw ClientConfigException(errMsg.c_str());
}
}
catch (std::exception& e)
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need to explicitly close the file. It will close automatically when destructor of ifstream is called:

Copy link
Contributor

Choose a reason for hiding this comment

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

Right now we catch and rethrow the exception, it makes no sense.

throw ClientConfigException(errMsg.c_str());
}
}
catch (std::exception& e)
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now we catch and rethrow the exception, it makes no sense.

CXX_LOG_INFO("Could not open a file. The file may not exist: %s",
in_filePath.c_str());
std::string errMsg = "Error finding client configuration file: " + in_filePath;
throw ClientConfigException(errMsg.c_str());
Copy link
Contributor

Choose a reason for hiding this comment

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

} else {
// USERPROFILE is empty, try HOMEDRIVE and HOMEPATH
char* homeDriveEnv = sf_getenv_s("HOMEDRIVE", envbuf, sizeof(envbuf));
char* homePathEnv = sf_getenv_s("HOMEPATH", envbuf, sizeof(envbuf));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we overwrite homePathEnv here. envbuf is used twice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add tests for resolveHomeDirConfigPath

Copy link
Contributor

@sfc-gh-jszczerbinski sfc-gh-jszczerbinski left a comment

Choose a reason for hiding this comment

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

In general looks good to me. We need to log and return false if contents of of the config are malformed;

sf_strcpy(out_clientConfig.logPath, logPathSize, logPath);
}
}
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume we should return true iff we successfully parsed the config, but if json doesn't have expected format (doesn't define log_level, "common" field is not an object etc.) we still return true.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The reason it still returns true is because if the user passes the log level but not log path to log_init() and the config file only has log path, we would still like the logger to log to the log path set in the config (and vice versa).

return false;
}

value commonProps = jsonConfig.get("common");
Copy link
Contributor

Choose a reason for hiding this comment

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

What if "common" does not exist in object or top-level is not an object?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added checks before and after

* Tests log settings from client config file with invalid json
*/
void test_client_config_log_invalid_json(void** unused) {
char clientConfigJSON[] = "{{{\"invalid json\"}";
Copy link
Contributor

Choose a reason for hiding this comment

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

What about tests when we have configs like [], { "log_level": "warn" } etc. ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added several new test cases for this

Copy link
Contributor

@sfc-gh-jszczerbinski sfc-gh-jszczerbinski left a comment

Choose a reason for hiding this comment

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

In general looks good to me.

client_config* out_clientConfig)
{
// Disable easy logging for 32-bit windows debug build due to linking issues
// with _osfile causing hanging/assertions until dynamic linking is available
Copy link
Collaborator

Choose a reason for hiding this comment

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

does it mean that with static library the EasyLogging feature is not available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Correct, but only with 32-bit windows debug build, all other configurations/platforms/bitness work fine


////////////////////////////////////////////////////////////////////////////////
sf_bool EasyLoggingConfigParser::loadClientConfig(
const std::string& in_configFilePath,
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need in_ out_ prefixes?

return clientConfigEnv;
}

// 3. Try DLL binary dir
Copy link
Collaborator

Choose a reason for hiding this comment

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

for not DLL can we just skip the check?
or maybe we should skip checking this location at all - we should more look in the place from which the user application was running, not the dll location?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was just ported from ODBC. Is this something we should change for libsnowflakeclient?

return cmocka_run_group_tests(tests, NULL, NULL);
}
/*
* Copyright (c) 2018-2019 Snowflake Computing, Inc. All rights reserved.
Copy link
Collaborator

Choose a reason for hiding this comment

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

2024 or 2025 if it will be merged next year ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants