-
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?
Changes from 75 commits
7ab80fb
836a82a
c1878f3
21673d4
806fb9c
4a5ad67
4e05bc7
a79370d
be865c1
f90848d
3823568
a863b05
994228b
70bdc60
309d118
65e29f1
21a3b11
8d97f3a
577925a
6a27fe5
fdb5187
2d66b20
e480074
39d1036
1ee5015
f162232
04993e8
0f494ee
0aaea59
6bc1b92
3c4f610
e3e1b88
4b2cc16
81bf6fb
96b9b87
8ce9da6
9fcc790
1f778c0
17e1d10
5c90290
ac79374
99daf14
16fd3bf
2a80a5e
47334d0
3e80253
0d642b2
c89424a
a742893
e16a67c
05331fb
f370f9b
d0cbc0c
e020429
d351866
5cc39ae
75eb366
8651788
48d562f
79128b4
98c4924
9d54bab
a5549aa
169063c
2c285bf
9ede47a
bc61cd7
9eaadd0
6c3aa65
c022028
b03491a
0d2e977
36faf07
be2a7b3
d6a00fe
726b897
a6e6ca0
301d333
21f7b13
31001f3
905bc6d
1d2512c
358b310
c220e29
44786f6
6a53849
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
/** | ||
* Copyright (c) 2024 Snowflake Computing | ||
*/ | ||
|
||
#include "snowflake/ClientConfigParser.hpp" | ||
#include "snowflake/Exceptions.hpp" | ||
#include "snowflake/client_config_parser.h" | ||
#include "../logger/SFLogger.hpp" | ||
#include "memory.h" | ||
|
||
#include <fstream> | ||
#include <sstream> | ||
#include <set> | ||
|
||
#undef snprintf | ||
#include <boost/filesystem.hpp> | ||
|
||
#ifndef _WIN32 | ||
#include <dlfcn.h> | ||
#endif | ||
|
||
using namespace Snowflake::Client; | ||
using namespace picojson; | ||
|
||
namespace | ||
{ | ||
// constants | ||
const std::string SF_CLIENT_CONFIG_FILE_NAME("sf_client_config.json"); | ||
const std::string SF_CLIENT_CONFIG_ENV_NAME("SF_CLIENT_CONFIG_FILE"); | ||
std::set<std::string> KnownCommonEntries = {"log_level", "log_path"}; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
sf_bool load_client_config( | ||
const char* in_configFilePath, | ||
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 | ||
#if !defined(_WIN32) && !defined(_DEBUG) | ||
try { | ||
EasyLoggingConfigParser configParser; | ||
configParser.loadClientConfig(in_configFilePath, *out_clientConfig); | ||
} catch (std::exception e) { | ||
CXX_LOG_ERROR("Error loading client configuration: %s", e.what()); | ||
return false; | ||
} | ||
#endif | ||
return true; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void free_client_config(client_config* clientConfig) | ||
{ | ||
if (clientConfig->logLevel != NULL) | ||
{ | ||
SF_FREE(clientConfig->logLevel); | ||
} | ||
if (clientConfig->logPath != NULL) | ||
{ | ||
SF_FREE(clientConfig->logPath); | ||
} | ||
} | ||
|
||
// Public ====================================================================== | ||
//////////////////////////////////////////////////////////////////////////////// | ||
EasyLoggingConfigParser::EasyLoggingConfigParser() | ||
{ | ||
; // Do nothing. | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
EasyLoggingConfigParser::~EasyLoggingConfigParser() | ||
{ | ||
; // Do nothing. | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void EasyLoggingConfigParser::loadClientConfig( | ||
const std::string& in_configFilePath, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need in_ out_ prefixes? |
||
client_config& out_clientConfig) | ||
{ | ||
std::string derivedConfigPath = resolveClientConfigPath(in_configFilePath); | ||
|
||
if (!derivedConfigPath.empty()) | ||
{ | ||
parseConfigFile(derivedConfigPath, out_clientConfig); | ||
} | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
std::string EasyLoggingConfigParser::resolveClientConfigPath( | ||
const std::string& in_configFilePath) | ||
{ | ||
char envbuf[MAX_PATH + 1]; | ||
if (!in_configFilePath.empty()) | ||
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved
Hide resolved
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// 1. Try config file if it was passed in | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Else no longer needed when we do an early return. |
||
{ | ||
// 2. Try environment variable SF_CLIENT_CONFIG_ENV_NAME | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Else no longer needed when we do an early return. |
||
{ | ||
// 3. Try DLL binary dir | ||
std::string binaryDir = getBinaryPath(); | ||
std::string binaryDirFilePath = binaryDir + SF_CLIENT_CONFIG_FILE_NAME; | ||
if (boost::filesystem::is_regular_file(binaryDirFilePath)) | ||
{ | ||
CXX_LOG_INFO("Using client configuration path from binary directory: %s", binaryDirFilePath.c_str()); | ||
return binaryDirFilePath; | ||
} | ||
else | ||
{ | ||
// 4. Try user home dir | ||
return resolveHomeDirConfigPath(); | ||
} | ||
} | ||
} | ||
|
||
// Private ===================================================================== | ||
//////////////////////////////////////////////////////////////////////////////// | ||
std::string EasyLoggingConfigParser::resolveHomeDirConfigPath() | ||
{ | ||
char envbuf[MAX_PATH + 1]; | ||
#if defined(WIN32) || defined(_WIN64) | ||
std::string homeDirFilePath; | ||
char* homeDir; | ||
if ((homeDir = sf_getenv_s("USERPROFILE", envbuf, sizeof(envbuf))) && strlen(homeDir) != 0) | ||
{ | ||
homeDirFilePath = homeDir + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME; | ||
} 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 commentThe reason will be displayed to describe this comment to others. Learn more. I think we overwrite homePathEnv here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add tests for |
||
if (homeDriveEnv && strlen(homeDriveEnv) != 0 && homePathEnv && strlen(homePathEnv) != 0) | ||
{ | ||
homeDirFilePath = std::string(homeDriveEnv) + homePathEnv + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME; | ||
} | ||
} | ||
if (boost::filesystem::is_regular_file(homeDirFilePath)) | ||
{ | ||
CXX_LOG_INFO("Using client configuration path from home directory: %s", homeDirFilePath.c_str()); | ||
return homeDirFilePath; | ||
} | ||
#else | ||
char* homeDir; | ||
if ((homeDir = sf_getenv_s("HOME", envbuf, sizeof(envbuf))) && (strlen(homeDir) != 0)) | ||
{ | ||
std::string homeDirFilePath = std::string(homeDir) + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME; | ||
if (boost::filesystem::is_regular_file(homeDirFilePath)) | ||
{ | ||
CXX_LOG_INFO("Using client configuration path from home directory: %s", homeDirFilePath.c_str()); | ||
return homeDirFilePath; | ||
} | ||
} | ||
#endif | ||
return ""; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void EasyLoggingConfigParser::parseConfigFile( | ||
const std::string& in_filePath, | ||
client_config& out_clientConfig) | ||
{ | ||
value jsonConfig; | ||
std::string err; | ||
std::ifstream configFile; | ||
try | ||
{ | ||
configFile.open(in_filePath, std::fstream::in | std::ios::binary); | ||
if (!configFile) | ||
{ | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Please avoid using exceptions (https://google.github.io/styleguide/cppguide.html#Exceptions) |
||
} | ||
#if !defined(WIN32) && !defined(_WIN64) | ||
checkIfValidPermissions(in_filePath); | ||
#endif | ||
err = parse(jsonConfig, configFile); | ||
|
||
if (!err.empty()) | ||
{ | ||
CXX_LOG_ERROR("Error in parsing JSON: %s, err: %s", in_filePath.c_str(), err.c_str()); | ||
std::string errMsg = "Error parsing client configuration file: " + in_filePath; | ||
throw ClientConfigException(errMsg.c_str()); | ||
} | ||
} | ||
catch (std::exception& e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Right now we catch and rethrow the exception, it makes no sense. |
||
{ | ||
configFile.close(); | ||
throw; | ||
} | ||
|
||
value commonProps = jsonConfig.get("common"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Added checks before and after |
||
checkUnknownEntries(commonProps); | ||
if (commonProps.is<object>()) | ||
{ | ||
if (commonProps.contains("log_level") && commonProps.get("log_level").is<std::string>()) | ||
{ | ||
const char* logLevel = commonProps.get("log_level").get<std::string>().c_str(); | ||
size_t logLevelSize = strlen(logLevel) + 1; | ||
out_clientConfig.logLevel = (char*)SF_CALLOC(1, logLevelSize); | ||
sf_strcpy(out_clientConfig.logLevel, logLevelSize, logLevel); | ||
} | ||
if (commonProps.contains("log_path") && commonProps.get("log_path").is<std::string>()) | ||
{ | ||
const char* logPath = commonProps.get("log_path").get<std::string>().c_str(); | ||
size_t logPathSize = strlen(logPath) + 1; | ||
out_clientConfig.logPath = (char*)SF_CALLOC(1, logPathSize); | ||
sf_strcpy(out_clientConfig.logPath, logPathSize, logPath); | ||
} | ||
} | ||
configFile.close(); | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void EasyLoggingConfigParser::checkIfValidPermissions(const std::string& in_filePath) | ||
{ | ||
boost::filesystem::file_status fileStatus = boost::filesystem::status(in_filePath); | ||
sfc-gh-jszczerbinski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
boost::filesystem::perms permissions = fileStatus.permissions(); | ||
if (permissions & boost::filesystem::group_write || | ||
permissions & boost::filesystem::others_write) | ||
{ | ||
CXX_LOG_ERROR("Error due to other users having permission to modify the config file: %s", | ||
in_filePath.c_str()); | ||
std::string errMsg = "Error due to other users having permission to modify the config file: " + in_filePath; | ||
throw ClientConfigException(errMsg.c_str()); | ||
} | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void EasyLoggingConfigParser::checkUnknownEntries(value& in_config) | ||
{ | ||
if (in_config.is<object>()) | ||
{ | ||
const value::object& configObj = in_config.get<object>(); | ||
for (value::object::const_iterator i = configObj.begin(); i != configObj.end(); ++i) | ||
{ | ||
std::string key = i->first; | ||
bool found = false; | ||
for (std::string knownEntry : KnownCommonEntries) | ||
{ | ||
if (sf_strncasecmp(key.c_str(), knownEntry.c_str(), knownEntry.length()) == 0) | ||
{ | ||
found = true; | ||
} | ||
} | ||
if (!found) | ||
{ | ||
std::string warnMsg = | ||
"Unknown configuration entry: " + key + " with value:" + i->second.to_str().c_str(); | ||
CXX_LOG_WARN(warnMsg.c_str()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
std::string EasyLoggingConfigParser::getBinaryPath() | ||
{ | ||
std::string binaryFullPath; | ||
#if defined(WIN32) || defined(_WIN64) | ||
std::wstring path; | ||
HMODULE hm = NULL; | ||
wchar_t appName[256]; | ||
GetModuleFileNameW(hm, appName, 256); | ||
path = appName; | ||
binaryFullPath = std::string(path.begin(), path.end()); | ||
#else | ||
Dl_info info; | ||
int result = dladdr((void*)load_client_config, &info); | ||
if (result) | ||
{ | ||
binaryFullPath = std::string(info.dli_fname); | ||
} | ||
#endif | ||
size_t pos = binaryFullPath.find_last_of(PATH_SEP); | ||
if (pos == std::string::npos) | ||
{ | ||
return ""; | ||
} | ||
std::string binaryPath = binaryFullPath.substr(0, pos + 1); | ||
return binaryPath; | ||
} |
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