-
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 all 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,286 @@ | ||
/** | ||
* 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* configFilePath, | ||
client_config* 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)) || defined(_WIN64) | ||
EasyLoggingConfigParser configParser; | ||
return configParser.loadClientConfig(configFilePath, *clientConfig); | ||
#else | ||
return true; | ||
#endif | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
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. | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
sf_bool EasyLoggingConfigParser::loadClientConfig( | ||
const std::string& configFilePath, | ||
client_config& clientConfig) | ||
{ | ||
std::string derivedConfigPath = resolveClientConfigPath(configFilePath); | ||
|
||
if (!derivedConfigPath.empty()) | ||
{ | ||
return parseConfigFile(derivedConfigPath, clientConfig); | ||
} | ||
return false; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
std::string EasyLoggingConfigParser::resolveClientConfigPath( | ||
const std::string& configFilePath) | ||
{ | ||
char envbuf[MAX_PATH + 1]; | ||
|
||
// 1. Try config file if it was passed in | ||
if (!configFilePath.empty()) | ||
{ | ||
CXX_LOG_INFO("Using client configuration path from a connection string: %s", configFilePath.c_str()); | ||
return configFilePath; | ||
} | ||
|
||
// 2. Try environment variable SF_CLIENT_CONFIG_ENV_NAME | ||
if (const char* clientConfigEnv = sf_getenv_s(SF_CLIENT_CONFIG_ENV_NAME.c_str(), envbuf, sizeof(envbuf))) | ||
{ | ||
CXX_LOG_INFO("Using client configuration path from an environment variable: %s", clientConfigEnv); | ||
return clientConfigEnv; | ||
} | ||
|
||
// 3. Try DLL binary dir | ||
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. for not DLL can we just skip the check? 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. This was just ported from ODBC. Is this something we should change for libsnowflakeclient? |
||
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; | ||
} | ||
|
||
// 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 = sf_getenv_s("USERPROFILE", envbuf, sizeof(envbuf)); | ||
if (homeDir && strlen(homeDir) != 0) | ||
{ | ||
homeDirFilePath = std::string(homeDir) + PATH_SEP + SF_CLIENT_CONFIG_FILE_NAME; | ||
} else { | ||
// USERPROFILE is empty, try HOMEDRIVE and HOMEPATH | ||
char envbuf2[MAX_PATH + 1]; | ||
char* homeDriveEnv = sf_getenv_s("HOMEDRIVE", envbuf, sizeof(envbuf)); | ||
char* homePathEnv = sf_getenv_s("HOMEPATH", envbuf2, sizeof(envbuf2)); | ||
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 ""; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
sf_bool EasyLoggingConfigParser::parseConfigFile( | ||
const std::string& filePath, | ||
client_config& clientConfig) | ||
{ | ||
value jsonConfig; | ||
std::string err; | ||
std::ifstream configFile; | ||
configFile.open(filePath, std::fstream::in | std::ios::binary); | ||
if (!configFile) | ||
{ | ||
CXX_LOG_INFO("Could not open a file. The file may not exist: %s", | ||
filePath.c_str()); | ||
return false; | ||
} | ||
#if !defined(WIN32) && !defined(_WIN64) | ||
if (!checkIfValidPermissions(filePath)) | ||
{ | ||
return false; | ||
} | ||
#endif | ||
err = parse(jsonConfig, configFile); | ||
|
||
if (!err.empty()) | ||
{ | ||
CXX_LOG_ERROR("Error in parsing JSON: %s, err: %s", filePath.c_str(), err.c_str()); | ||
return false; | ||
} | ||
|
||
if (jsonConfig.is<object>()) | ||
{ | ||
value commonProps = jsonConfig.get("common"); | ||
if (commonProps.is<object>()) | ||
{ | ||
checkUnknownEntries(commonProps); | ||
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; | ||
clientConfig.logLevel = (char*)SF_CALLOC(1, logLevelSize); | ||
sf_strcpy(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; | ||
clientConfig.logPath = (char*)SF_CALLOC(1, logPathSize); | ||
sf_strcpy(clientConfig.logPath, logPathSize, logPath); | ||
} | ||
return true; | ||
} | ||
} | ||
CXX_LOG_ERROR("Malformed client config file: %s", filePath.c_str()); | ||
return false; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
sf_bool EasyLoggingConfigParser::checkIfValidPermissions(const std::string& filePath) | ||
{ | ||
boost::filesystem::file_status fileStatus = boost::filesystem::status(filePath); | ||
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", | ||
filePath.c_str()); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
void EasyLoggingConfigParser::checkUnknownEntries(value& config) | ||
{ | ||
if (config.is<object>()) | ||
{ | ||
const value::object& configObj = 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