Files
khadhroony-solana-project/crates/ksp-logging-lib/USAGE.md
2026-08-23 11:15:57 +02:00

9.5 KiB

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 :

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 :

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

Le runtime supporte le multi-sink, les formats, le routing niveau/target et le routing structuré domain :

  • 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

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 :

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 :

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 :

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 :

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 :

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 :

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

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 :

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 :

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 :

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.