v0.1.4-pre.016
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-logging-lib/README.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# ksp-logging-lib
|
||||
|
||||
@@ -18,6 +18,8 @@ La crate possède :
|
||||
- l'héritage du `domain` effectif à travers les spans, avec possibilité pour un event ou un span enfant de le remplacer explicitement ;
|
||||
- l'installation unique du subscriber global ;
|
||||
- le hot reload via `reinitialize` sans second subscriber global ;
|
||||
- `LoggingRuntimeIdentity` pour isoler les fichiers persistants par lancement sans muter les `FileSettings` source ;
|
||||
- l'observabilité des file sinks actifs via `RuntimeFileMetadata`, avec maintien de la même identité à travers les hot reloads ;
|
||||
- le takeover des logs : les targets externes sont silencieux par défaut ;
|
||||
- les writers non bloquants console/fichier et leurs `WorkerGuard` ;
|
||||
- les compteurs agrégés de lignes abandonnées et le compteur cumulatif par `output_id` fichier ;
|
||||
@@ -54,7 +56,7 @@ Une crate KSP comportementale qui journalise son activité dépend de `ksp-loggi
|
||||
|
||||
Les événements utiles issus d'une dépendance externe ne sont pas renommés : la crate KSP propriétaire de l'opération réémet explicitement l'information utile sous son propre target KSP.
|
||||
|
||||
`ksp-logging-lib` ne dépend pas de `ksp-config-lib`. Config pourra construire un `LoggingSettings` puis appeler `initialize` ou `reinitialize`.
|
||||
`ksp-logging-lib` ne dépend pas de `ksp-config-lib`. Config peut construire un `LoggingSettings` puis une application appelle `initialize`, `initialize_with_identity` et `reinitialize` selon son besoin. L'identité de lancement reste une responsabilité de la couche application : Logging la valide, la conserve dans le `LoggingGuard` et l'applique aux noms de fichiers actifs.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<!-- file: crates/ksp-logging-lib/TODO.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# TODO ksp-logging-lib
|
||||
|
||||
## À fermer pendant `0.1.3`
|
||||
## État courant
|
||||
|
||||
Les trois tranches Logging nécessaires à la fondation Config sont maintenant couvertes fonctionnellement :
|
||||
La fondation Logging `0.1.2` et ses compléments nécessaires à Config `0.1.3` sont stables. `0.1.4-pre.016` complète le contrat runtime pour les applications desk avec une identité stable par lancement et l'observabilité des file sinks actifs, sans déplacer l'ownership du subscriber hors de `ksp-logging-lib`.
|
||||
|
||||
- `pre.004` : contrats/settings multi-output ;
|
||||
- `pre.005` : runtime multi-sink, formats et routing level/target ;
|
||||
- `pre.006` : routing structuré `domain`, héritage de spans et lifecycle.
|
||||
|
||||
Avant le gel de `std.logging.schema.json`, il reste uniquement à faire valider `pre.006` par les commandes workspace usuelles. Les travaux suivants de `0.1.3` reviennent ensuite à `ksp-config-lib`.
|
||||
|
||||
## Capacités désormais actives
|
||||
## Capacités actives
|
||||
|
||||
Le runtime supporte maintenant :
|
||||
|
||||
@@ -25,13 +19,17 @@ Le runtime supporte maintenant :
|
||||
- guards non bloquants indépendants ;
|
||||
- compteurs agrégés et compteurs cumulatifs par `output_id` fichier ;
|
||||
- hot reload transactionnel du groupe de sinks ;
|
||||
- héritage du `domain` effectif pour les events/spans et lifecycle de spans.
|
||||
- héritage du `domain` effectif pour les events/spans et lifecycle de spans ;
|
||||
- `LoggingRuntimeIdentity` pour isoler les fichiers persistants entre lancements ;
|
||||
- maintien automatique de la même identité lors des hot reloads d'un processus ;
|
||||
- `RuntimeFileMetadata` pour observer les file sinks réellement actifs sans exposer leurs writers.
|
||||
|
||||
## Capacités différées
|
||||
|
||||
Ces éléments ne sont pas requis par la fondation Config `0.1.3` :
|
||||
Ces éléments ne sont pas requis pour la clôture de `0.1.4` :
|
||||
|
||||
- rotation par taille, rétention/compression et symlink `latest` ;
|
||||
- OpenTelemetry/export réseau ;
|
||||
- watcher de fichiers de configuration, qui appartient à Config ou à une couche supérieure ;
|
||||
- benchmark/profiling de précision destiné aux chemins de trading sensibles à la latence.
|
||||
- benchmark/profiling de précision destiné aux chemins de trading sensibles à la latence ;
|
||||
- éventuelle couche générique supplémentaire pour transporter des événements runtime vers une WebView, à concevoir sans second subscriber et sans boucle de réémission.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-logging-lib/USAGE.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# Utilisation de ksp-logging-lib
|
||||
|
||||
@@ -92,6 +92,52 @@ let mut logging_guard = match initialize_result {
|
||||
|
||||
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 :
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/error.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub const ERROR_CODE_INVALID_SETTINGS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_settings");
|
||||
@@ -9,3 +9,5 @@ pub const ERROR_CODE_ALREADY_INITIALIZED: ksp_core_lib::ErrorCode = ksp_core_lib
|
||||
pub const ERROR_CODE_RELOAD_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "reload_failed");
|
||||
/// Error code used when the rolling file output cannot be initialized.
|
||||
pub const ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "file_output_initialization_failed");
|
||||
/// Error code used when an application Logging runtime identity is invalid.
|
||||
pub const ERROR_CODE_INVALID_RUNTIME_IDENTITY: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_runtime_identity");
|
||||
|
||||
71
crates/ksp-logging-lib/src/identity.rs
Normal file
71
crates/ksp-logging-lib/src/identity.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
// file: crates/ksp-logging-lib/src/identity.rs
|
||||
// version: 1
|
||||
|
||||
//! Stable runtime identity used to separate persistent file outputs between application launches.
|
||||
|
||||
/// Identity attached to one installed KSP Logging runtime for the lifetime of an application launch.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct LoggingRuntimeIdentity {
|
||||
application_id: std::string::String,
|
||||
launch_timestamp: std::string::String,
|
||||
}
|
||||
|
||||
impl LoggingRuntimeIdentity {
|
||||
/// Creates a validated runtime identity from an application identifier and launch timestamp token.
|
||||
pub fn new(
|
||||
application_id: impl std::convert::Into<std::string::String>,
|
||||
launch_timestamp: impl std::convert::Into<std::string::String>,
|
||||
) -> ksp_core_lib::Result<Self> {
|
||||
let application_id = application_id.into();
|
||||
let launch_timestamp = launch_timestamp.into();
|
||||
let application_validation = validate_identity_component(application_id.as_str(), "application_id");
|
||||
if let std::result::Result::Err(error) = application_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let timestamp_validation = validate_identity_component(launch_timestamp.as_str(), "launch_timestamp");
|
||||
if let std::result::Result::Err(error) = timestamp_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(Self { application_id, launch_timestamp });
|
||||
}
|
||||
|
||||
/// Returns the application identifier embedded in persistent runtime file names.
|
||||
#[must_use]
|
||||
pub fn application_id(&self) -> &str {
|
||||
return self.application_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the stable launch timestamp token embedded in persistent runtime file names.
|
||||
#[must_use]
|
||||
pub fn launch_timestamp(&self) -> &str {
|
||||
return self.launch_timestamp.as_str();
|
||||
}
|
||||
|
||||
pub(crate) fn file_name_prefix(&self, configured_prefix: &str) -> std::string::String {
|
||||
return format!("{}.{}.{}", self.application_id, self.launch_timestamp, configured_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_identity_component(value: &str, field: &'static str) -> ksp_core_lib::Result<()> {
|
||||
if value.is_empty() || value.len() > 160 {
|
||||
return invalid_identity(field);
|
||||
}
|
||||
for byte in value.bytes() {
|
||||
let accepted = byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-');
|
||||
if !accepted {
|
||||
return invalid_identity(field);
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn invalid_identity(field: &'static str) -> ksp_core_lib::Result<()> {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RUNTIME_IDENTITY, "KSP Logging runtime identity contains an invalid component")
|
||||
.with_context("field", field),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/identity.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/lib.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -7,12 +7,13 @@
|
||||
//! KSP-owned logging and tracing facade.
|
||||
//!
|
||||
//! This crate owns the KSP runtime logging contract. Behavioral KSP crates emit events and spans through this facade rather than depending directly on the
|
||||
//! `tracing` stack. The crate owns the single global subscriber, KSP takeover filtering, hot reload and non-blocking outputs. `0.1.3-pre.006` supports
|
||||
//! multiple simultaneous outputs with per-output level/target/domain routing, selectable formats, console ANSI and per-file dropped-line accounting. Structured
|
||||
//! `domain` routing remains distinct from targets and follows explicit event domains or inherited span domains.
|
||||
//! `tracing` stack. The crate owns the single global subscriber, KSP takeover filtering, transactional hot reload, non-blocking outputs, structured
|
||||
//! level/target/domain routing and optional per-launch identities used to isolate persistent file outputs. Structured `domain` routing remains distinct from
|
||||
//! targets and follows explicit event domains or inherited span domains.
|
||||
|
||||
mod domain;
|
||||
mod error;
|
||||
mod identity;
|
||||
mod macros;
|
||||
mod runtime;
|
||||
mod settings;
|
||||
@@ -23,16 +24,24 @@ mod writer;
|
||||
pub use self::error::ERROR_CODE_ALREADY_INITIALIZED;
|
||||
/// Error code used when the rolling file output cannot be initialized.
|
||||
pub use self::error::ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED;
|
||||
/// Error code used when an application Logging runtime identity is invalid.
|
||||
pub use self::error::ERROR_CODE_INVALID_RUNTIME_IDENTITY;
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub use self::error::ERROR_CODE_INVALID_SETTINGS;
|
||||
/// Error code used when a hot reload cannot replace the active runtime layers.
|
||||
pub use self::error::ERROR_CODE_RELOAD_FAILED;
|
||||
/// Stable application/launch identity used to isolate persistent Logging file outputs.
|
||||
pub use self::identity::LoggingRuntimeIdentity;
|
||||
/// Cumulative number of log lines dropped by non-blocking KSP outputs.
|
||||
pub use self::runtime::DroppedLines;
|
||||
/// Guard owning the mutable runtime state and non-blocking writers of the installed KSP logging subscriber.
|
||||
pub use self::runtime::LoggingGuard;
|
||||
/// Metadata for one active persistent Logging file output.
|
||||
pub use self::runtime::RuntimeFileMetadata;
|
||||
/// Installs the global KSP tracing subscriber.
|
||||
pub use self::runtime::initialize;
|
||||
/// Installs the global KSP tracing subscriber with a stable per-launch runtime identity.
|
||||
pub use self::runtime::initialize_with_identity;
|
||||
/// Replaces the active KSP logging settings without reinstalling the global subscriber.
|
||||
pub use self::runtime::reinitialize;
|
||||
/// Console stream selected for human-readable logs.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/runtime.rs
|
||||
// version: 11
|
||||
// version: 12
|
||||
|
||||
use tracing_subscriber::Layer; // rust-rules: trait-import
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
@@ -41,10 +41,46 @@ impl DroppedLines {
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for one active persistent Logging file output.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct RuntimeFileMetadata {
|
||||
output_id: std::string::String,
|
||||
directory: std::path::PathBuf,
|
||||
file_name_prefix: std::string::String,
|
||||
rotation: crate::FileRotation,
|
||||
}
|
||||
|
||||
impl RuntimeFileMetadata {
|
||||
/// Returns the stable output identifier.
|
||||
#[must_use]
|
||||
pub fn output_id(&self) -> &str {
|
||||
return self.output_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the resolved directory containing this launch's files.
|
||||
#[must_use]
|
||||
pub fn directory(&self) -> &std::path::Path {
|
||||
return self.directory.as_path();
|
||||
}
|
||||
|
||||
/// Returns the effective launch-specific filename prefix passed to the rolling appender.
|
||||
#[must_use]
|
||||
pub fn file_name_prefix(&self) -> &str {
|
||||
return self.file_name_prefix.as_str();
|
||||
}
|
||||
|
||||
/// Returns the configured rotation cadence.
|
||||
#[must_use]
|
||||
pub const fn rotation(&self) -> crate::FileRotation {
|
||||
return self.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// Guard owning the mutable runtime state and non-blocking writers of the installed KSP logging subscriber.
|
||||
pub struct LoggingGuard {
|
||||
reload_handle: RuntimeReloadHandle,
|
||||
settings: crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<crate::LoggingRuntimeIdentity>,
|
||||
outputs: RuntimeOutputs,
|
||||
retired_dropped_lines: crate::DroppedLines,
|
||||
retired_file_dropped_lines: std::collections::HashMap<std::string::String, usize>,
|
||||
@@ -57,6 +93,25 @@ impl LoggingGuard {
|
||||
return &self.settings;
|
||||
}
|
||||
|
||||
/// Returns the stable application/launch identity attached to this runtime, when one was supplied at initialization.
|
||||
#[must_use]
|
||||
pub const fn runtime_identity(&self) -> std::option::Option<&crate::LoggingRuntimeIdentity> {
|
||||
return self.runtime_identity.as_ref();
|
||||
}
|
||||
|
||||
/// Returns metadata for the currently active persistent file outputs.
|
||||
#[must_use]
|
||||
pub fn active_file_outputs(&self) -> std::vec::Vec<crate::RuntimeFileMetadata> {
|
||||
return self
|
||||
.outputs
|
||||
.files
|
||||
.iter()
|
||||
.map(|output| -> crate::RuntimeFileMetadata {
|
||||
return output.metadata.clone();
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Returns cumulative dropped-line counters across active and previously reloaded outputs.
|
||||
#[must_use]
|
||||
pub fn dropped_lines(&self) -> crate::DroppedLines {
|
||||
@@ -110,7 +165,7 @@ impl RuntimeOutputs {
|
||||
.files
|
||||
.iter()
|
||||
.find(|output| -> bool {
|
||||
return output.output_id == output_id;
|
||||
return output.metadata.output_id == output_id;
|
||||
})
|
||||
.map(|output| -> usize {
|
||||
return output.output.dropped_lines();
|
||||
@@ -120,7 +175,7 @@ impl RuntimeOutputs {
|
||||
fn accumulate_file_dropped_lines(&self, destination: &mut std::collections::HashMap<std::string::String, usize>) {
|
||||
for output in &self.files {
|
||||
let dropped = output.output.dropped_lines();
|
||||
match destination.entry(output.output_id.clone()) {
|
||||
match destination.entry(output.metadata.output_id.clone()) {
|
||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||
let cumulative = entry.get().saturating_add(dropped);
|
||||
*entry.get_mut() = cumulative;
|
||||
@@ -134,7 +189,7 @@ impl RuntimeOutputs {
|
||||
}
|
||||
|
||||
struct RuntimeFileOutput {
|
||||
output_id: std::string::String,
|
||||
metadata: crate::RuntimeFileMetadata,
|
||||
output: RuntimeOutput,
|
||||
}
|
||||
|
||||
@@ -155,7 +210,7 @@ struct PreparedOutput {
|
||||
}
|
||||
|
||||
struct PreparedFileOutput {
|
||||
output_id: std::string::String,
|
||||
metadata: crate::RuntimeFileMetadata,
|
||||
layer: BoxedRuntimeLayer,
|
||||
output: RuntimeOutput,
|
||||
}
|
||||
@@ -165,7 +220,19 @@ struct PreparedFileOutput {
|
||||
/// This function may succeed only once for the lifetime of the process. The returned guard owns all non-blocking writer guards and is then used by
|
||||
/// [`crate::reinitialize`] to replace the active KSP logging configuration without installing a second global subscriber.
|
||||
pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return prepare_runtime(settings).and_then(|prepared| -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return initialize_runtime(settings, std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Installs the global KSP tracing subscriber and isolates persistent file outputs with one stable application launch identity.
|
||||
pub fn initialize_with_identity(settings: &crate::LoggingSettings, identity: &crate::LoggingRuntimeIdentity) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return initialize_runtime(settings, std::option::Option::Some(identity.clone()));
|
||||
}
|
||||
|
||||
fn initialize_runtime(
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<crate::LoggingRuntimeIdentity>,
|
||||
) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return prepare_runtime_with_identity(settings, runtime_identity.as_ref()).and_then(|prepared| -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
let PreparedRuntime { layers, outputs } = prepared;
|
||||
let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(layers);
|
||||
let subscriber = tracing_subscriber::registry().with(reload_layer);
|
||||
@@ -174,6 +241,7 @@ pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<cra
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(crate::LoggingGuard {
|
||||
reload_handle,
|
||||
settings: settings.clone(),
|
||||
runtime_identity,
|
||||
outputs,
|
||||
retired_dropped_lines: crate::DroppedLines::zero(),
|
||||
retired_file_dropped_lines: std::collections::HashMap::new(),
|
||||
@@ -191,7 +259,7 @@ pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<cra
|
||||
/// configuration remains unchanged. After a successful layer swap, dropped-line counters from the retired outputs are retained cumulatively. Retired
|
||||
/// layers are then dropped before their worker guards so all retired `NonBlocking` senders are released before shutdown asks the workers to drain/flush.
|
||||
pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> {
|
||||
return prepare_runtime(settings).and_then(|prepared| -> ksp_core_lib::Result<()> {
|
||||
return prepare_runtime_with_identity(settings, guard.runtime_identity.as_ref()).and_then(|prepared| -> ksp_core_lib::Result<()> {
|
||||
let PreparedRuntime { layers, outputs } = prepared;
|
||||
let mut retired_layers = RuntimeLayers::new();
|
||||
let reload_result = guard.reload_handle.modify(|active_layers| {
|
||||
@@ -215,6 +283,13 @@ pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSe
|
||||
}
|
||||
|
||||
fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<PreparedRuntime> {
|
||||
return prepare_runtime_with_identity(settings, std::option::Option::None);
|
||||
}
|
||||
|
||||
fn prepare_runtime_with_identity(
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<&crate::LoggingRuntimeIdentity>,
|
||||
) -> ksp_core_lib::Result<PreparedRuntime> {
|
||||
let validation_error = settings.validate().err();
|
||||
if let std::option::Option::Some(error) = validation_error {
|
||||
return std::result::Result::Err(error);
|
||||
@@ -233,13 +308,13 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
|
||||
outputs.console = std::option::Option::Some(prepared_console.output);
|
||||
}
|
||||
for file in enabled_files {
|
||||
let prepared_file_result = build_file_output(file, settings);
|
||||
let prepared_file_result = build_file_output(file, settings, runtime_identity);
|
||||
let prepared_file = match prepared_file_result {
|
||||
std::result::Result::Ok(output) => output,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
output_layers.push(prepared_file.layer);
|
||||
outputs.files.push(RuntimeFileOutput { output_id: prepared_file.output_id, output: prepared_file.output });
|
||||
outputs.files.push(RuntimeFileOutput { metadata: prepared_file.metadata, output: prepared_file.output });
|
||||
}
|
||||
if output_layers.is_empty() {
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs });
|
||||
@@ -272,10 +347,18 @@ fn build_console_output(console: &crate::ConsoleSettings, settings: &crate::Logg
|
||||
};
|
||||
}
|
||||
|
||||
fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<PreparedFileOutput> {
|
||||
fn build_file_output(
|
||||
file: &crate::FileSettings,
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<&crate::LoggingRuntimeIdentity>,
|
||||
) -> ksp_core_lib::Result<PreparedFileOutput> {
|
||||
let file_name_prefix = match runtime_identity {
|
||||
std::option::Option::Some(identity) => identity.file_name_prefix(file.file_name_prefix()),
|
||||
std::option::Option::None => file.file_name_prefix().to_owned(),
|
||||
};
|
||||
let appender_result = tracing_appender::rolling::RollingFileAppender::builder()
|
||||
.rotation(map_file_rotation(file.rotation()))
|
||||
.filename_prefix(file.file_name_prefix())
|
||||
.filename_prefix(file_name_prefix.as_str())
|
||||
.build(file.directory());
|
||||
let appender = match appender_result {
|
||||
std::result::Result::Ok(appender) => appender,
|
||||
@@ -284,7 +367,7 @@ fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettin
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED, "unable to initialize the KSP rolling file appender")
|
||||
.with_context("output_id", file.output_id())
|
||||
.with_context("directory", file.directory().display().to_string())
|
||||
.with_context("file_name_prefix", file.file_name_prefix())
|
||||
.with_context("file_name_prefix", file_name_prefix.as_str())
|
||||
.with_source(error),
|
||||
);
|
||||
},
|
||||
@@ -292,7 +375,13 @@ fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettin
|
||||
let stripped_writer = crate::writer::StripAnsiWriter::new(appender);
|
||||
let thread_name = format!("ksp-logging-{}", file.output_id());
|
||||
let prepared = build_non_blocking_output(stripped_writer, thread_name.as_str(), settings.span_events(), false, false, file.format(), file.filter());
|
||||
return std::result::Result::Ok(PreparedFileOutput { output_id: file.output_id().to_string(), layer: prepared.layer, output: prepared.output });
|
||||
let metadata = crate::RuntimeFileMetadata {
|
||||
output_id: file.output_id().to_owned(),
|
||||
directory: file.directory().to_path_buf(),
|
||||
file_name_prefix,
|
||||
rotation: file.rotation(),
|
||||
};
|
||||
return std::result::Result::Ok(PreparedFileOutput { metadata, layer: prepared.layer, output: prepared.output });
|
||||
}
|
||||
|
||||
fn build_non_blocking_output<W>(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/tests/public_api.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Integration tests for the public crate-root surface of `ksp-logging-lib`.
|
||||
|
||||
@@ -81,10 +81,16 @@ fn all_span_levels_are_usable() {
|
||||
#[test]
|
||||
fn public_runtime_surface_is_addressable_without_installing_it() {
|
||||
let _initialize = ksp_logging_lib::initialize;
|
||||
let _initialize_with_identity = ksp_logging_lib::initialize_with_identity;
|
||||
let _reinitialize = ksp_logging_lib::reinitialize;
|
||||
let identity = ksp_logging_lib::LoggingRuntimeIdentity::new("ksp-test", "20260816-182519.123Z-p4242");
|
||||
assert!(identity.is_ok());
|
||||
let _already_initialized = ksp_logging_lib::ERROR_CODE_ALREADY_INITIALIZED;
|
||||
let _invalid_runtime_identity = ksp_logging_lib::ERROR_CODE_INVALID_RUNTIME_IDENTITY;
|
||||
let _reload_failed = ksp_logging_lib::ERROR_CODE_RELOAD_FAILED;
|
||||
let _file_initialization_failed = ksp_logging_lib::ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED;
|
||||
let _runtime_identity = ksp_logging_lib::LoggingGuard::runtime_identity;
|
||||
let _active_file_outputs = ksp_logging_lib::LoggingGuard::active_file_outputs;
|
||||
let _per_file_counter = ksp_logging_lib::LoggingGuard::dropped_file_lines;
|
||||
let dropped = ksp_logging_lib::DroppedLines::zero();
|
||||
assert_eq!(dropped.console(), 0);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/tests/runtime.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
|
||||
|
||||
@@ -119,7 +119,13 @@ fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_i
|
||||
std::option::Option::None,
|
||||
std::vec::Vec::new(),
|
||||
);
|
||||
let initialize_result = ksp_logging_lib::initialize(&disabled);
|
||||
let identity = ksp_logging_lib::LoggingRuntimeIdentity::new("ksp-logging-runtime-test", "20260816-182519.123Z-p4242");
|
||||
assert!(identity.is_ok());
|
||||
let identity = match identity {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let initialize_result = ksp_logging_lib::initialize_with_identity(&disabled, &identity);
|
||||
assert!(initialize_result.is_ok());
|
||||
let mut guard = match initialize_result {
|
||||
std::result::Result::Ok(guard) => guard,
|
||||
@@ -222,6 +228,12 @@ fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_i
|
||||
);
|
||||
let file_reload = ksp_logging_lib::reinitialize(&mut guard, &files_enabled);
|
||||
assert!(file_reload.is_ok());
|
||||
assert_eq!(guard.runtime_identity(), std::option::Option::Some(&identity));
|
||||
let active_files = guard.active_file_outputs();
|
||||
assert_eq!(active_files.len(), 4);
|
||||
for file in &active_files {
|
||||
assert!(file.file_name_prefix().starts_with("ksp-logging-runtime-test.20260816-182519.123Z-p4242."));
|
||||
}
|
||||
ksp_logging_lib::info!(target: LOGGING_TARGET, "logging info \x1b[31mmarker\x1b[0m");
|
||||
ksp_logging_lib::error!(target: LOGGING_TARGET, "logging error marker");
|
||||
ksp_logging_lib::info!(target: JSON_KSP_TARGET, "json info marker");
|
||||
|
||||
23
crates/ksp-logging-lib/unit_tests/identity.rs
Normal file
23
crates/ksp-logging-lib/unit_tests/identity.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/identity.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn runtime_identity_accepts_safe_application_and_timestamp_tokens() {
|
||||
let identity = crate::LoggingRuntimeIdentity::new("ksp-app-config-desk", "20260816-182519.123-p4242");
|
||||
assert!(identity.is_ok());
|
||||
if let std::result::Result::Ok(identity) = identity {
|
||||
assert_eq!(identity.application_id(), "ksp-app-config-desk");
|
||||
assert_eq!(identity.launch_timestamp(), "20260816-182519.123-p4242");
|
||||
assert_eq!(identity.file_name_prefix("ksp-debug.log"), "ksp-app-config-desk.20260816-182519.123-p4242.ksp-debug.log");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_identity_rejects_paths_whitespace_and_empty_components() {
|
||||
for application_id in ["", "ksp app", "../ksp-app", "ksp/app"] {
|
||||
let result = crate::LoggingRuntimeIdentity::new(application_id, "20260816-182519.123-p4242");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
let result = crate::LoggingRuntimeIdentity::new("ksp-app-config-desk", "2026/08/16");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/runtime.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
#[test]
|
||||
fn level_mapping_covers_all_ksp_levels() {
|
||||
@@ -258,3 +258,35 @@ fn lossy_non_blocking_builder_drops_lines_instead_of_blocking_a_stalled_producer
|
||||
drop(non_blocking);
|
||||
drop(worker_guard);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_identity_decorates_file_outputs_without_mutating_source_settings() {
|
||||
let root = std::env::temp_dir().join(format!("ksp-pre016-runtime-{}", std::process::id()));
|
||||
let _cleanup_before = std::fs::remove_dir_all(root.as_path());
|
||||
let file = crate::FileSettings::new(
|
||||
"file.runtime",
|
||||
true,
|
||||
root.clone(),
|
||||
"runtime.log",
|
||||
crate::FileRotation::Never,
|
||||
crate::LogFormat::Human,
|
||||
crate::OutputFilter::unrestricted(),
|
||||
);
|
||||
let settings = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec![file]);
|
||||
let identity = crate::LoggingRuntimeIdentity::new("ksp-app-config-desk", "20260816-182519.123-p4242");
|
||||
assert!(identity.is_ok());
|
||||
if let std::result::Result::Ok(identity) = identity {
|
||||
let prepared = super::prepare_runtime_with_identity(&settings, std::option::Option::Some(&identity));
|
||||
assert!(prepared.is_ok());
|
||||
if let std::result::Result::Ok(prepared) = prepared {
|
||||
assert_eq!(prepared.outputs.files.len(), 1);
|
||||
assert_eq!(prepared.outputs.files[0].metadata.output_id(), "file.runtime");
|
||||
assert_eq!(prepared.outputs.files[0].metadata.directory(), root.as_path());
|
||||
assert_eq!(prepared.outputs.files[0].metadata.file_name_prefix(), "ksp-app-config-desk.20260816-182519.123-p4242.runtime.log");
|
||||
assert_eq!(settings.files()[0].file_name_prefix(), "runtime.log");
|
||||
drop(prepared);
|
||||
}
|
||||
}
|
||||
let cleanup_after = std::fs::remove_dir_all(root.as_path());
|
||||
assert!(cleanup_after.is_ok());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user