28. 干货系列从零用Rust编写正反向代理,项目日志的源码实现 ...

打印 上一主题 下一主题

主题 845|帖子 845|积分 2535

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子
项目地址

国内: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
项目中的使用

目前需要将每条请求数据进入的日志,如access_log,或者项目相关的错误日志error_log记录下来。
以下将介绍项目中如何进行记录并格式化日志的
文件配置

当前需要根据项目中的配置进行相应的初始化,需要用代码将当前的配置进行初始化。
  1. [http]
  2. # 访问列表的写入文件及格式
  3. access_log = "access main debug"
  4. # 错误列表的写入文件及格式,错误的第二个是错误等级。
  5. error_log = "error debug"
  6. # 日志格式
  7. [http.log_format]
  8. main = "{d(%Y-%m-%d %H:%M:%S)} {client_ip} {l} {url} path:{path} query:{query} host:{host} status: {status} {up_status} referer: {referer} user_agent: {user_agent} cookie: {cookie}"
  9. [http.log_names]
  10. access = "logs/access.log trace"
  11. error = "logs/error.log"
  12. default = "logs/default.log"
复制代码
日志的组成部分

日志的组成分为三个部分

  • access_log及error_log的写入文件、格式及日志等级
  • log_names日志的别名,包含日志文件及可能包含日志等级,没有等级默认Info
  • 日志格式,记录日志携带的相关消息,如访问的客户端ip{client_ip}或者访问Url{url}等,遵循Rust的打印结构,用{}里面包含要打印的相关消息
以下是访问信息打印的数据
  1. 2023-11-16 15:02:00 127.0.0.1:55922 INFO http://127.0.0.1:82/root/?aaa=1 path:/root/ query:aaa=1 host:127.0.0.1 status: ???  referer:  user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0 cookie:
复制代码
注意点

因为access_log及error_log可以在[http]的层级下任意配置,第一步我们需要收集到合适的log_names进行初始化,我们用的是一个HashMap做键值对,防止重复:
  1. /// http.rs
  2. pub fn get_log_names(&self, names: &mut HashMap<String, String>) {
  3.     self.comm.get_log_names(names);
  4.     for s in &self.server {
  5.         s.get_log_names(names);
  6.     }
  7. }
  8. /// server.rs
  9. pub fn get_log_names(&self, names: &mut HashMap<String, String>)  {
  10.     self.comm.get_log_names(names);
  11.     for l in &self.location {
  12.         l.get_log_names(names);
  13.     }
  14. }
  15. /// common.rs
  16. pub fn get_log_names(&self, names: &mut HashMap<String, String>)  {
  17.     for val in &self.log_names         {
  18.         if !names.contains_key(val.0) {
  19.             names.insert(val.0.clone(), val.1.clone());
  20.         }
  21.     }
  22. }
复制代码
收集好正确的log文件后,我们需要对其初始化或者重加载,其中重新加载需要拥有上次初始化的Handle那么我们需对基进行存储:
  1. lazy_static! {
  2.     /// 用静态变量存储log4rs的Handle
  3.     static ref LOG4RS_HANDLE: Mutex<Option<log4rs::Handle>> = Mutex::new(None);
  4. }
  5. /// 尝试初始化, 如果已初始化则重新加载
  6. pub fn try_init_log(option: &ConfigOption) {
  7.     let log_names = option.get_log_names();
  8.     let mut log_config = log4rs::config::Config::builder();
  9.     let mut root = Root::builder();
  10.     for (name, path) in log_names {
  11.         let (path, level) = {
  12.             let vals: Vec<&str> = path.split(' ').collect();
  13.             if vals.len() == 1 {
  14.                 (path, Level::Info)
  15.             } else {
  16.                 (
  17.                     vals[0].to_string(),
  18.                     Level::from_str(vals[1]).ok().unwrap_or(Level::Info),
  19.                 )
  20.             }
  21.         };
  22.         // 设置默认的匹配类型打印时间信息
  23.         let parttern =
  24.             log4rs::encode::pattern::PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S)} {m}{n}");
  25.         let appender = FileAppender::builder()
  26.             .encoder(Box::new(parttern))
  27.             .build(path)
  28.             .unwrap();
  29.         if name == "default" {
  30.             root = root.appender(name.clone());
  31.         }
  32.         log_config =
  33.             log_config.appender(Appender::builder().build(name.clone(), Box::new(appender)));
  34.         log_config = log_config.logger(
  35.             Logger::builder()
  36.                 .appender(name.clone())
  37.                 // 当前target不在输出到stdout中
  38.                 .additive(false)
  39.                 .build(name.clone(), level.to_level_filter()),
  40.         );
  41.     }
  42.     if !option.disable_stdout {
  43.         let stdout: ConsoleAppender = ConsoleAppender::builder().build();
  44.         log_config = log_config.appender(Appender::builder().build("stdout", Box::new(stdout)));
  45.         root = root.appender("stdout");
  46.     }
  47.     let log_config = log_config.build(root.build(LevelFilter::Info)).unwrap();
  48.     // 检查静态变量中是否存在handle可能在多线程中,需加锁
  49.     if LOG4RS_HANDLE.lock().unwrap().is_some() {
  50.         LOG4RS_HANDLE
  51.             .lock()
  52.             .unwrap()
  53.             .as_mut()
  54.             .unwrap()
  55.             .set_config(log_config);
  56.     } else {
  57.         let handle = log4rs::init_config(log_config).unwrap();
  58.         *LOG4RS_HANDLE.lock().unwrap() = Some(handle);
  59.     }
  60. }
复制代码
我们需要在初始化参数的时候在重新调用该函数,保证新的日志信息能正确的初始化。
下面是将访问日志的数据打印下来:
  1. /// 记录HTTP的访问数据并将其格式化
  2. pub fn log_acess(
  3.     log_formats: &HashMap<String, String>,
  4.     access: &Option<ConfigLog>,
  5.     req: &Request<RecvStream>,
  6. ) {
  7.     if let Some(access) = access {
  8.         if let Some(formats) = log_formats.get(&access.format) {
  9.             // 需要先判断是否该日志已开启, 如果未开启直接写入将浪费性能
  10.             if log_enabled!(target: &access.name, access.level) {
  11.                 // 将format转化成pattern会有相当的性能损失, 此处缓存pattern结果
  12.                 let pw = FORMAT_PATTERN_CACHE.with(|m| {
  13.                     if !m.borrow().contains_key(&**formats) {
  14.                         let p = PatternEncoder::new(formats);
  15.                         m.borrow_mut()
  16.                             .insert(Box::leak(formats.clone().into_boxed_str()), Arc::new(p));
  17.                     }
  18.                     m.borrow()[&**formats].clone()
  19.                 });
  20.                 // 将其转化成Record然后进行encode
  21.                 let record = ProxyRecord::new_req(Record::builder().level(Level::Info).build(), req);
  22.                 let mut buf = vec![];
  23.                 pw.encode(&mut SimpleWriter(&mut buf), &record).unwrap();
  24.                 log::info!(target: &access.name, "{}", String::from_utf8_lossy(&buf[..]))
  25.             }
  26.         }
  27.     }
  28. }
复制代码
其中缓存pattern的结果性能损失的要求不高,但需要访问速度要高:
[code]thread_local! {    static FORMAT_PATTERN_CACHE: RefCell
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表