Implemented proxying of GSD requests
This commit is contained in:
10
.gitignore
vendored
10
.gitignore
vendored
@ -16,3 +16,13 @@ target/
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|
||||||
|
|
||||||
|
config.toml
|
||||||
|
|
||||||
|
.idea/
|
||||||
2061
Cargo.lock
generated
Normal file
2061
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "delivery-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8.6"
|
||||||
|
chrono = "0.4.42"
|
||||||
|
http = "1.3.1"
|
||||||
|
log = "0.4.28"
|
||||||
|
redis = { version = "0.32.6", features = ["connection-manager", "tokio-comp"] }
|
||||||
|
reqwest = { version = "0.12.23", features = ["json"] }
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
simplelog = "0.12.2"
|
||||||
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
|
toml = "0.9.7"
|
||||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM rust:1.90.0-slim-trixie as builder
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy manifests
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Build for release
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
# Runtime stage
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# Install runtime dependencies (if needed, e.g., for SSL)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN useradd -m -u 1000 appuser
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy only the binary from builder
|
||||||
|
COPY --from=builder /app/target/release/delivery-backend /app/service
|
||||||
|
|
||||||
|
# Change ownership to non-root user
|
||||||
|
RUN chown -R appuser:appuser /app
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Expose port (adjust as needed)
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the binary
|
||||||
|
CMD ["/app/service"]
|
||||||
9
config.toml.example
Normal file
9
config.toml.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
log_file_prefix = "delivery_backend"
|
||||||
|
host_ip = "127.0.0.1"
|
||||||
|
host_port = 3000
|
||||||
|
redis_url = "redis://127.0.0.1:6379"
|
||||||
|
gsd_app_key = "GSD-RestApi"
|
||||||
|
gsd_rest_url = "http://192.168.1.9:8334"
|
||||||
|
gsd_user = "GSDWebServiceTmp"
|
||||||
|
gsd_password = "<PASSWORD>"
|
||||||
|
gsd_app_names = ["GSD-RestApi"]
|
||||||
43
docker-compose.yaml
Normal file
43
docker-compose.yaml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: redis-server
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
microservice:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: rust-microservice
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
- RUST_LOG=info
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
||||||
|
driver: local
|
||||||
26
src/api.rs
Normal file
26
src/api.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use crate::middleware::AppState;
|
||||||
|
use axum::Extension;
|
||||||
|
use axum::extract::Request;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use http::StatusCode;
|
||||||
|
use log::error;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use axum::body::Body;
|
||||||
|
|
||||||
|
pub async fn handle_post(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
request: Request<Body>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match state.clone().gsd_service.forward_post_request(request).await {
|
||||||
|
Ok(e) => e.text().await.unwrap().into_response(),
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to forward post: {:?}", e);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_login() -> impl IntoResponse {
|
||||||
|
|
||||||
|
}
|
||||||
65
src/config.rs
Normal file
65
src/config.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const CONFIG_FILE: &str = "config.toml";
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
// Backend server configuration
|
||||||
|
pub log_file_prefix: String,
|
||||||
|
pub host_ip: String,
|
||||||
|
pub host_port: u16,
|
||||||
|
pub redis_url: String,
|
||||||
|
|
||||||
|
// GSD RestAPI configuration
|
||||||
|
pub gsd_app_key: String,
|
||||||
|
pub gsd_rest_url: String,
|
||||||
|
pub gsd_user: String,
|
||||||
|
pub gsd_password: String,
|
||||||
|
pub gsd_app_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn get_host_url(&self) -> String {
|
||||||
|
format!("{}:{}", self.host_ip, self.host_port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_absolute_path() -> PathBuf {
|
||||||
|
PathBuf::from(CONFIG_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
|
||||||
|
if fs::exists(get_config_absolute_path())? {
|
||||||
|
Ok(toml::from_str(&fs::read_to_string(CONFIG_FILE)?)?)
|
||||||
|
} else {
|
||||||
|
let config = create_standard_config();
|
||||||
|
save_config(&config)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_log_file_name(prefix: String) -> String {
|
||||||
|
format!("{}_{}.log", prefix, chrono::Local::now().to_rfc3339())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
fs::write(get_config_absolute_path(), toml::to_string(config)?)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_standard_config() -> Config {
|
||||||
|
Config {
|
||||||
|
log_file_prefix: String::from("delivery_backend"),
|
||||||
|
host_ip: String::from("127.0.0.1"),
|
||||||
|
host_port: 3000,
|
||||||
|
redis_url: String::from("redis://127.0.0.1:6379"),
|
||||||
|
gsd_rest_url: String::from("http://127.0.0.1:8334"),
|
||||||
|
gsd_app_key: String::from("GSD-RestApi"),
|
||||||
|
gsd_app_names: vec![String::from("GSD-RestApi")],
|
||||||
|
gsd_user: String::from("<GSD-USER>"),
|
||||||
|
gsd_password: String::from("<GSD-Password>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main.rs
Normal file
54
src/main.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use crate::api::{handle_login, handle_post};
|
||||||
|
use crate::config::load_config;
|
||||||
|
use crate::middleware::AppState;
|
||||||
|
use crate::repository::RedisRepository;
|
||||||
|
use crate::util::initialize_logging;
|
||||||
|
use axum::routing::post;
|
||||||
|
use axum::{Extension, Router};
|
||||||
|
use log::info;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod config;
|
||||||
|
mod middleware;
|
||||||
|
mod repository;
|
||||||
|
mod service_gsd;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let config = load_config()?;
|
||||||
|
initialize_logging(&config);
|
||||||
|
|
||||||
|
info!("Logging initialized");
|
||||||
|
info!("Starting Holzleitner Delivery Backend");
|
||||||
|
|
||||||
|
let redis_url = config.redis_url.clone();
|
||||||
|
let host_url = config.get_host_url().clone();
|
||||||
|
|
||||||
|
let state = Arc::new(AppState {
|
||||||
|
config: config.clone(),
|
||||||
|
repository: RedisRepository::try_new(redis_url).await?,
|
||||||
|
gsd_service: (&config).into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/login", post(handle_login))
|
||||||
|
.route("/{*wildcard}", post(handle_post))
|
||||||
|
.layer(Extension(state.clone()))
|
||||||
|
.route_layer(axum::middleware::from_fn_with_state(
|
||||||
|
state.clone(),
|
||||||
|
middleware::gsd_add_header,
|
||||||
|
))
|
||||||
|
.route_layer(axum::middleware::from_fn_with_state(
|
||||||
|
state.clone(),
|
||||||
|
middleware::auth_middleware,
|
||||||
|
))
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(host_url).await.unwrap();
|
||||||
|
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
82
src/middleware.rs
Normal file
82
src/middleware.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::config::Config;
|
||||||
|
use crate::repository::RedisRepository;
|
||||||
|
use crate::service_gsd::GSDService;
|
||||||
|
use axum::extract::{Request, State};
|
||||||
|
use axum::http::{HeaderValue, StatusCode};
|
||||||
|
use axum::middleware::Next;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use log::{error, info};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub config: Config,
|
||||||
|
pub repository: RedisRepository,
|
||||||
|
pub gsd_service: GSDService,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn auth_middleware(
|
||||||
|
State(_state): State<Arc<AppState>>,
|
||||||
|
request: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Response {
|
||||||
|
next.run(request).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn gsd_add_header(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
mut request: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Response {
|
||||||
|
let state_cloned = state.clone();
|
||||||
|
|
||||||
|
let session = state_cloned.repository.get_session().await;
|
||||||
|
match session {
|
||||||
|
Ok(session) => {
|
||||||
|
let session_value;
|
||||||
|
|
||||||
|
if session.is_none() {
|
||||||
|
match state_cloned.gsd_service.get_session().await {
|
||||||
|
Ok(session) => {
|
||||||
|
session_value = session.clone();
|
||||||
|
|
||||||
|
match state_cloned.repository.set_session(session.clone()).await {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Redis: saved session {}", &session);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(err) => {
|
||||||
|
error!("Redis: failed to save session: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
error!("Error getting session: {:?}", error);
|
||||||
|
return StatusCode::UNAUTHORIZED.into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session_value = session.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
request.headers_mut().insert(
|
||||||
|
"sessionId",
|
||||||
|
HeaderValue::from_str(session_value.as_str()).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
error!(
|
||||||
|
"Redis error occured during fetching current session id. Error: {}",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.headers_mut().insert(
|
||||||
|
"appkey",
|
||||||
|
HeaderValue::from_str(state_cloned.config.gsd_app_key.as_str()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
next.run(request).await
|
||||||
|
}
|
||||||
37
src/repository.rs
Normal file
37
src/repository.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use redis::aio::ConnectionManager;
|
||||||
|
use redis::{AsyncTypedCommands, Connection, RedisError, RedisResult};
|
||||||
|
|
||||||
|
pub fn get_redis_connection(redis_url: String) -> RedisResult<Connection> {
|
||||||
|
redis::Client::open(redis_url)?.get_connection()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RedisRepository {
|
||||||
|
connection_manager: ConnectionManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisRepository {
|
||||||
|
pub async fn try_new(redis_url: String) -> Result<RedisRepository, RedisError> {
|
||||||
|
Ok(RedisRepository {
|
||||||
|
connection_manager: redis::Client::open(redis_url)?
|
||||||
|
.get_connection_manager()
|
||||||
|
.await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_session(&self) -> RedisResult<Option<String>> {
|
||||||
|
self.connection_manager
|
||||||
|
.clone()
|
||||||
|
.get::<String>("current_session".to_string())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_session(&self, session: String) -> RedisResult<()> {
|
||||||
|
self.connection_manager
|
||||||
|
.clone()
|
||||||
|
.set("current_session", session)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/service_gsd.rs
Normal file
120
src/service_gsd.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use axum::body::Body;
|
||||||
|
use axum::extract::Request;
|
||||||
|
use log::{error, info};
|
||||||
|
use crate::config::Config;
|
||||||
|
use reqwest::Response;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GSDLoginRequestDTO {
|
||||||
|
user: String,
|
||||||
|
pass: String,
|
||||||
|
app_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GSDLoginResponseDTO {
|
||||||
|
status: GSDLoginResponseStatusDTO,
|
||||||
|
data: Option<GSDLoginResponseDataDTO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GSDLoginResponseDataDTO {
|
||||||
|
session_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GSDLoginResponseStatusDTO {
|
||||||
|
internal_status: String,
|
||||||
|
status_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GSDService {
|
||||||
|
host_url: String,
|
||||||
|
app_names: Vec<String>,
|
||||||
|
app_key: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GSDServiceError {
|
||||||
|
LoginFailed,
|
||||||
|
LoginResponseParsingFailed,
|
||||||
|
RequestError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GSDService {
|
||||||
|
pub async fn get_session(&self) -> Result<String, GSDServiceError> {
|
||||||
|
info!("Session: No session found. Generate session from GSD server {}", self.host_url);
|
||||||
|
|
||||||
|
let dto = GSDLoginRequestDTO {
|
||||||
|
user: self.username.clone(),
|
||||||
|
pass: self.password.clone(),
|
||||||
|
app_names: self.app_names.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = reqwest::Client::new()
|
||||||
|
.post(format!("{}/v1/login", self.host_url.clone()))
|
||||||
|
.header("appKey", self.app_key.as_str())
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&dto)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Session: error request to GSD: {}", e);
|
||||||
|
GSDServiceError::LoginFailed
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response_dto: GSDLoginResponseDTO = response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Session: error request to GSD: {}", e);
|
||||||
|
GSDServiceError::LoginResponseParsingFailed
|
||||||
|
})?;
|
||||||
|
if response_dto.status.internal_status != "0" {
|
||||||
|
error!("Session: error message from GSD: {}", response_dto.status.status_message);
|
||||||
|
Err(GSDServiceError::LoginFailed)
|
||||||
|
} else {
|
||||||
|
match response_dto.data {
|
||||||
|
Some(data) => {
|
||||||
|
info!("Session: successfully obtained session with session id {}", &data.session_id);
|
||||||
|
Ok(data.session_id.clone())
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
error!("Session: failed to obtain session id. No session id in request found.");
|
||||||
|
Err(GSDServiceError::LoginResponseParsingFailed)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn forward_post_request(&self, request: Request<Body>) -> Result<Response, GSDServiceError> {
|
||||||
|
let (parts, body) = request.into_parts();
|
||||||
|
|
||||||
|
reqwest::Client::new()
|
||||||
|
.post(format!("{}{}", self.host_url.clone(), parts.uri))
|
||||||
|
.headers(parts.headers)
|
||||||
|
.body(axum::body::to_bytes(body, usize::MAX).await.unwrap())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| GSDServiceError::RequestError(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Config> for GSDService {
|
||||||
|
fn from(config: &Config) -> Self {
|
||||||
|
Self {
|
||||||
|
host_url: config.gsd_rest_url.clone(),
|
||||||
|
app_names: config.gsd_app_names.clone(),
|
||||||
|
app_key: config.gsd_app_key.clone(),
|
||||||
|
username: config.gsd_user.clone(),
|
||||||
|
password: config.gsd_password.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/util.rs
Normal file
21
src/util.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::config::{Config, generate_log_file_name};
|
||||||
|
use log::LevelFilter;
|
||||||
|
use simplelog::{ColorChoice, CombinedLogger, TermLogger, TerminalMode, WriteLogger};
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
pub fn initialize_logging(config: &Config) {
|
||||||
|
CombinedLogger::init(vec![
|
||||||
|
TermLogger::new(
|
||||||
|
LevelFilter::Info,
|
||||||
|
simplelog::Config::default(),
|
||||||
|
TerminalMode::Mixed,
|
||||||
|
ColorChoice::Auto,
|
||||||
|
),
|
||||||
|
WriteLogger::new(
|
||||||
|
LevelFilter::Info,
|
||||||
|
simplelog::Config::default(),
|
||||||
|
File::create(generate_log_file_name(config.log_file_prefix.clone())).unwrap(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user