From b7f6bd1830444a5ce2bbe89cb54e5a8167be0671 Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Fri, 30 Jun 2023 16:28:29 +0200 Subject: [PATCH] Initial attempt to create Appmaps (currently manually but the supporting data structure is there) --- .gitignore | 4 + Cargo.toml | 13 ++ appmap.yml | 3 + src/appmap_definition.rs | 254 ++++++++++++++++++++++++++++++ src/appmap_definition/event_id.rs | 39 +++++ src/lib.rs | 16 ++ src/main.rs | 78 +++++++++ 7 files changed, 407 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 appmap.yml create mode 100644 src/appmap_definition.rs create mode 100644 src/appmap_definition/event_id.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da96c8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/Cargo.lock +/.idea +/maps diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..56de612 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "appmap_tracing_test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tracing = "0.1" +tracing-subscriber = "0.3" + +serde = { version = "1.0", features=["derive", "default"] } +serde_json = "1.0" diff --git a/appmap.yml b/appmap.yml new file mode 100644 index 0000000..bad68b4 --- /dev/null +++ b/appmap.yml @@ -0,0 +1,3 @@ +appmap_dir: maps/tmp/appmap +language: rust +name: appmap_tracing_test diff --git a/src/appmap_definition.rs b/src/appmap_definition.rs new file mode 100644 index 0000000..9fd7bd0 --- /dev/null +++ b/src/appmap_definition.rs @@ -0,0 +1,254 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +pub use event_id::EventId; + +use crate::appmap_definition::event_id::ObjectId; + +//region todo objects +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct MetadataObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ExceptionReturnObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct HttpServerRequestCallObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct HttpServerResponseCallObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct HttpClientRequestCallObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct HttpClientResponseCallObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct SqlQueryCallObject {} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct MessageCallObject {} +//endregion + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct AppMapObject { + pub version: String, + pub metadata: MetadataObject, + // pub class_map: Vec, + pub class_map: Vec, + pub events: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub event_updates: Option>, +} +//region events +mod event_id; +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct EventObject { + //region common + ///Required unique identifier. Example: 23522. + pub id: EventId, + ///Required identifier of the execution thread. Example: 70340688724000. + pub thread_id: u32, + //endregion + #[serde(flatten)] + pub event: EventObjectType, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(tag = "event")] +#[serde(rename_all = "camelCase")] +pub enum EventObjectType { + Call(CallObject), + Return(ReturnObject), +} +//region Return Objects +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ReturnObject { + ///Required id of the "call" event corresponding to this "return". + parent_id: u32, + ///Optional elapsed time in seconds of this function call. + elapsed: Option, + #[serde(flatten)] + data: ReturnObjectType, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +#[serde(rename_all = "camelCase")] +pub enum ReturnObjectType { + Normal, + Function(FunctionReturnObject), + Exception(ExceptionReturnObject), +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct FunctionReturnObject { + /// Optional object describing the return value. If present, this value uses parameter object format. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub return_value: Option, + /// Optional array of exceptions causing this method to exit. If present, this value uses exception + /// object format. When an exception is a wrapper for an underlying cause, the cause is the next + /// exception in the exceptions array. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub exceptions: Option>, +} +//endregion +//region call objects +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct CallObject { + ///Required name of the class which defines the method. Example: "MyApp::User". + pub defined_class: String, + ///Required name of the function which was called in this event. Example: "show". + pub method_id: String, + /// Recommended path name of the file which triggered the event. Example: "/src/architecture/lib/appland/local/client.rb". + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub path: Option, + ///Recommended line number which triggered the event. Example: 5. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub lineno: Option, + ///Optional parameter object describing the object on which the function is called. Corresponds to the receiver, self and this concept found in various programming languages. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub receiver: Option, + ///Recommended array of parameter objects describing the function call parameters. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub parameters: Option>, + ///Required flag if the method is class-scoped (static) or instance-scoped. Must be true or false. Example: true. + pub is_static: bool, + + #[serde(flatten)] + pub data: CallObjectType, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum CallObjectType { + Normal, + Function(FunctionCallObject), + HttpServerRequest(HttpServerRequestCallObject), + HttpServerResponse(HttpServerResponseCallObject), + HttpClientRequest(HttpClientRequestCallObject), + HttpClientResponse(HttpClientResponseCallObject), + SqlQuery(SqlQueryCallObject), + Message(MessageCallObject), +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +/// Note: +/// +/// In order to correlate function call events with function objects defined in the class map, the +/// path and lineno attributes of each "call" event should exactly match the location attribute of +/// the corresponding function in the classMap. +pub struct FunctionCallObject { + /// Recommended path name of the file which triggered the event. + /// Example: "/src/architecture/lib/appland/local/client.rb". + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub path: Option, + /// Recommended line number which triggered the event. Example: 5. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub lineno: Option, + /// Optional parameter object describing the object on which the function is called. Corresponds + /// to the receiver, self and this concept found in various programming languages. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub receiver: Option, + /// Recommended array of parameter objects describing the function call parameters. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub parameters: Option>, +} +//endregion + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ParameterObject { + /// + /// + ///Recommended name of the parameter. Example: "login". + name: Option, + /// Recommended unique id of the object. Example: 70340693307040 + object_id: Option, + ///Required fully qualified class or type name of the object. Example: "MyApp::User". + class: String, + ///Required string describing the object. This is not a strict JSON serialization, but rather a display string which is intended for the user. These strings should be trimmed in length to 100 characters. Example: "MyApp user 'alice'" + value: String, + /// Recommended number of elements in an array or hash object. Example. "5". + size: Option, + /// Optional schema indicating property names and types of hash and hash-like objects. Each entry is a name, class and optional nested properties or items. + properties: Option>, + /// Optional schema indicating element types of array and array-like objects. Each entry is a class and optional nested properties or items. + items: Option>, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct PropertiesObject { + pub name: String, + pub class: String, + pub properties: Option>, + pub items: Option>, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ItemObject { + pub class: String, + pub properties: Option>, + pub items: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ExceptionObject { + pub class: String, + pub message: String, + pub object_id: ObjectId, + pub path: Option, + pub lineno: Option, +} + +//endregion +//region class_map +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct CodeObject { + pub name: String, + pub ty: CodeObjectType, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum CodeObjectType { + Package(PackageCodeObject), + Class(ClassCodeObject), + Function(FunctionCodeObject), +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct PackageCodeObject { + pub name: String, + pub children: Option>, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct ClassCodeObject { + pub name: String, + pub children: Option>, +} +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct FunctionCodeObject { + pub name: String, + ///Recommended File path and line number, separated by a colon. Example: "/Users/alice/src/myapp/lib/myapp/main.rb:5". + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub location: Option, + ///Required flag if the method is class-scoped (static) or instance-scoped. Must be true or false. Example: true. + pub is_static: bool, + ///Optional list of arbitrary labels describing the function. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub labels: Option>, + ///Optional documentation comment for the function extracted from the source code. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub comment: Option, + ///Optional verbatim source code of the function. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub source: Option, +} + +//endregion diff --git a/src/appmap_definition/event_id.rs b/src/appmap_definition/event_id.rs new file mode 100644 index 0000000..ffd314c --- /dev/null +++ b/src/appmap_definition/event_id.rs @@ -0,0 +1,39 @@ +use std::ops::{Deref, DerefMut}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct EventId(u32); +impl Deref for EventId { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for EventId { + fn deref_mut(&mut self) -> &mut Self::Target { + self + } +} + +impl From for EventId { + fn from(value: u32) -> Self { + Self(value) + } +} + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct ObjectId(u64); +impl Deref for ObjectId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for ObjectId { + fn deref_mut(&mut self) -> &mut Self::Target { + self + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a72b09f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt; +use std::fmt::Debug; +use std::time::Instant; +use tracing::field::{Field, Visit}; +use tracing::span::{Attributes, Record}; +use tracing::{Id, Subscriber}; +use tracing_subscriber::layer::Context; +use tracing_subscriber::Layer; +pub mod appmap_definition; +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct AppMap { + #[serde(flatten)] + pub data: crate::appmap_definition::AppMapObject, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ff7530c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; +use std::error::Error; +use std::fmt::Error as FmtError; +use std::path::PathBuf; + +use appmap_tracing_test::appmap_definition::{ + AppMapObject, CallObject, CallObjectType, ClassCodeObject, CodeObject, CodeObjectType, EventId, + EventObject, EventObjectType, FunctionCallObject, FunctionCodeObject, MetadataObject, + PackageCodeObject, +}; +use appmap_tracing_test::AppMap; + +pub fn main() -> Result<(), Box> { + sample_json()?; + Ok(()) +} +fn sample_json() -> Result<(), Box> { + let data = AppMap { + data: AppMapObject { + metadata: MetadataObject {}, + class_map: vec![CodeObjectType::Package(PackageCodeObject { + name: "main pkg".to_string(), + children: Some(vec![CodeObjectType::Class(ClassCodeObject { + name: "main cls".to_string(), + children: Some(vec![CodeObjectType::Function(FunctionCodeObject { + name: "sample_json".to_string(), + location: Some(format!( + "{}:{}", + PathBuf::from("src/main.rs").to_str().ok_or(FmtError)?, + 14 + )), + is_static: true, + labels: Some(vec!["security".to_string()]), + comment: None, + source: None, + })]), + })]), + })], + events: vec![EventObject { + id: EventId::from(1), + thread_id: 9999, + event: EventObjectType::Call(CallObject { + defined_class: "main".to_string(), + method_id: "sample_json".to_string(), + path: Some(PathBuf::from("src/main.rs")), + lineno: Some(14), + receiver: None, + parameters: None, + is_static: true, + data: CallObjectType::Function(FunctionCallObject { + path: None, + lineno: None, + receiver: None, + parameters: None, + }), + }), + }], + version: String::from("1.12"), + event_updates: None, + }, + }; + + println!("data debug: {:?}", data); + let data_string = serde_json::to_string_pretty(&data)?; + println!("data to string: {}", data_string); + + let data_reversed: AppMap = sample_from_str(&data_string)?; + println!("data_reversed debug: {:?}", data_reversed); + assert_eq!(data, data_reversed); + println!("it works!"); + + Ok(()) +} +fn sample_from_str(s: &str) -> Result> { + let data_reversed: AppMap = serde_json::from_str(s)?; + println!("sample_from_str(s): result debug: {:?}", data_reversed); + Ok(data_reversed) +}