Skip to main content

fbc_starter/
lib.rs

1pub mod base;
2pub mod config;
3pub mod constants;
4pub mod entity;
5pub mod error;
6pub mod http;
7pub mod logging;
8pub mod server;
9pub mod state;
10pub mod utils;
11
12#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
13pub mod database;
14
15pub mod cache;
16
17#[cfg(feature = "balance")]
18pub mod balance;
19
20#[cfg(feature = "nacos")]
21pub mod nacos;
22
23#[cfg(feature = "kafka")]
24pub mod messaging;
25
26pub use base::{CursorPageBaseResp, R};
27pub use config::Config;
28#[cfg(feature = "kafka")]
29pub use config::{
30    KafkaConfig, KafkaConsumerConfig as ConfigKafkaConsumerConfig,
31    KafkaProducerConfig as ConfigKafkaProducerConfig,
32};
33pub use constants::{
34    CREATE_BY, CREATE_BY_FIELD, CREATE_ORG_ID_FIELD, CREATE_TIME, CREATE_TIME_FIELD, DELETE_FIELD,
35    ID_FIELD, LABEL, PARENT_ID, PARENT_ID_FIELD, SORT_VALUE, SORT_VALUE_FIELD, TENANT_ID,
36    UPDATE_BY, UPDATE_BY_FIELD, UPDATE_TIME, UPDATE_TIME_FIELD,
37};
38
39pub use entity::*;
40pub use error::{AppError, AppResult};
41pub use http::{
42    create_cors_layer, create_trace_layer, health_check, request_logger_middleware, root,
43};
44#[cfg(feature = "kafka")]
45pub use messaging::{
46    kafka::{KafkaConsumer, KafkaProducer},
47    Message, MessageConsumer, MessageConsumerType, MessageProducer, MessageProducerType,
48};
49#[cfg(feature = "consumer")]
50pub use messaging::{KafkaMessageHandler, KafkaMessageRouter};
51#[cfg(feature = "nacos")]
52pub use nacos::{
53    deregister_service, get_config, get_config_client, get_naming_client, get_service_instances,
54    get_subscribed_configs, get_subscribed_services, init_nacos, register_service,
55    subscribe_configs, subscribe_services,
56};
57pub use server::{Server, ServerBuilder};
58pub use state::AppState;
59pub use utils::get_uid_from_headers;
60
61/// 初始化日志系统(内部函数)
62///
63/// 设置时区(可配置,默认为东八区 UTC+8)
64/// 支持两种日志文件滚动策略:
65/// - daily: 按天自动滚动(使用 tracing_appender::rolling::daily)
66/// - size: 按文件大小手动滚动(需配置 size_limit_mb)
67pub(crate) fn init_logging(config: &Config) -> Result<(), anyhow::Error> {
68    use time::UtcOffset;
69    use tracing_subscriber::{
70        fmt::time::OffsetTime, layer::SubscriberExt, util::SubscriberInitExt,
71    };
72
73    // 从配置读取时区偏移(小时),默认东八区 +8
74    let tz_hour = config.log.timezone;
75    let offset = UtcOffset::from_hms(tz_hour as i8, 0, 0)
76        .map_err(|e| {
77            if tz_hour >= 0 {
78                anyhow::anyhow!("无效的时区偏移 UTC+{}: {}", tz_hour, e)
79            } else {
80                anyhow::anyhow!("无效的时区偏移 UTC{}: {}", tz_hour, e)
81            }
82        })?;
83    
84    eprintln!(
85        "ℹ 日志时区设置: UTC{}",
86        if tz_hour >= 0 {
87            format!("+{}", tz_hour)
88        } else {
89            tz_hour.to_string()
90        }
91    );
92
93    let timer = OffsetTime::new(
94        offset,
95        time::format_description::parse(
96            "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]",
97        )
98        .map_err(|e| anyhow::anyhow!("时间格式解析失败: {}", e))?,
99    );
100
101    // 初始化日志环境变量过滤器
102    let env_filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
103        config.log.level.as_str().into()
104    });
105
106    // 构建 stdout 层:JSON 或普通格式(使用 Option 层避免分支重复)
107    let stdout_json = if config.log.json {
108        Some(
109            tracing_subscriber::fmt::layer()
110                .json()
111                .with_timer(timer.clone())
112                .with_line_number(true),
113        )
114    } else {
115        None
116    };
117    let stdout_plain = if !config.log.json {
118        Some(
119            tracing_subscriber::fmt::layer()
120                .with_timer(timer.clone())
121                .with_line_number(true),
122        )
123    } else {
124        None
125    };
126
127    // 构建文件日志层(可选)
128    let (file_json, file_plain) = if let Some(ref file_config) = config.log.file {
129        // 创建日志文件目录
130        std::fs::create_dir_all(&file_config.directory)
131            .map_err(|e| anyhow::anyhow!("无法创建日志目录 {}: {}", file_config.directory, e))?;
132
133        // 清理旧日志文件(如果配置了限制)
134        if file_config.count_limit > 0 {
135            crate::logging::cleanup_old_logs(
136                &file_config.directory,
137                &file_config.filename,
138                file_config.count_limit,
139            ).ok();
140        }
141
142        // 创建文件 appender(按大小 or 按天,目前都使用按天滚动)
143        let file_appender = match file_config.rotation.as_str() {
144            "size" => {
145                eprintln!(
146                    "ℹ 日志配置: 按大小滚动 (限制: {}MB, 保留: {} 个文件)",
147                    if file_config.size_limit_mb == 0 {
148                        "无限制".to_string()
149                    } else {
150                        file_config.size_limit_mb.to_string()
151                    },
152                    if file_config.count_limit == 0 {
153                        "无限制".to_string()
154                    } else {
155                        file_config.count_limit.to_string()
156                    }
157                );
158                tracing_appender::rolling::daily(
159                    &file_config.directory,
160                    &file_config.filename,
161                )
162            }
163            _ => {
164                tracing_appender::rolling::daily(
165                    &file_config.directory,
166                    &file_config.filename,
167                )
168            }
169        };
170
171        let is_file_json = file_config.format.as_str() == "json";
172        let fj = if is_file_json {
173            Some(
174                tracing_subscriber::fmt::layer()
175                    .json()
176                    .with_writer(file_appender)
177                    .with_timer(timer.clone())
178                    .with_line_number(true)
179                    .with_ansi(false),
180            )
181        } else {
182            None
183        };
184        // 需要重新创建 file_appender(已被 move),如果不是 json 模式
185        let fp = if !is_file_json {
186            let file_appender2 = tracing_appender::rolling::daily(
187                &file_config.directory,
188                &file_config.filename,
189            );
190            Some(
191                tracing_subscriber::fmt::layer()
192                    .with_writer(file_appender2)
193                    .with_timer(timer)
194                    .with_line_number(true)
195                    .with_ansi(false),
196            )
197        } else {
198            None
199        };
200        (fj, fp)
201    } else {
202        (None, None)
203    };
204
205    // 组合所有层并初始化(Option<Layer> 自动实现 Layer)
206    tracing_subscriber::registry()
207        .with(env_filter)
208        .with(stdout_json)
209        .with(stdout_plain)
210        .with(file_json)
211        .with(file_plain)
212        .try_init()
213        .map_err(|e| anyhow::anyhow!("日志系统初始化失败: {}", e))?;
214
215    Ok(())
216}
217
218
219#[cfg(feature = "balance")]
220pub use balance::{
221    create_grpc_channel, create_grpc_client, get_load_balancer, get_service_endpoints,
222    grpc_call, get_instance_circuit_breaker, get_existing_instance_circuit_breaker,
223    CircuitBreaker, CircuitBreakerConfig, CircuitState, GrpcClientBuilder,
224    HealthCheckConfig, HealthStatus, LoadBalancer, ResilientGrpcClient, RetryConfig,
225    RoundRobinLoadBalancer, ServiceEndpoint, start_health_checker,
226};
227