rsactor 0.14.1

A Simple and Efficient In-Process Actor Model Implementation for Rust.
Documentation
# Tracing Support in rsActor

rsActor provides comprehensive tracing support through the optional `tracing` feature, offering deep observability into actor behavior, message handling, and performance characteristics with structured logging through the [`tracing`](https://crates.io/crates/tracing) crate.

## Enabling Tracing

Add the tracing feature to your `Cargo.toml`:

```toml
[dependencies]
rsactor = { version = "0.12", features = ["tracing"] }
tracing-subscriber = "0.3"  # For setting up a subscriber
```

## Setting Up Tracing

Initialize a tracing subscriber in your application:

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracing subscriber
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .with_target(false)
        .init();

    // Your actor code here...
    Ok(())
}
```

## What Gets Traced

When the tracing feature is enabled, rsActor automatically traces:

### Message Sending Operations
- **`actor_tell`** span: Fire-and-forget message operations
  - Actor ID and message type
  - Success/failure status with detailed error messages

- **`actor_ask`** span: Request-reply message operations
  - Actor ID, message type, and expected reply type
  - Message sending status
  - Reply reception status
  - Type downcast success/failure with expected vs actual types

- **`actor_tell_with_timeout`** and **`actor_ask_with_timeout`** spans:
  - All of the above plus timeout duration in milliseconds

- **`actor_blocking_tell`** and **`actor_blocking_ask`** spans:
  - Blocking operations for use from any thread (no runtime context required)
  - Timeout handling via `Option<Duration>` parameter
  - Note: Deprecated `actor_tell_blocking` and `actor_ask_blocking` spans still exist for backwards compatibility

### Actor Lifecycle Events
- **Actor Start**: When `on_start` completes successfully
- **Actor Termination**: Different termination scenarios with clear distinctions:
  - `Actor termination via kill() method` - Explicit kill() call
  - `Actor termination due to graceful stop` - Normal stop() call
  - `Actor termination due to all actor_ref instances being dropped` - Reference cleanup

### Actor Control Operations
- **`actor_kill`** span: Immediate actor termination
  - Kill signal sending success/failure
  - Actor state at termination

- **`actor_stop`** span: Graceful actor termination
  - Stop signal sending success/failure
  - Graceful shutdown process

### Message Processing
- **Message Handler Processing**: Automatic message processing traces
  - Actor ID and message type (detected automatically during downcasting)
  - Processing start and completion events
  - Processing duration in milliseconds
  - Reply sending status (for ask operations)
  - Error handling and error reply sending

### Error Handling
- **Downcast Failures**: Warnings when reply type downcasting fails
  - Expected type information logged
- **Timeout Events**: When operations exceed specified time limits
- **Channel Errors**: When actor mailboxes are closed or receivers drop
- **Dead Letters**: Messages that cannot be delivered are logged at WARN level (see Dead Letter Tracking)

## Example Trace Output

When tracing is enabled, you'll see structured logs like:

```
DEBUG actor_ask{actor_id=DemoActor message_type=Ping reply_type=String}: Sending ask message and waiting for reply
DEBUG actor_ask{actor_id=DemoActor message_type=Ping reply_type=String}: Ask reply received successfully
DEBUG actor_tell{actor_id=DemoActor message_type=Increment}: Sending tell message (fire-and-forget)
DEBUG actor_tell{actor_id=DemoActor message_type=Increment}: Tell message sent successfully
DEBUG actor_ask_with_timeout{actor_id=DemoActor message_type=SlowOperation reply_type=String timeout_ms=150}: Sending ask message with timeout
WARN  actor_ask_with_timeout{actor_id=DemoActor message_type=SlowOperation reply_type=String timeout_ms=150}: Ask with timeout failed error=Timeout { identity: Identity { name: "DemoActor", id: ... }, timeout: 150ms, operation: "ask" }
```

You'll also see message processing traces at the handler level:

```
DEBUG rsactor::actor: Actor processing message message_type=Ping actor_id=DemoActor
DEBUG rsactor::actor: Actor processing message message_type=Increment actor_id=DemoActor
WARN  rsactor::actor: Unhandled message type expected_types=["Ping", "Increment"] actual_type_id=TypeId { .. }
```

## Performance Impact

The tracing feature is designed with minimal performance impact:

- **Zero cost when disabled**: When the tracing feature is not enabled, all tracing code is completely removed at compile time
- **Conditional compilation**: All tracing code uses `#[cfg(feature = "tracing")]` attributes
- **Structured logging**: Uses the efficient `tracing` crate instead of string formatting

## Integration with Observability

The structured tracing output integrates well with observability tools:

- **OpenTelemetry**: Tracing spans can be exported to OpenTelemetry-compatible systems
- **Jaeger/Zipkin**: Distributed tracing visualization
- **Prometheus**: Metrics can be derived from trace events
- **Log aggregation**: JSON-formatted logs work with ELK stack, Fluentd, etc.

## Example Usage

See the following examples for complete tracing demonstrations:

```bash
# Comprehensive tracing demonstration
RUST_LOG=debug cargo run --example tracing_demo --features tracing

# Actor lifecycle and weak references
RUST_LOG=debug cargo run --example weak_reference_demo --features tracing

# Explicit kill scenario tracing
RUST_LOG=debug cargo run --example kill_demo --features tracing

# Run without tracing to see the difference
cargo run --example tracing_demo
```

### Basic Setup Example

```rust
use rsactor::{Actor, ActorRef, message_handlers, spawn};
use std::time::Duration;

#[derive(Actor)]
struct MyActor {
    counter: u64,
}

struct Increment;
struct GetCounter;

#[message_handlers]
impl MyActor {
    #[handler]
    async fn handle_increment(&mut self, _msg: Increment, _: &ActorRef<Self>) -> u64 {
        self.counter += 1;
        self.counter
    }

    #[handler]
    async fn handle_get_counter(&mut self, _msg: GetCounter, _: &ActorRef<Self>) -> u64 {
        self.counter
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracing subscriber
    #[cfg(feature = "tracing")]
    {
        tracing_subscriber::fmt()
            .with_max_level(tracing::Level::DEBUG)
            .with_target(false)
            .init();
        println!("🚀 Tracing is ENABLED");
    }

    #[cfg(not(feature = "tracing"))]
    {
        println!("📝 Tracing is DISABLED");
    }

    let actor = MyActor { counter: 0 };
    let (actor_ref, _handle) = spawn(actor);

    // This will generate tracing events when tracing feature is enabled
    let count = actor_ref.ask(Increment).await?;
    println!("Count: {}", count);

    actor_ref.stop().await?;
    Ok(())
}
```

## Span Hierarchy

The tracing spans are organized to show the complete message flow:

```
actor_ask/tell/tell_with_timeout/ask_with_timeout
├── message processing (via MessageHandler trait)
│   ├── automatic message type detection
│   ├── handler method execution
│   └── reply handling (for ask operations)
└── timeout handling (if applicable)

actor_kill/actor_stop
├── signal sending
└── termination confirmation
```

This structure allows you to:
- Track message flow from sender to processing
- Measure end-to-end latency for ask operations
- Identify bottlenecks in message processing
- Debug timeout and error scenarios
- Monitor actor performance and throughput
- Observe actor lifecycle events and termination patterns