Crypto triangle arbitrage dashboard, part 3: how to add logging

Logging to a storage medium is useful for troubleshooting and analysis when any note-worthy events have occurred. In this article, we will do a quick update to the code base we created in part 1: Crypto triangle arbitrage dashboard: how to, part 1 to add logging to a file for our rust backend. We will add logging to stdout and a file making use of the log4rs crate.

For details on how to use log4rs please check out my article about basic file logging with log4rs.

Prerequisites

Since we are updating the existing code base for the crypto triangle arbitrage dashboard backend. It is a good idea to clone or download that code base (branch: release/0.1.1) from my GitHub page: here.

Other than that, familiarity with log4rs and the structure of the backend project also helps, of course. See the articles linked in the introduction to this article.

Add dependencies related to logging

The first step is to add the dependencies required to do the logging in our rust backend using the log4rs crate.

The updated Crate.toml is listed below:

[package]
name = "rust-triangle-arbitrage-dashboard-tutorial"
version = "0.2.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version= "1", features = ["full"] }
tokio-stream = "0.1.6"
warp = "0.3"
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
futures = { version = "0.3", default-features=false}
uuid = { version = "0.8.2", features = ["serde", "v4"]}
tungstenite = { version="0.14.0", features = ["rustls-tls"]}
url = "2.1.0"
log = "0.4"
log4rs = { version="1", features = ["background_rotation"]}

The added lines are 18 and 19.

Configuring the logging for the Rust backend

As stated before, we will start logging to a file, we will also keep logging to stdout, of course. On top of that, it would be nice to have a separate log file for “potential profit”.

Let’s create a configuration YAML file called log_config.yaml in the root directory of the project:

appenders:
  stdout_logger:
    kind: console
    encoder:
      pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
  main_file_logger:
    kind: rolling_file
    path: "logs/main.log"
    encoder:
      pattern: "{d(%Y-%m-%d %H:%M:%S)(utc)} - {h({l})}: {m}{n}"
    policy:
      trigger:
        kind: size
        limit: 10mb
      roller:
        kind: fixed_window
        base: 1
        count: 10
        pattern: "logs/main{}.log"
  profit_file_logger:
    kind: rolling_file
    path: "logs/profit.log"
    encoder:
      pattern: "{d(%Y-%m-%d %H:%M:%S.%f)(utc)} - {h({l})}: {m}{n}"
    policy:
      trigger:
        kind: size
        limit: 10mb
      roller:
        kind: fixed_window
        base: 1
        count: 10
        pattern: "logs/profit{}.log"
root:
  level: debug
  appenders:
    - stdout_logger
    - file_logger
loggers:
  profit:
    level: info
    appenders:
      - profit_file_logger

Standard logging will write to the console and logs/main.log, with a maximum size for the log file at 10Mb. Then, we have the profit_file_logger appender which will log to logs/profit.log and create archive files when the log file exceeds 10Mb in size. Note that the pattern for the profit_file_logger appender is a little different as we are printing milliseconds in the date-time there because it would be useful to see approximately how long profitable situations exist, and they usually disappear within a second or so.

To use this configuration file we simply add some imports to main.rs and a single line at the top of the main() function:

use log::{debug, info};
use log4rs;
use std::{collections::HashMap, convert::Infallible, sync::Arc};
use tokio::sync::{mpsc, Mutex};
use url::Url;
use warp::{ws::Message, Filter, Rejection};

And in main():

#[tokio::main]
async fn main() {
    log4rs::init_file("log_config.yaml", Default::default()).unwrap();

Replace println! with logging macros

Now all that is left is to replace println! macros with log level macros like debug!, info!, warn!, error!. We won’t go over all of them, but most of the println! that are in the 0.1.1 codebase, can probably be replaced with info!.

Here is an example of the main() function with the new logging:

#[tokio::main]
async fn main() {
    log4rs::init_file("log_config.yaml", Default::default()).unwrap();

    let clients: Clients = Arc::new(Mutex::new(HashMap::new()));

    info!("Configuring websocket route");
    let ws_route = warp::path("ws")
        .and(warp::ws())
        .and(with_clients(clients.clone()))
        .and_then(handlers::ws_handler);

    let routes = ws_route.with(warp::cors().allow_any_origin());

    info!("Connecting to binance stream...");
    let binance_url = get_binance_streams_url();
    let (socket, response) = tungstenite::connect(binance_url).expect("Can't connect.");
    info!("Connected to binance stream.");
    debug!("HTTP status code: {}", response.status());
    debug!("Response headers:");
    for (ref header, ref header_value) in response.headers() {
        debug!("- {}: {:?}", header, header_value);
    }

    info!("Starting update loop");
    tokio::task::spawn(async move {
        workers::main_worker(clients.clone(), socket).await;
    });
    info!("Starting server");
    warp::serve(routes).run(([127, 0, 0, 1], 8000)).await;
}

Logging potential profit to a separate file

If we have logging we can let our program run for a while in the background. Then, come back later, and see if we had detected any trades that could have made a profit. To log whenever a positive profit is detected, add the following to workers.rs in the process_triangle_data() function where a value is pushed onto the profits vector:

        let norm_profit = triangle_profit - 1.0;
        profits.push(norm_profit);
        if norm_profit > 0.0 {
            info!(target: "profit", "{:?} positive profit: {:.5}% ({} {})", triangle, (norm_profit*100.0), norm_profit, 
                  triangle[0]);
        }

Here we log a special message to our profit logger when an actual profit is detected. It will show the triangle in question (btc, eth bnb), profit percentage, and the amount of profit in the unit (example: in BTC) of the first part in the triangle.

Here is an example:

Logging output for Rust backend
Logging output for Rust backend

Conclusion to logging for our Rust backend

We now have proper logging to a file in our Rust backend. The logging logs profit events to a specific log file for a quick overview. With this logging, we can comfortably let our application run for a while and come back to it later to analyze the results. In the future, we can upgrade this logging to log to a database.

The updated crypto triangle arbitrage dashboard backend repository can be found on my GitHub in the 0.2.0 branch.

Please follow me on Twitter to get updates on this series and other Rust and cryptocurrency-related articles:

Leave a Reply

Your email address will not be published. Required fields are marked *