diff --git a/Cargo.lock b/Cargo.lock index 1a765e1..c691268 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,9 +618,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lemmy_api_common" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed97d80c606745807e7a41d2e7a54912e6dc73b17f847453717cfa1e6693b31" +checksum = "17366fcde90b07f4e5a8fefa62378fe348424ba79c8e9fb3ddc63ef76d687493" dependencies = [ "chrono", "enum-map", @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "lemmy_db_schema" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a17c613a84befef3daadc179e2069e02848b83dfd80d7955ca825bdca92869b" +checksum = "5e2d9a0c6f8f3df4664f9479ceca138ee6217b89653bafb753a498cabc5f3ab5" dependencies = [ "async-trait", "chrono", @@ -656,9 +656,9 @@ dependencies = [ [[package]] name = "lemmy_db_views" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e360aaee508cb057c6df0160b27aeb8b845a3725127b2d7e4e4c877397418539" +checksum = "02659eb474ab54da6296d4edb5a974c0affa5cfcf8a2fa31bb52793dedef9d5a" dependencies = [ "lemmy_db_schema", "serde", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "lemmy_db_views_actor" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606962be1df35bf9214a22cea819cec181ecd4e71dfe693b92428291b69a7cd6" +checksum = "5187730858dc808b5cf06aadbb1d1f8ca360d4ed3c375e5606e63be4c9e24e06" dependencies = [ "chrono", "lemmy_db_schema", @@ -681,9 +681,9 @@ dependencies = [ [[package]] name = "lemmy_db_views_moderator" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64669a695c855143be2a9d4a662d481d235d7470621793db0607f60190be80" +checksum = "18f6fab36e1dcadc043b81c5916044ee65219d837f5d221a908296f9c55f3872" dependencies = [ "lemmy_db_schema", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3bab39f..2001f73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ systemd-units = { enable = false } [dependencies] chrono = "^0.4.26" -lemmy_api_common = "0.19.1" -lemmy_db_schema = "0.19.1" +lemmy_api_common = "0.19.3" +lemmy_db_schema = "0.19.3" once_cell = "^1.18.0" reqwest = { version = "^0.11.18", features = ["blocking", "json"] } serde = "^1.0.164" diff --git a/src/bot.rs b/src/bot.rs index eef7e6a..7916920 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,163 +1,163 @@ -use crate::config::{Config, PostBody, SeriesConfig}; +use crate::{config::{Config, PostBody, SeriesConfig}, fetchers::{jnovel}, lemmy}; use crate::fetchers::jnovel::JPostInfo; use crate::lemmy::{Lemmy, PostInfo}; use crate::post_history::SeriesHistory; -use crate::{fetchers::{jnovel}, lemmy, write_error, write_info, write_warn, SharedData}; -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, Duration, Timelike, Utc}; use lemmy_api_common::post::CreatePost; use lemmy_db_schema::newtypes::{CommunityId, LanguageId}; use lemmy_db_schema::PostFeatureType; use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; use tokio::time::sleep; use crate::fetchers::Fetcher; +use systemd_journal_logger::connected_to_journal; -pub(crate) async fn run(data: Arc>) { +macro_rules! info { + ($msg:tt) => { + match connected_to_journal() { + true => log::info!("[INFO] {}", $msg), + false => println!("[INFO] {}", $msg), + } + }; +} + +macro_rules! warn { + ($msg:tt) => { + match connected_to_journal() { + true => log::warn!("[WARN] {}", $msg), + false => println!("[WARN] {}", $msg), + } + }; +} + +macro_rules! error { + ($msg:tt) => { + match connected_to_journal() { + true => log::error!("[ERROR] {}", $msg), + false => eprintln!("[ERROR] {}", $msg), + } + }; +} + +pub(crate) async fn run() { let mut last_reload: DateTime; let mut lemmy: Lemmy; let mut login_error: bool; - let mut communities; - { - let mut write = data.write().await; + let mut communities: HashMap; + let mut post_history: SeriesHistory; + let mut start: DateTime; + let mut config: Config = Config::load(); + last_reload = Utc::now(); - // Errors during bot init are likely unrecoverable and therefore should panic the bot - // Does not really matter since the bot will get restarted anyway but this way the uptime url logs a downtime - write.config = Config::load(); - last_reload = Utc::now(); - } + lemmy = match lemmy::login(&config).await { + Ok(data) => data, + Err(_) => panic!(), + }; + login_error = false; - { - let read = data.read().await; - lemmy = match lemmy::login(&read.config).await { - Ok(data) => data, - Err(_) => panic!(), - }; - login_error = false; + communities = match lemmy.get_communities().await { + Ok(data) => data, + Err(_) => panic!(), + }; - communities = match lemmy.get_communities().await { - Ok(data) => data, - Err(_) => panic!(), - }; - } - - { - let mut write = data.write().await; - write.start = Utc::now(); - } + start = Utc::now(); let info_msg = "Bot init successful, starting normal operations".to_owned(); - write_info(info_msg); + info!(info_msg); loop { - idle(&data).await; + idle(&start, &config).await; + start = Utc::now(); - { - let mut write = data.write().await; - - write.start = Utc::now(); - - if write.start - last_reload >= Duration::seconds(write.config.config_reload_seconds as i64) { - write.config = Config::load(); - let message = "Config reloaded".to_owned(); - write_info(message); - } + // replace with watcher + if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) { + config = Config::load(); + let message = "Config reloaded".to_owned(); + info!(message); } - { - let read = data.read().await; - if login_error { - lemmy = match lemmy::login(&read.config).await { - Ok(data) => data, - Err(_) => continue, - }; - login_error = false; - } + if login_error { + let info_msg = "Login invalid, refreshing session"; + info!(info_msg); + lemmy = match lemmy::login(&config).await { + Ok(data) => data, + Err(_) => continue, + }; + login_error = false; } - { - let read = data.read().await; - if read.start - last_reload >= Duration::seconds(read.config.config_reload_seconds as i64) { - communities = match lemmy.get_communities().await { - Ok(data) => data, - Err(_) => { - login_error = true; - continue; - } - }; - let message = "Communities reloaded".to_owned(); - write_info(message); - last_reload = Utc::now(); - } - } - - { - let mut write = data.write().await; - write.post_history = SeriesHistory::load_history(); - } - - { - let read = data.read().await; - let series = read.config.series.clone(); - drop(read); - for series in series { - if handle_series(&series, &communities, &lemmy, &data) - .await - .is_err() - { + if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) { + communities = match lemmy.get_communities().await { + Ok(data) => data, + Err(_) => { login_error = true; continue; - }; - } + } + }; + let message = "Communities reloaded".to_owned(); + info!(message); + last_reload = Utc::now(); } - idle(&data).await; + post_history = SeriesHistory::load_history(); + + let series = config.series.clone(); + for series in series { + if handle_series(&series, &communities, &lemmy, &config, &mut post_history) + .await + .is_err() + { + login_error = true; + continue; + }; + } + + idle(&start, &config).await; } } -async fn idle(data: &Arc>) { - let read = data.read().await; +async fn idle(start: &DateTime, config: &Config) { let mut sleep_duration = Duration::seconds(30); - if Utc::now() - read.start > sleep_duration { + if Utc::now() - start > sleep_duration { sleep_duration = Duration::seconds(60); } - if let Some(status_url) = read.config.status_post_url.clone() { + if let Some(status_url) = config.status_post_url.clone() { match reqwest::get(status_url).await { Ok(_) => {} Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); } } }; - while Utc::now() - read.start < sleep_duration { + while Utc::now() - start < sleep_duration { sleep(Duration::milliseconds(100).to_std().unwrap()).await; } } -async fn handle_series(series: &SeriesConfig, communities: &HashMap, lemmy: &Lemmy, data: &Arc>) -> Result<(), ()> { +async fn handle_series(series: &SeriesConfig, communities: &HashMap, lemmy: &Lemmy, config: &Config, post_history: &mut SeriesHistory ) -> Result<(), ()> { let jnc = jnovel::JFetcherOptions::new(series.slug.clone(), series.parted); let post_list = match jnc.check_feed().await { Ok(data) => data, Err(_) => return Err(()), }; + + if post_list.is_empty() && Utc::now().minute() % 10 == 0 { + let info_msg = "No Updates found"; + info!(info_msg); + } for post_info in post_list.clone().iter() { - // todo .clone() likely not needed let post_part_info = post_info.get_part_info(); let post_lemmy_info = post_info.get_info(); - { - let read = data.read().await; - if read.post_history.check_for_post( - series.slug.as_str(), - post_part_info.as_string().as_str(), - post_lemmy_info.title.as_str(), - ) { - continue; - } + if post_history.check_for_post( + series.slug.as_str(), + post_part_info.as_string().as_str(), + post_lemmy_info.title.as_str(), + ) { + continue; } let post_series_config = match post_info { @@ -190,53 +190,46 @@ async fn handle_series(series: &SeriesConfig, communities: &HashMap part_history.chapter = post_info.get_info().title, @@ -263,11 +255,9 @@ async fn handle_series(series: &SeriesConfig, communities: &HashMap { + match connected_to_journal() { + true => log::error!("[ERROR] {}", $msg), + false => eprintln!("[ERROR] {}", $msg), + } + }; +} + +macro_rules! info { + ($msg:tt) => { + match connected_to_journal() { + true => log::info!("[INFO] {}", $msg), + false => println!("[INFO] {}", $msg), + } + }; +} static PAST_DAYS_ELIGIBLE: u8 = 4; @@ -266,13 +285,13 @@ impl Fetcher for JFetcherOptions { Ok(data) => data, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; @@ -281,7 +300,7 @@ impl Fetcher for JFetcherOptions { Ok(data) => data, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; @@ -318,7 +337,7 @@ impl Fetcher for JFetcherOptions { match part_number { Some(number) => new_part_info = Part(number), None => { - println!("No Part found, assuming 1"); + info!("No Part found, assuming 1"); new_part_info = Part(1); } } @@ -392,13 +411,13 @@ async fn get_latest_prepub(volume_slug: &str) -> Result data, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; @@ -407,7 +426,7 @@ async fn get_latest_prepub(volume_slug: &str) -> Result data, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; diff --git a/src/fetchers/mod.rs b/src/fetchers/mod.rs index fd143fb..126aeb2 100644 --- a/src/fetchers/mod.rs +++ b/src/fetchers/mod.rs @@ -1,7 +1,6 @@ use async_trait::async_trait; pub mod jnovel; -mod tobooks; #[async_trait] pub(crate) trait Fetcher { diff --git a/src/lemmy.rs b/src/lemmy.rs index a07b901..5d250e7 100644 --- a/src/lemmy.rs +++ b/src/lemmy.rs @@ -1,5 +1,5 @@ use crate::config::Config; -use crate::{write_error, HTTP_CLIENT}; +use crate::{HTTP_CLIENT}; use lemmy_api_common::community::{ListCommunities, ListCommunitiesResponse}; use lemmy_api_common::lemmy_db_views::structs::PostView; use lemmy_api_common::person::{Login, LoginResponse}; @@ -9,7 +9,18 @@ use lemmy_db_schema::newtypes::{CommunityId, PostId}; use lemmy_db_schema::{ListingType, PostFeatureType}; use reqwest::StatusCode; use std::collections::HashMap; +use serde::{Deserialize, Serialize}; use url::Url; +use systemd_journal_logger::connected_to_journal; + +macro_rules! error { + ($msg:tt) => { + match connected_to_journal() { + true => log::error!("[ERROR] {}", $msg), + false => eprintln!("[ERROR] {}", $msg), + } + }; +} pub(crate) struct Lemmy { jwt_token: Sensitive, @@ -45,7 +56,7 @@ pub(crate) async fn login(config: &Config) -> Result { Ok(data) => data, Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; @@ -63,14 +74,14 @@ pub(crate) async fn login(config: &Config) -> Result { }), None => { let err_msg = "Login did not return JWT token. Are the credentials valid?".to_owned(); - write_error(err_msg); + error!(err_msg); Err(()) } } } status => { let err_msg = format!("Unexpected HTTP Status '{}' during Login", status); - write_error(err_msg); + error!(err_msg); Err(()) } } @@ -78,68 +89,21 @@ pub(crate) async fn login(config: &Config) -> Result { impl Lemmy { pub(crate) async fn post(&self, post: CreatePost) -> Result { - let response = match HTTP_CLIENT - .post(format!("{}/api/v3/post", &self.instance)) - .bearer_auth(&self.jwt_token.to_string()) - .json(&post) - .send() - .await - { - Ok(data) => match data.text().await { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let response = self.fetch_data_json("/api/v3/post", &post).await?; - let json_data = match serde_json::from_str::>(&response) { - Ok(mut data) => data.remove("post_view").expect("Element should be present"), - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let json_data: PostView = self.parse_json(&response).await?; Ok(json_data.post.id) } async fn feature(&self, params: FeaturePost) -> Result { - let response = match HTTP_CLIENT - .post(format!("{}/api/v3/post/feature", &self.instance)) - .bearer_auth(&self.jwt_token.to_string()) - .json(¶ms) - .send() - .await - { - Ok(data) => match data.text().await { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let response = self.fetch_data_json("/api/v3/post/feature", ¶ms).await?; let json_data = match serde_json::from_str::>(&response) { Ok(mut data) => data.remove("post_view").expect("Element should be present"), Err(e) => { let err_msg = format!("{e}"); - write_error(err_msg); + error!(err_msg); return Err(()); } }; @@ -172,36 +136,9 @@ impl Lemmy { ..Default::default() }; - let response = match HTTP_CLIENT - .get(format!("{}/api/v3/post/list", &self.instance)) - .bearer_auth(&self.jwt_token.to_string()) - .query(&list_params) - .send() - .await - { - Ok(data) => match data.text().await { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let response = self.fetch_data_query("/api/v3/post/list", &list_params).await?; - let json_data: GetPostsResponse = match serde_json::from_str(&response) { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let json_data: GetPostsResponse = self.parse_json(&response).await?; Ok(json_data .posts @@ -217,36 +154,9 @@ impl Lemmy { ..Default::default() }; - let response = match HTTP_CLIENT - .get(format!("{}/api/v3/post/list", &self.instance)) - .bearer_auth(&self.jwt_token.to_string()) - .query(&list_params) - .send() - .await - { - Ok(data) => match data.text().await { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let response = self.fetch_data_query("/api/v3/post/list", &list_params).await?; - let json_data: GetPostsResponse = match serde_json::from_str(&response) { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let json_data: GetPostsResponse = self.parse_json(&response).await?; Ok(json_data .posts @@ -262,36 +172,9 @@ impl Lemmy { ..Default::default() }; - let response = match HTTP_CLIENT - .get(format!("{}/api/v3/community/list", &self.instance)) - .bearer_auth(&self.jwt_token.to_string()) - .query(&list_params) - .send() - .await - { - Ok(data) => match data.text().await { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let response = self.fetch_data_query("/api/v3/community/list", &list_params).await?; - let json_data: ListCommunitiesResponse = match serde_json::from_str(&response) { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + let json_data: ListCommunitiesResponse = self.parse_json(&response).await?; let mut communities: HashMap = HashMap::new(); for community_view in json_data.communities { @@ -301,4 +184,53 @@ impl Lemmy { Ok(communities) } + + async fn fetch_data_json(&self, route: &str, json: &T ) -> Result { + let res = HTTP_CLIENT + .post(format!("{}{route}", self.instance)) + .bearer_auth(&self.jwt_token.to_string()) + .json(&json) + .send() + .await; + self.extract_data(res).await + } + + async fn fetch_data_query(&self, route: &str, json: &T ) -> Result { + let res = HTTP_CLIENT + .post(format!("{}{route}", self.instance)) + .bearer_auth(&self.jwt_token.to_string()) + .query(&json) + .send() + .await; + self.extract_data(res).await + } + + async fn extract_data(&self, response: Result) -> Result { + match response { + Ok(data) => match data.text().await { + Ok(data) => Ok(data), + Err(e) => { + let err_msg = format!("{e}"); + error!(err_msg); + Err(()) + } + }, + Err(e) => { + let err_msg = format!("{e}"); + error!(err_msg); + Err(()) + } + } + } + + async fn parse_json<'a, T: Deserialize<'a>>(&self, response: &'a str) -> Result { + match serde_json::from_str::>(response) { + Ok(mut data) => Ok(data.remove("post_view").expect("Element should be present")), + Err(e) => { + let err_msg = format!("{e}"); + error!(err_msg); + Err(()) + } + } + } } diff --git a/src/main.rs b/src/main.rs index 6e077df..fc93d6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,8 @@ -use crate::config::Config; -use crate::post_history::SeriesHistory; -use chrono::{DateTime, Duration, Utc}; -use log::{error, info, warn, LevelFilter}; +use chrono::{Duration}; +use log::{LevelFilter}; use once_cell::sync::Lazy; use reqwest::Client; -use std::collections::HashMap; -use std::fmt::Debug; -use std::sync::Arc; -use systemd_journal_logger::{connected_to_journal, JournalLog}; -use tokio::sync::RwLock; -use tokio::time::sleep; +use systemd_journal_logger::{JournalLog}; mod bot; mod config; @@ -17,27 +10,6 @@ mod lemmy; mod post_history; mod fetchers; -pub(crate) fn write_error(err_msg: String) { - match connected_to_journal() { - true => error!("[ERROR] {err_msg}"), - false => println!("[ERROR] {err_msg}"), - } -} - -pub(crate) fn write_warn(warn_msg: String) { - match connected_to_journal() { - true => warn!("[WARN] {warn_msg}"), - false => println!("[WARN] {warn_msg}"), - } -} - -pub(crate) fn write_info(info_msg: String) { - match connected_to_journal() { - true => info!("[INFO] {info_msg}"), - false => println!("[INFO] {info_msg}"), - } -} - pub static HTTP_CLIENT: Lazy = Lazy::new(|| { Client::builder() .timeout(Duration::seconds(30).to_std().unwrap()) @@ -46,25 +18,6 @@ pub static HTTP_CLIENT: Lazy = Lazy::new(|| { .expect("build client") }); -#[derive(Clone, Debug)] -pub(crate) struct SharedData { - config: Config, - post_history: SeriesHistory, - start: DateTime, -} - -impl SharedData { - pub(crate) fn new() -> Self { - SharedData { - config: Config::default(), - post_history: SeriesHistory { - series: HashMap::new(), - }, - start: Utc::now(), - } - } -} - #[tokio::main] async fn main() { JournalLog::new() @@ -72,33 +25,5 @@ async fn main() { .install() .expect("Systemd-Logger crate error"); log::set_max_level(LevelFilter::Info); - - let mut data = SharedData::new(); - - loop { - let write_data = Arc::new(RwLock::new(data.clone())); - //let read_data = write_data.clone(); - let persistent_data = write_data.clone(); - - let bot_thread = tokio::spawn(async move { bot::run(write_data).await }); - - let _ = bot_thread.await; - - data = persistent_data.read().await.clone(); - - { - let err_msg = "Bot crashed due to unknown Error, restarting thread after wait..."; - match connected_to_journal() { - true => error!("[ERROR] {err_msg}"), - false => println!("[ERROR] {err_msg}"), - } - } - - sleep( - Duration::seconds(5) - .to_std() - .expect("Conversion should always work since static"), - ) - .await; - } + bot::run().await; } diff --git a/src/post_history.rs b/src/post_history.rs index 5e3b57e..71499b8 100644 --- a/src/post_history.rs +++ b/src/post_history.rs @@ -1,6 +1,24 @@ -use crate::write_error; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; +use systemd_journal_logger::connected_to_journal; + +macro_rules! info { + ($msg:tt) => { + match connected_to_journal() { + true => log::info!("[INFO] {}", $msg), + false => println!("[INFO] {}", $msg), + } + }; +} + +macro_rules! error { + ($msg:tt) => { + match connected_to_journal() { + true => log::error!("[ERROR] {}", $msg), + false => eprintln!("[ERROR] {}", $msg), + } + }; +} #[derive(Serialize, Deserialize, Default, Clone, Debug)] pub(crate) struct SeriesHistory { @@ -9,6 +27,8 @@ pub(crate) struct SeriesHistory { impl SeriesHistory { pub(crate) fn load_history() -> Self { + let info_msg = "Loading History"; + info!(info_msg); match confy::load(env!("CARGO_PKG_NAME"), "history") { Ok(data) => data, Err(e) => panic!("history.toml not found: {e}"), @@ -16,9 +36,11 @@ impl SeriesHistory { } pub(crate) fn save_history(&self) { + let info_msg = "Saving History"; + info!(info_msg); if let Err(e) = confy::store(env!("CARGO_PKG_NAME"), "history", self) { let err_msg = format!("Unexpected error saving to history.toml: {e}"); - write_error(err_msg); + error!(err_msg); } }