v0.1.2-pre.006

This commit is contained in:
2026-08-14 20:29:48 +02:00
parent d96f41fb8e
commit 8a0c119878
10 changed files with 745 additions and 35 deletions

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-logging-lib/Cargo.toml
# version: 3
# version: 4
[package]
name = "ksp-logging-lib"
@@ -13,5 +13,8 @@ tracing.workspace = true
tracing-subscriber.workspace = true
tracing-appender.workspace = true
[dev-dependencies]
tokio.workspace = true
[lints]
workspace = true

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-logging-lib/README.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# ksp-logging-lib
@@ -19,6 +19,8 @@ La crate possède :
- les `WorkerGuard`, compteurs de lignes abandonnées, rotation fichier et stripping ANSI ;
- l'instrumentation de scopes synchrones et de `Future` async.
L'API async de production reste indépendante de tout executor. Tokio est utilisé uniquement comme `dev-dependency` afin de valider `instrument(...)` sur un executor réel en mode current-thread et multi-thread ; il ne fait pas partie des dépendances runtime de la crate.
## Frontières
Une crate KSP comportementale qui journalise son activité dépend de `ksp-logging-lib` et n'utilise pas directement `tracing`, `tracing-subscriber` ou `tracing-appender`.

View File

@@ -1,14 +1,14 @@
<!-- file: crates/ksp-logging-lib/TODO.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# TODO ksp-logging-lib
## À fermer avant la stable 0.1.2
- exécuter les validations Cargo complètes de `pre.005` puis de la tranche finale ;
- exécuter explicitement le probe d'overhead ignoré et conserver son résultat dans le delta de validation approprié ;
- vérifier une dernière fois le graphe de dépendances/features et l'absence de contournement de la façade KSP ;
- synchroniser la documentation finale et le prompt `0.1.3 — ksp-config-lib`.
- exécuter les validations Cargo complètes de `pre.006`, y compris les tests Tokio current-thread/multi-thread ;
- vérifier que `cargo tree -p ksp-logging-lib -e normal` ne contient pas Tokio et que Tokio apparaît uniquement dans le graphe dev attendu ;
- refaire l'audit final du graphe/features et de l'ownership de la stack tracing ;
- après validation de la prerelease finale, préparer `rel.001`, publier `workspace.package.version = "0.1.2"` et taguer `v0.1.2` conformément aux règles de release.
## Capacités différées

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-logging-lib/USAGE.md -->
<!-- version: 1 -->
<!-- version: 2 -->
# Utilisation de ksp-logging-lib
@@ -116,3 +116,15 @@ ksp_logging_lib::warn!(
"logging queues dropped lines"
);
```
## Instrumentation async et executor
`instrument(span, future)` accepte une `Future` standard et ne dépend d'aucun executor particulier :
```rust
let span = ksp_logging_lib::trace_span!(target: LOGGING_TARGET, "load_transactions");
let result = ksp_logging_lib::instrument(span, async_operation()).await;
```
La crate ne requiert pas Tokio en production. 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. Un consumer peut donc utiliser l'executor adapté à son propre contexte sans que Logging lui en impose un.

View File

@@ -0,0 +1,118 @@
// file: crates/ksp-logging-lib/tests/tokio_span.rs
// version: 1
//! Integration tests for KSP span instrumentation on a real Tokio executor.
const TEST_TARGET: &str = "ksp-logging-lib";
#[derive(Clone)]
struct CountingSubscriber {
enters: std::sync::Arc<std::sync::atomic::AtomicU64>,
exits: std::sync::Arc<std::sync::atomic::AtomicU64>,
next_id: std::sync::Arc<std::sync::atomic::AtomicU64>,
}
impl CountingSubscriber {
fn new(enters: std::sync::Arc<std::sync::atomic::AtomicU64>, exits: std::sync::Arc<std::sync::atomic::AtomicU64>) -> Self {
return Self { enters, exits, next_id: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)) };
}
}
impl tracing::Subscriber for CountingSubscriber {
fn enabled(&self, _metadata: &tracing::Metadata<'_>) -> bool {
return true;
}
fn new_span(&self, _span: &tracing::span::Attributes<'_>) -> tracing::span::Id {
let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return tracing::span::Id::from_u64(id);
}
fn record(&self, _span: &tracing::span::Id, _values: &tracing::span::Record<'_>) {
return;
}
fn record_follows_from(&self, _span: &tracing::span::Id, _follows: &tracing::span::Id) {
return;
}
fn event(&self, _event: &tracing::Event<'_>) {
return;
}
fn enter(&self, _span: &tracing::span::Id) {
self.enters.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return;
}
fn exit(&self, _span: &tracing::span::Id) {
self.exits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return;
}
}
fn test_span(enters: std::sync::Arc<std::sync::atomic::AtomicU64>, exits: std::sync::Arc<std::sync::atomic::AtomicU64>) -> ksp_logging_lib::Span {
let subscriber = CountingSubscriber::new(enters, exits);
return tracing::subscriber::with_default(subscriber, || -> ksp_logging_lib::Span {
return ksp_logging_lib::trace_span!(target: TEST_TARGET, "tokio_runtime_span", domain = "logging", executor = "tokio");
});
}
#[tokio::test(flavor = "current_thread")]
async fn instrumented_span_reenters_across_real_tokio_suspensions() {
let enters = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let exits = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let span = test_span(std::sync::Arc::clone(&enters), std::sync::Arc::clone(&exits));
let observed_enters = std::sync::Arc::clone(&enters);
let future = ksp_logging_lib::instrument(span, async move {
assert!(observed_enters.load(std::sync::atomic::Ordering::Relaxed) >= 1);
tokio::task::yield_now().await;
assert!(observed_enters.load(std::sync::atomic::Ordering::Relaxed) >= 2);
tokio::task::yield_now().await;
assert!(observed_enters.load(std::sync::atomic::Ordering::Relaxed) >= 3);
return 42_u32;
});
let value = future.await;
assert_eq!(value, 42_u32);
let enter_count = enters.load(std::sync::atomic::Ordering::Relaxed);
let exit_count = exits.load(std::sync::atomic::Ordering::Relaxed);
assert!(enter_count >= 3);
assert_eq!(enter_count, exit_count);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn instrumented_spans_are_usable_on_tokio_multithread_runtime() {
let enters = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let exits = std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0));
let first_span = test_span(std::sync::Arc::clone(&enters), std::sync::Arc::clone(&exits));
let second_span = test_span(std::sync::Arc::clone(&enters), std::sync::Arc::clone(&exits));
let first_task = tokio::spawn(ksp_logging_lib::instrument(first_span, async {
for _iteration in 0..32 {
tokio::task::yield_now().await;
}
return 20_u32;
}));
let second_task = tokio::spawn(ksp_logging_lib::instrument(second_span, async {
for _iteration in 0..32 {
tokio::task::yield_now().await;
}
return 22_u32;
}));
let first_result = first_task.await;
assert!(first_result.is_ok(), "first Tokio task must complete successfully");
let first_value = match first_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let second_result = second_task.await;
assert!(second_result.is_ok(), "second Tokio task must complete successfully");
let second_value = match second_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(first_value + second_value, 42_u32);
let enter_count = enters.load(std::sync::atomic::Ordering::Relaxed);
let exit_count = exits.load(std::sync::atomic::Ordering::Relaxed);
assert!(enter_count >= 4);
assert_eq!(enter_count, exit_count);
}