ezlog/
logger.rs

1use std::io::Read;
2use std::path::PathBuf;
3use std::{
4    fs,
5    io,
6};
7use std::{
8    io::Write,
9    rc::Rc,
10};
11
12use byteorder::ReadBytesExt;
13use byteorder::{
14    BigEndian,
15    WriteBytesExt,
16};
17use integer_encoding::VarIntWriter;
18use time::OffsetDateTime;
19
20#[cfg(feature = "decode")]
21use crate::crypto::{
22    Aes128Gcm,
23    Aes256Gcm,
24};
25use crate::crypto::{
26    Aes128GcmSiv,
27    Aes256GcmSiv,
28};
29use crate::events::Event::{self,};
30use crate::{
31    appender::EZAppender,
32    compress::ZlibCodec,
33    errors::LogError,
34    CipherKind,
35    Compress,
36    CompressKind,
37    Cryptor,
38    EZLogConfig,
39    EZRecord,
40    RECORD_SIGNATURE_END,
41    RECORD_SIGNATURE_START,
42};
43use crate::{
44    errors,
45    event,
46    NonceGenFn,
47    V1_LOG_HEADER_SIZE,
48};
49use crate::{
50    Version,
51    V2_LOG_HEADER_SIZE,
52};
53
54type Result<T> = std::result::Result<T, LogError>;
55
56#[inline]
57pub(crate) fn create_size_chunk(size: usize) -> Result<Vec<u8>> {
58    let mut chunk: Vec<u8> = Vec::new();
59    chunk.write_varint(size)?;
60    Ok(chunk)
61}
62
63#[inline]
64pub(crate) fn encode_content(mut buf: Vec<u8>) -> Result<Vec<u8>> {
65    let mut chunk: Vec<u8> = Vec::new();
66    chunk.push(RECORD_SIGNATURE_START);
67    let size = buf.len();
68    let mut size_chunk = create_size_chunk(size)?;
69    chunk.append(&mut size_chunk);
70    chunk.append(&mut buf);
71    chunk.push(RECORD_SIGNATURE_END);
72    Ok(chunk)
73}
74
75#[allow(deprecated)]
76pub fn create_cryptor(config: &EZLogConfig) -> Result<Option<Box<dyn Cryptor>>> {
77    if let Some(key) = &config.cipher_key {
78        if let Some(nonce) = &config.cipher_nonce {
79            #[warn(unreachable_patterns)]
80            match config.cipher {
81                #[cfg(feature = "decode")]
82                CipherKind::AES128GCM => {
83                    let encryptor = Aes128Gcm::new(key, nonce)?;
84                    Ok(Some(Box::new(encryptor)))
85                }
86                #[cfg(feature = "decode")]
87                CipherKind::AES256GCM => {
88                    let encryptor = Aes256Gcm::new(key, nonce)?;
89                    Ok(Some(Box::new(encryptor)))
90                }
91                CipherKind::AES128GCMSIV => {
92                    let encryptor = Aes128GcmSiv::new(key, nonce)?;
93                    Ok(Some(Box::new(encryptor)))
94                }
95                CipherKind::AES256GCMSIV => {
96                    let encryptor = Aes256GcmSiv::new(key, nonce)?;
97                    Ok(Some(Box::new(encryptor)))
98                }
99                CipherKind::NONE => Ok(None),
100                unknown => Err(LogError::Crypto(format!("unknown cryption {}", unknown))),
101            }
102        } else {
103            Ok(None)
104        }
105    } else {
106        Ok(None)
107    }
108}
109
110pub fn create_compress(config: &EZLogConfig) -> Option<Box<dyn Compress>> {
111    match config.compress {
112        CompressKind::ZLIB => Some(Box::new(ZlibCodec::new(&config.compress_level))),
113        CompressKind::NONE => None,
114        CompressKind::UNKNOWN => None,
115    }
116}
117
118pub struct EZLogger {
119    pub(crate) config: Rc<EZLogConfig>,
120    pub(crate) appender: EZAppender,
121    pub(crate) compression: Option<Box<dyn Compress>>,
122    pub(crate) cryptor: Option<Box<dyn Cryptor>>,
123}
124
125impl EZLogger {
126    pub fn new(config: EZLogConfig) -> Result<Self> {
127        let rc_conf = Rc::new(config);
128        let mut appender = EZAppender::new(Rc::clone(&rc_conf))?;
129        appender.check_config_rolling(&rc_conf)?;
130        let compression = create_compress(&rc_conf);
131        let cryptor = create_cryptor(&rc_conf)?;
132
133        Ok(Self {
134            config: Rc::clone(&rc_conf),
135            appender,
136            compression,
137            cryptor,
138        })
139    }
140
141    pub(crate) fn append(&mut self, record: &EZRecord) -> Result<()> {
142        if record.content().len() > self.config.max_size as usize / 2 {
143            let mut e: Option<LogError> = None;
144
145            record.trunks(&self.config).iter().for_each(|record| {
146                match self
147                    .encode_as_block(record)
148                    .map(|buf| self.appender.write_all(&buf))
149                {
150                    Ok(_) => {}
151                    Err(err) => e = Some(err),
152                }
153            });
154            e.map_or(Ok(()), Err)
155        } else {
156            let buf = self.encode_as_block(record)?;
157            self.appender.write_all(&buf).map_err(|e| e.into())
158        }
159    }
160
161    #[inline]
162    fn encode(&mut self, record: &EZRecord) -> Result<Vec<u8>> {
163        let nonce_fn: NonceGenFn = self.gen_nonce();
164        let mut buf = self.format(record)?;
165        if buf.is_empty() {
166            return Ok(buf);
167        }
168        if self.config.version == Version::V1 {
169            if let Some(encryptor) = &self.cryptor {
170                event!(Event::Encrypt, &record.t_id());
171                buf = encryptor.encrypt(&buf, nonce_fn)?;
172                event!(Event::EncryptEnd, &record.t_id());
173            }
174            if let Some(compression) = &self.compression {
175                event!(Event::Compress, &record.t_id());
176                buf = compression.compress(&buf).map_err(LogError::Compress)?;
177                event!(Event::CompressEnd, &record.t_id());
178            }
179        } else {
180            let len = buf.len();
181            if let Some(compression) = &self.compression {
182                event!(Event::Compress, &record.t_id());
183                buf = compression.compress(&buf).map_err(LogError::Compress)?;
184                event!(
185                    Event::CompressEnd,
186                    &format!(
187                        "{} compress ratio = {} ",
188                        &record.t_id(),
189                        buf.len() as f64 / len as f64
190                    )
191                );
192            }
193            if let Some(encryptor) = &self.cryptor {
194                event!(Event::Encrypt, &record.t_id());
195                buf = encryptor.encrypt(&buf, nonce_fn)?;
196                event!(
197                    Event::EncryptEnd,
198                    &format!(
199                        "{} process ratio = {} ",
200                        &record.t_id(),
201                        buf.len() as f64 / len as f64
202                    )
203                );
204            }
205        }
206        Ok(buf)
207    }
208
209    /// Generates a nonce generation function for the current `EZLogger`.
210    ///
211    /// The nonce generation function XORs each input slice with a unique nonce that is generated based on the current
212    /// timestamp and recorder position of the `EZAppender`.
213    ///
214    /// # Returns
215    ///
216    /// A `NonceGenFn` closure that be used in encode and decode.
217    ///
218    fn gen_nonce(&mut self) -> NonceGenFn {
219        let timestamp = self.appender.inner.header().timestamp.unix_timestamp();
220        let position = self.appender.inner.header().recorder_position;
221        let combine = combine_time_position(timestamp, position.into());
222
223        // create and return a closure that XORs each input slice with the count
224        Box::new(move |input| xor_slice(input, &combine))
225    }
226
227    ///
228    #[inline]
229    pub fn encode_as_block(&mut self, record: &EZRecord) -> Result<Vec<u8>> {
230        let buf = self.encode(record)?;
231        encode_content(buf)
232    }
233
234    fn format(&self, record: &EZRecord) -> Result<Vec<u8>> {
235        crate::formatter().format(record)
236    }
237
238    pub(crate) fn flush(&mut self) -> std::result::Result<(), io::Error> {
239        self.appender.flush()
240    }
241
242    pub(crate) fn trim(&self) {
243        match fs::read_dir(&self.config.dir_path) {
244            Ok(dir) => {
245                for file in dir {
246                    match file {
247                        Ok(file) => {
248                            if let Some(name) = file.file_name().to_str() {
249                                match self.config.is_file_out_of_date(name) {
250                                    Ok(out_of_date) => {
251                                        if out_of_date {
252                                            fs::remove_file(file.path()).unwrap_or_else(|e| {
253                                                event!(
254                                                    Event::TrimError,
255                                                    "remove file err",
256                                                    &e.into()
257                                                )
258                                            });
259                                        }
260                                    }
261                                    Err(e) => {
262                                        event!(Event::TrimError, "judge file out of date error", &e)
263                                    }
264                                }
265                            };
266                        }
267                        Err(e) => {
268                            event!(Event::TrimError, "traversal file error", &e.into())
269                        }
270                    }
271                }
272            }
273            Err(e) => event!(Event::TrimError, "read dir error", &e.into()),
274        }
275    }
276
277    pub fn query_log_files_for_date(&self, date: OffsetDateTime) -> Vec<PathBuf> {
278        self.config.query_log_files_for_date(date)
279    }
280
281    pub(crate) fn rotate_if_not_empty(&mut self) -> Result<()> {
282        if self
283            .appender
284            .inner
285            .header()
286            .has_record_exclude_extra(&self.config)
287        {
288            self.appender.rotate()
289        } else {
290            Ok(())
291        }
292    }
293}
294
295pub(crate) fn combine_time_position(timestamp: i64, position: u64) -> Vec<u8> {
296    let position_bytes = position.to_be_bytes();
297    let time_bytes = timestamp.to_be_bytes();
298    let mut vec = time_bytes.to_vec();
299    vec.extend(position_bytes);
300    vec
301}
302
303pub(crate) fn xor_slice<'a>(slice: &'a [u8], vec: &'a [u8]) -> Vec<u8> {
304    let mut result = Vec::with_capacity(slice.len());
305    for (i, byte) in slice.iter().enumerate() {
306        if let Some(vec_byte) = vec.get(i) {
307            result.push(byte ^ vec_byte);
308        } else {
309            result.push(*byte);
310        }
311    }
312    result
313}
314
315use bitflags::bitflags;
316
317bitflags! {
318    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
319    pub(crate) struct Flags: u8 {
320        const NONE = 0b0000_0000;
321        const HAS_EXTRA = 0b0000_0001;
322    }
323}
324
325/// EZLog file Header
326///
327/// every log file starts with a header,
328/// which is used to describe the version, log length, compress type, cipher kind and so on.
329#[derive(Debug, Clone, PartialEq, Eq, Hash)]
330pub struct Header {
331    /// version code
332    pub(crate) version: Version,
333    /// flag
334    pub(crate) flag: Flags,
335    /// current log file write position
336    pub(crate) recorder_position: u32,
337    /// compress type
338    pub(crate) compress: CompressKind,
339    /// cipher kind
340    pub(crate) cipher: CipherKind,
341    // config key and nonce hash
342    pub(crate) cihper_hash: u32,
343    /// timestamp
344    pub(crate) timestamp: OffsetDateTime,
345    /// rotate time
346    pub(crate) rotate_time: Option<OffsetDateTime>,
347}
348
349impl Default for Header {
350    fn default() -> Self {
351        Self::new()
352    }
353}
354
355impl Header {
356    #[allow(deprecated)]
357    pub fn new() -> Self {
358        Header {
359            version: Version::V2,
360            flag: Flags::NONE,
361            recorder_position: 0,
362            compress: CompressKind::ZLIB,
363            cipher: CipherKind::AES128GCM,
364            cihper_hash: 0,
365            timestamp: OffsetDateTime::now_utc(),
366            rotate_time: None,
367        }
368    }
369
370    pub fn empty() -> Self {
371        Header {
372            version: Version::UNKNOWN,
373            flag: Flags::NONE,
374            recorder_position: 0,
375            compress: CompressKind::NONE,
376            cipher: CipherKind::NONE,
377            cihper_hash: 0,
378            timestamp: OffsetDateTime::UNIX_EPOCH,
379            rotate_time: None,
380        }
381    }
382
383    pub fn create(config: &EZLogConfig) -> Self {
384        let time = OffsetDateTime::now_utc();
385        let rotate_time = config.rotate_time(time);
386        let flag = if config.extra.is_some() {
387            Flags::HAS_EXTRA
388        } else {
389            Flags::NONE
390        };
391        Header {
392            version: config.version,
393            flag,
394            recorder_position: 0,
395            compress: config.compress,
396            cipher: config.cipher,
397            cihper_hash: config.cipher_hash(),
398            timestamp: OffsetDateTime::now_utc(),
399            rotate_time: Some(rotate_time),
400        }
401    }
402
403    pub fn max_length() -> usize {
404        V2_LOG_HEADER_SIZE
405    }
406
407    #[inline]
408    pub fn length_compat(version: &Version) -> usize {
409        match version {
410            Version::V1 => V1_LOG_HEADER_SIZE,
411            Version::V2 => V2_LOG_HEADER_SIZE,
412            Version::UNKNOWN => 0,
413        }
414    }
415
416    pub fn length(&self) -> usize {
417        Self::length_compat(&self.version)
418    }
419
420    pub fn encode(&self, writer: &mut dyn Write) -> std::result::Result<(), io::Error> {
421        match self.version {
422            Version::V1 => self.encode_v1(writer),
423            Version::V2 => self.encode_v2(writer),
424            Version::UNKNOWN => Err(io::Error::new(
425                io::ErrorKind::InvalidInput,
426                "unknown version",
427            )),
428        }
429    }
430
431    pub fn encode_v1(&self, writer: &mut dyn Write) -> std::result::Result<(), io::Error> {
432        writer.write_all(crate::FILE_SIGNATURE)?;
433        writer.write_u8(self.version.into())?;
434        writer.write_u8(self.flag.bits())?;
435        writer.write_u32::<BigEndian>(self.recorder_position)?;
436        writer.write_u8(self.compress.into())?;
437        writer.write_u8(self.cipher.into())
438    }
439
440    pub fn encode_v2(&self, writer: &mut dyn Write) -> std::result::Result<(), io::Error> {
441        writer.write_all(crate::FILE_SIGNATURE)?;
442        writer.write_u8(self.version.into())?;
443        writer.write_u8(self.flag.bits())?;
444        writer.write_i64::<BigEndian>(self.timestamp.unix_timestamp())?;
445        writer.write_u32::<BigEndian>(self.recorder_position)?;
446        writer.write_u8(self.compress.into())?;
447        writer.write_u8(self.cipher.into())?;
448        writer.write_u32::<BigEndian>(self.cihper_hash)
449    }
450
451    pub fn decode(reader: &mut dyn Read) -> std::result::Result<Self, errors::LogError> {
452        let mut signature = [0u8; 2];
453        reader.read_exact(&mut signature)?;
454        let version = Version::from(reader.read_u8()?);
455        let flag = Flags::from_bits(reader.read_u8()?).unwrap_or(Flags::NONE);
456        let mut timestamp = OffsetDateTime::now_utc().unix_timestamp();
457        if version == Version::V2 {
458            timestamp = reader.read_i64::<BigEndian>()?
459        }
460        let mut recorder_size = reader.read_u32::<BigEndian>()?;
461        if recorder_size < Header::length_compat(&version) as u32 {
462            recorder_size = Header::length_compat(&version) as u32;
463        }
464
465        let compress = reader.read_u8()?;
466        let cipher = reader.read_u8()?;
467        let mut hash: u32 = 0;
468
469        if version == Version::V2 {
470            hash = reader.read_u32::<BigEndian>()?;
471        }
472        Ok(Header {
473            version,
474            flag,
475            recorder_position: recorder_size,
476            compress: CompressKind::from(compress),
477            cipher: CipherKind::from(cipher),
478            cihper_hash: hash,
479            timestamp: OffsetDateTime::from_unix_timestamp(timestamp)
480                .unwrap_or_else(|_| OffsetDateTime::now_utc()),
481            rotate_time: None,
482        })
483    }
484
485    pub fn decode_and_config(
486        reader: &mut dyn Read,
487        config: &EZLogConfig,
488    ) -> std::result::Result<Self, errors::LogError> {
489        let mut decode = Self::decode(reader)?;
490        if !decode.is_config() {
491            decode = Self::create(config);
492        }
493        Ok(decode)
494    }
495
496    pub fn is_match(&self, config: &EZLogConfig) -> bool {
497        self.version == config.version
498            && self.compress == config.compress
499            && self.cipher == config.cipher
500            && self.cihper_hash == config.cipher_hash()
501    }
502
503    pub fn is_empty(&self) -> bool {
504        self.recorder_position <= self.length() as u32
505    }
506
507    pub fn is_config(&self) -> bool {
508        self.version != Version::UNKNOWN
509    }
510
511    pub fn has_record(&self) -> bool {
512        self.recorder_position > self.length() as u32
513    }
514
515    pub fn has_record_exclude_extra(&self, config: &EZLogConfig) -> bool {
516        let extra_len = match &config.extra {
517            Some(e) => {
518                let record = Vec::from(e.to_owned());
519                encode_content(record).map(|r| r.len()).unwrap_or(0)
520            }
521            None => 0,
522        };
523        // extra write as record
524        self.recorder_position > (self.length() + extra_len) as u32
525    }
526
527    pub fn version(&self) -> &Version {
528        &self.version
529    }
530
531    pub(crate) fn init_record_poition(&mut self) {
532        self.recorder_position = Self::length_compat(&self.version) as u32;
533    }
534}