v0.1.4-pre.017
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 88
|
||||
# version: 89
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.4-pre.16.fix.2"
|
||||
version = "0.1.4-pre.17"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-config-desk/README.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# `ksp-app-config-desk`
|
||||
|
||||
@@ -113,7 +113,13 @@ main -> ksp-app-config-desk.frontend.main
|
||||
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
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<!-- file: crates/ksp-app-config-desk/TODO.md -->
|
||||
<!-- version: 16 -->
|
||||
<!-- version: 17 -->
|
||||
|
||||
# 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 :
|
||||
|
||||
- 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.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- file: crates/ksp-app-config-desk/USAGE.md -->
|
||||
<!-- version: 19 -->
|
||||
<!-- version: 20 -->
|
||||
|
||||
# Utilisation de `ksp-app-config-desk`
|
||||
|
||||
## É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.
|
||||
|
||||
@@ -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
|
||||
|
||||
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 -->
|
||||
<!-- version: 14 -->
|
||||
<!-- version: 15 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -360,6 +360,66 @@
|
||||
</div>
|
||||
</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">
|
||||
<h3 class="h6 mb-3">Globals</h3>
|
||||
<div class="row g-3">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/frontend/ts/frontend_log.ts
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Frontend logging helpers routed through the KSP Logging facade.
|
||||
|
||||
@@ -45,47 +45,73 @@ async function sendFrontendLog(level: FrontendLogLevel, targetId: FrontendLogTar
|
||||
targetId,
|
||||
message,
|
||||
};
|
||||
try {
|
||||
await invoke("emit_frontend_log", { payload });
|
||||
} catch (caughtError) {
|
||||
originalConsole.error("KSP frontend logging bridge failed", caughtError);
|
||||
}
|
||||
await invoke("emit_frontend_log", { payload });
|
||||
}
|
||||
|
||||
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 {
|
||||
const message = formatMessage(items);
|
||||
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 {
|
||||
const message = formatMessage(items);
|
||||
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 {
|
||||
const message = formatMessage(items);
|
||||
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 {
|
||||
const message = formatMessage(items);
|
||||
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 {
|
||||
const message = formatMessage(items);
|
||||
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 {
|
||||
return (...items: unknown[]) => {
|
||||
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
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! 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 { frontendDebug, frontendTrace } from "./frontend_log";
|
||||
import { invokeKsp } from "./invoke";
|
||||
import { initializeLoggingTestPanel } from "./logging_test";
|
||||
|
||||
const LEVELS = ["off", "error", "warn", "info", "debug", "trace"] 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>("#addLoggingFile")?.addEventListener("click", addFile);
|
||||
document.querySelector<HTMLButtonElement>("#addLoggingTargetFilter")?.addEventListener("click", addTargetFilter);
|
||||
initializeLoggingTestPanel();
|
||||
frontendTrace("main", "Logging editor mutation handlers installed");
|
||||
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
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
import "bootstrap";
|
||||
import ResizeObserver from "resize-observer-polyfill";
|
||||
@@ -38,7 +38,7 @@ const viewCopy: Record<ViewId, { title: string; description: string }> = {
|
||||
},
|
||||
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
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! 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";
|
||||
/// Owning target for splash-window frontend events.
|
||||
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.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||
/// 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";
|
||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||
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
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! 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");
|
||||
/// 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");
|
||||
/// 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.
|
||||
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
|
||||
// version: 13
|
||||
// version: 14
|
||||
|
||||
//! Tauri desktop application for managing and validating KSP configuration.
|
||||
|
||||
@@ -17,6 +17,7 @@ mod errors;
|
||||
mod frontend_logging;
|
||||
mod logging_editor;
|
||||
mod logging_runtime;
|
||||
mod logging_test;
|
||||
mod profiles;
|
||||
mod secrets;
|
||||
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_LOGGING_EDITOR;
|
||||
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_SECRETS;
|
||||
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_MAIN;
|
||||
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::ConfigDocumentErrorDto;
|
||||
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_LOGGING_BOOTSTRAP_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_PROJECTION_FAILED;
|
||||
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::LoggingDocumentSaveResultDto;
|
||||
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::ConfigProfileDocumentDto;
|
||||
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
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
//! 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,
|
||||
apply_logging_profile,
|
||||
save_logging_document,
|
||||
emit_logging_test,
|
||||
emit_frontend_log,
|
||||
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]
|
||||
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
||||
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 -->
|
||||
<!-- version: 20 -->
|
||||
<!-- version: 21 -->
|
||||
|
||||
# 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
|
||||
- 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
|
||||
- sélection explicite du profil à appliquer indépendamment du default_profile
|
||||
- observabilité metadata/génération du runtime
|
||||
- consolidation des scénarios de rollback/runtime
|
||||
|
||||
pre.017 panneau Test Logging
|
||||
pre.017 panneau Test Logging [en cours]
|
||||
- champ message
|
||||
- niveau trace/debug/info/warn/error/tous
|
||||
- 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