Skip to content

Commit

Permalink
HPCC-32874
Browse files Browse the repository at this point in the history
Add WsLogAcces Health Report Method

- Adds healthreport method to logaccess interface
- Implements healthreport method for all logaccess plugins

Signed-off-by: Rodrigo Pastrana <[email protected]>
  • Loading branch information
rpastrana committed Dec 13, 2024
1 parent 17590a5 commit f77d122
Show file tree
Hide file tree
Showing 11 changed files with 763 additions and 33 deletions.
15 changes: 14 additions & 1 deletion esp/scm/ws_logaccess.ecm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

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);
Expand Down
27 changes: 27 additions & 0 deletions esp/services/ws_logaccess/WsLogAccessService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!\"");
success = false;
}
else
{
//queryRemoteLogAccessor()->healthReport(report, reportDetails);
queryRemoteLogAccessor()->healthReport(report, options);
}
report.append(" }");
resp.setReport(report.str());

return success;
}
1 change: 1 addition & 0 deletions esp/services/ws_logaccess/WsLogAccessService.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Cws_logaccessEx : public Cws_logaccess
virtual ~Cws_logaccessEx();
virtual bool onGetLogAccessInfo(IEspContext &context, IEspGetLogAccessInfoRequest &req, IEspGetLogAccessInfoResponse &resp);
virtual bool onGetLogs(IEspContext &context, IEspGetLogsRequest &req, IEspGetLogsResponse & resp);
virtual bool onGetHealthReport(IEspContext &context, IEspGetHealthReportRequest &req, IEspGetHealthReportResponse &resp);
};

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ global:
id: "1"
name: "Loki"
namespace:
name: "hpcc"
name: "default"
logFormat:
type: "json"
logMaps:
Expand Down
139 changes: 138 additions & 1 deletion system/jlib/jlog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1675,6 +1710,106 @@ struct LogQueryResultDetails
unsigned int totalReceived;
unsigned int totalAvailable;
};
/*
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
Expand All @@ -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
Expand All @@ -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
Expand Up @@ -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());
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
}
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

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;
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;
Expand Down
Loading

0 comments on commit f77d122

Please sign in to comment.