278 lines
9.5 KiB
Markdown
278 lines
9.5 KiB
Markdown
<!-- file: crates/ksp-logging-lib/USAGE.md -->
|
|
<!-- version: 6 -->
|
|
|
|
# Utilisation de ksp-logging-lib
|
|
|
|
## Target d'une crate consommatrice
|
|
|
|
Chaque crate KSP comportementale fournit explicitement son target, égal au nom Cargo de la crate :
|
|
|
|
```rust
|
|
const LOGGING_TARGET: &str = "ksp-store-lib";
|
|
|
|
ksp_logging_lib::trace!(
|
|
target: LOGGING_TARGET,
|
|
domain = "store",
|
|
component = "postgres",
|
|
operation = "load_transactions",
|
|
"executing store operation"
|
|
);
|
|
```
|
|
|
|
Les champs `domain`, `component`, `operation` et autres champs structurés sont ajoutés par le caller lorsqu'ils sont utiles ; ils ne remplacent pas le target propriétaire.
|
|
|
|
## Contrat des outputs
|
|
|
|
`LoggingSettings` possède :
|
|
|
|
- le `default_filter` global de takeover KSP ;
|
|
- les `TargetFilter` globaux ;
|
|
- la politique `SpanEvents` ;
|
|
- une console déclarée optionnellement ;
|
|
- zéro, un ou plusieurs `FileSettings`.
|
|
|
|
Chaque output possède un `OutputFilter` indépendant. Son niveau, ses targets et ses domains s'appliquent **en plus** de la politique globale. `*` signifie « tous » dans la dimension concernée.
|
|
|
|
Les formats publics sont :
|
|
|
|
```text
|
|
Human
|
|
Compact
|
|
Pretty
|
|
Json
|
|
```
|
|
|
|
Une sortie fichier possède un `output_id` stable et unique dans `LoggingSettings`. Cet identifiant appartient au runtime Logging et ne doit pas être confondu avec les `file_id` utilisés par `ksp-config-lib` pour identifier ses documents.
|
|
|
|
Les fichiers persistants interdisent `ansi = true`.
|
|
|
|
## Runtime multi-output et routing `domain`
|
|
|
|
`0.1.3-pre.005` active le multi-sink, les formats et le routing niveau/target ; `0.1.3-pre.006` complète le routing structuré `domain`. Le runtime supporte donc réellement :
|
|
|
|
- plusieurs fichiers simultanés ;
|
|
- les formats `Human`, `Compact`, `Pretty` et `Json` ;
|
|
- l'ANSI console ;
|
|
- le routing par niveau, target et `domain` pour chaque output ;
|
|
- le comptage cumulatif des lignes abandonnées par `output_id` fichier ;
|
|
- le hot reload de ces sorties sans réinstaller le subscriber global.
|
|
|
|
Le `domain` n'est jamais transformé en target. Un event portant directement `domain` utilise cette valeur ; sinon il hérite du `domain` effectif de son span. Un span explicite remplace le `domain` de son parent, tandis qu'un span sans `domain` l'hérite. Les lifecycle events d'un span utilisent le même `domain` effectif.
|
|
|
|
Pour `domains[]`, `["*"]` accepte tous les événements, y compris ceux sans `domain`. Un selector nommé correspond par préfixe et ne sélectionne pas une entrée sans `domain`.
|
|
|
|
Les helpers `ConsoleSettings::stdout()` et `ConsoleSettings::stderr()` restent des raccourcis `Human`, sans ANSI et sans restriction supplémentaire par output.
|
|
|
|
## Initialisation
|
|
|
|
```rust
|
|
let file = ksp_logging_lib::FileSettings::new(
|
|
"file.worker",
|
|
true,
|
|
"logs",
|
|
"worker.log",
|
|
ksp_logging_lib::FileRotation::Daily,
|
|
ksp_logging_lib::LogFormat::Human,
|
|
ksp_logging_lib::OutputFilter::unrestricted(),
|
|
);
|
|
|
|
let settings = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Info,
|
|
ksp_logging_lib::SpanEvents::NewAndClose,
|
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
|
std::vec![file],
|
|
);
|
|
|
|
let initialize_result = ksp_logging_lib::initialize(&settings);
|
|
let mut logging_guard = match initialize_result {
|
|
std::result::Result::Ok(guard) => guard,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
```
|
|
|
|
Une configuration sans output actif est valide et installe une infrastructure initialement silencieuse qui pourra être activée plus tard par hot reload.
|
|
|
|
|
|
|
|
## Identité de lancement et isolation des fichiers persistants
|
|
|
|
Une application qui utilise des sorties fichiers persistantes peut attacher une identité stable au runtime :
|
|
|
|
```rust
|
|
let identity = ksp_logging_lib::LoggingRuntimeIdentity::new(
|
|
"ksp-app-config-desk",
|
|
"20260816-182519.123Z-p4242",
|
|
);
|
|
let identity = match identity {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
|
|
let initialize_result = ksp_logging_lib::initialize_with_identity(&settings, &identity);
|
|
let mut logging_guard = match initialize_result {
|
|
std::result::Result::Ok(guard) => guard,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
```
|
|
|
|
Les composants de l'identité n'acceptent que des caractères ASCII alphanumériques et `._-`; les séparateurs de path et espaces sont refusés.
|
|
|
|
Si un `FileSettings` configure `ksp-debug.log`, l'identité ci-dessus produit un prefix runtime :
|
|
|
|
```text
|
|
ksp-app-config-desk.20260816-182519.123Z-p4242.ksp-debug.log
|
|
```
|
|
|
|
Le `FileSettings` d'origine reste `ksp-debug.log`. `reinitialize(&mut logging_guard, ...)` réutilise automatiquement l'identité stockée dans le guard : un hot reload ne crée donc pas une fausse nouvelle identité de lancement.
|
|
|
|
Les outputs réellement actifs sont inspectables sans exposer les writers :
|
|
|
|
```rust
|
|
for file in logging_guard.active_file_outputs() {
|
|
let output_id = file.output_id();
|
|
let directory = file.directory();
|
|
let prefix = file.file_name_prefix();
|
|
let rotation = file.rotation();
|
|
}
|
|
```
|
|
|
|
`initialize()` reste disponible pour les consommateurs qui n'ont pas besoin d'identité de lancement. `initialize_with_identity()` est la forme attendue pour les applications KSP qui activent des logs persistants et doivent empêcher la fusion de plusieurs lancements dans le même fichier.
|
|
|
|
## Construction d'un runtime multi-output
|
|
|
|
Le runtime peut activer plusieurs sorties ayant des formats et filtres niveau/target/domain distincts :
|
|
|
|
```rust
|
|
let console = ksp_logging_lib::ConsoleSettings::new(
|
|
true,
|
|
ksp_logging_lib::ConsoleOutput::Stderr,
|
|
true,
|
|
ksp_logging_lib::LogFormat::Compact,
|
|
ksp_logging_lib::OutputFilter::new(
|
|
ksp_logging_lib::LogFilterLevel::Debug,
|
|
std::vec!["*".to_string()],
|
|
std::vec!["*".to_string()],
|
|
),
|
|
);
|
|
|
|
let error_file = ksp_logging_lib::FileSettings::new(
|
|
"file.config.error",
|
|
true,
|
|
"logs/config",
|
|
"error.jsonl",
|
|
ksp_logging_lib::FileRotation::Daily,
|
|
ksp_logging_lib::LogFormat::Json,
|
|
ksp_logging_lib::OutputFilter::new(
|
|
ksp_logging_lib::LogFilterLevel::Error,
|
|
std::vec!["ksp-config-lib".to_string()],
|
|
std::vec!["*".to_string()],
|
|
),
|
|
);
|
|
|
|
let settings = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Warn,
|
|
ksp_logging_lib::SpanEvents::NewAndClose,
|
|
std::option::Option::Some(console),
|
|
std::vec![error_file],
|
|
);
|
|
|
|
let validation = settings.validate();
|
|
```
|
|
|
|
`validate()` vérifie le contrat structurel. `initialize/reinitialize` appliquent ensuite conjointement niveau, target et `domain` par output.
|
|
|
|
Par exemple, un fichier réservé au domaine Store peut utiliser :
|
|
|
|
```rust
|
|
ksp_logging_lib::OutputFilter::new(
|
|
ksp_logging_lib::LogFilterLevel::Debug,
|
|
std::vec!["ksp-store-lib".to_string()],
|
|
std::vec!["store".to_string()],
|
|
)
|
|
```
|
|
|
|
Un event `domain = "store.postgres"` correspond au selector `store`; un event sans `domain` n'y correspond pas.
|
|
|
|
## Hot reload
|
|
|
|
Une configuration peut être appliquée sans redémarrer le processus ou le worker :
|
|
|
|
```rust
|
|
let debug_settings = ksp_logging_lib::LoggingSettings::new(
|
|
ksp_logging_lib::LogFilterLevel::Info,
|
|
ksp_logging_lib::SpanEvents::NewAndClose,
|
|
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
|
std::vec::Vec::new(),
|
|
)
|
|
.with_target_filter(ksp_logging_lib::TargetFilter::new(
|
|
"ksp-store-lib",
|
|
ksp_logging_lib::LogFilterLevel::Debug,
|
|
));
|
|
|
|
let reload_result = ksp_logging_lib::reinitialize(&mut logging_guard, &debug_settings);
|
|
if let std::result::Result::Err(error) = reload_result {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
```
|
|
|
|
La nouvelle configuration est préparée avant la bascule. Si sa validation ou la création d'un nouveau sink échoue, l'ancienne configuration reste active.
|
|
|
|
## Spans synchrones
|
|
|
|
```rust
|
|
let span = ksp_logging_lib::trace_span!(
|
|
target: LOGGING_TARGET,
|
|
"materialize_transaction",
|
|
domain = "store"
|
|
);
|
|
|
|
let output = span.in_scope(|| {
|
|
return materialize_transaction();
|
|
});
|
|
```
|
|
|
|
Avec `SpanEvents::NewAndClose`, le formatter produit les événements de création/fermeture et les temps `busy` / `idle` à la fermeture.
|
|
|
|
## Spans async
|
|
|
|
Une `Future` doit être instrumentée avec `ksp_logging_lib::instrument` ; un guard d'entrée de span ne doit pas être conservé à travers `.await` :
|
|
|
|
```rust
|
|
let span = ksp_logging_lib::trace_span!(
|
|
target: LOGGING_TARGET,
|
|
"fetch_account",
|
|
domain = "transport"
|
|
);
|
|
|
|
let output = ksp_logging_lib::instrument(span, fetch_account()).await;
|
|
```
|
|
|
|
La future instrumentée entre/sort du span pendant ses polls et lors de son `Drop`, conformément au contrat de la primitive `tracing` sous-jacente.
|
|
|
|
## Lignes abandonnées
|
|
|
|
Les sorties utilisent des queues lossy afin de ne pas appliquer de backpressure au hot path. La vue agrégée console/fichier reste disponible :
|
|
|
|
```rust
|
|
let dropped = logging_guard.dropped_lines();
|
|
ksp_logging_lib::warn!(
|
|
target: LOGGING_TARGET,
|
|
console = dropped.console(),
|
|
file = dropped.file(),
|
|
total = dropped.total(),
|
|
"logging queues dropped lines"
|
|
);
|
|
```
|
|
|
|
Pour un fichier précis :
|
|
|
|
```rust
|
|
let dropped_for_output = logging_guard.dropped_file_lines("file.worker");
|
|
```
|
|
|
|
Le compteur par `output_id` reste cumulatif à travers les hot reloads tant que le même `LoggingGuard` est conservé.
|
|
|
|
## Instrumentation async et executor
|
|
|
|
`instrument(span, future)` accepte une `Future` standard et ne dépend d'aucun executor particulier. Tokio n'est présent qu'en `dev-dependency` pour valider la surface sur un executor réel, y compris après plusieurs suspensions et sur un runtime multi-thread.
|