A high-performance Rust library for integrating with Splunk, providing utilities for file logging, configuration processing, and HTTP event collection.
splunklib_rust is a comprehensive Rust library for Splunk integration, designed for use in Splunk apps and external applications. It provides:
- Splunk Location Detection: Automatically discover Splunk installation paths
- Configuration Processing: Parse and cache Splunk .conf files with full spec support
- File Logger: High-performance, thread-safe logging with rotation and batching
- HTTP Event Sender: Send events to Splunk HEC with gzip compression and batching
- 🚀 High Performance: Asynchronous I/O, connection pooling, zero-copy optimizations
- 🔒 Thread-Safe: All components are safe for concurrent access
- 📝 Splunk-Native: Matches Splunk's configuration format and behavior
- 🎯 Type-Safe: Rust's type system ensures compile-time correctness
- 🔧 Configurable: Extensive options for logging, compression, timeouts, etc.
- 📊 Observable: Built-in statistics and debug logging
Add this to your Cargo.toml:
[dependencies]
splunklib_rust = "0.2.0"Retrieve hostnames from Splunk configuration or system.
use splunklib_rust::get_splunk_hostname;
use std::path::Path;
// Get hostname from Splunk config or system
let hostname = get_splunk_hostname(Path::new("/opt/splunk"));
println!("Hostname: {}", hostname);Detect Splunk installation directory structure.
use splunklib_rust::get_splunk_location;
match get_splunk_location() {
Ok(loc) => {
println!("Root: {:?}", loc.root);
println!("App: {:?}", loc.app);
println!("Bin: {:?}", loc.bin);
}
Err(e) => eprintln!("Failed to detect: {}", e),
}Recursively search for files in Splunk directories.
use splunklib_rust::splunk_file_locator;
let files = splunk_file_locator::find_files_default(
"/opt/splunk",
"inputs.conf"
).expect("Search failed");
for file in files {
println!("Found: {:?}", file);
}Parse and cache Splunk configuration files.
use splunklib_rust::splunk_config_processor;
// Parse multiple files with merge semantics
let files = vec![
"/opt/splunk/etc/system/default/inputs.conf",
"/opt/splunk/etc/system/local/inputs.conf",
];
let config = splunk_config_processor::read_configs(&files);
// Access values
if let Some(default_stanza) = config.get("default") {
if let Some(host) = default_stanza.get("host") {
println!("Host: {}", host);
}
}- Stanza-based parsing:
[stanza_name]sections - Key-value pairs:
key = value - Multiline values: Continue with backslash (
\) - Comments: Lines starting with
#or; - LRU caching: Configurable cache size (default: 50MB)
- Merge semantics: Earlier files override later ones (like Splunk)
use splunklib_rust::splunk_config_processor;
// Set cache size to 100MB
splunk_config_processor::set_cache_size_mb(100);
// Get cache statistics
let stats = splunk_config_processor::get_cache_stats();
println!("Cache hits: {}, misses: {}", stats.hits, stats.misses);
// Clear cache
splunk_config_processor::clear_cache();Thread-safe file logger with rotation and batching.
use splunklib_rust::splunk_file_logger::{FileLogger, FileLoggerConfig};
use std::time::Duration;
let config = FileLoggerConfig::new("/var/log/myapp.log")
.with_rotate_size_bytes(Some(10 * 1024 * 1024)) // 10MB
.with_max_rotate_files(5)
.with_auto_flush_interval(Some(Duration::from_secs(5)))
.with_queue_capacity(Some(1024));
let logger = FileLogger::new(config)?;
// Log messages
logger.log("Hello, world!")?;
// Batch log
let messages = vec!["First", "Second", "Third"];
logger.log_batch(messages)?;
// Flush and shutdown
logger.flush()?;
logger.shutdown()?;- Asynchronous writes: Non-blocking logging via worker thread
- File rotation: Automatic rotation by size with configurable history
- Auto-flush: Periodic flush to ensure data durability
- Session tracking: Session ID prefix on each log entry
- Bounded queue: Backpressure to prevent memory exhaustion
- Statistics: Track bytes written, flush count, uptime, etc.
time=<epoch>, sid=<session_id>, <your_message>
Example:
time=1735032800, sid=123456789, Application started successfully
Send events to Splunk HTTP Event Collector (HEC).
use splunklib_rust::splunk_http_sender::{HttpEventSender, EventMetadata};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create sender
let sender = HttpEventSender::new(
"https://splunk:8088/services/collector/event",
true // verify SSL
)?;
// Set metadata
let metadata = EventMetadata::new(
"main", // index
"myapp", // source
"json", // sourcetype
"localhost" // host
);
// Send events
let events = vec![
serde_json::json!({"message": "First event"}),
serde_json::json!({"message": "Second event"}),
];
let (status, body) = sender.send_events(&metadata, &events).await?;
println!("Status: {}, Body: {}", status, body);
Ok(())
}- Async/await: Tokio-based async interface
- Connection pooling: Efficient reuse of HTTP connections
- Gzip compression: Automatic compression for large payloads
- Batch processing: Automatic chunking for large event batches
- Debug logging: Configurable logging of requests, headers, payloads
- Custom headers: Add authentication or other headers easily
- Metadata support: Index, source, sourcetype, host as query params
use splunklib_rust::splunk_http_sender::HttpEventSenderBuilder;
use std::time::Duration;
let sender = HttpEventSenderBuilder::new(
"https://splunk:8088/services/collector/event"
)
.verify_ssl(true)
.connect_timeout(Duration::from_secs(10))
.request_timeout(Duration::from_secs(30))
.enable_gzip(true, 1024) // Enable gzip for payloads >= 1024 bytes
.build()
.await?;// Add HEC token
sender.add_extra_header(
"Authorization".to_string(),
"Splunk <your-hec-token>".to_string()
).await;All components return Result types for proper error handling:
use splunklib_rust::splunk_http_sender::SplunkError;
match sender.send_events(&metadata, &events).await {
Ok((status, body)) => println!("Success: {}", status),
Err(SplunkError::HttpError(e)) => eprintln!("HTTP error: {}", e),
Err(SplunkError::HttpStatus { code, body }) => {
eprintln!("Splunk returned {}: {}", code, body);
}
Err(e) => eprintln!("Error: {}", e),
}All components are thread-safe and can be shared across threads:
use std::sync::Arc;
let logger = Arc::new(FileLogger::new(config)?);
let handles: Vec<_> = (0..10)
.map(|i| {
let logger = Arc::clone(&logger);
std::thread::spawn(move || {
logger.log(format!("Message from thread {}", i)).unwrap();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}- Use [
log_batch] instead of individual [log] calls for high-volume logging - Pre-allocate [
Vec] capacity when using iterators - Tune [
queue_capacity] based on your production load - Set [
auto_flush_interval] for durability vs throughput tradeoff
- Enable gzip for large payloads (>= 1KB)
- Use [
send_events_batched] for large event sets - Batch events to reduce HTTP overhead
- Configure appropriate timeouts for your network
- LRU cache is shared globally across all reads
- Set appropriate cache size for your workload
- Cache statistics can help tune
CACHE_SIZE_MB
See src/main.rs for a complete example demonstrating file logger usage.
cargo test