romance_core/addon/
observability.rs1use crate::addon::Addon;
2use anyhow::Result;
3use std::path::Path;
4
5pub struct ObservabilityAddon;
6
7impl Addon for ObservabilityAddon {
8 fn name(&self) -> &str {
9 "observability"
10 }
11
12 fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
13 super::check_romance_project(project_root)
14 }
15
16 fn is_already_installed(&self, project_root: &Path) -> bool {
17 project_root
18 .join("backend/src/middleware/request_id.rs")
19 .exists()
20 }
21
22 fn install(&self, project_root: &Path) -> Result<()> {
23 install_observability(project_root)
24 }
25
26 fn uninstall(&self, project_root: &Path) -> Result<()> {
27 use colored::Colorize;
28
29 println!("{}", "Uninstalling observability...".bold());
30
31 if super::remove_file_if_exists(
33 &project_root.join("backend/src/middleware/request_id.rs"),
34 )? {
35 println!(" {} backend/src/middleware/request_id.rs", "delete".red());
36 }
37 if super::remove_file_if_exists(
38 &project_root.join("backend/src/middleware/tracing_setup.rs"),
39 )? {
40 println!(
41 " {} backend/src/middleware/tracing_setup.rs",
42 "delete".red()
43 );
44 }
45
46 super::remove_line_from_file(
48 &project_root.join("backend/src/middleware/mod.rs"),
49 "request_id",
50 )?;
51 super::remove_line_from_file(
52 &project_root.join("backend/src/middleware/mod.rs"),
53 "tracing_setup",
54 )?;
55
56 super::remove_line_from_file(
58 &project_root.join("backend/src/routes/mod.rs"),
59 "request_id_layer",
60 )?;
61
62 let security_installed = project_root
64 .join("backend/src/middleware/security_headers.rs")
65 .exists();
66 if !security_installed {
67 super::remove_file_if_exists(&project_root.join("backend/src/middleware/mod.rs"))?;
68 let _ = std::fs::remove_dir(project_root.join("backend/src/middleware"));
69 super::remove_mod_from_main(project_root, "middleware")?;
70 }
71
72 crate::ai_context::regenerate(project_root).ok();
74
75 println!();
76 println!(
77 "{}",
78 "Observability uninstalled successfully.".green().bold()
79 );
80
81 Ok(())
82 }
83}
84
85fn install_observability(project_root: &Path) -> Result<()> {
86 use crate::template::TemplateEngine;
87 use crate::utils;
88 use colored::Colorize;
89 use tera::Context;
90
91 println!("{}", "Installing observability...".bold());
92
93 let engine = TemplateEngine::new()?;
94 let ctx = Context::new();
95
96 let content = engine.render("addon/observability/tracing_setup.rs.tera", &ctx)?;
98 utils::write_file(
99 &project_root.join("backend/src/middleware/tracing_setup.rs"),
100 &content,
101 )?;
102 println!(
103 " {} backend/src/middleware/tracing_setup.rs",
104 "create".green()
105 );
106
107 let content = engine.render("addon/observability/request_id.rs.tera", &ctx)?;
109 utils::write_file(
110 &project_root.join("backend/src/middleware/request_id.rs"),
111 &content,
112 )?;
113 println!(
114 " {} backend/src/middleware/request_id.rs",
115 "create".green()
116 );
117
118 let middleware_mod_path = project_root.join("backend/src/middleware/mod.rs");
120 if middleware_mod_path.exists() {
121 let content = std::fs::read_to_string(&middleware_mod_path)?;
122 let mut new_content = content.clone();
123 if !new_content.contains("mod tracing_setup;") {
124 new_content = format!("pub mod tracing_setup;\n{}", new_content);
125 }
126 if !new_content.contains("mod request_id;") {
127 new_content = format!("pub mod request_id;\n{}", new_content);
128 }
129 std::fs::write(&middleware_mod_path, new_content)?;
130 } else {
131 utils::write_file(
132 &middleware_mod_path,
133 "pub mod request_id;\npub mod tracing_setup;\n",
134 )?;
135 }
136
137 super::add_mod_to_main(project_root, "middleware")?;
139
140 let main_path = project_root.join("backend/src/main.rs");
142 let main_content = std::fs::read_to_string(&main_path)?;
143 if !main_content.contains("init_tracing()") {
144 if main_content.contains("tracing_subscriber::registry()") {
146 let new_main = if let Some(start) = main_content.find(" tracing_subscriber::registry()") {
148 if let Some(init_pos) = main_content[start..].find(".init();") {
149 let end = start + init_pos + ".init();".len();
150 format!(
151 "{} crate::middleware::tracing_setup::init_tracing();{}",
152 &main_content[..start],
153 &main_content[end..]
154 )
155 } else {
156 main_content.clone()
157 }
158 } else {
159 main_content.clone()
160 };
161 let new_main = new_main.replace(
163 "use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};\n",
164 "",
165 );
166 std::fs::write(&main_path, new_main)?;
167 println!(" {} backend/src/main.rs (replaced tracing init)", "update".green());
168 }
169 }
170
171 utils::insert_at_marker(
173 &project_root.join("backend/src/routes/mod.rs"),
174 "// === ROMANCE:MIDDLEWARE ===",
175 " .layer(crate::middleware::request_id::request_id_layer())",
176 )?;
177
178 crate::generator::auth::insert_cargo_dependency(
180 &project_root.join("backend/Cargo.toml"),
181 &[
182 ("tower-http", r#"{ version = "0.6", features = ["cors", "trace", "request-id", "propagate-header"] }"#),
183 ],
184 )?;
185
186 super::append_env_var(
188 &project_root.join("backend/.env"),
189 "RUST_LOG=info",
190 )?;
191 super::append_env_var(
192 &project_root.join("backend/.env.example"),
193 "RUST_LOG=info",
194 )?;
195
196 println!();
197 println!(
198 "{}",
199 "Observability installed successfully!".green().bold()
200 );
201 println!(" Structured logging with request ID propagation enabled.");
202 println!(" Set RUST_LOG=debug for verbose logging.");
203
204 Ok(())
205}