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