secra_logger/
config.rs

1//! 日志配置模型
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// 日志配置
8#[derive(Debug, Clone, Deserialize, Serialize)]
9pub struct LoggingConfig {
10    /// 日志级别(trace | debug | info | warn | error)
11    #[serde(default = "default_log_level")]
12    pub level: String,
13    
14    /// 日志输出模式(file | console | both)
15    #[serde(default = "default_output_mode")]
16    pub output_mode: OutputMode,
17    
18    /// 文件输出配置(output_mode 为 file 或 both 时有效)
19    #[serde(default)]
20    pub file: Option<LogFileConfig>,
21    
22    /// 控制台输出配置(output_mode 为 console 或 both 时有效)
23    #[serde(default)]
24    pub console: Option<LogConsoleConfig>,
25    
26    /// 是否启用 Request ID 注入
27    #[serde(default = "default_true")]
28    pub enable_request_id: bool,
29    
30    /// 日志字段规范化配置
31    #[serde(default)]
32    pub fields: LogFieldsConfig,
33    
34    /// Vector 日志收集器配置(可选)
35    #[serde(default)]
36    pub vector: Option<VectorConfig>,
37}
38
39/// 日志输出模式
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
41#[serde(rename_all = "lowercase")]
42pub enum OutputMode {
43    /// 只输出到文件
44    File,
45    /// 只输出到控制台
46    Console,
47    /// 同时输出到文件和控制台
48    Both,
49}
50
51/// 文件输出配置
52#[derive(Debug, Clone, Deserialize, Serialize)]
53pub struct LogFileConfig {
54    /// 日志文件路径(单文件,不滚动)
55    pub path: String,
56    
57    /// 是否启用(默认启用)
58    #[serde(default = "default_true")]
59    pub enabled: bool,
60    
61    /// Logrotate 配置(用于日志文件滚动管理)
62    #[serde(default)]
63    pub logrotate: Option<LogrotateConfig>,
64}
65
66/// 控制台输出配置
67#[derive(Debug, Clone, Deserialize, Serialize)]
68pub struct LogConsoleConfig {
69    /// 是否启用(默认启用)
70    #[serde(default = "default_true")]
71    pub enabled: bool,
72    
73    /// 是否使用彩色输出(默认 true)
74    #[serde(default = "default_true")]
75    pub ansi: bool,
76}
77
78/// 日志字段规范化配置
79#[derive(Debug, Clone, Deserialize, Serialize)]
80pub struct LogFieldsConfig {
81    /// 是否启用字段规范化
82    #[serde(default = "default_true")]
83    pub enabled: bool,
84    
85    /// 字段映射规则(可选)
86    #[serde(default)]
87    pub mappings: HashMap<String, String>,
88}
89
90impl Default for LogFieldsConfig {
91    fn default() -> Self {
92        Self {
93            enabled: default_true(),
94            mappings: HashMap::new(),
95        }
96    }
97}
98
99/// Logrotate 配置
100/// 
101/// Logrotate 是 Linux 系统上的日志轮转工具,用于管理日志文件的滚动、压缩和清理。
102/// 此配置用于生成 logrotate 配置文件,以便系统自动管理日志文件。
103/// 
104/// 支持 logrotate 的完整配置选项,包括轮转策略、压缩、脚本执行、邮件通知等。
105#[derive(Debug, Clone, Deserialize, Serialize)]
106pub struct LogrotateConfig {
107    /// 是否启用 logrotate(默认 false,需要手动配置)
108    #[serde(default = "default_false")]
109    pub enabled: bool,
110    
111    /// 日志文件路径模式(支持通配符,如 /var/log/app/*.log)
112    pub path: String,
113    
114    /// 轮转周期(daily | weekly | monthly | yearly | size)
115    /// - daily: 每天轮转
116    /// - weekly: 每周轮转
117    /// - monthly: 每月轮转
118    /// - yearly: 每年轮转
119    /// - size: 按大小轮转(需要配合 size 或 maxsize 使用)
120    #[serde(default = "default_rotate_period")]
121    pub rotate: String,
122    
123    /// 文件大小限制(当日志文件达到此大小时进行轮转,单位:KB, MB, GB)
124    /// 例如:100M, 1G。与 rotate 配合使用,当 rotate 为 size 时优先使用此选项
125    #[serde(default)]
126    pub size: Option<String>,
127    
128    /// 最大文件大小(当 rotate 为 size 时使用,单位:KB, MB, GB)
129    /// 例如:100M, 1G(已废弃,建议使用 size)
130    #[serde(default)]
131    pub maxsize: Option<String>,
132    
133    /// 最小文件大小(小于此大小的文件不进行轮转,单位:KB, MB, GB)
134    /// 例如:1M
135    #[serde(default)]
136    pub minsize: Option<String>,
137    
138    /// 保留的日志文件数量(超过此数量的旧日志将被删除)
139    #[serde(default = "default_rotate_count")]
140    pub rotate_count: u32,
141    
142    /// 最大保留天数(超过此天数的旧日志将被删除)
143    /// 例如:30 表示保留 30 天
144    #[serde(default)]
145    pub maxage: Option<u32>,
146    
147    /// 起始轮转编号(默认从 1 开始)
148    #[serde(default = "default_one")]
149    pub start: u32,
150    
151    /// 是否压缩旧日志文件(默认 true)
152    #[serde(default = "default_true")]
153    pub compress: bool,
154    
155    /// 压缩延迟(轮转后 N 个周期再压缩,0 表示立即压缩)
156    #[serde(default = "default_zero")]
157    pub delaycompress: u32,
158    
159    /// 压缩命令(默认使用 gzip)
160    /// 例如:/usr/bin/bzip2
161    #[serde(default)]
162    pub compresscmd: Option<String>,
163    
164    /// 解压缩命令(默认使用 gunzip)
165    /// 例如:/usr/bin/bunzip2
166    #[serde(default)]
167    pub uncompresscmd: Option<String>,
168    
169    /// 压缩文件扩展名(默认 .gz)
170    /// 例如:.bz2, .xz
171    #[serde(default = "default_compress_ext")]
172    pub compressext: String,
173    
174    /// 压缩选项(传递给压缩命令的选项)
175    /// 例如:-9(最高压缩级别)
176    #[serde(default)]
177    pub compressoptions: Option<String>,
178    
179    /// 是否在轮转时创建新文件(默认 true)
180    #[serde(default = "default_true")]
181    pub create: bool,
182    
183    /// 新文件的权限(八进制格式,如 0644)
184    #[serde(default = "default_file_mode")]
185    pub create_mode: String,
186    
187    /// 新文件的所有者(user:group,如 app:app)
188    #[serde(default)]
189    pub create_owner: Option<String>,
190    
191    /// 是否使用 copy 模式(复制原文件后截断,而不是移动)
192    /// 适用于无法重命名的日志文件(默认 false)
193    #[serde(default = "default_false")]
194    pub copy: bool,
195    
196    /// 是否使用 copytruncate 模式(复制后截断原文件)
197    /// 适用于无法重命名或发送信号的应用程序(默认 false)
198    #[serde(default = "default_false")]
199    pub copytruncate: bool,
200    
201    /// 是否在轮转后执行脚本(默认 false)
202    #[serde(default = "default_false")]
203    pub postrotate: bool,
204    
205    /// 轮转后执行的脚本命令(postrotate 为 true 时使用)
206    /// 例如:"/bin/kill -HUP `cat /var/run/app.pid 2> /dev/null` 2> /dev/null || true"
207    #[serde(default)]
208    pub postrotate_script: Option<String>,
209    
210    /// 是否在轮转前执行脚本(默认 false)
211    #[serde(default = "default_false")]
212    pub prerotate: bool,
213    
214    /// 轮转前执行的脚本命令(prerotate 为 true 时使用)
215    #[serde(default)]
216    pub prerotate_script: Option<String>,
217    
218    /// 是否在第一次轮转前执行脚本(默认 false)
219    #[serde(default = "default_false")]
220    pub firstaction: bool,
221    
222    /// 第一次轮转前执行的脚本命令(firstaction 为 true 时使用)
223    #[serde(default)]
224    pub firstaction_script: Option<String>,
225    
226    /// 是否在最后一次轮转后执行脚本(默认 false)
227    #[serde(default = "default_false")]
228    pub lastaction: bool,
229    
230    /// 最后一次轮转后执行的脚本命令(lastaction 为 true 时使用)
231    #[serde(default)]
232    pub lastaction_script: Option<String>,
233    
234    /// 是否忽略缺失的日志文件(默认 false,缺失文件会报错)
235    #[serde(default = "default_false")]
236    pub missingok: bool,
237    
238    /// 是否在日志文件为空时不轮转(默认 false)
239    #[serde(default = "default_false")]
240    pub notifempty: bool,
241    
242    /// 是否即使文件为空也轮转(与 notifempty 相反,默认 false)
243    #[serde(default = "default_false")]
244    pub ifempty: bool,
245    
246    /// 是否共享脚本(默认 false,每个日志组共享一个脚本)
247    #[serde(default = "default_false")]
248    pub sharedscripts: bool,
249    
250    /// 是否使用日期扩展名(默认 false,使用数字扩展名 .1, .2, ...)
251    /// 为 true 时使用日期格式,如 .20260114
252    #[serde(default = "default_false")]
253    pub dateext: bool,
254    
255    /// 日期扩展名格式(dateext 为 true 时使用)
256    /// 默认:%Y%m%d(年月日)
257    /// 其他选项:%Y%m%d-%H%M%S(年月日-时分秒)
258    #[serde(default = "default_date_format")]
259    pub dateformat: String,
260    
261    /// 旧日志文件目录(可选,用于将旧日志移动到指定目录)
262    /// 例如:/var/log/app/archive
263    #[serde(default)]
264    pub olddir: Option<String>,
265    
266    /// 是否不使用 olddir(默认 false)
267    #[serde(default = "default_false")]
268    pub noolddir: bool,
269    
270    /// 压缩文件扩展名(用于指定压缩后的文件扩展名)
271    /// 例如:.log.gz
272    #[serde(default)]
273    pub extension: Option<String>,
274    
275    /// 禁止的扩展名列表(这些扩展名的文件不会被轮转)
276    /// 例如:["~", ".bak"]
277    #[serde(default)]
278    pub tabooext: Vec<String>,
279    
280    /// 以指定用户身份运行脚本(user:group)
281    /// 例如:app:app
282    #[serde(default)]
283    pub su: Option<String>,
284    
285    /// 是否发送邮件通知(默认 false)
286    #[serde(default = "default_false")]
287    pub mail: bool,
288    
289    /// 邮件地址(轮转前发送邮件)
290    /// 例如:admin@example.com
291    #[serde(default)]
292    pub mailfirst: Option<String>,
293    
294    /// 是否在轮转后发送邮件(默认 false)
295    #[serde(default = "default_false")]
296    pub maillast: bool,
297    
298    /// 是否不发送邮件(默认 false)
299    #[serde(default = "default_false")]
300    pub nomail: bool,
301    
302    /// 包含其他配置文件
303    /// 例如:/etc/logrotate.d/includes
304    #[serde(default)]
305    pub include: Vec<String>,
306    
307    /// 是否安全删除文件(使用 shred 命令,默认 false)
308    #[serde(default = "default_false")]
309    pub shred: bool,
310    
311    /// 安全删除循环次数(shred 为 true 时使用,默认 0)
312    #[serde(default = "default_zero")]
313    pub shredcycles: u32,
314    
315    /// 是否不压缩(与 compress 相反,默认 false)
316    #[serde(default = "default_false")]
317    pub nocompress: bool,
318    
319    /// logrotate 配置文件输出路径(可选)
320    /// 如果未指定,将使用可执行文件目录下的 logrotate/app.conf
321    /// 例如:"./logrotate/app.conf" 或 "/etc/logrotate.d/app"
322    #[serde(default)]
323    pub output_path: Option<String>,
324}
325
326impl LogrotateConfig {
327    /// 生成 logrotate 配置文件内容
328    pub fn to_logrotate_config(&self) -> String {
329        let mut config = String::new();
330        
331        // 日志文件路径
332        config.push_str(&format!("{}\n", self.path));
333        config.push_str("{\n");
334        
335        // 轮转周期
336        config.push_str(&format!("    {}\n", self.rotate));
337        
338        // 文件大小限制
339        if let Some(size) = &self.size {
340            config.push_str(&format!("    size {}\n", size));
341        } else if let Some(maxsize) = &self.maxsize {
342            config.push_str(&format!("    maxsize {}\n", maxsize));
343        }
344        
345        // 最小文件大小
346        if let Some(minsize) = &self.minsize {
347            config.push_str(&format!("    minsize {}\n", minsize));
348        }
349        
350        // 保留文件数量
351        config.push_str(&format!("    rotate {}\n", self.rotate_count));
352        
353        // 最大保留天数
354        if let Some(maxage) = self.maxage {
355            config.push_str(&format!("    maxage {}\n", maxage));
356        }
357        
358        // 起始编号
359        if self.start != 1 {
360            config.push_str(&format!("    start {}\n", self.start));
361        }
362        
363        // 压缩选项
364        if self.nocompress {
365            config.push_str("    nocompress\n");
366        } else if self.compress {
367            config.push_str("    compress\n");
368            if self.delaycompress > 0 {
369                config.push_str(&format!("    delaycompress {}\n", self.delaycompress));
370            }
371            if let Some(cmd) = &self.compresscmd {
372                config.push_str(&format!("    compresscmd {}\n", cmd));
373            }
374            if let Some(cmd) = &self.uncompresscmd {
375                config.push_str(&format!("    uncompresscmd {}\n", cmd));
376            }
377            if self.compressext != ".gz" {
378                config.push_str(&format!("    compressext {}\n", self.compressext));
379            }
380            if let Some(opts) = &self.compressoptions {
381                config.push_str(&format!("    compressoptions {}\n", opts));
382            }
383        }
384        
385        // 文件创建
386        if self.create {
387            let owner = self.create_owner.as_ref()
388                .map(|o| format!(" {}", o))
389                .unwrap_or_default();
390            config.push_str(&format!("    create {} {}{}\n", 
391                self.create_mode, self.create_mode, owner));
392        }
393        
394        // copy 和 copytruncate
395        if self.copy {
396            config.push_str("    copy\n");
397        }
398        if self.copytruncate {
399            config.push_str("    copytruncate\n");
400        }
401        
402        // 文件处理选项
403        if self.missingok {
404            config.push_str("    missingok\n");
405        }
406        if self.notifempty {
407            config.push_str("    notifempty\n");
408        }
409        if self.ifempty {
410            config.push_str("    ifempty\n");
411        }
412        
413        // 日期扩展名
414        if self.dateext {
415            config.push_str("    dateext\n");
416            if self.dateformat != "%Y%m%d" {
417                config.push_str(&format!("    dateformat {}\n", self.dateformat));
418            }
419        }
420        
421        // 旧日志目录
422        if let Some(olddir) = &self.olddir {
423            config.push_str(&format!("    olddir {}\n", olddir));
424        }
425        if self.noolddir {
426            config.push_str("    noolddir\n");
427        }
428        
429        // 扩展名
430        if let Some(ext) = &self.extension {
431            config.push_str(&format!("    extension {}\n", ext));
432        }
433        
434        // 禁止的扩展名
435        if !self.tabooext.is_empty() {
436            config.push_str(&format!("    tabooext {}\n", self.tabooext.join(" ")));
437        }
438        
439        // 共享脚本
440        if self.sharedscripts {
441            config.push_str("    sharedscripts\n");
442        }
443        
444        // 用户身份
445        if let Some(su) = &self.su {
446            config.push_str(&format!("    su {}\n", su));
447        }
448        
449        // 邮件选项
450        if self.nomail {
451            config.push_str("    nomail\n");
452        } else if self.mail {
453            config.push_str("    mail\n");
454        }
455        if let Some(addr) = &self.mailfirst {
456            config.push_str(&format!("    mailfirst {}\n", addr));
457        }
458        if self.maillast {
459            config.push_str("    maillast\n");
460        }
461        
462        // 包含其他配置
463        for include_path in &self.include {
464            config.push_str(&format!("    include {}\n", include_path));
465        }
466        
467        // 安全删除
468        if self.shred {
469            config.push_str("    shred\n");
470            if self.shredcycles > 0 {
471                config.push_str(&format!("    shredcycles {}\n", self.shredcycles));
472            }
473        }
474        
475        // 脚本执行
476        if self.firstaction {
477            if let Some(script) = &self.firstaction_script {
478                config.push_str("    firstaction\n");
479                config.push_str(&format!("        {}\n", script));
480                config.push_str("    endscript\n");
481            }
482        }
483        
484        if self.prerotate {
485            if let Some(script) = &self.prerotate_script {
486                config.push_str("    prerotate\n");
487                config.push_str(&format!("        {}\n", script));
488                config.push_str("    endscript\n");
489            }
490        }
491        
492        if self.postrotate {
493            if let Some(script) = &self.postrotate_script {
494                config.push_str("    postrotate\n");
495                config.push_str(&format!("        {}\n", script));
496                config.push_str("    endscript\n");
497            }
498        }
499        
500        if self.lastaction {
501            if let Some(script) = &self.lastaction_script {
502                config.push_str("    lastaction\n");
503                config.push_str(&format!("        {}\n", script));
504                config.push_str("    endscript\n");
505            }
506        }
507        
508        config.push_str("}\n");
509        config
510    }
511    
512    /// 生成并保存 logrotate 配置文件
513    /// 
514    /// # 参数
515    /// * `default_path` - 默认输出路径(当 output_path 未指定时使用)
516    ///                     如果为 None,则自动使用当前可执行文件目录下的 logrotate/app.conf
517    /// 
518    /// # 返回值
519    /// * 生成的配置文件路径
520    pub fn generate_config_file(&self, default_path: Option<&str>) -> Result<String> {
521        if !self.enabled {
522            return Err(anyhow::anyhow!("Logrotate is not enabled"));
523        }
524        
525        // 确定输出路径
526        let output_path_str: String = if let Some(path) = &self.output_path {
527            // 如果配置中指定了 output_path,优先使用
528            path.clone()
529        } else if let Some(path) = default_path {
530            // 如果提供了 default_path 参数,使用它
531            path.to_string()
532        } else {
533            // 否则,自动使用当前可执行文件目录下的 logrotate/app.conf
534            let exe_path = std::env::current_exe()
535                .context("Failed to get current executable path")?;
536            let exe_dir = exe_path.parent()
537                .ok_or_else(|| anyhow::anyhow!("Failed to get executable directory"))?;
538            let auto_path = exe_dir.join("logrotate").join("app.conf");
539            auto_path.to_string_lossy().to_string()
540        };
541        
542        // 创建目录(如果不存在)
543        if let Some(parent) = std::path::Path::new(&output_path_str).parent() {
544            std::fs::create_dir_all(parent)
545                .with_context(|| format!("Failed to create logrotate directory: {}", parent.display()))?;
546        }
547        
548        // 生成配置文件内容
549        let config_content = self.to_logrotate_config();
550        
551        // 写入文件
552        std::fs::write(&output_path_str, config_content)
553            .with_context(|| format!("Failed to write logrotate config to {}", output_path_str))?;
554        
555        Ok(output_path_str)
556    }
557}
558
559/// Vector 日志收集器配置
560/// 
561/// Vector 是一个高性能的日志、指标和追踪数据收集器。
562/// 此配置用于生成 Vector 配置文件,以便将日志发送到各种目标(Loki、Elasticsearch、S3 等)。
563#[derive(Debug, Clone, Deserialize, Serialize)]
564pub struct VectorConfig {
565    /// 是否启用 Vector 配置生成(默认 false)
566    #[serde(default = "default_false")]
567    pub enabled: bool,
568    
569    /// Vector 配置输出路径(生成的配置文件保存位置)
570    #[serde(default = "default_vector_config_path")]
571    pub config_path: String,
572    
573    /// 日志源配置
574    #[serde(default)]
575    pub source: VectorSourceConfig,
576    
577    /// 日志转换配置
578    #[serde(default)]
579    pub transforms: Vec<VectorTransformConfig>,
580    
581    /// 日志输出目标配置
582    #[serde(default)]
583    pub sinks: Vec<VectorSinkConfig>,
584}
585
586/// Vector 源配置
587#[derive(Debug, Clone, Deserialize, Serialize)]
588pub struct VectorSourceConfig {
589    /// 源名称(默认 app_logs)
590    #[serde(default = "default_source_name")]
591    pub name: String,
592    
593    /// 日志文件路径(支持通配符,如 logs/*.log)
594    pub paths: Vec<String>,
595    
596    /// 读取模式(beginning | end)
597    /// - beginning: 从文件开头读取(适用于新文件)
598    /// - end: 从文件末尾读取(适用于已存在的文件)
599    #[serde(default = "default_read_from")]
600    pub read_from: String,
601    
602    /// 是否启用多行日志解析(默认 false)
603    #[serde(default = "default_false")]
604    pub multiline: bool,
605    
606    /// 多行日志模式(multiline 为 true 时使用)
607    /// 例如:r"^\d{4}-\d{2}-\d{2}" 匹配以日期开头的行
608    #[serde(default)]
609    pub multiline_pattern: Option<String>,
610    
611    /// 是否忽略未找到的文件(默认 false)
612    #[serde(default = "default_false")]
613    pub ignore_not_found: bool,
614    
615    /// 包含的文件模式(glob 模式,默认包含所有匹配的文件)
616    #[serde(default = "default_empty_vec")]
617    pub include: Vec<String>,
618    
619    /// 排除的文件模式(glob 模式)
620    #[serde(default)]
621    pub exclude: Vec<String>,
622}
623
624/// Vector 转换配置
625#[derive(Debug, Clone, Deserialize, Serialize)]
626pub struct VectorTransformConfig {
627    /// 转换器名称(唯一标识)
628    pub name: String,
629    
630    /// 转换器类型(remap | filter | reduce | sample 等)
631    pub transform_type: String,
632    
633    /// 输入源(来自哪个 source 或 transform)
634    pub inputs: Vec<String>,
635    
636    /// 转换脚本或配置(根据 transform_type 不同而不同)
637    /// 对于 remap 类型,这里是 VRL (Vector Remap Language) 脚本
638    pub source: Option<String>,
639    
640    /// 其他配置项(键值对)
641    #[serde(default)]
642    pub options: HashMap<String, String>,
643}
644
645/// Vector 输出目标配置
646#[derive(Debug, Clone, Deserialize, Serialize)]
647pub struct VectorSinkConfig {
648    /// 输出目标名称(唯一标识)
649    pub name: String,
650    
651    /// 输出类型(loki | elasticsearch | s3 | http | file | console 等)
652    pub sink_type: String,
653    
654    /// 输入源(来自哪个 transform)
655    pub inputs: Vec<String>,
656    
657    /// 端点 URL(根据 sink_type 不同而不同)
658    /// - loki: http://loki:3100
659    /// - elasticsearch: http://elasticsearch:9200
660    /// - s3: s3://bucket-name/path
661    /// - http: http://endpoint:port
662    #[serde(default)]
663    pub endpoint: Option<String>,
664    
665    /// 标签配置(用于 Loki 等需要标签的目标)
666    #[serde(default)]
667    pub labels: HashMap<String, String>,
668    
669    /// 编码格式(json | text | native)
670    #[serde(default = "default_encoding")]
671    pub encoding: String,
672    
673    /// 压缩方式(gzip | snappy | none)
674    #[serde(default = "default_compression")]
675    pub compression: String,
676    
677    /// 批量配置
678    #[serde(default)]
679    pub batch: Option<VectorBatchConfig>,
680    
681    /// 请求重试配置
682    #[serde(default)]
683    pub request: Option<VectorRequestConfig>,
684    
685    /// 其他配置项(键值对)
686    #[serde(default)]
687    pub options: HashMap<String, String>,
688}
689
690/// Vector 批量配置
691#[derive(Debug, Clone, Deserialize, Serialize)]
692pub struct VectorBatchConfig {
693    /// 最大批量大小(字节)
694    #[serde(default = "default_batch_max_bytes")]
695    pub max_bytes: u64,
696    
697    /// 最大批量事件数
698    #[serde(default = "default_batch_max_events")]
699    pub max_events: u32,
700    
701    /// 批量超时时间(秒)
702    #[serde(default = "default_batch_timeout")]
703    pub timeout_secs: u32,
704}
705
706/// Vector 请求配置
707#[derive(Debug, Clone, Deserialize, Serialize)]
708pub struct VectorRequestConfig {
709    /// 重试次数
710    #[serde(default = "default_retry_attempts")]
711    pub retry_attempts: u32,
712    
713    /// 重试退避时间(秒)
714    #[serde(default = "default_retry_backoff")]
715    pub retry_backoff_secs: u32,
716    
717    /// 请求超时时间(秒)
718    #[serde(default = "default_request_timeout")]
719    pub timeout_secs: u32,
720    
721    /// 请求速率限制(每秒请求数)
722    #[serde(default)]
723    pub rate_limit: Option<u32>,
724}
725
726// 默认值函数
727fn default_false() -> bool {
728    false
729}
730
731fn default_zero() -> u32 {
732    0
733}
734
735fn default_one() -> u32 {
736    1
737}
738
739fn default_rotate_period() -> String {
740    "daily".to_string()
741}
742
743fn default_rotate_count() -> u32 {
744    7
745}
746
747fn default_file_mode() -> String {
748    "0644".to_string()
749}
750
751fn default_date_format() -> String {
752    "%Y%m%d".to_string()
753}
754
755fn default_compress_ext() -> String {
756    ".gz".to_string()
757}
758
759fn default_vector_config_path() -> String {
760    "vector.toml".to_string()
761}
762
763fn default_source_name() -> String {
764    "app_logs".to_string()
765}
766
767fn default_read_from() -> String {
768    "beginning".to_string()
769}
770
771fn default_encoding() -> String {
772    "json".to_string()
773}
774
775fn default_compression() -> String {
776    "gzip".to_string()
777}
778
779fn default_batch_max_bytes() -> u64 {
780    1048576 // 1MB
781}
782
783fn default_batch_max_events() -> u32 {
784    500
785}
786
787fn default_batch_timeout() -> u32 {
788    10
789}
790
791fn default_retry_attempts() -> u32 {
792    5
793}
794
795fn default_retry_backoff() -> u32 {
796    1
797}
798
799fn default_request_timeout() -> u32 {
800    30
801}
802
803fn default_empty_vec() -> Vec<String> {
804    Vec::new()
805}
806
807impl Default for VectorSourceConfig {
808    fn default() -> Self {
809        Self {
810            name: default_source_name(),
811            paths: vec!["logs/app.log".to_string()],
812            read_from: default_read_from(),
813            multiline: default_false(),
814            multiline_pattern: None,
815            ignore_not_found: default_false(),
816            include: default_empty_vec(),
817            exclude: Vec::new(),
818        }
819    }
820}
821
822// 默认值函数
823fn default_log_level() -> String {
824    "info".to_string()
825}
826
827fn default_output_mode() -> OutputMode {
828    OutputMode::File
829}
830
831fn default_true() -> bool {
832    true
833}
834
835impl LoggingConfig {
836    /// 验证配置的有效性
837    pub fn validate(&self) -> Result<()> {
838        // 验证日志级别
839        match self.level.as_str() {
840            "trace" | "debug" | "info" | "warn" | "error" => {},
841            _ => anyhow::bail!("Invalid log level: {}", self.level),
842        }
843        
844        // 验证文件配置
845        if matches!(self.output_mode, OutputMode::File | OutputMode::Both) {
846            if let Some(file_config) = &self.file {
847                if file_config.enabled && file_config.path.is_empty() {
848                    anyhow::bail!("Log file path cannot be empty when file output is enabled");
849                }
850            } else {
851                anyhow::bail!("File config is required when output_mode is file or both");
852            }
853        }
854        
855        // 验证控制台配置
856        if matches!(self.output_mode, OutputMode::Console | OutputMode::Both) {
857            if self.console.is_none() {
858                anyhow::bail!("Console config is required when output_mode is console or both");
859            }
860        }
861        
862        Ok(())
863    }
864}
865
866impl Default for LoggingConfig {
867    fn default() -> Self {
868        Self {
869            level: default_log_level(),
870            output_mode: default_output_mode(),
871            file: Some(LogFileConfig {
872                path: "logs/app.log".to_string(),
873                enabled: true,
874                logrotate: None,
875            }),
876            console: None,
877            enable_request_id: true,
878            fields: LogFieldsConfig::default(),
879            vector: None,
880        }
881    }
882}