20 releases

0.2.5 Apr 29, 2021
0.2.4 Jul 7, 2020
0.2.3 Jun 16, 2020
0.2.1 Jan 29, 2020
0.1.5 Jul 26, 2019

#312 in Debugging

Download history 1/week @ 2024-06-17 23/week @ 2024-07-08 73/week @ 2024-09-23

73 downloads per month
Used in 4 crates (via realm)

MIT license

60KB
1.5K SLoC

Observer

** Observer is a library to capture observability of rust servers

Observer 2.0 in action

To use observer 2.0

  1. Add dependency into Cargo.toml.
observer = "0.2.0"
observer_attribute = "0.1.6"
  1. import these dependency into lib.rs.
extern crate observer;
#[macro_use]
extern crate observer_attribute;
  1. Define event json file and export events path as EVENTS_PATH.
export EVENTS_PATH="<Path of events.json file>"

event.json file

{
    "foo__create_temp" : {
      "critical" : true,
      "result_type": "list",
      "fields" : {
        "id" : "string"
      }
    },
    "update_temp" : {
      "critical" : false,
      "result_type": "i32",
      "fields" : {
        "id" : "string"
      }
    }
}
  1. Use observer into your project
// Here namespace and with_result are optional parameter.
// If define namespace so in event file needs to define `foo__create_policy`
// If with_result defined so it log error also, if function returns an error.
use observer::prelude::*;
use observer::Result;

pub struct Temp;

#[observed(with_result)]
pub fn update_temp(id: &str) -> observer::Result<Temp> {
    observe_field("id", id); // Need to tell type of id's value in event.json
    observe_result(2314);  // Need to tell type of result in event.json
    observer::observe_span_log("Message from update temp");
    Ok(Temp)
}

#[observed(namespace = "foo")]
pub fn create_temp(id: &str) -> observer::Result<Temp> {
    observe_field("id", "4839");
    observe_result(&vec![1,2,3,4]);
    update_temp(id)
}

fn main(){
    // define logger
    let logger = observer::backends::logger::Logger::builder()
            .with_path("/tmp/observer.log")
            .with_stdout()
            .build();

    // Initialize observer with logger
    observer::builder(Box::new(logger))
            .create_context("main")
            .init();

    // Call your functions
    let _result = create_temp("temp");

    // End of the observer.
    observer::end_context();

}

In stdout it should look like

logger_initialized
context: main [0ms, 2020-01-29 11:10:54.728594 UTC]
    foo__create_temp: 0ms
        @id: "4839"
        @@success: true
        #result: [1,2,3,4]
        update_temp: 0ms
            @id: "temp"
            @@success: true
            #result: 2314
            logs:
               - 0ms: Message from update temp

In log file it should look the same.

logger_initialized
context: main [0ms, 2020-01-29 11:10:54.728594 UTC]
    foo__create_temp: 0ms
        @id: "4839"
        @@success: true
        #result: [1,2,3,4]
        update_temp: 0ms
            @id: "temp"
            @@success: true
            #result: 2314
            logs:
               - 0ms: Message from update temp

Observer 0.1.* in action

To use Observer

  1. Have to define events file(json file and mandatory).
  2. Have to define logs dir else it will take default as /var/log/.

Firstly We have to define an events to observe functions. Here Events are nothing but same as function name and in events we have tell which fields has be save in breadcrumbs. And critical means whether to save this function locally or queue. If critical It will go directly to queue else Observer will save it local.

Here we have defined to events observer_me and observe_me_too (same as function name).

{
    "observe_me" : {
        "critical" : true,
        "fields" : {
            "foo" : "String",
            "foo1" : "f32"
        }
    },
    "observe_me_too" : {
        "critical" : false,
        "fields" : {
            "foo1" : "i32"
        }
    }
}
// src/bin/main.rs
use observer::{
    context::{observe_string, observe_i32, observe_f32},
    observe::observe,
    queue::Queue,
};

#[observed] // Need to define only this on top of fn which we want to observe
// Context reference is mandatory to pass in observer function.
// fn should be return Result type.
fn observe_me(ctx: &Context, other_params: i32)-> Result<String> {
    // in "foo" can store only string value else it will give compile error
    // It will this in breadcrumbs in Frame
    observe_field("foo", "value".to_string());
    // in "foo1" can store only f32 value else it will give compile error
    // It will this in breadcrumbs in Frame
    observe_field("foo1", 32.02);
    some_other_fn_call();

    // Observing this fn also and it will become a sub-frame of observe_me
    observe_me_too(ctx);
    Ok("observed")
}

fn some_other_fn_call() {}

#[observed]
fn observe_me_too(ctx: &Context) -> Result<i32> {
    observe_field("foo1", 32);
    Ok(12)
}


#[derive(Serialize, Debug, Deserialize)]
pub struct DemoQueue {
    pub name: String,
}

#[typetag::serde(name = "Abc")]
impl Queue for DemoQueue {
    // TODO: Will give complete definition of in next version surely
    fn enqueue(&mut self, data: serde_json::Value) {
        println!("Data: {}", data)
    }
}


fn main() {
    let ctx = Context::new(
        "test_context".to_string(),
        Box::new(DemoQueue{name: "Abrar".to_string()})
    );
    let _result = observe_me(&ctx, 12);
    ctx.finalise();
}

We are calling observer_me as first function and observer_me_too inside it. In case of Context Object Observer will create frame observer_me and observer_me_too. Because observer_me_too is calling calling inside from observer_me so it will become sub-frame of observer_me.

It will create log dir by given path or default(/var/log/) and save context into log_dir_path/context and events in log_dir_path/observe_me and log_dir_path/observe_me_too based of criticality of of an event.

Context log will look like this

{
  "frame": {
    "breadcrumbs": {},
    "key": "17eb437f-a5e2-4243-8dac-fa636429dcf9",
    "result": null,
    "sub_frames": [
      {
        "breadcrumbs": {
          "foo": 32
        },
        "key": "59471fc8-3391-4619-b341-931658a2296e",
        "result": 12,
        "sub_frames": [
          {
            "breadcrumbs": {
              "foo1": 32.02
            },
            "key": "399c8d43-16fb-4cd3-8273-b2666026f2f0",
            "result": "observed",
            "sub_frames": [],
            "success": true,
            "end_time": "2019-07-06T08:27:20.451786Z",
            "id": "observe_me_too",
            "start_time": "2019-07-06T08:27:20.451642Z"
          }
        ],
        "success": true,
        "end_time": "2019-07-06T08:27:20.452680Z",
        "id": "observe_me",
        "start_time": "2019-07-06T08:27:20.451618Z"
      }
    ],
    "success": null,
    "end_time": "2019-07-06T08:27:20.452683Z",
    "id": "main",
    "start_time": "2019-07-06T08:27:20.451590Z"
  },
  "key": "302a5760-107a-4826-8670-2efd57db27c2",
  "queue": {
    "type": "Abc",
    "value": {
      "name": "Abrar"
    }
  },
  "id": "test_context"
}

Frame/Event log will look like this

{
  "key": "399c8d43-16fb-4cd3-8273-b2666026f2f0",
  "id": "observe_me_too",
  "breadcrumbs": {
    "foo1": 32.02
  },
  "end_time": "2019-07-06T08:27:20.451786Z",
  "result": "observed",
  "start_time": "2019-07-06T08:27:20.451642Z",
  "sub_frames": [],
  "success": true
}

TO run it

It will take to path from env, EVENTS_PATH(Mandatory) and LOG_DIR (If not exists so it will take /var/log/)

EVENTS_PATH="" LOG_DIR="" cargo run --bin main

Dependencies

~8–16MB
~226K SLoC