-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: master
Are you sure you want to change the base?
Implement Easy Logging using a config file #747
Conversation
tests/test_unit_logger.c
Outdated
@@ -25,6 +28,47 @@ void test_log_str_to_level(void **unused) { | |||
} | |||
|
|||
#ifndef _WIN32 | |||
/** |
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
{ | ||
#if defined(WIN32) || defined(_WIN64) | ||
// 4. Try user home dir | ||
std::string homeDir = sf_getenv_s("USERPROFILE", envbuf, sizeof(envbuf)); |
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
|
||
// Private ===================================================================== | ||
//////////////////////////////////////////////////////////////////////////////// | ||
bool ClientConfigParser::checkFileExists(const std::string& in_filePath) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 = "" }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
cpp/lib/ClientConfigParser.cpp
Outdated
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void load_client_config( |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
FILE* configFile; | ||
try | ||
{ | ||
configFile = sf_fopen(&configFile, in_filePath.c_str(), "r"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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))) |
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
CXX_LOG_INFO("Using client configuration path from an environment variable: %s", clientConfigEnv); | ||
return clientConfigEnv; | ||
} | ||
else |
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
throw ClientConfigException(errMsg.c_str()); | ||
} | ||
} | ||
catch (std::exception& e) |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
throw ClientConfigException(errMsg.c_str()); | ||
} | ||
} | ||
catch (std::exception& e) |
There was a problem hiding this comment.
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.
cpp/lib/ClientConfigParser.cpp
Outdated
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid using exceptions (https://google.github.io/styleguide/cppguide.html#Exceptions)
cpp/lib/ClientConfigParser.cpp
Outdated
} 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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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;
cpp/lib/ClientConfigParser.cpp
Outdated
sf_strcpy(out_clientConfig.logPath, logPathSize, logPath); | ||
} | ||
} | ||
return true; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
cpp/lib/ClientConfigParser.cpp
Outdated
return false; | ||
} | ||
|
||
value commonProps = jsonConfig.get("common"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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\"}"; |
There was a problem hiding this comment.
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. ?
There was a problem hiding this comment.
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
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
cpp/lib/ClientConfigParser.cpp
Outdated
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
sf_bool EasyLoggingConfigParser::loadClientConfig( | ||
const std::string& in_configFilePath, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
tests/test_unit_logger.c
Outdated
return cmocka_run_group_tests(tests, NULL, NULL); | ||
} | ||
/* | ||
* Copyright (c) 2018-2019 Snowflake Computing, Inc. All rights reserved. |
There was a problem hiding this comment.
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 ;)
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