Skip to content

Commit 2f40ac5

Browse files
committed
feat(instrument): add tracing-based instrumentation
This commit introduces optional tracing instrumentation to the mti crate using the tracing library. When the "instrument" feature is enabled, the crate will emit detailed trace events for key operations such as TypeID creation, parsing, and component extraction. This allows consuming applications to gain deeper insights into the crate's behavior for debugging, performance analysis, and operational monitoring by setting up a tracing subscriber. The Cargo.toml has been updated to include tracing as an optional dependency and the "instrument" feature now activates dep:tracing. Relevant functions across src/magic_type_id.rs, src/magic_type_id_ext.rs, and src/errors.rs have been annotated with #[instrument(...)] and include various trace calls. Documentation in README.md has been updated to explain how to enable and use this new instrumentation feature, including an example of setting up a basic tracing-subscriber.
1 parent 0447799 commit 2f40ac5

File tree

6 files changed

+224
-11
lines changed

6 files changed

+224
-11
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ categories = ["data-structures", "development-tools", "encoding", "parser-implem
1515
typeid_prefix = {version ="1.0.5"}
1616
typeid_suffix = {version="1.0.2"}
1717
serde = { version = "1.0", features = ["derive"], optional = true }
18+
tracing = { version = "0.1", optional = true }
1819

1920
[dev-dependencies]
2021
uuid = { version = "1.10.0", features = ["v4"] }
@@ -23,7 +24,7 @@ serde_json = "1.0"
2324

2425
[features]
2526
default = []
26-
instrument = ["typeid_prefix/instrument", "typeid_suffix/instrument"]
27+
instrument = ["dep:tracing", "typeid_prefix/instrument", "typeid_suffix/instrument"]
2728
serde = ["dep:serde", "typeid_prefix/serde", "typeid_suffix/serde"]
2829

2930
[lints.rust]

README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,54 @@ mti = { version = "1.0", features = ["serde"] } # Or the latest version, ensure
8585
```
8686
This will enable Serde's `Serialize` and `Deserialize` traits for `MagicTypeId`.
8787

88+
**Optional Tracing Instrumentation:**
89+
90+
For detailed operational insights, `mti` supports instrumentation via the [`tracing`](https://crates.io/crates/tracing) crate. When enabled, `mti` will emit trace events for key operations like ID creation and parsing. This is invaluable for debugging, performance analysis, and understanding the crate's behavior within your application.
91+
92+
To enable this feature, add `instrument` to the `features` list in your `Cargo.toml`:
93+
94+
```toml
95+
[dependencies]
96+
mti = { version = "1.0", features = ["instrument"] } # Or your current version
97+
98+
# Your application will also need a tracing subscriber
99+
tracing = "0.1" # The tracing facade
100+
tracing-subscriber = { version = "0.3", features = ["fmt"] } # Example subscriber
101+
```
102+
103+
**How it Works:**
104+
105+
When the `instrument` feature is active, `mti` functions are annotated with `#[instrument(...)]` and contain `trace!`, `debug!`, etc., calls from the `tracing` crate. Your application can then configure a `tracing` subscriber (like `tracing-subscriber`) to collect, filter, format, and output these trace events. This gives you control over the level of detail and destination of the trace data (e.g., console, file, or a distributed tracing system).
106+
107+
*Example of setting up a basic subscriber in your application:*
108+
109+
```rust
110+
// In your application's main.rs or initialization code:
111+
use tracing_subscriber::fmt::format::FmtSpan;
112+
use mti::prelude::*; // For create_type_id and MagicTypeId
113+
114+
fn setup_tracing() {
115+
tracing_subscriber::fmt()
116+
.with_max_level(tracing::Level::DEBUG) // Adjust level as needed (TRACE, DEBUG, INFO, etc.)
117+
.with_span_events(FmtSpan::CLOSE) // Configure span event reporting
118+
.init(); // Initialize the global subscriber
119+
}
120+
121+
fn main() {
122+
setup_tracing(); // Call this early in your application
123+
124+
// Operations in mti will now emit traces if the "instrument" feature is enabled
125+
let product_id = "product".create_type_id::<V7>(); // This will be traced
126+
println!("Product ID: {}", product_id);
127+
128+
match MagicTypeId::from_str("test_01h2xcejqg4wh1r27hsdgzeqp4") { // This parsing will be traced
129+
Ok(id) => println!("Parsed ID: {}", id),
130+
Err(e) => eprintln!("Error parsing ID: {}", e),
131+
}
132+
}
133+
```
134+
This setup allows the host application to effectively leverage the instrumentation within `mti`.
135+
88136
Then, in your Rust code:
89137

90138
```rust
@@ -127,6 +175,9 @@ match MagicTypeId::from_str(order_id_str) {
127175
* **Optional Serde Support**: Easily serialize and deserialize `MagicTypeId` instances using Serde by enabling the `serde` feature flag.
128176
* *Benefit:* Seamless integration with common serialization formats like JSON, YAML, TOML, etc., for data interchange and storage.
129177

178+
* **Optional Tracing Instrumentation**: Enables detailed operational tracing using the `tracing` crate when the `instrument` feature is active.
179+
* *Benefit:* Provides deep insights into the crate's internal workings for debugging and performance analysis, configurable by the host application's `tracing` subscriber.
180+
130181
## Usage Examples
131182

132183
### Creating MagicTypeIds with Different UUID Versions
@@ -394,10 +445,10 @@ impl UserId {
394445
}
395446

396447
// Optional: Implement FromStr for UserId
397-
fn from_str_validated(s: &str) -> Result<Self, MTIError> {
448+
fn from_str_validated(s: &str) -> Result<Self, MagicTypeIdError> { // Corrected MTIError to MagicTypeIdError
398449
let mti = MagicTypeId::from_str(s)?;
399450
if mti.prefix_str() != "user" {
400-
Err(MTIError::Validation("Invalid prefix for UserId, expected 'user'".to_string()))
451+
Err(MagicTypeIdError::Validation("Invalid prefix for UserId, expected 'user'".to_string()))
401452
} else {
402453
Ok(UserId(mti))
403454
}

src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::fmt;
88
use typeid_prefix::prelude::*;
99
use typeid_suffix::prelude::*;
1010

11+
#[cfg(feature = "instrument")]
12+
use tracing::{error, instrument};
13+
1114
/// Represents errors that can occur when working with `MagicTypeIds`.
1215
///
1316
/// This enum encapsulates errors from both the prefix and suffix components
@@ -46,13 +49,19 @@ impl std::error::Error for MagicTypeIdError {
4649
}
4750

4851
impl From<ValidationError> for MagicTypeIdError {
52+
#[cfg_attr(feature = "instrument", instrument(level = "error", fields(error = %err)))]
4953
fn from(err: ValidationError) -> Self {
54+
#[cfg(feature = "instrument")]
55+
error!("Converting ValidationError to MagicTypeIdError: {}", err);
5056
Self::Prefix(err)
5157
}
5258
}
5359

5460
impl From<DecodeError> for MagicTypeIdError {
61+
#[cfg_attr(feature = "instrument", instrument(level = "error", fields(error = %err)))]
5562
fn from(err: DecodeError) -> Self {
63+
#[cfg(feature = "instrument")]
64+
error!("Converting DecodeError to MagicTypeIdError: {}", err);
5665
Self::Suffix(err)
5766
}
5867
}

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ pub mod prelude {
236236
///
237237
/// This trait is implemented for `str`, allowing for easy creation of `MagicTypeId`s from string literals.
238238
pub use crate::magic_type_id_ext::MagicTypeIdExt;
239+
240+
/// Re-exports the `tracing` crate when the "instrument" feature is enabled.
241+
///
242+
/// This allows users of this crate to access tracing functionality without having to
243+
/// add a direct dependency on the `tracing` crate.
244+
#[cfg(feature = "instrument")]
245+
pub use tracing;
239246
}
240247

241248
#[cfg(test)]

src/magic_type_id.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use crate::errors::MagicTypeIdError;
1111
#[cfg(feature = "serde")]
1212
use serde::{Deserialize, Deserializer, Serialize, Serializer};
1313

14+
#[cfg(feature = "instrument")]
15+
use tracing::{debug, instrument, trace};
16+
1417
/// A type-safe identifier combining a prefix and a UUID-based suffix.
1518
///
1619
/// `MagicTypeId` represents a `TypeID` as specified in the [TypeID Specification](https://github.com/jetpack-io/typeid/blob/main/spec/SPEC.md).
@@ -170,12 +173,19 @@ impl MagicTypeId {
170173
/// assert!(type_id.to_string().starts_with("user_"));
171174
/// ```
172175
#[must_use]
176+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(prefix, suffix), fields(prefix = %prefix, suffix = %suffix)))]
173177
pub fn new(prefix: TypeIdPrefix, suffix: TypeIdSuffix) -> Self {
174178
let string_repr = if prefix.is_empty() {
179+
#[cfg(feature = "instrument")]
180+
trace!("Creating MagicTypeId with empty prefix");
175181
suffix.to_string()
176182
} else {
183+
#[cfg(feature = "instrument")]
184+
trace!("Creating MagicTypeId with prefix and suffix");
177185
format!("{prefix}_{suffix}")
178186
};
187+
#[cfg(feature = "instrument")]
188+
debug!("Created MagicTypeId: {}", string_repr);
179189
Self { prefix, suffix, string_repr }
180190
}
181191

@@ -279,16 +289,31 @@ impl FromStr for MagicTypeId {
279289
///
280290
/// assert!(MagicTypeId::from_str("invalid!_01h455vb4pex5vsknk084sn02q").is_err());
281291
/// ```
292+
#[cfg_attr(feature = "instrument", instrument(level = "debug", fields(input = %s)))]
282293
fn from_str(s: &str) -> Result<Self, Self::Err> {
283294
if let Some((prefix_str, suffix_str)) = s.rsplit_once('_') {
295+
#[cfg(feature = "instrument")]
296+
trace!("Parsing MagicTypeId with prefix '{}' and suffix '{}'", prefix_str, suffix_str);
297+
284298
if prefix_str.is_empty() {
299+
#[cfg(feature = "instrument")]
300+
debug!("Empty prefix found, returning error");
285301
return Err(MagicTypeIdError::Prefix(ValidationError::InvalidStartCharacter));
286302
}
287303
let prefix = TypeIdPrefix::from_str(prefix_str)?;
288304
let suffix = TypeIdSuffix::from_str(suffix_str)?;
305+
306+
#[cfg(feature = "instrument")]
307+
debug!("Successfully parsed MagicTypeId with prefix and suffix");
289308
Ok(Self::new(prefix, suffix))
290309
} else {
310+
#[cfg(feature = "instrument")]
311+
trace!("Parsing MagicTypeId with no prefix, only suffix '{}'", s);
312+
291313
let suffix = TypeIdSuffix::from_str(s)?;
314+
315+
#[cfg(feature = "instrument")]
316+
debug!("Successfully parsed MagicTypeId with no prefix");
292317
Ok(Self::new(TypeIdPrefix::default(), suffix))
293318
}
294319
}

src/magic_type_id_ext.rs

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use typeid_suffix::prelude::*;
66
use crate::errors::MagicTypeIdError;
77
use crate::magic_type_id::MagicTypeId;
88

9+
#[cfg(feature = "instrument")]
10+
use tracing::{debug, instrument, trace, warn};
11+
912
/// Extends string-like types with `TypeID` functionality.
1013
///
1114
/// This trait provides methods to parse, validate, and create `TypeIDs` and their components.
@@ -333,59 +336,176 @@ pub trait MagicTypeIdExt {
333336
}
334337

335338
impl MagicTypeIdExt for str {
339+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
336340
fn prefix_str(&self) -> Result<String, MagicTypeIdError> {
337-
self.prefix().map(|p| p.to_string())
341+
#[cfg(feature = "instrument")]
342+
trace!("Extracting prefix string from TypeID");
343+
let result = self.prefix().map(|p| p.to_string());
344+
345+
#[cfg(feature = "instrument")]
346+
match &result {
347+
Ok(prefix) => debug!("Successfully extracted prefix: '{}'", prefix),
348+
Err(err) => warn!("Failed to extract prefix: {}", err),
349+
}
350+
351+
result
338352
}
339353

354+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
340355
fn suffix_str(&self) -> Result<String, MagicTypeIdError> {
341-
self.suffix().map(|s| s.to_string())
356+
#[cfg(feature = "instrument")]
357+
trace!("Extracting suffix string from TypeID");
358+
let result = self.suffix().map(|s| s.to_string());
359+
360+
#[cfg(feature = "instrument")]
361+
match &result {
362+
Ok(suffix) => debug!("Successfully extracted suffix: '{}'", suffix),
363+
Err(err) => warn!("Failed to extract suffix: {}", err),
364+
}
365+
366+
result
342367
}
343368

369+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
344370
fn uuid_str(&self) -> Result<String, MagicTypeIdError> {
345-
self.uuid().map(|u| u.to_string())
371+
#[cfg(feature = "instrument")]
372+
trace!("Extracting UUID string from TypeID");
373+
let result = self.uuid().map(|u| u.to_string());
374+
375+
#[cfg(feature = "instrument")]
376+
match &result {
377+
Ok(uuid) => debug!("Successfully extracted UUID: '{}'", uuid),
378+
Err(err) => warn!("Failed to extract UUID: {}", err),
379+
}
380+
381+
result
346382
}
347383

384+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
348385
fn prefix(&self) -> Result<TypeIdPrefix, MagicTypeIdError> {
386+
#[cfg(feature = "instrument")]
387+
trace!("Extracting TypeIdPrefix from TypeID");
388+
349389
if self.is_empty() {
390+
#[cfg(feature = "instrument")]
391+
debug!("Input is empty, returning default TypeIdPrefix");
350392
return Ok(TypeIdPrefix::default());
351393
}
352-
match self.rsplit_once('_') {
353-
Some((prefix, _)) => TypeIdPrefix::try_from(prefix).map_err(MagicTypeIdError::Prefix),
354-
None => Ok(TypeIdPrefix::default()),
394+
395+
if let Some((prefix, _)) = self.rsplit_once('_') {
396+
#[cfg(feature = "instrument")]
397+
trace!("Found prefix part: '{}'", prefix);
398+
let result = TypeIdPrefix::try_from(prefix).map_err(MagicTypeIdError::Prefix);
399+
400+
#[cfg(feature = "instrument")]
401+
match &result {
402+
Ok(prefix) => debug!("Successfully created TypeIdPrefix: '{}'", prefix),
403+
Err(err) => warn!("Failed to create TypeIdPrefix: {}", err),
404+
}
405+
406+
result
407+
} else {
408+
#[cfg(feature = "instrument")]
409+
debug!("No prefix separator found, returning default TypeIdPrefix");
410+
Ok(TypeIdPrefix::default())
355411
}
356412
}
357413

414+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
358415
fn suffix(&self) -> Result<TypeIdSuffix, MagicTypeIdError> {
416+
#[cfg(feature = "instrument")]
417+
trace!("Extracting TypeIdSuffix from TypeID");
418+
359419
if self.is_empty() {
420+
#[cfg(feature = "instrument")]
421+
warn!("Input is empty, returning InvalidSuffix error");
360422
return Err(MagicTypeIdError::Suffix(DecodeError::InvalidSuffix(InvalidSuffixReason::InvalidLength)));
361423
}
424+
362425
let suffix_str = self.rsplit_once('_').map_or(self, |(_, suffix)| suffix);
363-
TypeIdSuffix::from_str(suffix_str).map_err(MagicTypeIdError::Suffix)
426+
#[cfg(feature = "instrument")]
427+
trace!("Found suffix part: '{}'", suffix_str);
428+
429+
let result = TypeIdSuffix::from_str(suffix_str).map_err(MagicTypeIdError::Suffix);
430+
431+
#[cfg(feature = "instrument")]
432+
match &result {
433+
Ok(suffix) => debug!("Successfully created TypeIdSuffix: '{}'", suffix),
434+
Err(err) => warn!("Failed to create TypeIdSuffix: {}", err),
435+
}
436+
437+
result
364438
}
365439

440+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self)))]
366441
fn uuid(&self) -> Result<Uuid, MagicTypeIdError> {
367-
self.suffix().map(|s| s.to_uuid())
442+
#[cfg(feature = "instrument")]
443+
trace!("Extracting UUID from TypeID");
444+
445+
let result = self.suffix().map(|s| s.to_uuid());
446+
447+
#[cfg(feature = "instrument")]
448+
match &result {
449+
Ok(uuid) => debug!("Successfully extracted UUID: '{}'", uuid),
450+
Err(err) => warn!("Failed to extract UUID: {}", err),
451+
}
452+
453+
result
368454
}
369455

456+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self, uuid_version = std::any::type_name::<V>())))]
370457
fn create_type_id<V: UuidVersion + Default>(&self) -> MagicTypeId {
458+
#[cfg(feature = "instrument")]
459+
trace!("Creating MagicTypeId with sanitized prefix");
460+
371461
let prefix = self.create_prefix_sanitized();
462+
#[cfg(feature = "instrument")]
463+
debug!("Sanitized prefix: '{}'", prefix);
464+
372465
let suffix = TypeIdSuffix::new::<V>();
466+
#[cfg(feature = "instrument")]
467+
debug!("Created new TypeIdSuffix: '{}'", suffix);
468+
373469
MagicTypeId::new(prefix, suffix)
374470
}
375471

472+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self, suffix), fields(input = %self, suffix = %suffix)))]
376473
fn create_type_id_with_suffix<V: UuidVersion + Default>(&self, suffix: TypeIdSuffix) -> MagicTypeId {
474+
#[cfg(feature = "instrument")]
475+
trace!("Creating MagicTypeId with sanitized prefix and provided suffix");
476+
377477
let prefix = self.create_prefix_sanitized();
478+
#[cfg(feature = "instrument")]
479+
debug!("Sanitized prefix: '{}'", prefix);
480+
378481
MagicTypeId::new(prefix, suffix)
379482
}
380483

484+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self), fields(input = %self, uuid_version = std::any::type_name::<V>())))]
381485
fn try_create_type_id<V: UuidVersion + Default>(&self) -> Result<MagicTypeId, MagicTypeIdError> {
486+
#[cfg(feature = "instrument")]
487+
trace!("Attempting to create MagicTypeId with validated prefix");
488+
382489
let prefix = TypeIdPrefix::try_from(self)?;
490+
#[cfg(feature = "instrument")]
491+
debug!("Successfully validated prefix: '{}'", prefix);
492+
383493
let suffix = TypeIdSuffix::new::<V>();
494+
#[cfg(feature = "instrument")]
495+
debug!("Created new TypeIdSuffix: '{}'", suffix);
496+
384497
Ok(MagicTypeId::new(prefix, suffix))
385498
}
386499

500+
#[cfg_attr(feature = "instrument", instrument(level = "debug", skip(self, suffix), fields(input = %self, suffix = %suffix)))]
387501
fn try_create_type_id_with_suffix<V: UuidVersion + Default>(&self, suffix: TypeIdSuffix) -> Result<MagicTypeId, MagicTypeIdError> {
502+
#[cfg(feature = "instrument")]
503+
trace!("Attempting to create MagicTypeId with validated prefix and provided suffix");
504+
388505
let prefix = TypeIdPrefix::try_from(self)?;
506+
#[cfg(feature = "instrument")]
507+
debug!("Successfully validated prefix: '{}'", prefix);
508+
389509
Ok(MagicTypeId::new(prefix, suffix))
390510
}
391511
}

0 commit comments

Comments
 (0)