backend-app-api: log at the point of plugin startup failure

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-09-19 15:00:48 +02:00
parent 04af116eea
commit 1db5312db8
2 changed files with 77 additions and 61 deletions
@@ -277,71 +277,76 @@ export class BackendInitializer {
// All plugins are initialized in parallel
const results = await Promise.allSettled(
allPluginIds.map(async pluginId => {
// Initialize all eager services
await this.#serviceRegistry.initializeEagerServicesWithScope(
'plugin',
pluginId,
);
// Modules are initialized before plugins, so that they can provide extension to the plugin
const modules = moduleInits.get(pluginId);
if (modules) {
const tree = DependencyGraph.fromIterable(
Array.from(modules).map(([moduleId, moduleInit]) => ({
value: { moduleId, moduleInit },
// Relationships are reversed at this point since we're only interested in the extension points.
// If a modules provides extension point A we want it to be initialized AFTER all modules
// that depend on extension point A, so that they can provide their extensions.
consumes: Array.from(moduleInit.provides).map(p => p.id),
provides: Array.from(moduleInit.consumes).map(c => c.id),
})),
);
const circular = tree.detectCircularDependency();
if (circular) {
throw new ConflictError(
`Circular dependency detected for modules of plugin '${pluginId}', ${circular
.map(({ moduleId }) => `'${moduleId}'`)
.join(' -> ')}`,
);
}
await tree.parallelTopologicalTraversal(
async ({ moduleId, moduleInit }) => {
const moduleDeps = await this.#getInitDeps(
moduleInit.init.deps,
pluginId,
moduleId,
);
await moduleInit.init.func(moduleDeps).catch(error => {
throw new ForwardedError(
`Module '${moduleId}' for plugin '${pluginId}' startup failed`,
error,
);
});
},
);
}
// Once all modules have been initialized, we can initialize the plugin itself
const pluginInit = pluginInits.get(pluginId);
// We allow modules to be installed without the accompanying plugin, so the plugin may not exist
if (pluginInit) {
const pluginDeps = await this.#getInitDeps(
pluginInit.init.deps,
try {
// Initialize all eager services
await this.#serviceRegistry.initializeEagerServicesWithScope(
'plugin',
pluginId,
);
await pluginInit.init.func(pluginDeps).catch(error => {
throw new ForwardedError(
`Plugin '${pluginId}' startup failed`,
error,
// Modules are initialized before plugins, so that they can provide extension to the plugin
const modules = moduleInits.get(pluginId);
if (modules) {
const tree = DependencyGraph.fromIterable(
Array.from(modules).map(([moduleId, moduleInit]) => ({
value: { moduleId, moduleInit },
// Relationships are reversed at this point since we're only interested in the extension points.
// If a modules provides extension point A we want it to be initialized AFTER all modules
// that depend on extension point A, so that they can provide their extensions.
consumes: Array.from(moduleInit.provides).map(p => p.id),
provides: Array.from(moduleInit.consumes).map(c => c.id),
})),
);
});
const circular = tree.detectCircularDependency();
if (circular) {
throw new ConflictError(
`Circular dependency detected for modules of plugin '${pluginId}', ${circular
.map(({ moduleId }) => `'${moduleId}'`)
.join(' -> ')}`,
);
}
await tree.parallelTopologicalTraversal(
async ({ moduleId, moduleInit }) => {
const moduleDeps = await this.#getInitDeps(
moduleInit.init.deps,
pluginId,
moduleId,
);
await moduleInit.init.func(moduleDeps).catch(error => {
throw new ForwardedError(
`Module '${moduleId}' for plugin '${pluginId}' startup failed`,
error,
);
});
},
);
}
// Once all modules have been initialized, we can initialize the plugin itself
const pluginInit = pluginInits.get(pluginId);
// We allow modules to be installed without the accompanying plugin, so the plugin may not exist
if (pluginInit) {
const pluginDeps = await this.#getInitDeps(
pluginInit.init.deps,
pluginId,
);
await pluginInit.init.func(pluginDeps).catch(error => {
throw new ForwardedError(
`Plugin '${pluginId}' startup failed`,
error,
);
});
}
initLogger.onPluginStarted(pluginId);
// Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully
const lifecycleService = await this.#getPluginLifecycleImpl(pluginId);
await lifecycleService.startup();
} catch (error) {
initLogger.onPluginFailed(pluginId);
throw error;
}
initLogger.onPluginStarted(pluginId);
// Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully
const lifecycleService = await this.#getPluginLifecycleImpl(pluginId);
await lifecycleService.startup();
}),
);
@@ -27,6 +27,7 @@ export function createInitializationLogger(
rootLogger?: RootLoggerService,
): {
onPluginStarted(pluginId: string): void;
onPluginFailed(pluginId: string): void;
onAllStarted(): void;
} {
const logger = rootLogger?.child({ type: 'initialization' });
@@ -67,6 +68,16 @@ export function createInitializationLogger(
starting.delete(pluginId);
started.add(pluginId);
},
onPluginFailed(pluginId: string) {
starting.delete(pluginId);
const status =
starting.size > 0
? `, waiting for ${starting.size} other plugins to finish before shutting down the process`
: '';
logger?.error(
`Plugin '${pluginId}' thew an error during startup${status}`,
);
},
onAllStarted() {
logger?.info(`Plugin initialization complete${getInitStatus()}`);