Convenient Linux services

A suite of Linux tools and libraries developed in Rust, designed for creating robust, efficient, and secure service-based solutions

Krossbar tools

Everything you need to build robust, efficient services

Bus

  • Krossbar uses RPC to connect services using fast, permission-based bus.
  • Make remote procedure calls, subscribe to signals and maintain states.
  • Use monitoring tools to watch message exchange in real time.
  • Let Krossbar reconnect and resubscribe to a client when restarted.
See Examples

Logging

  • Krossbar logging utilizes Krossbar RPC to send logs to a logging service.
  • Keep your logs in one place. Control where to store service logs using service settings.
  • Monitor and filter logs as you need.
  • Manage logging settings in one place using management tools.
See Examples

App settings

  • Krossbar uses JSON based settings files to store application settings.
  • Store and query service state. View service state in real time.
  • Use Krossbar bus interfaces to expose service state if needed.
  • Update and read state automatically across service runs.
See Examples

Why Choose Krossbar?

Designed with performance and developer experience in mind

High-performance Libraries

Krossbar is built on top of high-performance async libraries, which can handle hundreds of thousands of RPC calls per second.

Comprehensive Toolkit

Krossbar includes utilities to monitor, debug, and manage services. This reduces the time and effort required to develop complex service-based applications.

Safety and Security

Rust's guarantees around memory safety and data race prevention make Krossbar an ideal choice for developing secure services.

Modern Tooling

Use Rust's ecosystem and tooling, to improve development experience with easy dependency management, testing, and building.

Code Examples

See Krossbar in action with these practical examples

Krossbar Bus

Create services

Register methods, signals, and states to provide service to client service.

use std::{
    path::PathBuf,
    sync::atomic::{AtomicU64, Ordering},
};

use log::{info, LevelFilter};
use tokio::{self};

use krossbar_bus_common::DEFAULT_HUB_SOCKET_PATH;
use krossbar_bus_lib::service::Service;

#[tokio::main]
async fn main() {
    pretty_env_logger::formatted_builder()
        .filter_level(LevelFilter::Info)
        .init();

    // Making a new Krossbar service
    let mut service = Service::new(
        "com.examples.demo_server",
        &PathBuf::from(DEFAULT_HUB_SOCKET_PATH),
    )
    .await
    .unwrap();

    // Register a state which holds each 10th client
    let mut state = service
        .register_state("10th_client", (0u64, String::new()))
        .unwrap();

    // Register a signal which emits when we've a new method call
    let signal = service.register_signal("client_count").unwrap();

    let client_counter = AtomicU64::from(0);

    // Register a method to greet clients
    service
        .register_async_method("greet", move |service_name, name: String| {
            info!("A call from \"{service_name}\": \"{name}\"");

            let client_count = client_counter.fetch_add(1, Ordering::Relaxed);

            // Note: `signal` and `state` methods to broadcast values don't
            // capture `self`, which allows moving the result futures
            // into async block below
            let signal_sender = signal.clone().emit(client_count);
            let state_sender = if client_count % 10 == 0 {
                Some(state.set((client_count, name.clone())))
            } else {
                None
            };

            async move {
                signal_sender.await.unwrap();
                if let Some(state_sender) = state_sender {
                    state_sender.await.unwrap();
                }

                format!("Hello, {name}")
            }
        })
        .unwrap();

    // Register a method to receive one-way messages
    service
        .register_method("messages", move |service_name, name: String| {
            info!("A message from \"{service_name}\": \"{name}\"");
        })
        .unwrap();

    // Spawn a task to poll incoming data
    tokio::spawn(service.run());
    let _ = tokio::signal::ctrl_c().await;
}

Connect clients

Call methods and states, subscribes to singlas and state changes from the clients.

use std::{path::PathBuf, time::Duration};

use futures::StreamExt;
use log::{LevelFilter, *};
use tokio::{self, select};

use krossbar_bus_common::DEFAULT_HUB_SOCKET_PATH;
use krossbar_bus_lib::service::Service;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    pretty_env_logger::formatted_builder()
        .filter_level(LevelFilter::Info)
        .init();

    // Register new Krossbar service
    let mut service = Service::new(
        "com.examples.demo_client",
        &PathBuf::from(DEFAULT_HUB_SOCKET_PATH),
    )
    .await
    .unwrap();

    // Connect to a demo server
    let server_connection = service
        .connect_await("com.examples.demo_server")
        .await
        .unwrap();

    // Spawn a task to poll incoming messages
    // Note: you can move service handle into the task and continue using
    // `Client` handle
    tokio::spawn(service.run());

    // Subscribe to call counter
    let mut subscription = server_connection
        .subscribe::<u64>("client_count")
        .await
        .unwrap();

    // Subscribe for each 10th client info
    let mut state_subscription = server_connection
        .subscribe::<(u64, String)>("10th_client")
        .await
        .unwrap();

    let mut counter = 0u64;
    'outer: loop {
        let client_name = format!("Client-{counter}");

        // Make a regular call
        let response: String = server_connection.call("greet", &client_name)
            .await
            .unwrap();
        info!("Call response \"{response}\"");

        // Random frequence here
        if counter % 3 == 0 {
            // Get last 10th client
            let response: (u64, String) = server_connection.get("10th_client")
                .await
                .unwrap();
            info!("10th client call \"{response:?}\"");
        }

        // Random frequence here too
        if counter % 7 == 0 {
            let message = format!("Message from {client_name}");

            // One-way message
            server_connection
                .message("messages", &message)
                .await
                .unwrap();
        }

        loop {
            select! {
                value = subscription.next() => {
                    info!("Client count: {value:?}");
                },
                value = state_subscription.next() => {
                    info!("10th client: {value:?}");
                },
                _ = tokio::time::sleep(Duration::from_secs(1)) => {
                    break;
                }
                _ = tokio::signal::ctrl_c() => {
                    break 'outer;
                }
            };
        }

        counter += 1;
    }
}

Bus monitor

Monitor message exchange using Krossbar monitor tool.

Bus connect

Connect to services to inspect registered endpoint, or make interactive calls.

Krossbar Log

Connect logging

Connect services to a logger to keep all logs in one place. Log into stdout if needed. Log only into stdout during developments.

use std::time::Duration;

use log::*;

use krossbar_log_lib::init_logger;

#[tokio::main]
async fn main() {
    let logger = init_logger("com.examples.logging", LevelFilter::Trace, true).await;

    tokio::spawn(logger.run());

    loop {
        error!("Error message");
        warn!("Warning message");
        info!("Info message");
        debug!("Debug message");
        trace!("Trace message");

        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}

Monitor logs

Use krossbar-log-viewer to view log file. Despite Krossbar logs are plain text files, the viewer highlights each process, log level, and timestamp for easy monitoring. Use 'follow' mode to watch log as they appear

Krossbar Settings

Manage service settings

Use Krossbar settings to store and retrieve service settings accross sessions. Use settings library to automatically save any serializeable structure.

use serde::{Deserialize, Serialize};

use krossbar_settings_lib::Settings;

#[derive(Serialize, Deserialize)]
struct ComplexStructure {
    integer: i32,
    string: String,
    vec: Vec<bool>,
}

fn main() {
    let storage = Settings::init("krossbar.viewer.example").unwrap();

    storage.read_or_insert("int", 42).unwrap();

    storage
        .read_or_insert("string", "Hello, world!".to_owned())
        .unwrap();

    storage
        .read_or_insert(
            "struct",
            ComplexStructure {
                integer: 42,
                string: "Test".into(),
                vec: vec![true, false],
            },
        )
        .unwrap();
}

Read service settings

Use krossbar-settings-viewer to view current service settings

Ready to get started?

Join our community and build amazing service-based applications with Krossbar