-
Notifications
You must be signed in to change notification settings - Fork 304
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
HPCC-32874 Add WsLogAcces Health Report Method #19307
base: candidate-9.8.x
Are you sure you want to change the base?
Changes from 1 commit
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 |
---|---|---|
|
@@ -229,10 +229,23 @@ ESPResponse GetLogsResponse | |
[min_ver("1.02")] unsigned int TotalLogLinesAvailable; | ||
}; | ||
|
||
ESPservice [auth_feature("WsLogAccess:READ"), version("1.06"), default_client_version("1.06"), exceptions_inline("xslt/exceptions.xslt")] ws_logaccess | ||
ESPRequest GetHealthReportRequest | ||
{ | ||
bool IncludeServerInternals(true); | ||
bool IncludePluginInternals(true); | ||
bool IncludeSampleQuery(true); | ||
}; | ||
|
||
ESPResponse GetHealthReportResponse | ||
{ | ||
string Report; | ||
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'd much rather see a structured response that has some common subset of useful values for ECLWatch. 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. Agreed, commented below for further discussion |
||
}; | ||
|
||
ESPservice [auth_feature("WsLogAccess:READ"), version("1.07"), default_client_version("1.07"), exceptions_inline("xslt/exceptions.xslt")] ws_logaccess | ||
{ | ||
ESPmethod GetLogAccessInfo(GetLogAccessInfoRequest, GetLogAccessInfoResponse); | ||
ESPmethod GetLogs(GetLogsRequest, GetLogsResponse); | ||
ESPmethod [min_ver("1.07")] GetHealthReport(GetHealthReportRequest, GetHealthReportResponse); | ||
}; | ||
|
||
SCMexportdef(ws_logaccess); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -385,3 +385,30 @@ bool Cws_logaccessEx::onGetLogs(IEspContext &context, IEspGetLogsRequest &req, I | |
|
||
return true; | ||
} | ||
|
||
bool Cws_logaccessEx::onGetHealthReport(IEspContext &context, IEspGetHealthReportRequest &req, IEspGetHealthReportResponse &resp) | ||
{ | ||
StringBuffer report; | ||
//LogAccessHealthReportDetails reportDetails; | ||
LogAccessHealthReportOptions options; | ||
options.IncludeServerInternals = req.getIncludeServerInternals(); | ||
options.IncludePluginInternals = req.getIncludePluginInternals(); | ||
options.IncludeSampleQuery = req.getIncludeSampleQuery(); | ||
|
||
report.set("{ "); | ||
bool success = true; | ||
if (!queryRemoteLogAccessor()) | ||
{ | ||
report.append("\"Error\": \"LogAccess plugin not available, review logAccess configuration!\""); | ||
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 should also be part of a structured response. 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. In agreement. what is the ESP standard for these types of messages? (ie, what's the expected structure format) |
||
success = false; | ||
} | ||
else | ||
{ | ||
//queryRemoteLogAccessor()->healthReport(report, reportDetails); | ||
queryRemoteLogAccessor()->healthReport(report, options); | ||
} | ||
report.append(" }"); | ||
resp.setReport(report.str()); | ||
|
||
return success; | ||
} | ||
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. Missing final newline |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ global: | |
id: "1" | ||
name: "Loki" | ||
namespace: | ||
name: "hpcc" | ||
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 need to revert this change |
||
name: "default" | ||
logFormat: | ||
type: "json" | ||
logMaps: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1500,9 +1500,44 @@ enum LogAccessMappedField | |
LOGACCESS_MAPPEDFIELD_host, | ||
LOGACCESS_MAPPEDFIELD_traceid, | ||
LOGACCESS_MAPPEDFIELD_spanid, | ||
LOGACCESS_MAPPEDFIELD_global, | ||
LOGACCESS_MAPPEDFIELD_container, | ||
LOGACCESS_MAPPEDFIELD_message, | ||
LOGACCESS_MAPPEDFIELD_unmapped | ||
}; | ||
|
||
inline const char * MappedFieldTypeToString(LogAccessMappedField mappedField) | ||
{ | ||
if (mappedField == LOGACCESS_MAPPEDFIELD_timestamp) | ||
return "timestamp"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_jobid) | ||
return "jobid"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_component) | ||
return "component"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_class) | ||
return "class"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_audience) | ||
return "audience"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_instance) | ||
return "instance"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_pod) | ||
return "pod"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_host) | ||
return "host"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_traceid) | ||
return "traceID"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_spanid) | ||
return "spanID"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_global) | ||
return "global"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_container) | ||
return "container"; | ||
else if (mappedField == LOGACCESS_MAPPEDFIELD_message) | ||
return "message"; | ||
else | ||
return "UNKNOWNFIELDTYPE"; | ||
} | ||
|
||
enum SortByDirection | ||
{ | ||
SORTBY_DIRECTION_none, | ||
|
@@ -1675,6 +1710,106 @@ struct LogQueryResultDetails | |
unsigned int totalReceived; | ||
unsigned int totalAvailable; | ||
}; | ||
/* | ||
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'm guessing before you finalize you'll remove these comments and those in WsLogAccessService.cpp. 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. Some, but kept the structure around as a mechanism to track structured responses for the ESP |
||
typedef enum | ||
{ | ||
LOGACCESS_STATUS_unknown, | ||
LOGACCESS_STATUS_ok, | ||
LOGACCESS_STATUS_fail | ||
} LogAccessHealthStatus; | ||
|
||
struct LogAccessConnectionDetails | ||
{ | ||
StringAttr connectionString; | ||
StringAttr connectionInfo; | ||
StringAttr connectionStatus; | ||
StringAttr sampleQueryStatus; | ||
|
||
void toJSON(StringBuffer & out) | ||
{ | ||
} | ||
}; | ||
|
||
struct LogAccessConfigLogMap | ||
{ | ||
LogAccessMappedField logFieldType; | ||
StringAttr fieldName; | ||
StringAttr sourceName; | ||
LogAccessConfigLogMap(LogAccessMappedField type, const char * name, const char * source) | ||
{ | ||
logFieldType = type; | ||
fieldName.set(name); | ||
sourceName.set(source); | ||
} | ||
|
||
void toJSON(StringBuffer & out) | ||
{ | ||
out.appendf("\"%s\": { \"ColName\": \"%s\", \"Source\": \"%s\" }", MappedFieldTypeToString(logFieldType), fieldName.str(), sourceName.str()); | ||
} | ||
}; | ||
|
||
struct LogAccessConfigDetails | ||
{ | ||
StringAttr connectionInfo; | ||
StringArray logMaps; | ||
//IArrayOf<LogAccessConfigLogMap> logMaps; | ||
|
||
void appendLogMap(LogAccessConfigLogMap logMap) | ||
{ | ||
StringBuffer logMapJson; | ||
logMap.toJSON(logMapJson); | ||
logMaps.append(logMapJson.str()); | ||
//logMaps.append(logMap); | ||
} | ||
|
||
void toJSON(StringBuffer & out) | ||
{ | ||
out.appendf("\"ConfigInfo\": { \"LogMaps\": {"); | ||
|
||
ForEachItemIn(i, logMaps) | ||
//logMaps.item(i).toJSON(out); | ||
out.append(logMaps.item(i)); | ||
|
||
//close out the logmaps | ||
out.append(" }"); | ||
//close out the ConfigInfo | ||
out.append(" }"); | ||
} | ||
}; | ||
|
||
struct LogAccessHealthReportDetails | ||
{ | ||
LogAccessConnectionDetails connectionInfo; | ||
LogAccessConfigDetails configInfo; | ||
StringBuffer JsonMessages; | ||
|
||
void appendLogMap(LogAccessConfigLogMap logMap) | ||
{ | ||
configInfo.appendLogMap(logMap); | ||
} | ||
|
||
void toJSON(StringBuffer & out) | ||
{ | ||
StringBuffer scratch; | ||
|
||
out.append("{ \"Connection\": "); | ||
connectionInfo.toJSON(scratch); | ||
out.append(scratch.str()); | ||
|
||
out.append(", \"ConfigInfo\": "); | ||
configInfo.toJSON(scratch.clear()); | ||
out.append(scratch.str()); | ||
|
||
out.appendf(", \"Messages\": \"%s\"", JsonMessages.str()); | ||
} | ||
}; | ||
*/ | ||
struct LogAccessHealthReportOptions | ||
{ | ||
bool IncludeServerInternals = true; | ||
bool IncludePluginInternals = true; | ||
bool IncludeSampleQuery = true; | ||
}; | ||
|
||
// Log Access Interface - Provides filtered access to persistent logging - independent of the log storage mechanism | ||
// -- Declares method to retrieve log entries based on options set | ||
|
@@ -1690,6 +1825,8 @@ interface IRemoteLogAccess : extends IInterface | |
virtual IPropertyTree * queryLogMap() const = 0; | ||
virtual const char * fetchConnectionStr() const = 0; | ||
virtual bool supportsResultPaging() const = 0; | ||
//virtual bool healthReport(StringBuffer & messages, LogAccessHealthReportDetails & report) = 0; | ||
virtual bool healthReport(StringBuffer & report, LogAccessHealthReportOptions options) = 0; | ||
}; | ||
|
||
// Helper functions to construct log access filters | ||
|
@@ -1714,7 +1851,7 @@ extern jlib_decl bool fetchLog(LogQueryResultDetails & resultDetails, StringBuff | |
extern jlib_decl bool fetchJobIDLog(LogQueryResultDetails & resultDetails, StringBuffer & returnbuf, IRemoteLogAccess & logAccess, const char *jobid, LogAccessTimeRange timeRange, StringArray & cols, LogAccessLogFormat format); | ||
extern jlib_decl bool fetchComponentLog(LogQueryResultDetails & resultDetails, StringBuffer & returnbuf, IRemoteLogAccess & logAccess, const char * component, LogAccessTimeRange timeRange, StringArray & cols, LogAccessLogFormat format); | ||
extern jlib_decl bool fetchLogByAudience(LogQueryResultDetails & resultDetails, StringBuffer & returnbuf, IRemoteLogAccess & logAccess, MessageAudience audience, LogAccessTimeRange timeRange, StringArray & cols, LogAccessLogFormat format); | ||
extern jlib_decl bool fetchLogByClass(LogQueryResultDetails & resultDetails, StringBuffer & returnbuf, IRemoteLogAccess & logAccess, LogMsgClass logclass, LogAccessTimeRange timeRange, StringArray & cols, LogAccessLogFormat format); | ||
extern jlib_decl bool fetchLogByClass(LogQueryResultDetails & resultDetails, StringBuffer & returnbuf, IRemoteLogAccess & logAccess, LogMsgClass logclass, LogAccessTimeRange timeRange, StringArray & cols, LogAccessLogFormat format); | ||
extern jlib_decl IRemoteLogAccess * queryRemoteLogAccessor(); | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -1081,6 +1081,151 @@ bool AzureLogAnalyticsCurlClient::processSearchJsonResp(LogQueryResultDetails & | |||
return true; | ||||
} | ||||
|
||||
bool AzureLogAnalyticsCurlClient::healthReport(StringBuffer & report, LogAccessHealthReportOptions options) | ||||
{ | ||||
try | ||||
{ | ||||
report.appendf("\"ConnectionInfo\": { \"TargetALAWorkspaceID\": \"%s\" ", m_logAnalyticsWorkspaceID.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. For this and the other report building code, maybe it would be better to use the JSON builder functions in jstring like |
||||
report.appendf(", \"TargetALATenantID\": \"%s\"", m_aadTenantID.str()); | ||||
report.appendf(", \"TargetALAClientID\": \"%s\"", m_aadClientID.str()); | ||||
report.appendf(", \"TargetALASecret\": \"%sempty\"", m_aadClientSecret.length()==0 ? "" : "not "); | ||||
report.appendf(", \"TargetsContainerLogV2\": \"%s\"", targetIsContainerLogV2 ? "true" : "false"); | ||||
report.appendf(", \"ComponentsQueryJoins\": \"%sabled\"", m_disableComponentNameJoins ? "dis" : "en"); | ||||
report.appendf(", \"BlobModeUnstructuredLogData\": \"%sabled\"", m_blobMode ? "en" : "dis"); | ||||
report.append( "}"); //close conninfo | ||||
|
||||
report.append(", \"ConfigurationInfo\": { "); | ||||
|
||||
if (m_pluginCfg) | ||||
{ | ||||
StringBuffer configJSON; | ||||
toJSON(m_pluginCfg, configJSON, 0); | ||||
report.appendf("\"ConfigurationTree\": %s", configJSON.str()); //json encode | ||||
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. Same question here as in ElasticStackLogAccess.cpp about JSON vs JSON in a string which needs encoding. |
||||
} | ||||
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. Is there any sensitive information in the configuration that shouldn't be exposed, like maybe tokens or passwords or internal URLs? On the other hand maybe this is a moot question since ECLWatch already gives access to component configs for roxie, esp etc. 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. Fair question, I don't think we support credentials in the config, but I'll make sure we don't, and if we do I'll mask it 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. HELM chart Schema doesn't call for any sensitive data, I think we're ok. HPCC-Platform/helm/hpcc/values.schema.json Line 3192 in 70ccefc
|
||||
else | ||||
{ | ||||
report.append("\"Error\": \"Configuration tree is empty!!!\""); | ||||
} | ||||
report.append(" }"); // close config info | ||||
|
||||
report.append(", \"Internals\": { "); | ||||
if (options.IncludeServerInternals) | ||||
{ | ||||
report.appendf("\"Plugin\": { \"LogMaps\": {"); | ||||
report.appendf("\"Global\": { \"ColName\": \"%s\", \"Source\": \"%s\", \"TimeStampCol\": \"%s\"}", m_globalSearchColName.str(), m_globalIndexSearchPattern.str(), m_globalIndexTimestampField.str()); | ||||
report.appendf(", \"Workunits\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_workunitSearchColName.str(), m_workunitIndexSearchPattern.str()); | ||||
report.appendf(", \"Components\": { \"ColName\": \"%s\", \"Source\": \"%s\", \"LookupKey\": \"%s\", \"TimeStampCol\": \"%s\"}", m_componentsSearchColName.str(), m_componentsIndexSearchPattern.str(), m_componentsLookupKeyColumn.str(), m_componentsTimestampField.str()); | ||||
report.appendf(", \"Audience\": { \"ColName\": \"%s\", \"Source\": \"%s\" }", m_audienceSearchColName.str(), m_audienceIndexSearchPattern.str()); | ||||
report.appendf(", \"Class\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_classSearchColName.str(), m_classIndexSearchPattern.str()); | ||||
report.appendf(", \"Instance\": { \"ColName\": \"%s\", \"Source\": \"%s\", \"LookupKey\": \"%s\"}", m_instanceSearchColName.str(), m_instanceIndexSearchPattern.str(), m_instanceLookupKeyColumn.str()); | ||||
report.appendf(", \"Pod\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_podSearchColName.str(), m_podIndexSearchPattern.str()); | ||||
report.appendf(", \"TraceID\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_traceSearchColName.str(), m_traceIndexSearchPattern.str()); | ||||
report.appendf(", \"SpanID\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_spanSearchColName.str(), m_spanIndexSearchPattern.str()); | ||||
report.appendf(", \"Host\": { \"ColName\": \"%s\", \"Source\": \"%s\"}", m_hostSearchColName.str(), m_hostIndexSearchPattern.str()); | ||||
report.append(" }"); //close logmaps | ||||
report.append(" }"); //close plugin | ||||
} | ||||
|
||||
if (options.IncludeServerInternals) | ||||
{ | ||||
report.append(", \"Server\": { }"); | ||||
} | ||||
|
||||
report.append(" }"); //close internals | ||||
if (options.IncludeSampleQuery) | ||||
{ | ||||
report.append(", \"SampleTokenRequest\": { "); | ||||
try | ||||
{ | ||||
|
||||
StringBuffer token; | ||||
requestLogAnalyticsAccessToken(token, m_aadClientID, m_aadClientSecret, m_aadTenantID); //throws if issues encountered | ||||
rpastrana marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
if (token.isEmpty()) | ||||
report.append("\"Error\": \"Empty token received\""); | ||||
|
||||
} | ||||
catch(IException * e) | ||||
{ | ||||
StringBuffer description; | ||||
e->errorMessage(description); | ||||
report.appendf("\"Error\": \"Exception while requesting token (%d) - %s\"", e->errorCode(), description.str()); | ||||
e->Release(); | ||||
} | ||||
catch(...) | ||||
{ | ||||
report.append("\"Error\": \"Unknown exception while requesting token\""); | ||||
} | ||||
report.append(" }"); //close sample token request | ||||
|
||||
report.append(", \"SampleQuery\": { "); | ||||
try | ||||
{ | ||||
report.appendf("\"Query\": { \"LogFormat\": \"JSON\","); | ||||
LogAccessLogFormat outputFormat = LOGACCESS_LOGFORMAT_json; | ||||
LogAccessConditions queryOptions; | ||||
|
||||
report.appendf("\"Filter\": {\"type\": \"byWildcard\", \"value\": \"*\" },"); | ||||
queryOptions.setFilter(getWildCardLogAccessFilter("*")); | ||||
|
||||
struct LogAccessTimeRange range; | ||||
CDateTime endtt; | ||||
endtt.setNow(); | ||||
range.setEnd(endtt); | ||||
StringBuffer endstr; | ||||
endtt.getString(endstr); | ||||
|
||||
CDateTime startt; | ||||
startt.setNow(); | ||||
startt.adjustTimeSecs(-60); //an hour ago | ||||
range.setStart(startt); | ||||
|
||||
StringBuffer startstr; | ||||
startt.getString(startstr); | ||||
report.appendf("\"TimeRange\": {\"Start\": \"%s\", \"End\": \"%s\" },", startstr.str(), endstr.str()); | ||||
|
||||
queryOptions.setTimeRange(range); | ||||
queryOptions.setLimit(5); | ||||
report.appendf("\"Limit\": \"5\" }, "); | ||||
|
||||
StringBuffer queryString, queryIndex; | ||||
populateKQLQueryString(queryString, queryIndex, queryOptions); | ||||
|
||||
StringBuffer encodedValue; | ||||
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. Is this the only string that needs to be encoded, or are there other cases where user input could be part of any value returned in the report? Would it be a good idea to encode all strings returned? 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. Good question, within the sample query section, yes. But point taken, I'll look around and see if anything else could/should be encoded. |
||||
encodeJSON(encodedValue, queryString.str()); | ||||
|
||||
report.appendf("\"KQLQuery\": \"%s\", ", encodedValue.str()); | ||||
report.appendf("\"QueryIndex\": \"%s\", ", queryIndex.str()); | ||||
|
||||
StringBuffer logs; | ||||
LogQueryResultDetails resultDetails; | ||||
fetchLog(resultDetails, queryOptions, logs, outputFormat); | ||||
report.appendf("\"ResultCount\": \"%d\", ", resultDetails.totalReceived); | ||||
report.appendf("\"Results\": %s", logs.str()); | ||||
} | ||||
catch(IException * e) | ||||
{ | ||||
StringBuffer description; | ||||
e->errorMessage(description); | ||||
report.appendf("\"Error\": \"Exception while executing sample ALA query (%d) - %s\"", e->errorCode(), description.str()); | ||||
e->Release(); | ||||
} | ||||
catch(...) | ||||
{ | ||||
report.append("\"Error\": \"Unknown exception while executing sample ALA query\""); | ||||
} | ||||
report.append(" }"); //close sample query | ||||
} | ||||
} | ||||
catch(...) | ||||
{ | ||||
report.append("\"Error\": \"Encountered unexpected exception during health report\""); | ||||
return false; | ||||
} | ||||
|
||||
return true; | ||||
} | ||||
|
||||
bool AzureLogAnalyticsCurlClient::fetchLog(LogQueryResultDetails & resultDetails, const LogAccessConditions & options, StringBuffer & returnbuf, LogAccessLogFormat format) | ||||
{ | ||||
StringBuffer token; | ||||
|
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 should have [exceptions_inline] also