v0.1.4-pre.017
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 88
|
# version: 89
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.4-pre.16.fix.2"
|
version = "0.1.4-pre.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||||
<!-- version: 19 -->
|
<!-- version: 20 -->
|
||||||
|
|
||||||
# `ksp-app-config-desk`
|
# `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -113,7 +113,13 @@ main -> ksp-app-config-desk.frontend.main
|
|||||||
splash -> ksp-app-config-desk.frontend.splash
|
splash -> ksp-app-config-desk.frontend.splash
|
||||||
```
|
```
|
||||||
|
|
||||||
Rust valide le niveau et le `targetId`, choisit un callsite statique puis émet exclusivement via les macros de `ksp-logging-lib`. Le package applicatif n'importe pas directement `tracing`. Le pont actuel garantit donc le trajet WebView -> Rust tout en conservant l'affichage local des appels `console.*` dans la console WebKit. Il ne réémet pas encore les événements Rust arbitraires vers la console WebKit : cette direction nécessitera une intégration contrôlée de `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, sans second subscriber ni boucle avec le bridge KSP. Le panneau manuel de test Logging, ses targets `test.*` et ses domaines personnalisables restent une surface distincte prévue plus tard.
|
Rust valide le niveau et le `targetId`, choisit un callsite statique puis émet exclusivement via les macros de `ksp-logging-lib`. Le package applicatif n'importe pas directement `tracing`. Le pont actuel garantit donc le trajet WebView -> Rust tout en conservant l'affichage local des appels `console.*` dans la console WebKit. Il ne réémet pas encore les événements Rust arbitraires vers la console WebKit : cette direction nécessitera une intégration contrôlée de `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, sans second subscriber ni boucle avec le bridge KSP. Le panneau **Test Logging** complète désormais ce bridge : il peut émettre des événements backend via `ksp-logging-lib` avec target KSP statique et domain contrôlé, ou réutiliser le bridge frontend existant avec son target/domain fixes.
|
||||||
|
|
||||||
|
## Panneau Test Logging
|
||||||
|
|
||||||
|
`pre.017` ajoute une surface de validation volontairement explicite. Le champ **Message** est le contenu qui sera réellement journalisé ; il ne doit donc jamais recevoir de Secret. **Log backend** appelle `emit_logging_test`, qui valide le niveau (`trace/debug/info/warn/error/tous`), choisit un target statique (`ksp-app-config-desk` ou `ksp-app-config-desk.logging-test`) et applique un domain absent, connu ou personnalisé borné avant d'émettre exclusivement avec les macros `ksp-logging-lib`. Le résultat retourne uniquement des métadonnées sûres : nombre d'événements, niveau demandé, target/domain effectifs et génération runtime.
|
||||||
|
|
||||||
|
**Log via bridge frontend** réutilise le bridge déjà installé. Son contrat reste volontairement fixe : `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. Cela permet de comparer dans le même panneau le routing backend et le chemin WebView -> Rust -> `ksp-logging-lib`, avant et après modification/hot reload des filtres.
|
||||||
|
|
||||||
## Bootstrap Config et Logging
|
## Bootstrap Config et Logging
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
||||||
<!-- version: 16 -->
|
<!-- version: 17 -->
|
||||||
|
|
||||||
# TODO `ksp-app-config-desk`
|
# TODO `ksp-app-config-desk`
|
||||||
|
|
||||||
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et Environnement avec management `.env`/reveal Secret privilégié, ainsi que l'édition typée/persistence atomique du document Logging, son hot reload transactionnel, la sélection explicite du profil runtime et les fichiers isolés par identité de lancement sont maintenant présents.
|
Le gabarit frontend, le bootstrap Config/Logging, `AppState`, le `LoggingGuard` durable, les premiers DTO TS-RS, le bridge frontend logging KSP, le lifecycle `splash -> main`, les panneaux Documents, Profils/provenance et Environnement avec management `.env`/reveal Secret privilégié, ainsi que l'édition typée/persistence atomique du document Logging, son hot reload transactionnel, la sélection explicite du profil runtime, les fichiers isolés par identité de lancement et le panneau Test Logging backend/frontend sont maintenant présents.
|
||||||
|
|
||||||
Les prochaines tranches doivent encore :
|
Les prochaines tranches doivent encore :
|
||||||
|
|
||||||
- ramener le target applicatif de `debug` à `info` ou `warn` avant clôture de la release ;
|
- ramener le target applicatif de `debug` à `info` ou `warn` avant clôture de la release ;
|
||||||
- démontrer le routing `trace/debug/info/warn/error`, targets et domains avant/après hot reload avec le panneau Test Logging ;
|
|
||||||
- étudier puis intégrer le retour Rust -> console WebKit via `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, afin de pouvoir utiliser `attachConsole()` sans second subscriber, double émission ni boucle avec le bridge KSP.
|
- étudier puis intégrer le retour Rust -> console WebKit via `tauri_plugin_tracing::WebviewLayer` dans le subscriber possédé par `ksp-logging-lib`, afin de pouvoir utiliser `attachConsole()` sans second subscriber, double émission ni boucle avec le bridge KSP.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||||
<!-- version: 19 -->
|
<!-- version: 20 -->
|
||||||
|
|
||||||
# Utilisation de `ksp-app-config-desk`
|
# Utilisation de `ksp-app-config-desk`
|
||||||
|
|
||||||
## État actuel
|
## État actuel
|
||||||
|
|
||||||
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et Environnement/`.env` sont fonctionnels, y compris les mutations `.env` create/update/remove et les reveals Secrets privilégiés. Logging expose maintenant un brouillon typé éditable, une persistence atomique, un hot reload sans redémarrage, un profil runtime sélectionnable indépendamment du `default_profile` et une identité de lancement observable pour isoler les fichiers persistants.
|
Le gabarit Rust/Tauri et le frontend Vite/TypeScript/SCSS sont présents. Le backend initialise Config, le runtime Logging et `AppState`. Les panneaux Documents, Profils et Environnement/`.env` sont fonctionnels, y compris les mutations `.env` create/update/remove et les reveals Secrets privilégiés. Logging expose maintenant un brouillon typé éditable, une persistence atomique, un hot reload sans redémarrage, un profil runtime sélectionnable indépendamment du `default_profile`, une identité de lancement observable pour isoler les fichiers persistants et un panneau de test du routing backend/frontend.
|
||||||
|
|
||||||
La fenêtre `splash` est visible au démarrage. Après readiness du frontend et temporisation résolue par Config, l'application effectue le fade-out, affiche/focalise `main` puis détruit `splash`. La fenêtre principale expose immédiatement la navigation monofenêtre de référence.
|
La fenêtre `splash` est visible au démarrage. Après readiness du frontend et temporisation résolue par Config, l'application effectue le fade-out, affiche/focalise `main` puis détruit `splash`. La fenêtre principale expose immédiatement la navigation monofenêtre de référence.
|
||||||
|
|
||||||
@@ -214,6 +214,23 @@ Le sélecteur **Profil édité** travaille sur un brouillon local. `logs_directo
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Test Logging — routing backend et bridge frontend
|
||||||
|
|
||||||
|
La section **Test Logging — routing contrôlé** utilise un message explicitement destiné aux logs. Ne jamais y copier une valeur `KSP_SECRET_*` / `KSPB_SECRET_*`.
|
||||||
|
|
||||||
|
Pour le backend :
|
||||||
|
|
||||||
|
1. choisir `trace`, `debug`, `info`, `warn`, `error` ou `tous` ;
|
||||||
|
2. choisir `ksp-app-config-desk` ou `ksp-app-config-desk.logging-test` ;
|
||||||
|
3. choisir un domain `absent`, `connu` (`config.logging_test`, `config.logging_runtime`, `frontend`) ou `personnalisé` ;
|
||||||
|
4. cliquer **Log backend**.
|
||||||
|
|
||||||
|
La commande `emit_logging_test` renvoie le nombre d'événements émis et la génération runtime active, mais ne duplique pas le message dans son DTO de résultat. Le test `tous` émet exactement cinq événements, un par niveau.
|
||||||
|
|
||||||
|
**Log via bridge frontend** réutilise le même message et le même niveau, mais garde le contrat du bridge : `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. En mode `tous`, cinq événements sont envoyés. Cette voie permet de vérifier que les changements de console/file sinks/target/domain appliqués par hot reload affectent aussi les événements issus de la WebView.
|
||||||
|
|
||||||
|
Scénario conseillé : émettre `tous`, modifier les filtres du profil actif, **Sauvegarder et appliquer**, réémettre exactement le même test et comparer les sinks réellement alimentés sans redémarrer l'application.
|
||||||
|
|
||||||
## Runtime Logging — profil actif, génération et fichiers par lancement
|
## Runtime Logging — profil actif, génération et fichiers par lancement
|
||||||
|
|
||||||
La section **Runtime actif** du panneau Logging ne décrit pas le brouillon : elle interroge l'état réellement installé dans `ksp-logging-lib`.
|
La section **Runtime actif** du panneau Logging ne décrit pas le brouillon : elle interroge l'état réellement installé dans `ksp-logging-lib`.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
<!-- file: crates/ksp-app-config-desk/frontend/main.html -->
|
||||||
<!-- version: 14 -->
|
<!-- version: 15 -->
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
@@ -360,6 +360,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="border rounded p-3 mb-4" aria-label="Test Logging">
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-start justify-content-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="h6 mb-1">Test Logging — routing contrôlé</h3>
|
||||||
|
<p class="text-body-secondary small mb-0">Émet des événements volontairement journalisés pour vérifier niveaux, targets, domains, sinks et hot reload. Ne colle jamais de Secret dans le message de test.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-xl-6">
|
||||||
|
<label class="form-label" for="loggingTestMessage">Message</label>
|
||||||
|
<input id="loggingTestMessage" class="form-control" type="text" maxlength="500" autocomplete="off" value="Config Desk Logging routing test">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-xl-2">
|
||||||
|
<label class="form-label" for="loggingTestLevel">Niveau</label>
|
||||||
|
<select id="loggingTestLevel" class="form-select">
|
||||||
|
<option value="trace">trace</option><option value="debug">debug</option><option value="info" selected>info</option><option value="warn">warn</option><option value="error">error</option><option value="all">tous</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5 col-xl-4">
|
||||||
|
<label class="form-label" for="loggingTestTarget">Target backend contrôlé</label>
|
||||||
|
<select id="loggingTestTarget" class="form-select font-monospace">
|
||||||
|
<option value="app">ksp-app-config-desk</option>
|
||||||
|
<option value="logging_test" selected>ksp-app-config-desk.logging-test</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label" for="loggingTestDomainMode">Domain</label>
|
||||||
|
<select id="loggingTestDomainMode" class="form-select">
|
||||||
|
<option value="absent">absent</option>
|
||||||
|
<option value="known" selected>connu</option>
|
||||||
|
<option value="custom">personnalisé</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="loggingTestKnownDomain">Domain connu</label>
|
||||||
|
<select id="loggingTestKnownDomain" class="form-select font-monospace">
|
||||||
|
<option value="config.logging_test">config.logging_test</option>
|
||||||
|
<option value="config.logging_runtime">config.logging_runtime</option>
|
||||||
|
<option value="frontend">frontend</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label" for="loggingTestCustomDomain">Domain personnalisé</label>
|
||||||
|
<input id="loggingTestCustomDomain" class="form-control font-monospace" type="text" maxlength="80" autocomplete="off" placeholder="custom.routing" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||||
|
<button id="emitLoggingTestBackend" class="btn btn-outline-primary" type="button">
|
||||||
|
<i class="fa-solid fa-terminal me-1" aria-hidden="true"></i>
|
||||||
|
Log backend
|
||||||
|
</button>
|
||||||
|
<button id="emitLoggingTestFrontend" class="btn btn-outline-secondary" type="button">
|
||||||
|
<i class="fa-solid fa-code me-1" aria-hidden="true"></i>
|
||||||
|
Log via bridge frontend
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text mb-3">Le test backend respecte le target/domain choisis. Le bridge frontend conserve volontairement son contrat fixe <code>target=ksp-app-config-desk.frontend.main</code>, <code>domain=frontend</code> et réutilise uniquement le message/niveau.</div>
|
||||||
|
<div id="loggingTestStatus" class="alert alert-secondary mb-0" role="status" aria-live="polite">Aucun événement de test émis pendant ce lancement.</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="border rounded p-3 mb-4 bg-body-tertiary" aria-label="Globals Logging">
|
<section class="border rounded p-3 mb-4 bg-body-tertiary" aria-label="Globals Logging">
|
||||||
<h3 class="h6 mb-3">Globals</h3>
|
<h3 class="h6 mb-3">Globals</h3>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
//! Frontend logging helpers routed through the KSP Logging facade.
|
//! Frontend logging helpers routed through the KSP Logging facade.
|
||||||
|
|
||||||
@@ -45,47 +45,73 @@ async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTar
|
|||||||
targetId,
|
targetId,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
try {
|
await invoke("emit_frontend_log", { payload });
|
||||||
await invoke("emit_frontend_log", { payload });
|
}
|
||||||
} catch (caughtError) {
|
|
||||||
originalConsole.error("KSP frontend logging bridge failed", caughtError);
|
function writeOriginalConsole(level: FrontendLogLevel, message: string): void {
|
||||||
}
|
return level === "trace"
|
||||||
|
? originalConsole.trace(message)
|
||||||
|
: level === "debug"
|
||||||
|
? originalConsole.debug(message)
|
||||||
|
: level === "info"
|
||||||
|
? originalConsole.info(message)
|
||||||
|
: level === "warn"
|
||||||
|
? originalConsole.warn(message)
|
||||||
|
: originalConsole.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits one frontend event and resolves only after the Rust bridge accepted it.
|
||||||
|
export async function emitFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTargetId, message: string): Promise<void> {
|
||||||
|
writeOriginalConsole(level, message);
|
||||||
|
await sendFrontendLog(level, targetId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
export function frontendTrace(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
const message = formatMessage(items);
|
const message = formatMessage(items);
|
||||||
originalConsole.trace(message);
|
originalConsole.trace(message);
|
||||||
void sendFrontendLog("trace", targetId, message);
|
void sendFrontendLog("trace", targetId, message).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
export function frontendDebug(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
const message = formatMessage(items);
|
const message = formatMessage(items);
|
||||||
originalConsole.debug(message);
|
originalConsole.debug(message);
|
||||||
void sendFrontendLog("debug", targetId, message);
|
void sendFrontendLog("debug", targetId, message).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
export function frontendInfo(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
const message = formatMessage(items);
|
const message = formatMessage(items);
|
||||||
originalConsole.info(message);
|
originalConsole.info(message);
|
||||||
void sendFrontendLog("info", targetId, message);
|
void sendFrontendLog("info", targetId, message).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
export function frontendWarn(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
const message = formatMessage(items);
|
const message = formatMessage(items);
|
||||||
originalConsole.warn(message);
|
originalConsole.warn(message);
|
||||||
void sendFrontendLog("warn", targetId, message);
|
void sendFrontendLog("warn", targetId, message).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
export function frontendError(targetId: FrontendLogTargetId, ...items: unknown[]): void {
|
||||||
const message = formatMessage(items);
|
const message = formatMessage(items);
|
||||||
originalConsole.error(message);
|
originalConsole.error(message);
|
||||||
void sendFrontendLog("error", targetId, message);
|
void sendFrontendLog("error", targetId, message).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod {
|
function buildConsoleBridge(level: FrontendLogLevel, targetId: FrontendLogTargetId, original: ConsoleMethod): ConsoleMethod {
|
||||||
return (...items: unknown[]) => {
|
return (...items: unknown[]) => {
|
||||||
original(...items);
|
original(...items);
|
||||||
void sendFrontendLog(level, targetId, formatMessage(items));
|
void sendFrontendLog(level, targetId, formatMessage(items)).catch(caughtError =>
|
||||||
|
originalConsole.error("KSP frontend logging bridge failed", caughtError),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/logging.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/logging.ts
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
//! Typed Logging editor backed by ConfigManagement persistence and KSP-owned runtime hot reload.
|
//! Typed Logging editor backed by ConfigManagement persistence and KSP-owned runtime hot reload.
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import type { LoggingTargetFilterDto } from "./bindings/ksp_app_config_desk/logg
|
|||||||
import type { LoggingRuntimeStatusDto } from "./bindings/ksp_app_config_desk/logging_runtime/LoggingRuntimeStatusDto";
|
import type { LoggingRuntimeStatusDto } from "./bindings/ksp_app_config_desk/logging_runtime/LoggingRuntimeStatusDto";
|
||||||
import { frontendDebug, frontendTrace } from "./frontend_log";
|
import { frontendDebug, frontendTrace } from "./frontend_log";
|
||||||
import { invokeKsp } from "./invoke";
|
import { invokeKsp } from "./invoke";
|
||||||
|
import { initializeLoggingTestPanel } from "./logging_test";
|
||||||
|
|
||||||
const LEVELS = ["off", "error", "warn", "info", "debug", "trace"] as const;
|
const LEVELS = ["off", "error", "warn", "info", "debug", "trace"] as const;
|
||||||
const ROTATIONS = ["never", "hourly", "daily"] as const;
|
const ROTATIONS = ["never", "hourly", "daily"] as const;
|
||||||
@@ -772,6 +773,7 @@ export function initializeLoggingPanel(): void {
|
|||||||
document.querySelector<HTMLButtonElement>("#loggingDiscardDraftConfirm")?.addEventListener("click", confirmReload);
|
document.querySelector<HTMLButtonElement>("#loggingDiscardDraftConfirm")?.addEventListener("click", confirmReload);
|
||||||
document.querySelector<HTMLButtonElement>("#addLoggingFile")?.addEventListener("click", addFile);
|
document.querySelector<HTMLButtonElement>("#addLoggingFile")?.addEventListener("click", addFile);
|
||||||
document.querySelector<HTMLButtonElement>("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter);
|
document.querySelector<HTMLButtonElement>("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter);
|
||||||
|
initializeLoggingTestPanel();
|
||||||
frontendTrace("main", "Logging editor mutation handlers installed");
|
frontendTrace("main", "Logging editor mutation handlers installed");
|
||||||
void refreshLoggingDocument("startup");
|
void refreshLoggingDocument("startup");
|
||||||
}
|
}
|
||||||
|
|||||||
122
crates/ksp-app-config-desk/frontend/ts/logging_test.ts
Normal file
122
crates/ksp-app-config-desk/frontend/ts/logging_test.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/frontend/ts/logging_test.ts
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Controlled Logging test panel for proving backend routing and the frontend bridge.
|
||||||
|
|
||||||
|
import type { LoggingTestRequestDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestRequestDto";
|
||||||
|
import type { LoggingTestResultDto } from "./bindings/ksp_app_config_desk/logging_test/LoggingTestResultDto";
|
||||||
|
import { emitFrontendLog, frontendTrace, type FrontendLogLevel } from "./frontend_log";
|
||||||
|
import { invokeKsp } from "./invoke";
|
||||||
|
|
||||||
|
const FRONTEND_BRIDGE_TARGET = "ksp-app-config-desk.frontend.main";
|
||||||
|
const FRONTEND_BRIDGE_DOMAIN = "frontend";
|
||||||
|
|
||||||
|
function inputValue(selector: string): string {
|
||||||
|
return document.querySelector<HTMLInputElement>(selector)?.value ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedValue(selector: string): string {
|
||||||
|
return document.querySelector<HTMLSelectElement>(selector)?.value ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(message: string, tone: "secondary" | "success" | "warning" | "danger" = "secondary"): void {
|
||||||
|
const status = document.querySelector<HTMLElement>("#loggingTestStatus");
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.className = `alert alert-${tone} mb-0`;
|
||||||
|
status.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDomainControls(): void {
|
||||||
|
const mode = selectedValue("#loggingTestDomainMode");
|
||||||
|
const known = document.querySelector<HTMLSelectElement>("#loggingTestKnownDomain");
|
||||||
|
const custom = document.querySelector<HTMLInputElement>("#loggingTestCustomDomain");
|
||||||
|
if (known) {
|
||||||
|
known.disabled = mode !== "known";
|
||||||
|
}
|
||||||
|
if (custom) {
|
||||||
|
custom.disabled = mode !== "custom";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function effectiveDomain(): string {
|
||||||
|
const mode = selectedValue("#loggingTestDomainMode");
|
||||||
|
if (mode === "known") {
|
||||||
|
return selectedValue("#loggingTestKnownDomain");
|
||||||
|
}
|
||||||
|
if (mode === "custom") {
|
||||||
|
return inputValue("#loggingTestCustomDomain");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRequest(): LoggingTestRequestDto {
|
||||||
|
return {
|
||||||
|
level: selectedValue("#loggingTestLevel"),
|
||||||
|
targetId: selectedValue("#loggingTestTarget"),
|
||||||
|
domainMode: selectedValue("#loggingTestDomainMode"),
|
||||||
|
domain: effectiveDomain(),
|
||||||
|
message: inputValue("#loggingTestMessage"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emitBackendTest(): Promise<void> {
|
||||||
|
const request = buildRequest();
|
||||||
|
setStatus("Émission backend en cours...");
|
||||||
|
try {
|
||||||
|
const result = await invokeKsp<LoggingTestResultDto>("main", "emit_logging_test", { request });
|
||||||
|
const domain = result.domain ?? "<absent>";
|
||||||
|
const summary = `${result.emittedEventCount} événement(s) backend émis · niveau=${result.requestedLevel} · target=${result.target}`
|
||||||
|
+ ` · domain=${domain} · génération=${result.loggingGeneration}.`;
|
||||||
|
setStatus(summary, "success");
|
||||||
|
} catch {
|
||||||
|
setStatus("Émission backend refusée. Vérifie le message, le target et le domain sélectionnés.", "danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emitFrontendLevel(level: FrontendLogLevel, message: string): Promise<void> {
|
||||||
|
await emitFrontendLog(level, "main", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function emitFrontendBridgeTest(): Promise<void> {
|
||||||
|
const message = inputValue("#loggingTestMessage").trim();
|
||||||
|
if (message.length === 0 || message.length > 500) {
|
||||||
|
setStatus("Le message de test frontend doit contenir entre 1 et 500 caractères.", "danger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const level = selectedValue("#loggingTestLevel");
|
||||||
|
setStatus("Émission via bridge frontend en cours...");
|
||||||
|
try {
|
||||||
|
if (level === "all") {
|
||||||
|
for (const item of ["trace", "debug", "info", "warn", "error"] as const) {
|
||||||
|
await emitFrontendLevel(item, `[logging-test frontend/${item}] ${message}`);
|
||||||
|
}
|
||||||
|
setStatus(
|
||||||
|
`5 événements acceptés par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`,
|
||||||
|
"success",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (level === "trace" || level === "debug" || level === "info" || level === "warn" || level === "error") {
|
||||||
|
await emitFrontendLevel(level, `[logging-test frontend/${level}] ${message}`);
|
||||||
|
setStatus(
|
||||||
|
`1 événement accepté par le bridge frontend · target=${FRONTEND_BRIDGE_TARGET} · domain=${FRONTEND_BRIDGE_DOMAIN}.`,
|
||||||
|
"success",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatus("Le niveau de test frontend sélectionné n'est pas supporté.", "danger");
|
||||||
|
} catch {
|
||||||
|
setStatus("Le bridge frontend n'a pas accepté l'événement de test.", "danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the controlled Logging test surface.
|
||||||
|
export function initializeLoggingTestPanel(): void {
|
||||||
|
document.querySelector<HTMLSelectElement>("#loggingTestDomainMode")?.addEventListener("change", updateDomainControls);
|
||||||
|
document.querySelector<HTMLButtonElement>("#emitLoggingTestBackend")?.addEventListener("click", () => void emitBackendTest());
|
||||||
|
document.querySelector<HTMLButtonElement>("#emitLoggingTestFrontend")?.addEventListener("click", () => void emitFrontendBridgeTest());
|
||||||
|
updateDomainControls();
|
||||||
|
frontendTrace("main", "Logging test panel handlers installed");
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
// file: crates/ksp-app-config-desk/frontend/ts/main.ts
|
||||||
// version: 11
|
// version: 12
|
||||||
|
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import ResizeObserver from "resize-observer-polyfill";
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
@@ -38,7 +38,7 @@ const viewCopy: Record<ViewId, { title: string; description: string }> = {
|
|||||||
},
|
},
|
||||||
logging: {
|
logging: {
|
||||||
title: "Logging",
|
title: "Logging",
|
||||||
description: "Édition typée et persistence atomique des profils, sinks et filtres Logging ; le hot reload runtime reste dans la tranche suivante.",
|
description: "Édition typée, hot reload runtime, profils actifs et panneau de test du routing Logging KSP.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/constants.rs
|
// file: crates/ksp-app-config-desk/src/constants.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
//! Application-owned tracing targets and domains.
|
//! Application-owned tracing targets and domains.
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ pub(crate) const TRACING_TARGET_FRONTEND: &str = "ksp-app-config-desk.frontend";
|
|||||||
pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-config-desk.frontend.main";
|
pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-config-desk.frontend.main";
|
||||||
/// Owning target for splash-window frontend events.
|
/// Owning target for splash-window frontend events.
|
||||||
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
|
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
|
||||||
|
/// Dedicated target used by the controlled Logging test panel.
|
||||||
|
pub(crate) const TRACING_TARGET_LOGGING_TEST: &str = "ksp-app-config-desk.logging-test";
|
||||||
/// Structured domain used while bootstrapping Config and Logging.
|
/// Structured domain used while bootstrapping Config and Logging.
|
||||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||||
/// Structured domain used by technical frontend events.
|
/// Structured domain used by technical frontend events.
|
||||||
@@ -29,3 +31,5 @@ pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets";
|
|||||||
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
||||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||||
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
||||||
|
/// Structured domain used by controlled Logging test events.
|
||||||
|
pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/errors.rs
|
// file: crates/ksp-app-config-desk/src/errors.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
//! Application-local error codes for the configuration desktop shell.
|
//! Application-local error codes for the configuration desktop shell.
|
||||||
|
|
||||||
@@ -18,6 +18,8 @@ pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp
|
|||||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
||||||
/// Frontend logging requested a target outside the application whitelist.
|
/// Frontend logging requested a target outside the application whitelist.
|
||||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
||||||
|
/// Controlled Logging test request contains an unsupported or invalid selector.
|
||||||
|
pub(crate) const ERROR_CODE_LOGGING_TEST_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_test_request_invalid");
|
||||||
|
|
||||||
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
||||||
pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing");
|
pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||||
// version: 13
|
// version: 14
|
||||||
|
|
||||||
//! Tauri desktop application for managing and validating KSP configuration.
|
//! Tauri desktop application for managing and validating KSP configuration.
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ mod errors;
|
|||||||
mod frontend_logging;
|
mod frontend_logging;
|
||||||
mod logging_editor;
|
mod logging_editor;
|
||||||
mod logging_runtime;
|
mod logging_runtime;
|
||||||
|
mod logging_test;
|
||||||
mod profiles;
|
mod profiles;
|
||||||
mod secrets;
|
mod secrets;
|
||||||
mod splash;
|
mod splash;
|
||||||
@@ -36,6 +37,7 @@ pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
|
|||||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
|
||||||
|
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
|
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
|
||||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||||
@@ -43,6 +45,7 @@ pub(crate) use self::constants::TRACING_TARGET;
|
|||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||||
|
pub(crate) use self::constants::TRACING_TARGET_LOGGING_TEST;
|
||||||
pub(crate) use self::documents::ConfigDocumentDetailDto;
|
pub(crate) use self::documents::ConfigDocumentDetailDto;
|
||||||
pub(crate) use self::documents::ConfigDocumentErrorDto;
|
pub(crate) use self::documents::ConfigDocumentErrorDto;
|
||||||
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
||||||
@@ -58,6 +61,7 @@ pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
|||||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
|
||||||
|
pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID;
|
||||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
|
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
|
||||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
|
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
|
||||||
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
|
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
|
||||||
@@ -73,6 +77,7 @@ pub(crate) use self::logging_editor::LoggingDocumentCandidateDto;
|
|||||||
pub(crate) use self::logging_editor::LoggingDocumentDto;
|
pub(crate) use self::logging_editor::LoggingDocumentDto;
|
||||||
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
|
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
|
||||||
pub(crate) use self::logging_runtime::{LoggingRuntimeFileDto, LoggingRuntimeStatusDto};
|
pub(crate) use self::logging_runtime::{LoggingRuntimeFileDto, LoggingRuntimeStatusDto};
|
||||||
|
pub(crate) use self::logging_test::{LoggingTestRequestDto, LoggingTestResultDto};
|
||||||
pub(crate) use self::profiles::ConfigProfileDetailDto;
|
pub(crate) use self::profiles::ConfigProfileDetailDto;
|
||||||
pub(crate) use self::profiles::ConfigProfileDocumentDto;
|
pub(crate) use self::profiles::ConfigProfileDocumentDto;
|
||||||
pub(crate) use self::secrets::SecretRevealRequestDto;
|
pub(crate) use self::secrets::SecretRevealRequestDto;
|
||||||
|
|||||||
428
crates/ksp-app-config-desk/src/logging_test.rs
Normal file
428
crates/ksp-app-config-desk/src/logging_test.rs
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/src/logging_test.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Controlled Logging test events for validating KSP runtime routing from Config Desk.
|
||||||
|
|
||||||
|
use ts_rs::TS; // rust-rules: derive-import
|
||||||
|
|
||||||
|
/// Controlled request emitted through the backend KSP Logging facade.
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/logging_test/LoggingTestRequestDto.ts")]
|
||||||
|
pub(crate) struct LoggingTestRequestDto {
|
||||||
|
/// Requested level: trace/debug/info/warn/error/all.
|
||||||
|
pub(crate) level: String,
|
||||||
|
/// Controlled backend target identifier.
|
||||||
|
pub(crate) target_id: String,
|
||||||
|
/// Domain selection mode: absent/known/custom.
|
||||||
|
pub(crate) domain_mode: String,
|
||||||
|
/// Selected known domain or custom domain token.
|
||||||
|
pub(crate) domain: String,
|
||||||
|
/// Human test message emitted as the event body.
|
||||||
|
pub(crate) message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safe result metadata for one controlled backend Logging test request.
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/logging_test/LoggingTestResultDto.ts")]
|
||||||
|
pub(crate) struct LoggingTestResultDto {
|
||||||
|
/// Number of Logging events emitted by the request.
|
||||||
|
pub(crate) emitted_event_count: u32,
|
||||||
|
/// Requested level selector.
|
||||||
|
pub(crate) requested_level: String,
|
||||||
|
/// Effective static KSP target used by the emitted events.
|
||||||
|
pub(crate) target: String,
|
||||||
|
/// Effective domain, or `None` when domain routing is intentionally absent.
|
||||||
|
pub(crate) domain: std::option::Option<String>,
|
||||||
|
/// Logging runtime generation active while the events were emitted.
|
||||||
|
pub(crate) logging_generation: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum LoggingTestLevel {
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum LoggingTestTarget {
|
||||||
|
App,
|
||||||
|
Dedicated,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
||||||
|
pub(crate) fn emit(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
|
||||||
|
let level = parse_level(request.level.as_str());
|
||||||
|
let level = match level {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let target = parse_target(request.target_id.as_str());
|
||||||
|
let target = match target {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let domain = resolve_domain(request.domain_mode.as_str(), request.domain.as_str());
|
||||||
|
let domain = match domain {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let message = validate_message(request.message.as_str());
|
||||||
|
let message = match message {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let runtime_status = state.logging_runtime_status();
|
||||||
|
let runtime_status = match runtime_status {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let emitted_event_count = emit_levels(level, target, domain.as_deref(), message);
|
||||||
|
return std::result::Result::Ok(LoggingTestResultDto {
|
||||||
|
emitted_event_count,
|
||||||
|
requested_level: request.level.trim().to_ascii_lowercase(),
|
||||||
|
target: target_value(target).to_owned(),
|
||||||
|
domain,
|
||||||
|
logging_generation: runtime_status.generation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_level(level: &str) -> ksp_core_lib::Result<LoggingTestLevel> {
|
||||||
|
return match level.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"trace" => std::result::Result::Ok(LoggingTestLevel::Trace),
|
||||||
|
"debug" => std::result::Result::Ok(LoggingTestLevel::Debug),
|
||||||
|
"info" => std::result::Result::Ok(LoggingTestLevel::Info),
|
||||||
|
"warn" => std::result::Result::Ok(LoggingTestLevel::Warn),
|
||||||
|
"error" => std::result::Result::Ok(LoggingTestLevel::Error),
|
||||||
|
"all" => std::result::Result::Ok(LoggingTestLevel::All),
|
||||||
|
_ => invalid_request("Logging test level is not supported"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_target(target_id: &str) -> ksp_core_lib::Result<LoggingTestTarget> {
|
||||||
|
return match target_id.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"app" => std::result::Result::Ok(LoggingTestTarget::App),
|
||||||
|
"logging_test" => std::result::Result::Ok(LoggingTestTarget::Dedicated),
|
||||||
|
_ => invalid_request("Logging test target is not supported"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_domain(domain_mode: &str, domain: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||||
|
return match domain_mode.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"absent" => std::result::Result::Ok(std::option::Option::None),
|
||||||
|
"known" => resolve_known_domain(domain),
|
||||||
|
"custom" => resolve_custom_domain(domain),
|
||||||
|
_ => invalid_request("Logging test domain mode is not supported"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_known_domain(domain: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||||
|
return match domain.trim() {
|
||||||
|
crate::TRACING_DOMAIN_LOGGING_TEST | crate::TRACING_DOMAIN_LOGGING_RUNTIME | crate::TRACING_DOMAIN_FRONTEND => {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(domain.trim().to_owned()))
|
||||||
|
},
|
||||||
|
_ => invalid_request("Logging test known domain is not supported"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_custom_domain(domain: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||||
|
let domain = domain.trim();
|
||||||
|
if domain.is_empty() || domain.len() > 80 || !domain.chars().all(is_domain_character) {
|
||||||
|
return invalid_request("Logging test custom domain is invalid");
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(std::option::Option::Some(domain.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_domain_character(character: char) -> bool {
|
||||||
|
return character.is_ascii_alphanumeric() || character == '.' || character == '_' || character == '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_message(message: &str) -> ksp_core_lib::Result<&str> {
|
||||||
|
let message = message.trim();
|
||||||
|
if message.is_empty() || message.len() > 500 {
|
||||||
|
return invalid_request("Logging test message must contain between 1 and 500 bytes");
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_request<T>(message: &str) -> ksp_core_lib::Result<T> {
|
||||||
|
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn target_value(target: LoggingTestTarget) -> &'static str {
|
||||||
|
return match target {
|
||||||
|
LoggingTestTarget::App => crate::TRACING_TARGET,
|
||||||
|
LoggingTestTarget::Dedicated => crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_levels(level: LoggingTestLevel, target: LoggingTestTarget, domain: std::option::Option<&str>, message: &str) -> u32 {
|
||||||
|
return match level {
|
||||||
|
LoggingTestLevel::Trace => {
|
||||||
|
emit_one(LoggingTestLevel::Trace, target, domain, message);
|
||||||
|
1
|
||||||
|
},
|
||||||
|
LoggingTestLevel::Debug => {
|
||||||
|
emit_one(LoggingTestLevel::Debug, target, domain, message);
|
||||||
|
1
|
||||||
|
},
|
||||||
|
LoggingTestLevel::Info => {
|
||||||
|
emit_one(LoggingTestLevel::Info, target, domain, message);
|
||||||
|
1
|
||||||
|
},
|
||||||
|
LoggingTestLevel::Warn => {
|
||||||
|
emit_one(LoggingTestLevel::Warn, target, domain, message);
|
||||||
|
1
|
||||||
|
},
|
||||||
|
LoggingTestLevel::Error => {
|
||||||
|
emit_one(LoggingTestLevel::Error, target, domain, message);
|
||||||
|
1
|
||||||
|
},
|
||||||
|
LoggingTestLevel::All => {
|
||||||
|
emit_one(LoggingTestLevel::Trace, target, domain, message);
|
||||||
|
emit_one(LoggingTestLevel::Debug, target, domain, message);
|
||||||
|
emit_one(LoggingTestLevel::Info, target, domain, message);
|
||||||
|
emit_one(LoggingTestLevel::Warn, target, domain, message);
|
||||||
|
emit_one(LoggingTestLevel::Error, target, domain, message);
|
||||||
|
5
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_one(level: LoggingTestLevel, target: LoggingTestTarget, domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match target {
|
||||||
|
LoggingTestTarget::App => emit_app_target(level, domain, message),
|
||||||
|
LoggingTestTarget::Dedicated => emit_dedicated_target(level, domain, message),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_target(level: LoggingTestLevel, domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match level {
|
||||||
|
LoggingTestLevel::Trace => emit_app_trace(domain, message),
|
||||||
|
LoggingTestLevel::Debug => emit_app_debug(domain, message),
|
||||||
|
LoggingTestLevel::Info => emit_app_info(domain, message),
|
||||||
|
LoggingTestLevel::Warn => emit_app_warn(domain, message),
|
||||||
|
LoggingTestLevel::Error => emit_app_error(domain, message),
|
||||||
|
LoggingTestLevel::All => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_target(level: LoggingTestLevel, domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match level {
|
||||||
|
LoggingTestLevel::Trace => emit_dedicated_trace(domain, message),
|
||||||
|
LoggingTestLevel::Debug => emit_dedicated_debug(domain, message),
|
||||||
|
LoggingTestLevel::Info => emit_dedicated_info(domain, message),
|
||||||
|
LoggingTestLevel::Warn => emit_dedicated_warn(domain, message),
|
||||||
|
LoggingTestLevel::Error => emit_dedicated_error(domain, message),
|
||||||
|
LoggingTestLevel::All => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_trace(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "trace",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "trace",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_debug(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "debug",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "debug",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_info(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::info!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "info",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::info!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "info",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_warn(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "warn",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "warn",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_app_error(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::error!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "error",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::error!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "error",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_trace(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "trace",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::trace!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "trace",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_debug(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "debug",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::debug!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "debug",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_info(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::info!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "info",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::info!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "info",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_warn(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "warn",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "warn",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_dedicated_error(domain: std::option::Option<&str>, message: &str) {
|
||||||
|
return match domain {
|
||||||
|
std::option::Option::Some(domain) => ksp_logging_lib::error!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
domain = domain,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "error",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
std::option::Option::None => ksp_logging_lib::error!(
|
||||||
|
target: crate::TRACING_TARGET_LOGGING_TEST,
|
||||||
|
action = "logging_test",
|
||||||
|
test_source = "backend",
|
||||||
|
test_level = "error",
|
||||||
|
"{message}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "../unit_tests/logging_test.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||||
// version: 14
|
// version: 15
|
||||||
|
|
||||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
|||||||
get_logging_runtime_status,
|
get_logging_runtime_status,
|
||||||
apply_logging_profile,
|
apply_logging_profile,
|
||||||
save_logging_document,
|
save_logging_document,
|
||||||
|
emit_logging_test,
|
||||||
emit_frontend_log,
|
emit_frontend_log,
|
||||||
splash_frontend_ready
|
splash_frontend_ready
|
||||||
]);
|
]);
|
||||||
@@ -216,6 +217,18 @@ fn save_logging_document(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn emit_logging_test(
|
||||||
|
request: crate::LoggingTestRequestDto,
|
||||||
|
state: tauri::State<'_, crate::AppState>,
|
||||||
|
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
|
||||||
|
let result = crate::logging_test::emit(&state, request);
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||||
let result = crate::emit_frontend_log_event(payload);
|
let result = crate::emit_frontend_log_event(payload);
|
||||||
|
|||||||
51
crates/ksp-app-config-desk/unit_tests/logging_test.rs
Normal file
51
crates/ksp-app-config-desk/unit_tests/logging_test.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// file: crates/ksp-app-config-desk/unit_tests/logging_test.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
use super::{LoggingTestLevel, LoggingTestTarget};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn controlled_logging_test_levels_include_all_five_levels_and_batch_mode() {
|
||||||
|
assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(LoggingTestLevel::Trace)));
|
||||||
|
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(LoggingTestLevel::Debug)));
|
||||||
|
assert!(matches!(super::parse_level("info"), std::result::Result::Ok(LoggingTestLevel::Info)));
|
||||||
|
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(LoggingTestLevel::Warn)));
|
||||||
|
assert!(matches!(super::parse_level("error"), std::result::Result::Ok(LoggingTestLevel::Error)));
|
||||||
|
assert!(matches!(super::parse_level("all"), std::result::Result::Ok(LoggingTestLevel::All)));
|
||||||
|
assert!(super::parse_level("fatal").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn controlled_logging_test_targets_are_static_ksp_targets() {
|
||||||
|
assert!(matches!(super::parse_target("app"), std::result::Result::Ok(LoggingTestTarget::App)));
|
||||||
|
assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(LoggingTestTarget::Dedicated)));
|
||||||
|
assert_eq!(super::target_value(LoggingTestTarget::App), crate::TRACING_TARGET);
|
||||||
|
assert_eq!(super::target_value(LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST);
|
||||||
|
assert!(super::parse_target("external").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn logging_test_domain_modes_cover_absent_known_and_bounded_custom_values() {
|
||||||
|
let absent = super::resolve_domain("absent", "ignored");
|
||||||
|
assert!(matches!(absent, std::result::Result::Ok(std::option::Option::None)));
|
||||||
|
let known = super::resolve_domain("known", crate::TRACING_DOMAIN_LOGGING_TEST);
|
||||||
|
assert!(matches!(
|
||||||
|
known.as_ref(),
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) if value == crate::TRACING_DOMAIN_LOGGING_TEST
|
||||||
|
));
|
||||||
|
let custom = super::resolve_domain("custom", "custom.route_1-test");
|
||||||
|
assert!(matches!(
|
||||||
|
custom.as_ref(),
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) if value == "custom.route_1-test"
|
||||||
|
));
|
||||||
|
assert!(super::resolve_domain("custom", "custom route").is_err());
|
||||||
|
assert!(super::resolve_domain("custom", "").is_err());
|
||||||
|
assert!(super::resolve_domain("known", "unknown.domain").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn logging_test_message_is_required_and_bounded() {
|
||||||
|
let message = super::validate_message(" test ");
|
||||||
|
assert!(matches!(message, std::result::Result::Ok("test")));
|
||||||
|
assert!(super::validate_message(" ").is_err());
|
||||||
|
assert!(super::validate_message("x".repeat(501).as_str()).is_err());
|
||||||
|
}
|
||||||
91
deltas/0.1.4/pre.017.md
Normal file
91
deltas/0.1.4/pre.017.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<!-- file: deltas/0.1.4/pre.017.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.1.4-pre.017` — panneau Test Logging
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
`0.1.4-pre.016-fix.002` est validée : `cargo fmt --all`, `cargo check --workspace` et `cargo clippy --workspace --all-targets` sont propres ; 56 tests `ksp-app-config-desk`, 88 tests `ksp-config-lib`, 37 tests unitaires `ksp-logging-lib`, les audits ownership, APIs publiques et tests runtime/intégration passent.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Ajouter une surface manuelle et contrôlée pour démontrer le routing réel du runtime Logging avant/après hot reload, sans contourner `ksp-logging-lib` et sans introduire de target dynamique arbitraire.
|
||||||
|
|
||||||
|
## Changements
|
||||||
|
|
||||||
|
### Backend contrôlé
|
||||||
|
|
||||||
|
Config Desk ajoute `emit_logging_test` et deux DTO TS-RS :
|
||||||
|
|
||||||
|
- `LoggingTestRequestDto` ;
|
||||||
|
- `LoggingTestResultDto`.
|
||||||
|
|
||||||
|
Le service accepte :
|
||||||
|
|
||||||
|
- niveau `trace`, `debug`, `info`, `warn`, `error` ou `all` ;
|
||||||
|
- target logique `app` -> `ksp-app-config-desk` ;
|
||||||
|
- target logique `logging_test` -> `ksp-app-config-desk.logging-test` ;
|
||||||
|
- domain `absent`, `known` ou `custom`.
|
||||||
|
|
||||||
|
Les domains connus sont limités à `config.logging_test`, `config.logging_runtime` et `frontend`. Un domain personnalisé est borné à 80 caractères ASCII alphanumériques plus `.`, `_` et `-`. Le message doit contenir de 1 à 500 octets.
|
||||||
|
|
||||||
|
Les targets restent des callsites statiques. L'application n'importe pas `tracing` et émet exclusivement via les macros `ksp-logging-lib`.
|
||||||
|
|
||||||
|
Le mode `all` émet exactement cinq événements, un par niveau. Le résultat ne réexpose pas le message ; il contient uniquement le nombre d'événements, le niveau demandé, target/domain effectifs et la génération runtime active.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
La vue Logging ajoute **Test Logging — routing contrôlé** avec :
|
||||||
|
|
||||||
|
- message ;
|
||||||
|
- niveau ;
|
||||||
|
- target backend contrôlé ;
|
||||||
|
- domain absent/connu/personnalisé ;
|
||||||
|
- **Log backend** ;
|
||||||
|
- **Log via bridge frontend**.
|
||||||
|
|
||||||
|
La voie frontend réutilise le bridge existant et conserve son contrat fixe `target=ksp-app-config-desk.frontend.main`, `domain=frontend`. Elle permet de comparer le routing d'un événement backend et celui d'un événement issu de la WebView dans les mêmes sinks.
|
||||||
|
|
||||||
|
Le message du panneau est volontairement journalisé : l'UI rappelle explicitement de ne jamais y placer de Secret.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Les tests ajoutés couvrent :
|
||||||
|
|
||||||
|
- les cinq niveaux et le mode batch `all` ;
|
||||||
|
- les deux targets statiques ;
|
||||||
|
- les trois modes de domain ;
|
||||||
|
- le rejet des domains personnalisés non sûrs ;
|
||||||
|
- la borne du message ;
|
||||||
|
- les exports TS-RS des DTO.
|
||||||
|
|
||||||
|
## Version technique
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.1.4-pre.17
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation attendue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-app-config-desk
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo test -p ksp-logging-lib
|
||||||
|
cargo tauri dev -c crates/ksp-app-config-desk/tauri.conf.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Scénario fonctionnel :
|
||||||
|
|
||||||
|
1. avec le profil actif actuel, lancer `tous` sur `ksp-app-config-desk.logging-test` + `config.logging_test` ;
|
||||||
|
2. noter quels niveaux apparaissent en console et dans les file sinks ;
|
||||||
|
3. modifier niveaux/targets/domains, **Sauvegarder et appliquer** ;
|
||||||
|
4. relancer exactement le même test sans redémarrage ;
|
||||||
|
5. vérifier que le routing suit immédiatement le nouveau runtime ;
|
||||||
|
6. répéter avec **Log via bridge frontend** et vérifier le target `ksp-app-config-desk.frontend.main` / domain `frontend`.
|
||||||
|
|
||||||
|
## Suite
|
||||||
|
|
||||||
|
`0.1.4-pre.018` couvrira robustesse/extensibilité/tests desktop : source invalide au démarrage, audits, tests frontend/Tauri/build et ajout futur d'un `file_id`/editor sans refonte du shell.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
<!-- file: docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md -->
|
||||||
<!-- version: 20 -->
|
<!-- version: 21 -->
|
||||||
|
|
||||||
# Plan `0.1.4` — `ksp-app-config-desk`
|
# Plan `0.1.4` — `ksp-app-config-desk`
|
||||||
|
|
||||||
@@ -1247,13 +1247,13 @@ pre.015 Logging editor — mutations/persistence [réalisé]
|
|||||||
- Recharger le document = resynchronisation du brouillon uniquement
|
- Recharger le document = resynchronisation du brouillon uniquement
|
||||||
- rollback source + ancien runtime conservé si l’application échoue
|
- rollback source + ancien runtime conservé si l’application échoue
|
||||||
|
|
||||||
pre.016 Logging runtime — compléments [en cours]
|
pre.016 Logging runtime — compléments [réalisé]
|
||||||
- fichiers applicatifs uniques par lancement avec timestamp de démarrage
|
- fichiers applicatifs uniques par lancement avec timestamp de démarrage
|
||||||
- sélection explicite du profil à appliquer indépendamment du default_profile
|
- sélection explicite du profil à appliquer indépendamment du default_profile
|
||||||
- observabilité metadata/génération du runtime
|
- observabilité metadata/génération du runtime
|
||||||
- consolidation des scénarios de rollback/runtime
|
- consolidation des scénarios de rollback/runtime
|
||||||
|
|
||||||
pre.017 panneau Test Logging
|
pre.017 panneau Test Logging [en cours]
|
||||||
- champ message
|
- champ message
|
||||||
- niveau trace/debug/info/warn/error/tous
|
- niveau trace/debug/info/warn/error/tous
|
||||||
- select target KSP contrôlé
|
- select target KSP contrôlé
|
||||||
|
|||||||
BIN
logs_v0.1.4-pre.017.zip
Normal file
BIN
logs_v0.1.4-pre.017.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user