-
Notifications
You must be signed in to change notification settings - Fork 311
HPCC-35558 Standardize logaccess plugin load logic #20792
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
base: candidate-10.0.x
Are you sure you want to change the base?
HPCC-35558 Standardize logaccess plugin load logic #20792
Conversation
|
Jira Issue: https://hpccsystems.atlassian.net//browse/HPCC-35558 Jirabot Action Result: |
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.
Pull request overview
This PR standardizes the logic for loading logaccess plugins by introducing a diagnostic framework to better identify and report plugin loading failures. The key changes include adding an enum and struct for diagnostic states, a new diagnostic function that safely attempts plugin loading without raising exceptions, and enhanced error reporting in the WsLogAccess health report endpoint.
- Added diagnostic infrastructure (enum and struct) to track plugin loading states
- Created
diagnoseLogAccessPluginLoad()function for safe plugin loading diagnostics - Enhanced health report endpoint with detailed diagnostic messages based on loading state
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| system/jlib/jlog.hpp | Added LogAccessDiagnosticState enum, LogAccessPluginDiagnostics struct, and diagnoseLogAccessPluginLoad() function declaration for diagnostic infrastructure |
| system/jlib/jlog.cpp | Refactored queryRemoteLogAccessor() to use consistent plugin naming and implemented new diagnoseLogAccessPluginLoad() diagnostic function |
| esp/services/ws_logaccess/WsLogAccessService.cpp | Enhanced onGetHealthReport() to use diagnostic framework and provide detailed, state-specific error messages |
eb4323b to
ab2aad6
Compare
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.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| e->errorMessage(msg); | ||
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | ||
| e->Release(); | ||
| } | ||
| catch (...) | ||
| { |
Copilot
AI
Jan 6, 2026
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 outer exception handler at lines 3567-3572 swallows the exception after logging it to diagnostics.statusMessage. If getGlobalConfigSP()->getPropTree() throws an exception, this is caught but the function continues to execute without setting any diagnostic state. Consider setting a diagnostic state (such as LoadFailed) when exceptions are caught at the outer level to ensure diagnostics.logAccessPluginLoadState is always set to a meaningful value before returning.
| e->errorMessage(msg); | |
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | |
| e->Release(); | |
| } | |
| catch (...) | |
| { | |
| e->errorMessage(msg); | |
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | |
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | |
| e->Release(); | |
| } | |
| catch (...) | |
| { | |
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; |
| e->errorMessage(msg); | ||
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | ||
| e->Release(); | ||
| } | ||
| catch (...) | ||
| { |
Copilot
AI
Jan 6, 2026
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.
Similarly, the catch-all handler at lines 3574-3577 logs an error but doesn't set a diagnostic state. This means if an unknown exception occurs, diagnostics.logAccessPluginLoadState will remain at its default value of ConfigNotFound (from line 1813), which is misleading. Set logAccessPluginLoadState to LoadFailed or another appropriate state before returning.
| e->errorMessage(msg); | |
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | |
| e->Release(); | |
| } | |
| catch (...) | |
| { | |
| e->errorMessage(msg); | |
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | |
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | |
| e->Release(); | |
| } | |
| catch (...) | |
| { | |
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; |
| switch (diagnostics.logAccessPluginLoadState) | ||
| { | ||
| case LogAccessDiagnosticState::ConfigNotFound: | ||
| { | ||
| messages.append("Warning: No logaccess plugin configuration found."); | ||
| code.set("Warning"); | ||
| break; | ||
| } | ||
| case LogAccessDiagnosticState::ConfigFoundNoType: | ||
| { | ||
| VStringBuffer configTypeMsg("LogAccess configuration found but plugin type not specified: '%s'", diagnostics.statusMessage.str()); | ||
| messages.append(configTypeMsg.str()); | ||
| code.set("Fail"); | ||
| break; | ||
| } | ||
| case LogAccessDiagnosticState::LoadFailed: | ||
| { | ||
| // Configuration exists, report on load attempt | ||
| VStringBuffer configMsg("LogAccess configuration found - Plugin Type: '%s'", diagnostics.pluginType.str()); | ||
| messages.append(configMsg.str()); | ||
|
|
||
| VStringBuffer failMsg("Failed to verify plugin '%s': %s", diagnostics.libName.str(), diagnostics.statusMessage.str()); | ||
| messages.append(failMsg.str()); | ||
| code.set("Fail"); | ||
| break; | ||
| } | ||
| case LogAccessDiagnosticState::LoadSucceeded: | ||
| { | ||
| VStringBuffer configMsg("LogAccess configuration found - Plugin Type: '%s'", diagnostics.pluginType.str()); | ||
| messages.append(configMsg.str()); | ||
| VStringBuffer successMsg("Plugin library '%s' and factory procedure verified successfully. Plugin instance failed to initialize during service startup - check service logs for initialization errors.", diagnostics.libName.str()); | ||
| messages.append(successMsg.str()); | ||
| code.set("Warning"); | ||
| break; | ||
| } | ||
| default: | ||
| { | ||
| messages.append("Unknown diagnostic state encountered"); | ||
| code.set("Fail"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Add configuration details if requested | ||
| if (options.IncludeConfiguration) | ||
| { | ||
| const char* stateStr = ""; | ||
| switch (diagnostics.logAccessPluginLoadState) | ||
| { | ||
| case LogAccessDiagnosticState::ConfigNotFound: | ||
| stateStr = "ConfigNotFound"; | ||
| break; | ||
| case LogAccessDiagnosticState::ConfigFoundNoType: | ||
| stateStr = "ConfigFoundNoType"; | ||
| break; | ||
| case LogAccessDiagnosticState::LoadFailed: | ||
| stateStr = "LoadFailed"; | ||
| break; | ||
| case LogAccessDiagnosticState::LoadSucceeded: | ||
| stateStr = "LoadSucceeded"; | ||
| break; | ||
| default: | ||
| stateStr = "Unknown"; | ||
| break; | ||
| } |
Copilot
AI
Jan 6, 2026
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.
There's code duplication in the two switch statements at lines 410-451 and 457-474 that handle LogAccessDiagnosticState enum values. Both switches convert the same enum values to strings/messages. Consider extracting a helper function that converts LogAccessDiagnosticState to a string representation to reduce duplication and improve maintainability. This would also ensure consistency if new states are added to the enum.
| void diagnoseLogAccessPluginLoad(LogAccessPluginDiagnostics & diagnostics) | ||
| { | ||
| constexpr const char * methodName = "diagnoseLogAccessPluginLoad"; | ||
| constexpr const char * instFactoryName = "createInstance"; | ||
|
|
||
| try | ||
| { | ||
| Owned<IPropertyTree> logAccessPluginConfig = getGlobalConfigSP()->getPropTree("logAccess"); | ||
|
|
||
| if (!logAccessPluginConfig) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::ConfigNotFound; | ||
| diagnostics.statusMessage.setf("%s: No logaccess configuration found in global config", methodName); | ||
| return; | ||
| } | ||
|
|
||
| logAccessPluginConfig->getProp("@type", diagnostics.pluginType); | ||
| if (diagnostics.pluginType.isEmpty()) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::ConfigFoundNoType; | ||
| diagnostics.statusMessage.setf("%s: RemoteLogAccess plugin type not specified in configuration", methodName); | ||
| return; | ||
| } | ||
|
|
||
| StringBuffer pluginName; | ||
| pluginName.append(diagnostics.pluginType.str()).append("logaccess"); | ||
| diagnostics.libName.set(pluginName.str()); | ||
|
|
||
| //Attempt to load the DLL/SO | ||
| HINSTANCE logAccessPluginLib = nullptr; | ||
| try | ||
| { | ||
| logAccessPluginLib = LoadSharedObject(pluginName.str(), false, false); // raiseOnError=false to capture error | ||
| if (!logAccessPluginLib) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | ||
| diagnostics.statusMessage.setf("%s: Failed to load plugin '%s' (%s%s%s): Library not found or load failed", methodName, pluginName.str(), SharedObjectPrefix, pluginName.str(), SharedObjectExtension); | ||
| return; | ||
| } | ||
|
|
||
| // Try to get the factory procedure | ||
| void * xproc = GetSharedProcedure(logAccessPluginLib, instFactoryName); | ||
| if (xproc == nullptr) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | ||
| diagnostics.statusMessage.setf("%s: Cannot locate procedure '%s' in library '%s'", methodName, instFactoryName, diagnostics.libName.str()); | ||
| FreeSharedObject(logAccessPluginLib); | ||
| return; | ||
| } | ||
|
|
||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadSucceeded; | ||
| diagnostics.statusMessage.setf("%s: Plugin library and factory procedure verified successfully", methodName); | ||
| FreeSharedObject(logAccessPluginLib); | ||
| } | ||
| catch (IException *e) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | ||
| StringBuffer errorMsg; | ||
| e->errorMessage(errorMsg); | ||
| diagnostics.statusMessage.setf("%s: Failed to load or verify plugin library '%s': %s", methodName, diagnostics.libName.str(), errorMsg.str()); | ||
| if (logAccessPluginLib) | ||
| FreeSharedObject(logAccessPluginLib); | ||
| e->Release(); | ||
| return; | ||
| } | ||
| catch (...) | ||
| { | ||
| diagnostics.logAccessPluginLoadState = LogAccessDiagnosticState::LoadFailed; | ||
| diagnostics.statusMessage.setf("%s: Failed to load or verify plugin library '%s': Unknown error", methodName, diagnostics.libName.str()); | ||
| if (logAccessPluginLib) | ||
| FreeSharedObject(logAccessPluginLib); | ||
| return; | ||
| } | ||
| } | ||
| catch (IException *e) | ||
| { | ||
| StringBuffer msg; | ||
| e->errorMessage(msg); | ||
| diagnostics.statusMessage.setf("%s: Exception during plugin diagnostics: %s", methodName, msg.str()); | ||
| e->Release(); | ||
| } | ||
| catch (...) | ||
| { | ||
| diagnostics.statusMessage.setf("%s: Unknown exception during plugin diagnostics", methodName); | ||
| } | ||
| } |
Copilot
AI
Jan 6, 2026
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 new diagnoseLogAccessPluginLoad function provides diagnostic capabilities for plugin loading but lacks unit test coverage. The codebase has comprehensive unit tests in testing/unittests/jlibtests.cpp for jlib functionality. Consider adding tests that verify the diagnostic function correctly identifies various failure scenarios (ConfigNotFound, ConfigFoundNoType, LoadFailed, LoadSucceeded) and properly handles exceptions without leaking resources. This is especially important given the complexity of the error handling paths and the need to ensure diagnostic state is properly set in all cases.
|
blocked by #20753 |
- Adds plugin/configuration issue analysis - Add enum values: ConfigNotFound, ConfigFoundNoType, LoadFailed, LoadSucceeded - Update WsLogAccessService.cpp to use clean switch-based logic instead of nested if-else Co-authored-by: rodrigo pastrana <[email protected]>
- Standardizes the shared object loading logic for logaccess - Updates the HealthReport logic to mimic loading logic Signed-off-by: Rodrigo Pastrana <[email protected]>
ab2aad6 to
ac53be6
Compare
Type of change:
Checklist:
Smoketest:
Testing: