Skip to main content

sklears_utils/
logging.rs

1//! Comprehensive logging framework for sklears
2//!
3//! This module provides structured logging with configurable levels, performance logging,
4//! distributed logging support, and log analysis utilities.
5
6use serde_json::{json, Value};
7use std::collections::HashMap;
8use std::fmt::{self, Display};
9use std::fs::{File, OpenOptions};
10use std::io::{BufWriter, Write};
11use std::path::Path;
12use std::str::FromStr;
13use std::sync::{Arc, Mutex, RwLock};
14use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
15
16/// Log levels in order of verbosity
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum LogLevel {
19    Error = 0,
20    Warn = 1,
21    Info = 2,
22    Debug = 3,
23    Trace = 4,
24}
25
26impl Display for LogLevel {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            LogLevel::Error => write!(f, "ERROR"),
30            LogLevel::Warn => write!(f, "WARN"),
31            LogLevel::Info => write!(f, "INFO"),
32            LogLevel::Debug => write!(f, "DEBUG"),
33            LogLevel::Trace => write!(f, "TRACE"),
34        }
35    }
36}
37
38impl FromStr for LogLevel {
39    type Err = String;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        match s.to_uppercase().as_str() {
43            "ERROR" => Ok(LogLevel::Error),
44            "WARN" => Ok(LogLevel::Warn),
45            "INFO" => Ok(LogLevel::Info),
46            "DEBUG" => Ok(LogLevel::Debug),
47            "TRACE" => Ok(LogLevel::Trace),
48            _ => Err(format!("Invalid log level: {s}")),
49        }
50    }
51}
52
53/// Structured log entry
54#[derive(Debug, Clone)]
55pub struct LogEntry {
56    pub timestamp: SystemTime,
57    pub level: LogLevel,
58    pub message: String,
59    pub module: String,
60    pub file: String,
61    pub line: u32,
62    pub thread_id: String,
63    pub fields: HashMap<String, Value>,
64}
65
66impl LogEntry {
67    pub fn new(level: LogLevel, message: String, module: String, file: String, line: u32) -> Self {
68        Self {
69            timestamp: SystemTime::now(),
70            level,
71            message,
72            module,
73            file,
74            line,
75            thread_id: format!("{:?}", std::thread::current().id()),
76            fields: HashMap::new(),
77        }
78    }
79
80    pub fn with_field<V: Into<Value>>(mut self, key: String, value: V) -> Self {
81        self.fields.insert(key, value.into());
82        self
83    }
84
85    pub fn to_json(&self) -> Value {
86        let timestamp_ms = self
87            .timestamp
88            .duration_since(UNIX_EPOCH)
89            .unwrap_or_default()
90            .as_millis();
91
92        let mut json = json!({
93            "timestamp": timestamp_ms,
94            "level": self.level.to_string(),
95            "message": self.message,
96            "module": self.module,
97            "file": self.file,
98            "line": self.line,
99            "thread_id": self.thread_id,
100        });
101
102        if let Value::Object(ref mut map) = json {
103            for (key, value) in &self.fields {
104                map.insert(key.clone(), value.clone());
105            }
106        }
107
108        json
109    }
110
111    pub fn to_text(&self) -> String {
112        let timestamp = self
113            .timestamp
114            .duration_since(UNIX_EPOCH)
115            .unwrap_or_default()
116            .as_millis();
117
118        format!(
119            "[{}] {} [{}:{}] [{}] {} {}",
120            timestamp, self.level, self.file, self.line, self.thread_id, self.module, self.message
121        )
122    }
123}
124
125/// Log formatter trait
126pub trait LogFormatter: Send + Sync {
127    fn format(&self, entry: &LogEntry) -> String;
128}
129
130/// JSON formatter
131pub struct JsonFormatter;
132
133impl LogFormatter for JsonFormatter {
134    fn format(&self, entry: &LogEntry) -> String {
135        entry.to_json().to_string()
136    }
137}
138
139/// Text formatter
140pub struct TextFormatter;
141
142impl LogFormatter for TextFormatter {
143    fn format(&self, entry: &LogEntry) -> String {
144        entry.to_text()
145    }
146}
147
148/// Log output destination trait
149pub trait LogOutput: Send + Sync {
150    fn write(&mut self, formatted_log: &str) -> Result<(), std::io::Error>;
151    fn flush(&mut self) -> Result<(), std::io::Error>;
152}
153
154/// Console output
155pub struct ConsoleOutput;
156
157impl LogOutput for ConsoleOutput {
158    fn write(&mut self, formatted_log: &str) -> Result<(), std::io::Error> {
159        println!("{formatted_log}");
160        Ok(())
161    }
162
163    fn flush(&mut self) -> Result<(), std::io::Error> {
164        std::io::stdout().flush()
165    }
166}
167
168/// File output
169pub struct FileOutput {
170    writer: BufWriter<File>,
171}
172
173impl FileOutput {
174    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
175        let file = OpenOptions::new().create(true).append(true).open(path)?;
176        Ok(Self {
177            writer: BufWriter::new(file),
178        })
179    }
180}
181
182impl LogOutput for FileOutput {
183    fn write(&mut self, formatted_log: &str) -> Result<(), std::io::Error> {
184        writeln!(self.writer, "{formatted_log}")?;
185        Ok(())
186    }
187
188    fn flush(&mut self) -> Result<(), std::io::Error> {
189        self.writer.flush()
190    }
191}
192
193/// Logger configuration
194#[derive(Debug, Clone)]
195pub struct LoggerConfig {
196    pub level: LogLevel,
197    pub module_filters: HashMap<String, LogLevel>,
198    pub enable_performance_logging: bool,
199    pub buffer_size: usize,
200    pub auto_flush: bool,
201    pub include_caller_info: bool,
202}
203
204impl Default for LoggerConfig {
205    fn default() -> Self {
206        Self {
207            level: LogLevel::Info,
208            module_filters: HashMap::new(),
209            enable_performance_logging: false,
210            buffer_size: 1000,
211            auto_flush: true,
212            include_caller_info: true,
213        }
214    }
215}
216
217/// Main logger implementation
218pub struct Logger {
219    config: Arc<RwLock<LoggerConfig>>,
220    outputs: Arc<Mutex<Vec<Box<dyn LogOutput>>>>,
221    formatter: Arc<dyn LogFormatter>,
222    buffer: Arc<Mutex<Vec<LogEntry>>>,
223    stats: Arc<Mutex<LogStats>>,
224}
225
226#[derive(Debug, Default)]
227pub struct LogStats {
228    pub total_logs: u64,
229    pub logs_by_level: HashMap<LogLevel, u64>,
230    pub logs_by_module: HashMap<String, u64>,
231    pub buffer_overflows: u64,
232    pub write_errors: u64,
233}
234
235impl Logger {
236    pub fn new(config: LoggerConfig) -> Self {
237        Self {
238            config: Arc::new(RwLock::new(config)),
239            outputs: Arc::new(Mutex::new(Vec::new())),
240            formatter: Arc::new(TextFormatter),
241            buffer: Arc::new(Mutex::new(Vec::new())),
242            stats: Arc::new(Mutex::new(LogStats::default())),
243        }
244    }
245
246    pub fn with_formatter(mut self, formatter: Arc<dyn LogFormatter>) -> Self {
247        self.formatter = formatter;
248        self
249    }
250
251    pub fn add_output(&self, output: Box<dyn LogOutput>) {
252        let mut outputs = self.outputs.lock().expect("operation should succeed");
253        outputs.push(output);
254    }
255
256    pub fn log(&self, entry: LogEntry) {
257        let config = self.config.read().expect("operation should succeed");
258
259        // Check if we should log this entry
260        if !self.should_log(&entry.level, &entry.module, &config) {
261            return;
262        }
263
264        // Update stats
265        {
266            let mut stats = self.stats.lock().expect("operation should succeed");
267            stats.total_logs += 1;
268            *stats.logs_by_level.entry(entry.level).or_insert(0) += 1;
269            *stats
270                .logs_by_module
271                .entry(entry.module.clone())
272                .or_insert(0) += 1;
273        }
274
275        // Add to buffer
276        {
277            let mut buffer = self.buffer.lock().expect("operation should succeed");
278            if buffer.len() >= config.buffer_size {
279                buffer.remove(0); // Remove oldest entry
280                let mut stats = self.stats.lock().expect("operation should succeed");
281                stats.buffer_overflows += 1;
282            }
283            buffer.push(entry.clone());
284        }
285
286        // Write immediately if auto_flush is enabled
287        if config.auto_flush {
288            self.flush_entry(&entry);
289        }
290    }
291
292    fn should_log(&self, level: &LogLevel, module: &str, config: &LoggerConfig) -> bool {
293        // Check module-specific filter first
294        if let Some(module_level) = config.module_filters.get(module) {
295            return level <= module_level;
296        }
297
298        // Fall back to global level
299        level <= &config.level
300    }
301
302    fn flush_entry(&self, entry: &LogEntry) {
303        let formatted = self.formatter.format(entry);
304        let mut outputs = self.outputs.lock().expect("operation should succeed");
305
306        for output in outputs.iter_mut() {
307            if output.write(&formatted).is_err() {
308                let mut stats = self.stats.lock().expect("operation should succeed");
309                stats.write_errors += 1;
310            }
311        }
312    }
313
314    pub fn flush(&self) {
315        let buffer = {
316            let mut buffer = self.buffer.lock().expect("operation should succeed");
317            let entries = buffer.clone();
318            buffer.clear();
319            entries
320        };
321
322        for entry in buffer {
323            self.flush_entry(&entry);
324        }
325
326        // Flush all outputs
327        let mut outputs = self.outputs.lock().expect("operation should succeed");
328        for output in outputs.iter_mut() {
329            let _ = output.flush();
330        }
331    }
332
333    pub fn set_level(&self, level: LogLevel) {
334        let mut config = self.config.write().expect("operation should succeed");
335        config.level = level;
336    }
337
338    pub fn set_module_level(&self, module: String, level: LogLevel) {
339        let mut config = self.config.write().expect("operation should succeed");
340        config.module_filters.insert(module, level);
341    }
342
343    pub fn stats(&self) -> LogStats {
344        self.stats.lock().expect("operation should succeed").clone()
345    }
346
347    pub fn clear_stats(&self) {
348        let mut stats = self.stats.lock().expect("operation should succeed");
349        *stats = LogStats::default();
350    }
351}
352
353impl Clone for LogStats {
354    fn clone(&self) -> Self {
355        Self {
356            total_logs: self.total_logs,
357            logs_by_level: self.logs_by_level.clone(),
358            logs_by_module: self.logs_by_module.clone(),
359            buffer_overflows: self.buffer_overflows,
360            write_errors: self.write_errors,
361        }
362    }
363}
364
365/// Performance logger for tracking operation timings
366pub struct PerformanceLogger {
367    logger: Arc<Logger>,
368    operations: Arc<Mutex<HashMap<String, Vec<Duration>>>>,
369}
370
371impl PerformanceLogger {
372    pub fn new(logger: Arc<Logger>) -> Self {
373        Self {
374            logger,
375            operations: Arc::new(Mutex::new(HashMap::new())),
376        }
377    }
378
379    pub fn time_operation<F, R>(&self, name: &str, operation: F) -> R
380    where
381        F: FnOnce() -> R,
382    {
383        let start = Instant::now();
384        let result = operation();
385        let duration = start.elapsed();
386
387        // Record timing
388        {
389            let mut operations = self.operations.lock().expect("operation should succeed");
390            operations
391                .entry(name.to_string())
392                .or_default()
393                .push(duration);
394        }
395
396        // Log performance
397        let entry = LogEntry::new(
398            LogLevel::Debug,
399            format!("Operation '{name}' completed"),
400            "performance".to_string(),
401            "performance_logger.rs".to_string(),
402            0,
403        )
404        .with_field("operation".to_string(), name.to_string())
405        .with_field("duration_ms".to_string(), duration.as_millis() as f64);
406
407        self.logger.log(entry);
408
409        result
410    }
411
412    pub fn get_operation_stats(&self, name: &str) -> Option<OperationStats> {
413        let operations = self.operations.lock().expect("operation should succeed");
414        if let Some(durations) = operations.get(name) {
415            if durations.is_empty() {
416                return None;
417            }
418
419            let total_ms: f64 = durations.iter().map(|d| d.as_millis() as f64).sum();
420            let count = durations.len();
421            let avg_ms = total_ms / count as f64;
422
423            let mut sorted_durations = durations.clone();
424            sorted_durations.sort();
425
426            let min_ms = sorted_durations
427                .first()
428                .expect("operation should succeed")
429                .as_millis() as f64;
430            let max_ms = sorted_durations
431                .last()
432                .expect("operation should succeed")
433                .as_millis() as f64;
434
435            let median_ms = if count % 2 == 0 {
436                let mid = count / 2;
437                (sorted_durations[mid - 1].as_millis() + sorted_durations[mid].as_millis()) as f64
438                    / 2.0
439            } else {
440                sorted_durations[count / 2].as_millis() as f64
441            };
442
443            Some(OperationStats {
444                name: name.to_string(),
445                count,
446                total_ms,
447                avg_ms,
448                min_ms,
449                max_ms,
450                median_ms,
451            })
452        } else {
453            None
454        }
455    }
456
457    pub fn clear_operation_stats(&self, name: &str) {
458        let mut operations = self.operations.lock().expect("operation should succeed");
459        operations.remove(name);
460    }
461
462    pub fn get_all_operations(&self) -> Vec<String> {
463        let operations = self.operations.lock().expect("operation should succeed");
464        operations.keys().cloned().collect()
465    }
466}
467
468#[derive(Debug, Clone)]
469pub struct OperationStats {
470    pub name: String,
471    pub count: usize,
472    pub total_ms: f64,
473    pub avg_ms: f64,
474    pub min_ms: f64,
475    pub max_ms: f64,
476    pub median_ms: f64,
477}
478
479/// Distributed logging coordinator
480pub struct DistributedLogger {
481    local_logger: Arc<Logger>,
482    node_id: String,
483    cluster_nodes: Arc<RwLock<Vec<String>>>,
484}
485
486impl DistributedLogger {
487    pub fn new(local_logger: Arc<Logger>, node_id: String) -> Self {
488        Self {
489            local_logger,
490            node_id,
491            cluster_nodes: Arc::new(RwLock::new(Vec::new())),
492        }
493    }
494
495    pub fn add_node(&self, node_id: String) {
496        let mut nodes = self
497            .cluster_nodes
498            .write()
499            .expect("operation should succeed");
500        if !nodes.contains(&node_id) {
501            nodes.push(node_id);
502        }
503    }
504
505    pub fn remove_node(&self, node_id: &str) {
506        let mut nodes = self
507            .cluster_nodes
508            .write()
509            .expect("operation should succeed");
510        nodes.retain(|id| id != node_id);
511    }
512
513    pub fn log_distributed(&self, mut entry: LogEntry) {
514        // Add node information
515        entry = entry.with_field("node_id".to_string(), self.node_id.clone());
516
517        // Log locally
518        self.local_logger.log(entry);
519
520        // In a real implementation, you would send logs to other nodes here
521        // This is a placeholder for distributed logging functionality
522    }
523
524    pub fn get_cluster_nodes(&self) -> Vec<String> {
525        self.cluster_nodes
526            .read()
527            .expect("operation should succeed")
528            .clone()
529    }
530}
531
532/// Log analysis utilities
533pub struct LogAnalyzer {
534    entries: Vec<LogEntry>,
535}
536
537impl LogAnalyzer {
538    pub fn new() -> Self {
539        Self {
540            entries: Vec::new(),
541        }
542    }
543
544    pub fn add_entries(&mut self, entries: Vec<LogEntry>) {
545        self.entries.extend(entries);
546    }
547
548    pub fn analyze_patterns(&self) -> LogAnalysis {
549        let mut analysis = LogAnalysis::default();
550
551        for entry in &self.entries {
552            analysis.total_entries += 1;
553            *analysis.entries_by_level.entry(entry.level).or_insert(0) += 1;
554            *analysis
555                .entries_by_module
556                .entry(entry.module.clone())
557                .or_insert(0) += 1;
558
559            // Detect error patterns
560            if entry.level == LogLevel::Error {
561                *analysis
562                    .error_patterns
563                    .entry(entry.message.clone())
564                    .or_insert(0) += 1;
565            }
566        }
567
568        analysis
569    }
570
571    pub fn find_errors_in_timeframe(&self, start: SystemTime, end: SystemTime) -> Vec<LogEntry> {
572        self.entries
573            .iter()
574            .filter(|entry| {
575                entry.level == LogLevel::Error && entry.timestamp >= start && entry.timestamp <= end
576            })
577            .cloned()
578            .collect()
579    }
580
581    pub fn get_module_activity(&self, module: &str) -> Vec<LogEntry> {
582        self.entries
583            .iter()
584            .filter(|entry| entry.module == module)
585            .cloned()
586            .collect()
587    }
588}
589
590#[derive(Debug, Default)]
591pub struct LogAnalysis {
592    pub total_entries: u64,
593    pub entries_by_level: HashMap<LogLevel, u64>,
594    pub entries_by_module: HashMap<String, u64>,
595    pub error_patterns: HashMap<String, u64>,
596}
597
598impl Default for LogAnalyzer {
599    fn default() -> Self {
600        Self::new()
601    }
602}
603
604lazy_static::lazy_static! {
605    /// Global logger instance
606    static ref GLOBAL_LOGGER: Arc<Logger> = {
607        let config = LoggerConfig::default();
608        let logger = Arc::new(Logger::new(config));
609        logger.add_output(Box::new(ConsoleOutput));
610        logger
611    };
612}
613
614/// Logging macros
615#[macro_export]
616macro_rules! log_error {
617    ($($arg:tt)*) => {
618        $crate::logging::log_with_level($crate::logging::LogLevel::Error, format!($($arg)*))
619    };
620}
621
622#[macro_export]
623macro_rules! log_warn {
624    ($($arg:tt)*) => {
625        $crate::logging::log_with_level($crate::logging::LogLevel::Warn, format!($($arg)*))
626    };
627}
628
629#[macro_export]
630macro_rules! log_info {
631    ($($arg:tt)*) => {
632        $crate::logging::log_with_level($crate::logging::LogLevel::Info, format!($($arg)*))
633    };
634}
635
636#[macro_export]
637macro_rules! log_debug {
638    ($($arg:tt)*) => {
639        $crate::logging::log_with_level($crate::logging::LogLevel::Debug, format!($($arg)*))
640    };
641}
642
643#[macro_export]
644macro_rules! log_trace {
645    ($($arg:tt)*) => {
646        $crate::logging::log_with_level($crate::logging::LogLevel::Trace, format!($($arg)*))
647    };
648}
649
650pub fn log_with_level(level: LogLevel, message: String) {
651    let entry = LogEntry::new(
652        level,
653        message,
654        "global".to_string(),
655        "unknown".to_string(),
656        0,
657    );
658    GLOBAL_LOGGER.log(entry);
659}
660
661pub fn get_global_logger() -> Arc<Logger> {
662    GLOBAL_LOGGER.clone()
663}
664
665pub fn set_global_level(level: LogLevel) {
666    GLOBAL_LOGGER.set_level(level);
667}
668
669pub fn flush_global_logger() {
670    GLOBAL_LOGGER.flush();
671}
672
673#[allow(non_snake_case)]
674#[cfg(test)]
675mod tests {
676    use super::*;
677    use std::sync::Arc;
678    use tempfile::NamedTempFile;
679
680    #[test]
681    fn test_log_levels() {
682        assert!(LogLevel::Error < LogLevel::Warn);
683        assert!(LogLevel::Warn < LogLevel::Info);
684        assert!(LogLevel::Info < LogLevel::Debug);
685        assert!(LogLevel::Debug < LogLevel::Trace);
686    }
687
688    #[test]
689    fn test_log_entry_creation() {
690        let entry = LogEntry::new(
691            LogLevel::Info,
692            "Test message".to_string(),
693            "test_module".to_string(),
694            "test.rs".to_string(),
695            42,
696        );
697
698        assert_eq!(entry.level, LogLevel::Info);
699        assert_eq!(entry.message, "Test message");
700        assert_eq!(entry.module, "test_module");
701        assert_eq!(entry.file, "test.rs");
702        assert_eq!(entry.line, 42);
703    }
704
705    #[test]
706    fn test_log_entry_with_fields() {
707        let entry = LogEntry::new(
708            LogLevel::Debug,
709            "Debug message".to_string(),
710            "test".to_string(),
711            "test.rs".to_string(),
712            1,
713        )
714        .with_field("key1".to_string(), "value1".to_string())
715        .with_field("key2".to_string(), 42);
716
717        assert_eq!(entry.fields.len(), 2);
718        assert_eq!(
719            entry.fields.get("key1").expect("operation should succeed"),
720            &Value::String("value1".to_string())
721        );
722        assert_eq!(
723            entry.fields.get("key2").expect("operation should succeed"),
724            &Value::Number(42.into())
725        );
726    }
727
728    #[test]
729    fn test_logger_creation() {
730        let config = LoggerConfig::default();
731        let logger = Logger::new(config);
732
733        let stats = logger.stats();
734        assert_eq!(stats.total_logs, 0);
735    }
736
737    #[test]
738    fn test_logger_with_file_output() {
739        let temp_file = NamedTempFile::new().expect("operation should succeed");
740        let config = LoggerConfig::default();
741        let logger = Logger::new(config);
742
743        let file_output = FileOutput::new(temp_file.path()).expect("operation should succeed");
744        logger.add_output(Box::new(file_output));
745
746        let entry = LogEntry::new(
747            LogLevel::Info,
748            "Test log".to_string(),
749            "test".to_string(),
750            "test.rs".to_string(),
751            1,
752        );
753
754        logger.log(entry);
755        logger.flush();
756
757        let stats = logger.stats();
758        assert_eq!(stats.total_logs, 1);
759    }
760
761    #[test]
762    fn test_performance_logger() {
763        let config = LoggerConfig::default();
764        let logger = Arc::new(Logger::new(config));
765        let perf_logger = PerformanceLogger::new(logger);
766
767        let result = perf_logger.time_operation("test_op", || {
768            std::thread::sleep(std::time::Duration::from_millis(10));
769            42
770        });
771
772        assert_eq!(result, 42);
773
774        let stats = perf_logger
775            .get_operation_stats("test_op")
776            .expect("operation should succeed");
777        assert_eq!(stats.count, 1);
778        assert!(stats.avg_ms >= 10.0);
779    }
780
781    #[test]
782    fn test_log_analyzer() {
783        let mut analyzer = LogAnalyzer::new();
784
785        let entries = vec![
786            LogEntry::new(
787                LogLevel::Info,
788                "Info message".to_string(),
789                "module1".to_string(),
790                "test.rs".to_string(),
791                1,
792            ),
793            LogEntry::new(
794                LogLevel::Error,
795                "Error message".to_string(),
796                "module1".to_string(),
797                "test.rs".to_string(),
798                2,
799            ),
800            LogEntry::new(
801                LogLevel::Debug,
802                "Debug message".to_string(),
803                "module2".to_string(),
804                "test.rs".to_string(),
805                3,
806            ),
807        ];
808
809        analyzer.add_entries(entries);
810        let analysis = analyzer.analyze_patterns();
811
812        assert_eq!(analysis.total_entries, 3);
813        assert_eq!(
814            *analysis
815                .entries_by_level
816                .get(&LogLevel::Info)
817                .expect("operation should succeed"),
818            1
819        );
820        assert_eq!(
821            *analysis
822                .entries_by_level
823                .get(&LogLevel::Error)
824                .expect("operation should succeed"),
825            1
826        );
827        assert_eq!(
828            *analysis
829                .entries_by_module
830                .get("module1")
831                .expect("operation should succeed"),
832            2
833        );
834        assert_eq!(
835            *analysis
836                .entries_by_module
837                .get("module2")
838                .expect("operation should succeed"),
839            1
840        );
841    }
842
843    #[test]
844    fn test_distributed_logger() {
845        let config = LoggerConfig::default();
846        let local_logger = Arc::new(Logger::new(config));
847        let dist_logger = DistributedLogger::new(local_logger, "node1".to_string());
848
849        dist_logger.add_node("node2".to_string());
850        dist_logger.add_node("node3".to_string());
851
852        let nodes = dist_logger.get_cluster_nodes();
853        assert_eq!(nodes.len(), 2);
854        assert!(nodes.contains(&"node2".to_string()));
855        assert!(nodes.contains(&"node3".to_string()));
856    }
857}