Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logger for generated microservice, simplify justfile commands #94

Merged
merged 3 commits into from
Jul 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Crustagen is a Rust code generator that takes AsyncAPI specifications as input a
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Limitations](#limitations)
- [Contribute](#contribute)
- [License](#license)

Expand All @@ -25,7 +26,7 @@ Clone and build the Crustagen project:

```sh
git clone https://github.com/Programmierpraktikum-MVA/AsyncAPI.git
cd crustagen
cd AsyncAPI
just install # Alternatively, you can use 'cargo build --release'
```

Expand All @@ -34,30 +35,30 @@ just install # Alternatively, you can use 'cargo build --release'
To generate Rust code from an AsyncAPI specification, use the following `just` command:

```sh
just run-generator specfile_path="./example/specs/basic.yaml" output="./output" # Alternatively, you can use 'cargo run -- -s ./example/specs/basic.yaml -o ./output'
just run example/specs/basic.yaml output # Alternatively, you can use 'cargo run -- -s ./example/specs/basic.yaml -o ./output'
```

This will generate a Rust project in the specified output directory.

To run the server, navigate to the output directory (replace `{project-id}` with the actual project directory name, the title of the spec) and use the `just` command:

```sh
just start-service service_name={project-id} # Alternatively, you can use 'cd output/{project-id} && cargo run'
just start-service {project-id} # Alternatively, you can use 'cd output/{project-id} && cargo run'
```

To view the auto-generated documentation, use the following command:

```sh
just generate-service-docs service_name={project-id} # Alternatively, you can use 'cd output/{project-id} && cargo doc --open'
just service-doc {project-id} # Alternatively, you can use 'cd output/{project-id} && cargo doc --open'
```

Remember to replace `{project-id}` with the actual project directory name.
Remember to replace `{project-id}` with the name of your generated microservice (`title` field from the provided spec).

## Limitations

- only json payloads are currently supported for automatic deserialization
- only one server is currently supported and only nats protocol is supported
- only one message is currently supported per channel, payloads can be choosen freely including anyOf/oneOf/allOf
- Only json payloads are currently supported for automatic deserialization
- Only one server is currently supported and only nats protocol is supported
- Only one message is currently supported per channel, payloads can be choosen freely including anyOf/oneOf/allOf

## Contribute

Expand Down
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ install:
cargo build --release

# Run the generator with the provided specification file, and optional title and output directory
run-generator specfile_path="example/specs/basic.yaml" output="output" title="":
run specfile_path="example/specs/basic.yaml" output="output" title="":
set -e
if [ -z "{{title}}" ]; then \
cargo run --release -- --specification {{specfile_path}} --output {{output}}; \
Expand All @@ -21,7 +21,7 @@ test:
cargo test

# Generate documentation for the generator project
generate-docs:
doc:
cargo doc --open

# Clean up build artifacts
Expand All @@ -41,7 +41,7 @@ start-service service_name:

# Generate documentation for the generated microservice
# Uses the path to the last generated output directory with the provided service_name
generate-service-docs service_name:
service-doc service_name:
if [ -f .last_output_directory ]; then \
output_directory=$(cat .last_output_directory); \
cd "$output_directory/{{service_name}}" && cargo doc --open; \
Expand Down
2 changes: 1 addition & 1 deletion src/generator/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ macro_rules! cargo_command {
/// checks if project with name already exists, if yes asks for permission to overwrite
pub fn check_for_overwrite(output_path: &Path, project_title: &str) {
if output_path.exists() {
println!("A project with the name {} already exists in the current directory, do you want to overwrite the existing project? \nWARNING: This will delete all files in the directory and all applied. \nType 'y' to continue or anything else to exit.", project_title);
println!("\nA project with the name {} already exists in the current directory: {}. Do you want to overwrite it? \n\n❗ WARNING: This will permanently delete all files in the directory! \n\nType 'y' to continue or anything else to exit.", project_title, output_path.to_string_lossy());
let mut input = String::new();
match std::io::stdin().read_line(&mut input) {
Ok(_) => {
Expand Down
24 changes: 17 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ fn main() {
let template_path = Path::new("./templates/");

let spec: AsyncAPI = match parser::asyncapi_model_parser::parse_spec_to_model(specfile_path) {
Ok(spec) => {
println!("🎉 Specification was parsed successfully!");
spec
}
Ok(spec) => spec,
Err(e) => {
eprintln!("❌ Error parsing the specification: {}", e);
std::process::exit(1);
Expand Down Expand Up @@ -69,6 +66,7 @@ fn main() {
"src/utils/common.go",
"src/config/mod.go",
"src/tracing/mod.go",
"src/logger/mod.go",
]
.into_iter(),
);
Expand All @@ -79,19 +77,31 @@ fn main() {

// make output a compilable project in output_path
cargo_command!("init", "--bin", output_path);
// runs cargo format on path
cargo_command!("fmt", "--", output_path.join("src/main.rs"));
// add dependencies
append_file_to_file(
template_path.join("dependencies.toml"),
output_path.join("Cargo.toml"),
)
.unwrap();

println!("✨ Successfully added dependencies, formatting code...");
// runs cargo format on path
cargo_command!("fmt", "--", output_path.join("src/main.rs"));
// cargo fix, mostly for cleaning unused imports
cargo_command!("fix", "--bin", output_path, "--allow-dirty");
cargo_command!(
"fix",
"--manifest-path",
output_path.join("Cargo.toml"),
"--allow-dirty"
);

if args.doc {
println!("📚 Generating docs...");
cargo_command!(output_path, "doc", "--no-deps");
}

println!(
"🎉 Generation finished!\n\n Run the service using:\n cd {} && cargo run\n\n If you are in the generator root, start the service using:\n just start-service {}\n",
output_path.to_string_lossy(), title.replace(' ', "_").to_lowercase()
);
}
2 changes: 1 addition & 1 deletion src/parser/asyncapi_model_parser/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pub fn validate_asyncapi_schema(validator: &serde_json::Value, instance: &serde_
}
panic!("Validation failed");
} else {
println!("✅ Validation succeeded!");
println!("✅ Specification valid!");
}
}
4 changes: 3 additions & 1 deletion templates/dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ tokio = { version = "1.28.2", features = ["full"] }
dotenv = "0.15.0"
clap = {version = "4.3.0", features = ["derive"]}
opentelemetry = { version = "*", features = ["rt-tokio"] }
opentelemetry-jaeger = { version = "*", features = ["rt-tokio", "isahc_collector_client"] }
opentelemetry-jaeger = { version = "*", features = ["rt-tokio", "isahc_collector_client"] }
log = "0.4.0"
env_logger = "0.10.0"
18 changes: 9 additions & 9 deletions templates/src/handler.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use async_nats::{Client, Message, jetstream};
use async_nats::jetstream::Context;
use crate::{publish_message,stream_publish_message,model::*,config::*};
use std::time;
use opentelemetry::global;
use opentelemetry::trace::Tracer;
use log::{debug, warn};


{{ range .publish_channels }}
Expand All @@ -25,22 +25,22 @@ use opentelemetry::trace::Tracer;
{{ if .payload}}
match serde_json::from_slice::<{{ .payload.struct_reference }}>(&message.message.payload.as_ref()) {
Ok(deserialized_message) => {
println!("Received message {:#?}", deserialized_message);
debug!("Received message {:#?}", deserialized_message);
// TODO: Replace this with your own handler code
{{ if eq .payload.model_type "enum"}}
match deserialized_message {
{{$enumName := .payload.unique_id}}
{{ range .payload.related_models }}
{{ $enumName }}::{{ .unique_id }}(payload) => {
// TODO: Replace this with your own handler code
println!("Received message payload {{ .unique_id }} {:?}", payload);
debug!("Received message payload {{ .unique_id }} {:?}", payload);
}
{{ end }}
}
{{ end }}
},
Err(_) => {
println!("Failed to deserialize message payload: {{ .unique_id }}\nOriginal message: {:#?}", message);
debug!("Failed to deserialize message payload: {{ .unique_id }}\nOriginal message: {:#?}", message);
// TODO: Handle the failed deserialization here
},
}
Expand All @@ -61,17 +61,17 @@ use opentelemetry::trace::Tracer;
{{ range .payload.related_models }}
{{ $enumName }}::{{ .unique_id }}(payload) => {
// TODO: Replace this with your own handler code
println!("Received message payload {{ .unique_id }} {:?}", payload);
debug!("Received message payload {{ .unique_id }} {:?}", payload);
}
{{ end }}
}
{{else}}
println!("Received message {:#?}", deserialized_message);
debug!("Received message {:#?}", deserialized_message);
// TODO: Replace this with your own handler code
{{ end }}
},
Err(_) => {
println!("Failed to deserialize message payload: {{ .unique_id }}\nOriginal message: {:#?}", message);
warn!("Failed to deserialize message payload: {{ .unique_id }}\nOriginal message: {:#?}", message);
// TODO: Handle the failed deserialization here
},
}
Expand Down Expand Up @@ -105,7 +105,7 @@ use opentelemetry::trace::Tracer;
let payload = match serde_json::to_string(&payload) {
Ok(payload) => payload,
Err(_) => {
println!("Failed to serialize message payload: {{ .payload.struct_reference }}");
warn!("Failed to serialize message payload: {{ .payload.struct_reference }}");
return;
}
};
Expand All @@ -125,7 +125,7 @@ use opentelemetry::trace::Tracer;
let payload = match serde_json::to_string(&payload) {
Ok(payload) => payload,
Err(_) => {
println!("Failed to serialize message payload: {{ .payload.struct_reference }}");
warn!("Failed to serialize message payload: {{ .payload.struct_reference }}");
return;
}
};
Expand Down
34 changes: 34 additions & 0 deletions templates/src/logger/mod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::str::FromStr;
use log::LevelFilter;

#[derive(Debug)]
pub enum LogLevelParseError {
InvalidLogLevel,
}

pub struct LogLevel(LevelFilter);

impl FromStr for LogLevel {
type Err = LogLevelParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let level = match s {
"OFF" => Ok(LevelFilter::Off),
"ERROR" => Ok(LevelFilter::Error),
"WARN" => Ok(LevelFilter::Warn),
"INFO" => Ok(LevelFilter::Info),
"DEBUG" => Ok(LevelFilter::Debug),
"TRACE" => Ok(LevelFilter::Trace),
_ => Err(LogLevelParseError::InvalidLogLevel),
}?;
Ok(LogLevel(level))
}
}

pub fn init_logger(log_level: &str) {
let log_level: LevelFilter = match log_level.parse::<LogLevel>() {
Ok(LogLevel(level)) => level,
Err(_) => panic!("Invalid log level"),
};
env_logger::Builder::new().filter(None, log_level).init();
}
34 changes: 22 additions & 12 deletions templates/src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@ use utils::*;
use crate::handler::*;
use async_nats::jetstream::{self};
use std::{collections::HashMap};
use dotenv::dotenv;
use log::info;
mod config;
mod tracing;

mod logger;

#[tokio::main]
async fn main() -> Result<(), async_nats::Error> {
// Load .env file
let env: HashMap<String,String> = config::get_env();
let args = cli::Args::parse();

// Initialize logger
let log_lvl = env.get("LOG_LEVEL").unwrap().parse().unwrap_or("INFO".to_string());
logger::init_logger(&log_lvl);

// Initialize tracing
let tracing_enabled: bool = env.get("TRACING_ENABLED").unwrap().parse().unwrap();
if (tracing_enabled) {
// Initialize Jaeger Tracer
let tracer = tracing::init_jaeger_tracer("{{ .title}}");
if tracing_enabled {
let _tracer = tracing::init_jaeger_tracer("{{ .title}}");
}

// Connect to NATS server
let nats_url = env.get("SERVER_URL").unwrap();
info!("Connecting to a NATS server: {}", nats_url);
let client = async_nats::connect(nats_url).await?;

let client = async_nats::connect(env.get("SERVER_URL").unwrap()).await?;

// Subscribe to channels
{{ range .publish_channels }}
{{ if (index . 1).original_operation.bindings }}
{{ if (index . 1).original_operation.bindings.nats.queue }}
Expand All @@ -42,9 +50,11 @@ async fn main() -> Result<(), async_nats::Error> {
{{end}}
{{end}}

// Parse CLI arguments
let args = cli::Args::parse();
handle_cli(&client, &args.command, &args.message).await?;


// Listen for messages
tokio::join!(
{{ range .subscribe_channels }}
{{ $isStream := false }}
Expand All @@ -65,10 +75,10 @@ async fn main() -> Result<(), async_nats::Error> {
{{ end }}
);

if (tracing_enabled) {
// Shutdown Jaeger Tracer
// Shutdown Jaeger Tracer
if tracing_enabled {
tracing::shutdown_tracer_provider();
}
println!("fin");
info!("Shutting down...");
Ok(())
}
4 changes: 1 addition & 3 deletions templates/src/tracing/mod.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use opentelemetry::global;
use opentelemetry::trace::Tracer;


pub fn init_jaeger_tracer(service_name: &str) {
global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
let tracer = opentelemetry_jaeger::new_agent_pipeline()
opentelemetry_jaeger::new_agent_pipeline()
.with_service_name(service_name)
.install_batch(opentelemetry::runtime::Tokio)
.expect("Failed to initialize Jaeger Tracer");
Expand Down
5 changes: 3 additions & 2 deletions templates/src/utils/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use async_nats::{Client, Message, Subscriber};
use futures::StreamExt;
use log::debug;

pub async fn listen_for_message<'a, F, Fut>(sub: &mut Subscriber, handler: F, client: &'a Client)
where
Expand All @@ -8,7 +9,7 @@ where
{
while let Some(message) = sub.next().await {
handler(message, client).await;
println!("Message received by Subscriber: {:?}", sub);
debug!("Message received by Subscriber: {:?}", sub);
}
}

Expand All @@ -18,6 +19,6 @@ pub async fn publish_message(client: &Client, channel: &str, payload: &str) {
.publish(channel.to_string(), owned_payload)
.await
.unwrap();
println!("sent");
debug!("Published message to channel: {}", channel);
}

Loading
Loading