Initial attempt to create Appmaps (currently manually but the supporting data structure is there)

This commit is contained in:
OMGeeky
2023-06-30 16:28:29 +02:00
commit b7f6bd1830
7 changed files with 407 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
/Cargo.lock
/.idea
/maps

13
Cargo.toml Normal file
View File

@@ -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"

3
appmap.yml Normal file
View File

@@ -0,0 +1,3 @@
appmap_dir: maps/tmp/appmap
language: rust
name: appmap_tracing_test

254
src/appmap_definition.rs Normal file
View File

@@ -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<CodeObject>,
pub class_map: Vec<CodeObjectType>,
pub events: Vec<EventObject>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub event_updates: Option<HashMap<u32, EventObject>>,
}
//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<usize>,
#[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<ParameterObject>,
/// 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<Vec<ExceptionObject>>,
}
//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<PathBuf>,
///Recommended line number which triggered the event. Example: 5.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub lineno: Option<usize>,
///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<ParameterObject>,
///Recommended array of parameter objects describing the function call parameters.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub parameters: Option<Vec<ParameterObject>>,
///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<PathBuf>,
/// Recommended line number which triggered the event. Example: 5.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub lineno: Option<usize>,
/// 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<ParameterObject>,
/// Recommended array of parameter objects describing the function call parameters.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub parameters: Option<Vec<ParameterObject>>,
}
//endregion
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct ParameterObject {
///
///
///Recommended name of the parameter. Example: "login".
name: Option<String>,
/// Recommended unique id of the object. Example: 70340693307040
object_id: Option<ObjectId>,
///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<usize>,
/// 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<Vec<PropertiesObject>>,
/// Optional schema indicating element types of array and array-like objects. Each entry is a class and optional nested properties or items.
items: Option<Vec<ItemObject>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct PropertiesObject {
pub name: String,
pub class: String,
pub properties: Option<Vec<PropertiesObject>>,
pub items: Option<Vec<ItemObject>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct ItemObject {
pub class: String,
pub properties: Option<Vec<PropertiesObject>>,
pub items: Option<Vec<ItemObject>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct ExceptionObject {
pub class: String,
pub message: String,
pub object_id: ObjectId,
pub path: Option<PathBuf>,
pub lineno: Option<usize>,
}
//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<Vec<CodeObjectType>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct ClassCodeObject {
pub name: String,
pub children: Option<Vec<CodeObjectType>>,
}
#[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<String>,
///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<Vec<String>>,
///Optional documentation comment for the function extracted from the source code.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub comment: Option<String>,
///Optional verbatim source code of the function.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub source: Option<String>,
}
//endregion

View File

@@ -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<u32> 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
}
}

16
src/lib.rs Normal file
View File

@@ -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,
}

78
src/main.rs Normal file
View File

@@ -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<dyn Error>> {
sample_json()?;
Ok(())
}
fn sample_json() -> Result<(), Box<dyn Error>> {
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<AppMap, Box<dyn Error>> {
let data_reversed: AppMap = serde_json::from_str(s)?;
println!("sample_from_str(s): result debug: {:?}", data_reversed);
Ok(data_reversed)
}