# [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:

- 從上截圖可以看到,一開始先對 `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`。

- 從以上截圖可以看到,一開始都沒啟動這兩個 trace,所以印出來的訊息都沒有這兩者。
- 接下來,啟動 `MyTrace1`,重新啟動 `logApp` 後就會將 `MyTrace1` 的訊息紀錄到 syslog。
- 之後又啟用 `MyTrace2`,就可看到用 `MyTrace2` 的訊息有被紀錄到 syslog。
- 最後可看到,不論將 log level 設定為多少,只要有啟用指定的 trace,都會將其訊息紀錄到 syslog。
### `log stoptrace <KEYWORD_STR> [DESTINATION]`
- 中止指定 keyword 的 trace。
Example:

- 此例子,先中止 `MyTrace1`,就可看到 `MyTrace1` 的訊息沒有被記錄下來。最後又中止 `MyTrace2`,所以 `MyTrace2` 的訊息也沒被記錄下來。
### `log forget <PROCESS_NAME>`
- 將指定的 process 的 log configuration 回歸到預設值,也就是 log level 為 `INFO`,且不啟用任何 trace。
Example:

## 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:

:::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

### 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

### 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 = #
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

#### Example - `LE_ASSERT`
```cpp
int num = 100;
int *numPtr = #
LE_ASSERT(numPtr != NULL);
LE_INFO("numPtr is not NULL");
numPtr = NULL;
LE_ASSERT(numPtr != NULL);
```
Output

#### Example - `LE_ASSERT_OK`
```cpp
le_result_t res = LE_OK;
LE_ASSERT_OK(res);
res = LE_DEADLOCK;
LE_ASSERT_OK(LE_DEADLOCK);
```
Output

### 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

### 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

## 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

- 雖然 `.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 = #
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

## `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