# [Legato] Log > Logging:https://docs.legato.io/latest/conceptsLogs.html > log(target tool):https://docs.legato.io/latest/toolsTarget_log.html > Logging API:https://docs.legato.io/latest/c_logging.html > Logging API Documentation:https://docs.legato.io/latest/le__log_8h.html > Qualcomm Logging API:https://docs.qualcomm.com/bundle/publicresource/topics/80-41102-2/page_c_logging.html#Documentationa00789 <!-- 預設上,會將 process 的 `stdout` 和 `stderr` 重新導向 `syslog`。其中 `stdout` 會被 log 到 `INFO` level,`stderr` 則會被 log 到 `ERR` level。 --> ## Log Level Legato 的 log 訊息分成以下六種 levels,越後面的代表越嚴重: 1. `DEBUG`:Handy for troubleshooting. 2. `INFORMATION`:Expected to happen; can be interesting even when not troubleshooting. 3. `WARNING`:Should not normally happen; may not have any real impact on system performance. 4. `ERROR`:Fault that may result in noticeable short-term system misbehaviour. Needs attention. 5. `CRITICAL`:Fault needs urgent attention. Will likely result in system failure. 6. `EMERGENCY`:Definite system failure. 預設上,會將 `INFORMATION` level(含)以上的 log 記錄下來。若想要紀錄 `DEBUG` level 的 log,或者只想要特定等級以上的 log,則需要用 `log` 工具做設定。 ## Log Formats Legato 的 log 大概都含有以下這些資訊: - timestamp (century, year, month, day, hours, minutes, seconds, milliseconds, microseconds) - log level (debug, info, warning, etc.) or trace keyword - process ID - component name - thread name - source code file name - function name - source code line number Log messages have the following format: ```log Jan 3 02:37:56 INFO | processName[pid]/componentName T=threadName | fileName.c funcName() lineNum | Message ``` ## 查看 syslog 在 target machine 可以用 `logread` 來查看 syslog。 ```bash # 查看最新的 100 個 syslog logread | tail -n 100 # 若需要即時監控 syslog,則可用 $ logread -f & # 或者只顯示有特定關鍵字的 syslog $ logread -f | grep <keyword> & ``` ## `log` Tool 在 target machine 可以用 `log` 這個工具來做與 syslog 有關的設定,像是指定要紀錄某個 level 以上的 syslog,或者是啟用指定 keyword 的 log trace。 以下是此工具的用法: ``` log help log list log level <FILTER_STR> [DESTINATION] log trace <KEYWORD_STR> [DESTINATION] log stoptrace <KEYWORD_STR> [DESTINATION] log forget <PROCESS_NAME> ``` ### `[DESTINATION]` - `[DESTINATION]` 可填可不填。 - 若有指定 `[DESTINATION]`,則其格式為: ``` process/componentName ``` - 其中的 `process` 可以是 process name 或 PID。 - 若 `process` 是 process name,則輸入的指令對相同 process name 的 process 都會生效;但若 `process` 是 PID,則指令只會對符合該 PID 的 process 生效。 - 若沒有指定 `[DESTINATION]`,則預設的 destination 會是 `*/*`,也就是會套用到全部的 process 和 component。 - 若 `process` 是 process name,那我們甚至可以在該 process 尚未被安裝時就先用 `log` 工具對該 process 做設定。待該 process 被安裝後,就會立刻套用我們預先指定的設定。 - 但若 `process` 是 PID 則無法這樣做。 ### `log help` - 查看 `log` 此工具的使用說明。 ### `log list` - 列出目前系統中 Legato App 的 processes 和 components 的 `log` 相關 configuration。 ### `log level <FILTER_STR> [DESTINATION]` - 設定 log filter level,也就是 log 嚴重程度大於等於所設定的 `<FILTER_STR>` 的 log 才會被記錄下來。 - `FILTER_STR` 有以下等級,編號越高代表越嚴重: 1. `DEBUG` 2. `INFO` 3. `WARNING` 4. `ERROR` 5. `CRITICAL` 6. `EMERGENCY` Example: ![image](https://hackmd.io/_uploads/S1-Tkq700.png) - 從上截圖可以看到,一開始先對 `logProc/logComp` 設定 level 為 `ERROR`,那有被紀錄下來的 log level 就是 `ERROR`、`CRITICAL`、和 `EMERGENCY`。 - 之後將 level 設定為 `DEBUB`,那全部 level 的 log 都會被記錄下來。 - 最後設定為 `CRITICAL`,則只有 `CRITICAL` 和 `EMERGENCY` level 的 log 訊息有被記錄下來。 ### `log trace <KEYWORD_STR> [DESTINATION]` - 啟用自己設定的 trace,只要是 `KEYWORD_STR` 的 trace 的訊息都會被紀錄在 syslog。 - 關於如何設定自定義的 trace 後面會再提到。 Example: 以下的例子,我的程式有自定義兩個 trace keyword,分別是 `MyTrace1` 和 `MyTrace2`。 ![Screenshot from 2024-09-27 10-14-18_compressed](https://hackmd.io/_uploads/rJG4H5mAC.png) - 從以上截圖可以看到,一開始都沒啟動這兩個 trace,所以印出來的訊息都沒有這兩者。 - 接下來,啟動 `MyTrace1`,重新啟動 `logApp` 後就會將 `MyTrace1` 的訊息紀錄到 syslog。 - 之後又啟用 `MyTrace2`,就可看到用 `MyTrace2` 的訊息有被紀錄到 syslog。 - 最後可看到,不論將 log level 設定為多少,只要有啟用指定的 trace,都會將其訊息紀錄到 syslog。 ### `log stoptrace <KEYWORD_STR> [DESTINATION]` - 中止指定 keyword 的 trace。 Example: ![Screenshot from 2024-09-27 10-24-37_compressed](https://hackmd.io/_uploads/Bytqv5XR0.png) - 此例子,先中止 `MyTrace1`,就可看到 `MyTrace1` 的訊息沒有被記錄下來。最後又中止 `MyTrace2`,所以 `MyTrace2` 的訊息也沒被記錄下來。 ### `log forget <PROCESS_NAME>` - 將指定的 process 的 log configuration 回歸到預設值,也就是 log level 為 `INFO`,且不啟用任何 trace。 Example: ![image](https://hackmd.io/_uploads/rJeWK57RR.png) ## App Crash Logs 若某個 process 有啟動此功能,則當該 process crash 了,會將 syslog 儲存下來。 (這裡的 crash 包含了使用 `LE_FATAL` 等會 kill calling process 的 API。) 若要啟動此功能,必須在 `.adef` 內的 `processes` section 設定以下 subsection: - `maxCoreDumpFileBytes`: - Specifies the maximum size (in bytes) of core dump files that can be generated by processes - Core dump file size is limited by `maxFileBytes` - If the core file is generated in the sandbox's temporary runtime file system, it'll also be limited by `maxFileSystemBytes` - `maxFileBytes`: - Specifies the maximum size processes can make files. The K suffix permits specifying in kilobytes (multiples of 1024 bytes) - Example: ```cpp executables: { logExec = ( logComp ) } processes: { run: { logProc = ( logExec ) } maxCoreDumpFileBytes: 100K maxFileBytes: 100K maxFileSystemBytes: 200K } ``` - https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_processMaxCoreDumpFileBytes 若 crash 發生,則會產生以下目錄,並將 syslog 存進去: ```bash /tmp/telaf_crash_logs ``` - 為了要省 RAM 的空間,只會儲存最近 4 次 crash 的 syslog。 下圖標起來的兩個檔案就是因為 `logApp` 發生 crash,而儲存的 syslog: ![image](https://hackmd.io/_uploads/BJ0TojfRR.png) :::info Legato 官網說是會放在 `/tmp/legato_logs/`,但是在 TelAF simulation 的話則是會放在 `/tmp/telaf_crash_logs` ::: :::danger 此外,Legato 官網說若 process 的 `faultAction` 是 `reboot` target,則 syslog 會被放到以下資料夾: ```bash /mnt/flash/legato_logs/ ``` 這裡的 `faultAction` 是指在 `.adef` 內的 `processes` section 的 `faultAction` subsection。 但 TelAF 的話則是會放在以下資料夾: ```bash /data/telaf_crash_logs/ ``` PS. 當我測試了 `faultAction: reboot`,我的 TelAF simulation docker container 就壞掉了,重新安裝也沒用 = _ ='' ,所以**強烈建議不要測試這件事 @ _ @**,我最後只能靠著 VirtualBox 的 snapshot 救回來。 ::: ## Logging APIs Legato 有一些跟 log 有關的 API 可以使用。 ### Basic Logging - `LE_DEBUG( printf-style arguments )` - `LE_INFO( printf-style arguments )` - `LE_WARN( printf-style arguments )` - `LE_ERROR( printf-style arguments )` - `LE_CRIT( printf-style arguments )` - `LE_EMERG( printf-style arguments )` Example ```cpp char *levels[] = { "DEBUG", "INFO", "WARN", "ERROR", "CRIT", "EMERG" }; LE_DEBUG("This log is at %s level", levels[0]); LE_INFO("This log is at %s level", levels[1]); LE_WARN("This log is at %s level", levels[2]); LE_ERROR("This log is at %s level", levels[3]); LE_CRIT("This log is at %s level", levels[4]); LE_EMERG("This log is at %s level", levels[5]); ``` Output ![image](https://hackmd.io/_uploads/S1_Op57R0.png) ### Conditional Logging 與前者相似,但第一個參數是 conditional expression,只有當 conditional expression 是 `true`(也就是非 0),才會產生 log 訊息。 - `LE_DEBUG_IF( conditional expression, printf-style arguments )` - `LE_INFO_IF( conditional expression, printf-style arguments )` - `LE_WARN_IF( conditional expression, printf-style arguments )` - `LE_ERROR_IF( conditional expression, printf-style arguments )` - `LE_CRIT_IF( conditional expression, printf-style arguments )` - `LE_EMERG_IF( conditional expression, printf-style arguments )` Example ```cpp int nums[] = { -10, 0, 10 }; for (int i = 0; i < 3; ++i) { LE_INFO_IF(nums[i], "testing LE_INFO_IF with the condition of %d", nums[i]); } bool booleans[] = { false, true }; for (int i = 0; i < 2; ++i) { LE_ERROR_IF(booleans[i], "testing LE_ERROR_IF with the codition of %s", ( booleans[i] == true ) ? "TRUE" : "FALSE"); } LE_EMERG_IF(nums[0] == 0, "testing LE_EMERG_IF with 'nums[0] == 0'"); LE_EMERG_IF(nums[1] == 0, "testing LE_EMERG_IF with 'nums[1] == 0'"); ``` Output ![image](https://hackmd.io/_uploads/H1R8xoQAA.png) ### Fatal Errors - `LE_FATAL( printf-style arguments )` ++Always KILLS the calling process++ after logging the message at EMERGENCY level (never returns). - `LE_FATAL_IF( conditional expression, printf-style arguments )` If the condition is **TRUE**, ++kills the calling process++ after logging the message at EMERGENCY level. - `LE_ASSERT( conditional expression )` If the condition is **TRUE**, ++does nothing++. If the condition is **FALSE**, logs the source code text of the condition at EMERGENCY level and ++kills the calling process++. - `LE_ASSERT_OK( conditional expression )` If the condition is `LE_OK`(也就是 0), does nothing. If the condition is anything else(非 0 值), logs the a message at EMERGENCY level, containing the source code text of the condition, indicating that it did not evaluate to `LE_OK`, and ++kills the calling process++. - `le_result_t` 的介紹:https://docs.legato.io/latest/le__basics_8h.html#a1cca095ed6ebab24b57a636382a6c86c #### Example - `LE_FATAL_IF` ```cpp int num = 100; int *numPtr = &num; LE_FATAL_IF(numPtr == NULL, "testing LE_FATAL_IF"); LE_INFO("numPtr is not NULL"); numPtr = NULL; LE_FATAL_IF(numPtr == NULL, "ERROR: numPtr == NULL"); ``` Output ![image](https://hackmd.io/_uploads/SyogEjQAA.png) #### Example - `LE_ASSERT` ```cpp int num = 100; int *numPtr = &num; LE_ASSERT(numPtr != NULL); LE_INFO("numPtr is not NULL"); numPtr = NULL; LE_ASSERT(numPtr != NULL); ``` Output ![image](https://hackmd.io/_uploads/SJdCNoQAC.png) #### Example - `LE_ASSERT_OK` ```cpp le_result_t res = LE_OK; LE_ASSERT_OK(res); res = LE_DEADLOCK; LE_ASSERT_OK(LE_DEADLOCK); ``` Output ![image](https://hackmd.io/_uploads/HykxLs7RR.png) ### Tracing 這就是前面 `log` 工具的 `log trace/stoptrace` 的部份,我們可以自定義某個 keyword 來 trace 訊息,這個 trace 訊息跟 log 的 level 無關,只要有啟用某個 trace,則該訊息一定會被紀錄到 syslog,不論當時設定的 log level 是多少。 使用方式: 1. 使用 [le_log_GetTraceRef( keyword )](https://docs.legato.io/latest/le__log_8h.html#ae22a34f206b8f6d8518f0d19de524f50) 來定義 trace keyword 並取得該 trace 的 reference,如: - `le_log_TraceRef_t myTraceRef = le_log_GetTraceRef("MyTrace");` 2. 在想要紀錄的地方用 [LE_TRACE( traceRef, string )](https://docs.legato.io/latest/le__log_8h.html#a331fb6c78ccddeafc455ad9c64e42008) 來指定用哪個 trace reference 以及訊息,如: - `LE_TRACE(myTraceRef, "Hello! This is MyTrace ^U^");` 3. 在程式碼中,可以用 [LE_IS_TRACE_ENABLED( traceRef )](https://docs.legato.io/latest/le__log_8h.html#a0746e2e37585f61ccdf7ce4976909936) 在程式執行時 runtime 查看指定的 trace reference 是否有被啟用,如: - `LE_INFO("is MyTrace enabled? %s", (LE_IS_TRACE_ENABLED(myTraceRef1) == true) ? "TRUE" : "FALSE");` 4. 在 target machine 使用 `log trace/stoptrace <keyword> [DESTINATION]` 來啟用和關閉某個 keyword 的 trace。 #### Example ```cpp le_log_TraceRef_t myTraceRef1 = le_log_GetTraceRef("MyTrace1"); le_log_TraceRef_t myTraceRef2 = le_log_GetTraceRef("MyTrace2"); LE_TRACE(myTraceRef1, "Hello! This is MyTrace1 ^U^"); LE_INFO("is MyTrace1 enabled? %s", (LE_IS_TRACE_ENABLED(myTraceRef1) == true) ? "TRUE" : "FALSE"); LE_INFO("is MyTrace2 enabled? %s", (LE_IS_TRACE_ENABLED(myTraceRef2) == true) ? "TRUE" : "FALSE"); LE_TRACE(myTraceRef2, "Hi! This is MyTrace2, and I'm enabled now ^_^"); ``` Output ![image](https://hackmd.io/_uploads/Hy7OhiXAC.png) ### Result Code Text [LE_RESULT_TXT( le_result_t res )](https://docs.legato.io/latest/le__log_8h.html#a99402d6a983f318e5b8bfcdf5dfe9024) 的參數是 `le_result_t` 這個 enum 的變數,它會將傳進去的變數是哪種 `le_result_t` 以 string 的形式印出來。 - 關於 `le_result_t` 的說明:https://docs.legato.io/latest/le__basics_8h.html#a1cca095ed6ebab24b57a636382a6c86c #### Example ```cpp le_result_t res = 0; for (int i = 0; i > -25; --i) { LE_INFO("le_result_t with enum value %d: %s", i, LE_RESULT_TXT(res--)); } ``` Output ![Screenshot from 2024-09-27 13-38-51_compressed](https://hackmd.io/_uploads/HyMXrTmAR.png) ## Environment Variables https://docs.legato.io/latest/c_logging.html#c_log_control_env_trace https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_processEnvVars 我們可以設定 `LE_LOG_LEVEL` 和 `LE_LOG_TRACE` 這兩個環境變數,如果是在 `.adef` 的 `processes` section 內設定環境變數,那在 process start-up 階段就會生效。 或者直接在 target machine 輸入 `export LE_LOG_LEVEL=ERROR` 或 `export LE_LOG_TRACE=compName/keyword`。 :::danger 不知道為什麼,我在 TelAF simulation 內用 `export` 的方式都沒有效果 @@ ::: 若有另外用 `log` tool 來做設定,則會覆蓋掉環境變數的設定。 ### `LE_LOG_LEVEL` 此環境變數的值可以是: - `EMERGENCY` - `CRITICAL` - `ERROR` - `WARNING` - `INFO` - `DEBUG` ### `LE_LOG_TRACE` 根據官方的說法( https://docs.legato.io/latest/c_logging.html#c_log_control_env_trace )這個的用法是: ``` LE_LOG_TRACE=compName1/keyword1:compName1/keyword2:compName2/keyword3 ``` - 每個 trace keyword 前面要有 component name,且要用 `/` 分隔 component name 和 keyword。 - keyword 跟 keyword 之間用 `:` 分隔。 :::danger 不知道為什麼,`LE_LOG_TRACE` 我不論是在 `.adef` 內設定,或者是在 target machine 用 `export` 設定,都沒有效果 @@ ::: ### Example `logApp.adef` ```adef executables: { logExec = ( logComp ) } processes: { run: { logProc = ( logExec ) } maxCoreDumpFileBytes: 100K maxFileBytes: 100K envVars: { LE_LOG_TRACE = logComp/MyTrace1:logComp/MyTrace2 LE_LOG_LEVEL = ERROR } } ``` Output ![Screenshot from 2024-09-27 14-41-58_compressed](https://hackmd.io/_uploads/Hk9JVCQR0.png) - 雖然 `.adef` 內有設定 `LE_LOG_TRACE` 來啟用 `MyTrace1` 和 `MyTrace2`,但從截圖可以看到,不知道為什麼這個設定都沒生效,只有 `LE_LOG_LEVEL` 的設定有生效。 - 後續也可看到,若有用 `log` 這個工具來做設定,則會覆蓋掉環境變數的設定。 ## Sample Code Project Structure ``` ~/logApp$ tree . ├── logApp.adef └── logComp ├── Component.cdef └── log.c ``` `logApp.adef` ```adef executables: { logExec = ( logComp ) } processes: { run: { logProc = ( logExec ) } maxCoreDumpFileBytes: 100K maxFileBytes: 100K envVars: { LE_LOG_TRACE = logComp/MyTrace1:logComp/MyTrace2 LE_LOG_LEVEL = ERROR } } ``` `logComp/Component.cdef` ```cdef sources: { log.c } ``` `logComp/log.c` ```cpp #include "legato.h" #include "interfaces.h" COMPONENT_INIT { /* Basic Logging */ char *levels[] = { "DEBUG", "INFO", "WARN", "ERROR", "CRIT", "EMERG" }; LE_DEBUG("This log is at %s level", levels[0]); LE_INFO("This log is at %s level", levels[1]); LE_WARN("This log is at %s level", levels[2]); LE_ERROR("This log is at %s level", levels[3]); LE_CRIT("This log is at %s level", levels[4]); LE_EMERG("This log is at %s level", levels[5]); /* Conditional Logging * if the condition is TRUE, which means the value of the condition is NOT zero, the macro will generates the log output */ int nums[] = { -10, 0, 10 }; for (int i = 0; i < 3; ++i) { LE_INFO_IF(nums[i], "testing LE_INFO_IF with the condition of %d", nums[i]); } bool booleans[] = { false, true }; for (int i = 0; i < 2; ++i) { LE_ERROR_IF(booleans[i], "testing LE_ERROR_IF with the codition of %s", ( booleans[i] == true ) ? "TRUE" : "FALSE"); } LE_EMERG_IF(nums[0] == 0, "testing LE_EMERG_IF with 'nums[0] == 0'"); // LE_EMERG_IF(nums[1] == 0, "testing LE_EMERG_IF with 'nums[1] == 0'"); /* Fatal Errors */ // LE_FATAL("Using LE_FATAL"); // Always kills the calling process after logging the message at EMERGENCY level (never returns) int num = 100; int *numPtr = &num; LE_FATAL_IF(numPtr == NULL, "testing LE_FATAL_IF"); LE_INFO("numPtr is not NULL"); // numPtr = NULL; // LE_FATAL_IF(numPtr == NULL, "ERROR: numPtr == NULL"); // If the condition is true, kills the calling process after logging the message at EMERGENCY level LE_ASSERT(numPtr != NULL); LE_INFO("numPtr is not NULL"); // numPtr = NULL; // LE_ASSERT(numPtr != NULL); // If the condition is true, does nothing. // If the condition is false, logs the source code text of the condition at EMERGENCY level and kills the calling process le_result_t res = LE_OK; LE_ASSERT_OK(res); // res = LE_DEADLOCK; // LE_ASSERT_OK(LE_DEADLOCK); // If the condition is LE_OK, which is the value of zero, does nothing. // If the condition is anything else, logs the a message at EMERGENCY level, containing the source code text of the condition, indicating that it did not evaluate to LE_OK, and kills the calling process. /* Tracing */ le_log_TraceRef_t myTraceRef1 = le_log_GetTraceRef("MyTrace1"); le_log_TraceRef_t myTraceRef2 = le_log_GetTraceRef("MyTrace2"); LE_TRACE(myTraceRef1, "Hello! This is MyTrace1 ^U^"); LE_INFO("is MyTrace1 enabled? %s", (LE_IS_TRACE_ENABLED(myTraceRef1) == true) ? "TRUE" : "FALSE"); LE_INFO("is MyTrace2 enabled? %s", (LE_IS_TRACE_ENABLED(myTraceRef2) == true) ? "TRUE" : "FALSE"); LE_TRACE(myTraceRef2, "Hi! This is MyTrace2, and I'm enabled now ^_^"); /* le_result_t */ res = 0; for (int i = 0; i > -25; --i) { LE_TRACE( (((i & 1) == 0) ? myTraceRef1 : myTraceRef2), "le_result_t with enum value %d: %s", i, LE_RESULT_TXT(res--)); } } ``` Output ![Screenshot from 2024-09-27 14-59-34_compressed](https://hackmd.io/_uploads/rkk7OAQCA.png) ## `do { ... } while (0)` Macro 在 Legato Log API 的官方網頁( https://docs.legato.io/latest/le__log_8h.html ),可以看到許多 macro 都有用到 `do { ... } while (0)` 這個用法。這麼做是為了要避免 dangling `else`,見下說明: 假設有以下 macro: ```cpp #define CALL_FUNCS \ if (a) printf(....) ``` 假如我們這樣使用它: ```cpp if (condition) CALL_FUNCS else bar(); ``` 實際展開會變這樣: ```cpp if (condition) if (a) printf(....) else bar(); ``` 這個時候,`else bar()` 本來是屬於第一層 `if` 的,但 C 語言的 `else` 是會找最近的 `else`,所以若 `if (a)` 是 false,那就會跳到 `else bar()`,也就是程式碼實際的縮排變成以下: ```cpp if (condition) if (a) printf(....) else bar(); ``` 此時,若使用 `do { ... } while (0)` macro,則可將 `if (a)` 被包裝在 `do { ... }` 裡面,如下: ```cpp if (condition) do { if (a) printf(....) } while (0) else bar(); ``` 另外,也可以改成以下程式碼: ```cpp if (condition) do { if (a) break; if (b) break; if (c) break; } while (0) // 用 break 就可以跳出 while loop,這樣就可避免使用 goto else bar(); ``` Reference - 成大黃敬群老師(jserv),你所不知道的 C 語言:前置處理器應用篇 https://hackmd.io/@sysprog/c-preprocessor#do----while0-%E5%B7%A8%E9%9B%86