logger.rs (4889B)
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().unwrap().as_ref())) 79 .ctx(ctx)?; 80 std::io::copy(&mut from, &mut to).ctx(ctx)?; 81 } 82 } else { 83 std::fs::File::create_new(dir.join(format!("payload.{ty}"))) 84 .ctx(ctx)? 85 .write_all(content) 86 .ctx(ctx)?; 87 } 88 } 89 Ok(()) 90 } 91 92 /** Log a protocol step [request] */ 93 pub fn log_request(&self, ctx: &EbicsCtx, xml: &str) -> Result<(), EbicsError> { 94 if let Some(root) = &self.dir { 95 let dir = Self::log_dir(root, ctx)?; 96 let path = dir.join(format!("{}request.xml", Self::prefix(ctx))); 97 std::fs::File::create_new(path) 98 .ctx(ctx)? 99 .write_all(xml.as_bytes()) 100 .ctx(ctx)?; 101 } 102 Ok(()) 103 } 104 105 /** Log a protocol step failure */ 106 pub async fn log_failure(&self, ctx: &EbicsCtx<'_>, res: Response) -> Result<(), EbicsError> { 107 if let Some(root) = &self.dir { 108 let dir = Self::log_dir(root, ctx)?; 109 let path = dir.join(format!("{}failure.xml", Self::prefix(ctx))); 110 let fs = std::fs::File::create_new(path).ctx(ctx)?; 111 let mut w = BufWriter::new(fs); 112 writeln!(w, "{:?} {}", res.version(), res.status()).ctx(ctx)?; 113 for (k, v) in res.headers() { 114 writeln!(w, "{k}:{}", String::from_utf8_lossy(v.as_bytes())).ctx(ctx)?; 115 } 116 writeln!(w).ctx(ctx)?; 117 if let Ok(body) = res.bytes().await { 118 w.write_all(&body).ctx(ctx)?; 119 } 120 } 121 Ok(()) 122 } 123 124 /** Log a protocol step [response] */ 125 pub fn log_response(&self, ctx: &EbicsCtx, xml: &[u8]) -> Result<(), EbicsError> { 126 if let Some(root) = &self.dir { 127 let dir = Self::log_dir(root, ctx)?; 128 let path = dir.join(format!("{}response.xml", Self::prefix(ctx))); 129 std::fs::File::create_new(path) 130 .ctx(ctx)? 131 .write_all(xml) 132 .ctx(ctx)?; 133 } 134 Ok(()) 135 } 136 }