libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

logger.rs (4846B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2024, 2025, 2026 Taler Systems S.A.
      4 
      5 * LibEuFin is free software; you can redistribute it and/or modify
      6 * it under the terms of the GNU Affero General Public License as
      7 * published by the Free Software Foundation; either version 3, or
      8 * (at your option) any later version.
      9 
     10 * LibEuFin is distributed in the hope that it will be useful, but
     11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13 * Public License for more details.
     14 
     15 * You should have received a copy of the GNU Affero General Public
     16 * License along with LibEuFin; see the file COPYING.  If not, see
     17 * <http://www.gnu.org/licenses/>
     18 */
     19 
     20 use std::{
     21     fmt::Display,
     22     io::{BufWriter, Cursor, Write},
     23     path::{Path, PathBuf},
     24 };
     25 
     26 use jiff::{Zoned, tz::TimeZone};
     27 use reqwest::Response;
     28 use tracing::info;
     29 
     30 use crate::ebics::{EbicsCtx, EbicsError, EbicsErrorHelper};
     31 
     32 /** Log EBICS transactions steps and payload if dir is not null */
     33 pub struct EbicsLogger {
     34     dir: Option<PathBuf>,
     35 }
     36 
     37 impl EbicsLogger {
     38     pub fn new(dir: Option<PathBuf>) -> std::io::Result<Self> {
     39         if let Some(dir) = &dir {
     40             // Create logging directory if missing
     41             std::fs::create_dir_all(dir)?;
     42             info!(target: "ebics", "Logging EBICS to {dir:?}");
     43         }
     44         Ok(Self { dir })
     45     }
     46 
     47     fn log_dir(root: &Path, ctx: &EbicsCtx) -> Result<PathBuf, EbicsError> {
     48         let now_utc = Zoned::new(ctx.now, TimeZone::UTC).datetime();
     49         // yyyy-MM-dd per day directory & HH:mm:ss.SSS-name per transaction directory
     50         let path = root.join(format!(
     51             "{}/{}-{}",
     52             now_utc.date(),
     53             now_utc.strftime("%H:%M:%S.%3f"),
     54             ctx.order,
     55         ));
     56         std::fs::create_dir_all(&path).ctx(ctx)?;
     57         Ok(path)
     58     }
     59 
     60     fn prefix(ctx: &EbicsCtx) -> impl Display {
     61         std::fmt::from_fn(|f| match &ctx.phase {
     62             Some(phase) => write!(f, "{phase}-"),
     63             None => Ok(()),
     64         })
     65     }
     66 
     67     /** Log a [content] EBICS transaction payload of [type] */
     68     pub fn log_payload(&self, ctx: &EbicsCtx, content: &[u8], ty: &str) -> Result<(), EbicsError> {
     69         if let Some(root) = &self.dir {
     70             let dir = Self::log_dir(root, ctx)?;
     71             let ty = ty.to_lowercase();
     72             if ty == "zip" {
     73                 let dir = dir.join("payload");
     74                 std::fs::create_dir_all(&dir).ctx(ctx)?;
     75                 let mut z = zip::ZipArchive::new(Cursor::new(content)).unwrap();
     76                 for i in 0..z.len() {
     77                     let mut from = z.by_index(i).unwrap();
     78                     let mut to = std::fs::File::create_new(dir.join(from.name())).ctx(ctx)?;
     79                     std::io::copy(&mut from, &mut to).ctx(ctx)?;
     80                 }
     81             } else {
     82                 std::fs::File::create_new(dir.join(format!("payload.{ty}")))
     83                     .ctx(ctx)?
     84                     .write_all(content)
     85                     .ctx(ctx)?;
     86             }
     87         }
     88         Ok(())
     89     }
     90 
     91     /** Log a protocol step [request] */
     92     pub fn log_request(&self, ctx: &EbicsCtx, xml: &str) -> Result<(), EbicsError> {
     93         if let Some(root) = &self.dir {
     94             let dir = Self::log_dir(root, ctx)?;
     95             let path = dir.join(format!("{}request.xml", Self::prefix(ctx)));
     96             std::fs::File::create_new(path)
     97                 .ctx(ctx)?
     98                 .write_all(xml.as_bytes())
     99                 .ctx(ctx)?;
    100         }
    101         Ok(())
    102     }
    103 
    104     /** Log a protocol step failure */
    105     pub async fn log_failure(&self, ctx: &EbicsCtx<'_>, res: Response) -> Result<(), EbicsError> {
    106         if let Some(root) = &self.dir {
    107             let dir = Self::log_dir(root, ctx)?;
    108             let path = dir.join(format!("{}failure.xml", Self::prefix(ctx)));
    109             let fs = std::fs::File::create_new(path).ctx(ctx)?;
    110             let mut w = BufWriter::new(fs);
    111             writeln!(w, "{:?} {}", res.version(), res.status()).ctx(ctx)?;
    112             for (k, v) in res.headers() {
    113                 writeln!(w, "{k}:{}", String::from_utf8_lossy(v.as_bytes())).ctx(ctx)?;
    114             }
    115             writeln!(w).ctx(ctx)?;
    116             if let Ok(body) = res.bytes().await {
    117                 w.write_all(&body).ctx(ctx)?;
    118             }
    119         }
    120         Ok(())
    121     }
    122 
    123     /** Log a protocol step [response] */
    124     pub fn log_response(&self, ctx: &EbicsCtx, xml: &[u8]) -> Result<(), EbicsError> {
    125         if let Some(root) = &self.dir {
    126             let dir = Self::log_dir(root, ctx)?;
    127             let path = dir.join(format!("{}response.xml", Self::prefix(ctx)));
    128             std::fs::File::create_new(path)
    129                 .ctx(ctx)?
    130                 .write_all(xml)
    131                 .ctx(ctx)?;
    132         }
    133         Ok(())
    134     }
    135 }