Documentation Index
Fetch the complete documentation index at: https://mintlify.com/iii-hq/sdk/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Context API provides access to function-scoped resources like logging and tracing spans. Every function handler runs within a context that is accessible via get_context().
Accessing Context
get_context
Get the current function’s context.
pub fn get_context() -> Context
The context for the currently executing function
Example:
use iii_sdk::{III, get_context};
use serde_json::{json, Value};
let iii = III::new("ws://localhost:49134");
iii.register_function("process", |input: Value| async move {
let ctx = get_context();
ctx.logger.info("Function started", None);
// Your logic here
let result = json!({ "status": "ok" });
ctx.logger.info("Function completed", Some(result.clone()));
Ok(result)
});
get_context() returns a default context when called outside of a function handler (e.g., in application startup code).
with_context
Execute a function within a custom context.
pub async fn with_context<F, Fut, T>(context: Context, f: F) -> T
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = T>
Custom context to use for the execution
Async function to execute within the context
Example:
use iii_sdk::{Context, Logger, with_context};
let custom_context = Context {
logger: Logger::new(Some("custom-function".to_string())),
span: None,
};
with_context(custom_context, || async {
let ctx = get_context();
ctx.logger.info("Inside custom context", None);
}).await;
The SDK automatically wraps function handlers with with_context, so you typically don’t need to call this manually.
Context Structure
Context
The context available within function handlers.
pub struct Context {
pub logger: Logger,
pub span: Option<tracing::Span>,
}
Fields:
logger: Logger instance scoped to the current function
span: Active tracing span (used internally by the SDK, typically not accessed directly)
Logger
The Logger provides structured logging with automatic trace context integration.
Logger Methods
info
Log an informational message.
pub fn info(&self, message: &str, data: Option<Value>)
Optional structured data to include with the log
Example:
let ctx = get_context();
ctx.logger.info("User logged in", None);
ctx.logger.info("Order processed", Some(json!({
"order_id": "order-123",
"amount": 99.99
})));
warn
Log a warning message.
pub fn warn(&self, message: &str, data: Option<Value>)
Example:
let ctx = get_context();
ctx.logger.warn("Rate limit approaching", Some(json!({
"remaining": 5,
"reset_at": "2024-01-15T10:30:00Z"
})));
error
Log an error message.
pub fn error(&self, message: &str, data: Option<Value>)
Example:
let ctx = get_context();
ctx.logger.error("Database connection failed", Some(json!({
"error": "Connection timeout",
"retry_count": 3
})));
debug
Log a debug message.
pub fn debug(&self, message: &str, data: Option<Value>)
Example:
let ctx = get_context();
ctx.logger.debug("Cache hit", Some(json!({
"key": "user:123",
"ttl": 3600
})));
OpenTelemetry Integration
When the otel feature is enabled, logs are automatically exported via OpenTelemetry:
#[cfg(feature = "otel")]
{
use iii_sdk::{III, get_context, OtelConfig};
use serde_json::json;
let otel_config = OtelConfig {
enabled: Some(true),
logs_enabled: Some(true),
..Default::default()
};
let iii = III::new("ws://localhost:49134");
iii.set_otel_config(otel_config);
iii.connect().await?;
iii.register_function("process", |input| async move {
let ctx = get_context();
// These logs are exported as OpenTelemetry LogRecords
// and automatically include trace context
ctx.logger.info("Processing started", None);
ctx.logger.debug("Input data", Some(input.clone()));
// Your logic
ctx.logger.info("Processing completed", None);
Ok(json!({ "status": "ok" }))
});
}
OpenTelemetry LogRecords include:
- Timestamp (observed and actual)
- Severity level (Debug, Info, Warn, Error)
- Message body
- Function name attribute
- Structured data as attributes
- Trace context (trace_id, span_id, trace_flags)
When the otel feature is disabled, logs fall back to the tracing crate.
Creating Custom Loggers
Logger::new
Create a logger with a custom function name.
pub fn new(function_name: Option<String>) -> Self
Function name to include in log records
Example:
use iii_sdk::Logger;
let logger = Logger::new(Some("background-task".to_string()));
logger.info("Task started", None);
Complete Example
Here’s a complete example showing context usage:
use iii_sdk::{III, get_context, IIIError};
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let iii = III::new("ws://localhost:49134");
// Register a function that uses context
iii.register_function("user.create", |input: Value| async move {
let ctx = get_context();
// Log the start
ctx.logger.info("Creating new user", Some(input.clone()));
// Validate input
let email = input.get("email")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ctx.logger.error("Missing email field", None);
IIIError::Handler("email is required".into())
})?;
ctx.logger.debug("Email validated", Some(json!({ "email": email })));
// Create user (simulated)
let user_id = uuid::Uuid::new_v4().to_string();
let result = json!({
"id": user_id,
"email": email,
"created_at": chrono::Utc::now().to_rfc3339()
});
ctx.logger.info("User created successfully", Some(result.clone()));
Ok(result)
});
// Register a function that calls another function
iii.register_function("order.create", move |input: Value| {
let iii = iii.clone();
async move {
let ctx = get_context();
ctx.logger.info("Creating order", None);
// Get user data
let user_id = input.get("user_id")
.and_then(|v| v.as_str())
.ok_or_else(|| IIIError::Handler("user_id required".into()))?;
ctx.logger.debug("Fetching user", Some(json!({ "user_id": user_id })));
// Call user service (context is propagated)
let user = iii.call("user.get", json!({ "id": user_id })).await?;
ctx.logger.info("User fetched", Some(user.clone()));
// Create order
let order = json!({
"id": uuid::Uuid::new_v4().to_string(),
"user": user,
"items": input.get("items").cloned().unwrap_or(json!([]))
});
ctx.logger.info("Order created", Some(order.clone()));
Ok(order)
}
});
iii.connect().await?;
// Test the functions
let user = iii.call("user.create", json!({
"email": "alice@example.com"
})).await?;
println!("Created user: {}", user);
let order = iii.call("order.create", json!({
"user_id": user.get("id").unwrap(),
"items": [{"sku": "ABC123", "qty": 2}]
})).await?;
println!("Created order: {}", order);
iii.shutdown_async().await;
Ok(())
}
Log Levels
When to use each level:
- debug: Detailed information for debugging (verbose)
- info: General informational messages about application flow
- warn: Warning messages for potentially problematic situations
- error: Error messages for failures that require attention
Best Practices
-
Always use structured data: Pass JSON objects to the
data parameter instead of formatting strings:
// Good
ctx.logger.info("User created", Some(json!({ "user_id": id })));
// Avoid
ctx.logger.info(&format!("User {} created", id), None);
-
Log at appropriate levels: Use
debug for verbose details, info for key events, warn for issues, and error for failures.
-
Include context in structured data: Add relevant IDs and metadata to help with debugging:
ctx.logger.error("Payment failed", Some(json!({
"order_id": order_id,
"amount": amount,
"error_code": code
})));
-
Don’t log sensitive data: Avoid logging passwords, tokens, or PII:
// Bad - logs password
ctx.logger.debug("Auth attempt", Some(json!({ "password": pwd })));
// Good - doesn't log password
ctx.logger.debug("Auth attempt", Some(json!({ "username": user })));
See Also