Skip to main content

state_engine/common/
log_format.rs

1// LogFormat - ログメッセージの統一フォーマット
2//
3// declare-engine の LogMethodCall trait と同等の機能を提供
4
5use serde_json::Value;
6
7/// ログメッセージのフォーマットユーティリティ
8pub struct LogFormat;
9
10impl LogFormat {
11    /// メソッド呼び出しログを生成
12    ///
13    /// 例: `State::get('cache.user')`
14    ///
15    /// # Arguments
16    /// * `class` - クラス名
17    /// * `method` - メソッド名
18    /// * `args` - 引数のスライス
19    pub fn method_call(class: &str, method: &str, args: &[String]) -> String {
20        let args_str = args.join(", ");
21        format!("{}::{}({})", class, method, args_str)
22    }
23
24    /// エラーログを生成
25    ///
26    /// 例: `State::get: metadata not found`
27    ///
28    /// # Arguments
29    /// * `class` - クラス名
30    /// * `method` - メソッド名
31    /// * `message` - エラーメッセージ
32    pub fn error(class: &str, method: &str, message: &str) -> String {
33        format!("{}::{}: {}", class, method, message)
34    }
35
36    /// 引数を読みやすくフォーマット(declare-engine の formatArgs 相当)
37    ///
38    /// - 文字列: 50文字で省略
39    /// - 配列: 要素数を表示
40    /// - オブジェクト: フィールド数を表示
41    /// - null/bool/数値: そのまま
42    pub fn format_arg(value: &Value) -> String {
43        match value {
44            Value::String(s) if s.len() > 50 => {
45                format!("'{}'...", &s[..47])
46            }
47            Value::String(s) => {
48                format!("'{}'", s)
49            }
50            Value::Array(arr) => {
51                if arr.is_empty() {
52                    "[]".to_string()
53                } else {
54                    format!("[{} items]", arr.len())
55                }
56            }
57            Value::Object(obj) => {
58                if obj.is_empty() {
59                    "{}".to_string()
60                } else {
61                    format!("{{{} fields}}", obj.len())
62                }
63            }
64            Value::Null => "null".to_string(),
65            Value::Bool(b) => b.to_string(),
66            Value::Number(n) => n.to_string(),
67        }
68    }
69
70    /// 文字列引数をフォーマット
71    pub fn format_str_arg(s: &str) -> String {
72        if s.len() > 50 {
73            format!("'{}'...", &s[..47])
74        } else {
75            format!("'{}'", s)
76        }
77    }
78}
79
80/// ログマクロ: メソッド呼び出し
81///
82/// # Examples
83/// ```ignore
84/// use state_engine::log_method;
85///
86/// log_method!("State", "get", "cache.user");
87/// // Logs: State::get('cache.user')
88/// ```
89#[macro_export]
90macro_rules! log_method {
91    ($class:expr, $method:expr $(, $arg:expr)*) => {{
92        #[cfg(feature = "logging")]
93        {
94            let args: Vec<String> = vec![
95                $(
96                    $crate::common::log_format::LogFormat::format_str_arg($arg),
97                )*
98            ];
99            log::debug!("{}", $crate::common::log_format::LogFormat::method_call($class, $method, &args));
100        }
101    }};
102}
103
104/// ログマクロ: エラー
105///
106/// # Examples
107/// ```ignore
108/// use state_engine::log_err;
109///
110/// log_err!("State", "get", "metadata not found");
111/// // Logs: State::get: metadata not found
112/// ```
113#[macro_export]
114macro_rules! log_err {
115    ($class:expr, $method:expr, $msg:expr) => {{
116        #[cfg(feature = "logging")]
117        {
118            log::error!("{}", $crate::common::log_format::LogFormat::error($class, $method, $msg));
119        }
120    }};
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use serde_json::json;
127
128    #[test]
129    fn test_method_call() {
130        let result = LogFormat::method_call("State", "get", &["'cache.user'".to_string()]);
131        assert_eq!(result, "State::get('cache.user')");
132
133        let result = LogFormat::method_call("State", "get", &[
134            "'cache.user'".to_string(),
135            "null".to_string(),
136        ]);
137        assert_eq!(result, "State::get('cache.user', null)");
138    }
139
140    #[test]
141    fn test_error() {
142        let result = LogFormat::error("State", "get", "metadata not found");
143        assert_eq!(result, "State::get: metadata not found");
144    }
145
146    #[test]
147    fn test_format_arg_string() {
148        assert_eq!(LogFormat::format_arg(&json!("hello")), "'hello'");
149
150        let long_str = "a".repeat(60);
151        let result = LogFormat::format_arg(&json!(long_str));
152        assert!(result.starts_with("'aaa"));
153        assert!(result.ends_with("'..."));
154        assert_eq!(result.len(), 52); // ' + 47 chars + '...
155    }
156
157    #[test]
158    fn test_format_arg_array() {
159        assert_eq!(LogFormat::format_arg(&json!([])), "[]");
160        assert_eq!(LogFormat::format_arg(&json!([1, 2, 3])), "[3 items]");
161    }
162
163    #[test]
164    fn test_format_arg_object() {
165        assert_eq!(LogFormat::format_arg(&json!({})), "{}");
166        assert_eq!(LogFormat::format_arg(&json!({"a": 1, "b": 2})), "{2 fields}");
167    }
168
169    #[test]
170    fn test_format_arg_primitives() {
171        assert_eq!(LogFormat::format_arg(&json!(null)), "null");
172        assert_eq!(LogFormat::format_arg(&json!(true)), "true");
173        assert_eq!(LogFormat::format_arg(&json!(false)), "false");
174        assert_eq!(LogFormat::format_arg(&json!(42)), "42");
175        assert_eq!(LogFormat::format_arg(&json!(3.14)), "3.14");
176    }
177
178    #[test]
179    fn test_format_str_arg() {
180        assert_eq!(LogFormat::format_str_arg("hello"), "'hello'");
181
182        let long_str = "a".repeat(60);
183        let result = LogFormat::format_str_arg(&long_str);
184        assert!(result.starts_with("'aaa"));
185        assert!(result.ends_with("'..."));
186    }
187}