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 }