logz 0.1.2

Zephyr RTOS logging implementation
Documentation
# LOGZ

This repo provides a crate with a Rust Logger implementation for use on an embedded device with Zephyr RTOS. `bindgen` and `cbindgen` are used to bind Rust calls to Zephyr RTOS's log2 implementation. Since this crate is targetting NRF52 and NRF53 deployments, it is `no_std` and does not allocate.

## Build

Include this crate in another crate that needs a logger implementation and make the usual log macro calls like `log::trace!("2.0 * 2.0 = {}", 2. * 2.);`. With a high initialization priority, the global logger is initialized to call the matching LOG_DBG, LOG_INF, LOG_WRN, or LOG_ERR macro in the bridging C.

### Build Tools

In order to build there are several tools to install

* NRF Connect SDK
  * v1.9.1
* Rustup and Cargo
  * stable
  * v1.24.3 and v1.61.0
* Cross compiling targets
  * M4(F): `thumbv7em-none-eabihf`
  * M33(F): `thumbv8m.main-none-eabihf`
* Tools and Subcommands
  * `cargo install cargo-make,cbindgen`
  * `brew install llvm`
    * `llvm-config` needs to be in your `PATH`
    * `echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc`
    * `sudo xcode-select --install`
  
If you have all of that, then `cargo make test` should work.

## Example

In the example, we have a `blinky` application compiled for NRF52 or NRF53. The CMakeLists.txt of the sample project adds library that needs a logger, but is built as a static library for the main.c of Zephyr RTOS. The example project includes logz as a crate dependency and adds a lib/logz submodule. The submodule holds the C headers and src file to include in the example CMakeLists.txt.

The full example is here: https://github.com/trueb2/logz-example-rs


In the example CMakeLists.txt, we build the project with a static library and the bridge.c of logz
```cmake
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky)

### EXAMPLE STATIC LIBRARY :: BEGIN ###

# Allow app to include headers from the library including normal or bindgen headers
set(LOGZ_EXAMPLE_LIB_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/logz-example-rs)
set(LOGZ_LIB_SRC_DIR ${LOGZ_EXAMPLE_LIB_SRC_DIR}/lib/logz)
# Cortex-M4F (NRF52)
# set(LOGZ_EXAMPLE_LIB_DIR ${LOGZ_EXAMPLE_LIB_SRC_DIR}/target/thumbv7em-none-eabihf/release)
# Cortex-M33F (NRF53)
set(LOGZ_EXAMPLE_LIB_DIR ${LOGZ_EXAMPLE_LIB_SRC_DIR}/target/thumbv8m.main-none-eabihf/release)
include_directories(AFTER ${LOGZ_EXAMPLE_LIB_SRC_DIR}/include ${LOGZ_EXAMPLE_LIB_SRC_DIR}/include/generated ${LOGZ_LIB_SRC_DIR}/include/generated)
add_library(liblogz_example_rs STATIC IMPORTED GLOBAL)
set_target_properties(liblogz_example_rs PROPERTIES IMPORTED_LOCATION ${LOGZ_EXAMPLE_LIB_DIR}/liblogz_example_rs.a)
# Always let cargo decide if the library should be rebuilt
add_custom_target(
  liblogz_example_rs_target
  ALL
  BYPRODUCTS ${LOGZ_EXAMPLE_LIB_DIR}/liblogz_example_rs.a
  WORKING_DIRECTORY ${LOGZ_EXAMPLE_LIB_SRC_DIR}
  COMMAND cargo make build
  )
add_dependencies(app liblogz_example_rs_target)
# A couple Cortex M math functions get multiple definitions
target_link_libraries(app PRIVATE ${LOGZ_EXAMPLE_LIB_DIR}/liblogz_example_rs.a -Wl,--allow-multiple-definition)

### EXAMPLE STATIC LIBRARY :: END ###


### The logger bindings still need to be compiled for zephyr, which is why we still have to build the static lib and bridge
target_sources(app PRIVATE src/main.c ${LOGZ_LIB_SRC_DIR}/src/bridge.c)
```

This makes sure that the log macro calls will be piped to an initialized Logger instance that writes its logs to the Zephyr LOG2 implementation as a string formatted in Rust

In Zephyr, we call a Rust library function, `example_foo()`
```C
void flip_timer(struct k_timer* timer) {
	LOG_INF("LED: %d", (int) on);
	gpio_pin_set(led_dev, PIN, (int)on);
	on = !on;
	example_foo();
}
```

In Rust, we format the logs and invoke the LOG2 macros
```rust
#[no_mangle]
pub extern "C" fn example_foo() {
    log::trace!("Foo");
    log::debug!("Bar");
    log::info!("Fizz");
    log::warn!("Buzz");
    log::error!("Fizzle");
}
```
```C
void log_inf(const char *restrict msg)
{
    LOG_INF("%s", msg);
}
```

In the UART, RTT, or whatever you have configured in your Kconfig, we get normal logs.
```
[00:04:32.166,320] <inf> rs: logz_example_rs: Fizz
[00:04:32.166,351] <wrn> rs: logz_example_rs: Buzz
[00:04:32.166,381] <err> rs: logz_example_rs: Fizzle
[00:04:32.416,198] <inf> blinky: LED: 0
[00:04:32.416,259] <inf> rs: logz_example_rs: Foo
[00:04:32.416,290] <inf> rs: logz_example_rs: Bar
[00:04:32.416,320] <inf> rs: logz_example_rs: Fizz
[00:04:32.416,351] <wrn> rs: logz_example_rs: Buzz
[00:04:32.416,412] <err> rs: logz_example_rs: Fizzle
[00:04:32.666,198] <inf> blinky: LED: 1
[00:04:32.666,259] <inf> rs: logz_example_rs: Foo
[00:04:32.666,290] <inf> rs: logz_example_rs: Bar
[00:04:32.666,351] <inf> rs: logz_example_rs: Fizz
[00:04:32.666,381] <wrn> rs: logz_example_rs: Buzz
[00:04:32.666,412] <err> rs: logz_example_rs: Fizzle
[00:04:32.916,229] <inf> blinky: LED: 0
[00:04:32.916,290] <inf> rs: logz_example_rs: Foo
[00:04:32.916,320] <inf> rs: logz_example_rs: Bar
[00:04:32.916,351] <inf> rs: logz_example_rs: Fizz
[00:04:32.916,381] <wrn> rs: logz_example_rs: Buzz
[00:04:32.916,442] <err> rs: logz_example_rs: Fizzle
```