├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── ci ├── install.sh └── script.sh ├── examples ├── global.rs └── local.rs ├── macros ├── Cargo.toml └── src │ ├── lib.rs │ └── spanned.rs ├── src ├── lib.rs └── spanned.rs └── stlog.x /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | matrix: 4 | include: 5 | # MSRV 6 | - env: T=x86_64-unknown-linux-gnu 7 | rust: 1.31.0 8 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 9 | 10 | - env: T=thumbv7m-none-eabi 11 | rust: 1.31.0 12 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 13 | 14 | - env: T=x86_64-unknown-linux-gnu 15 | rust: nightly 16 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 17 | 18 | - env: T=thumbv7m-none-eabi 19 | rust: nightly 20 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 21 | 22 | before_install: set -e 23 | 24 | install: 25 | - bash ci/install.sh 26 | 27 | script: 28 | - bash ci/script.sh 29 | 30 | after_script: set +e 31 | 32 | cache: cargo 33 | 34 | before_cache: 35 | - chmod -R a+r $HOME/.cargo; 36 | 37 | branches: 38 | only: 39 | - master 40 | - staging 41 | - trying 42 | 43 | notifications: 44 | email: 45 | on_success: never 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [Unreleased] 7 | 8 | ## [v0.3.3] - 2019-11-27 9 | 10 | ### Added 11 | 12 | - A `NullLogger` that implements both `Log` and `GlobalLog` but does nothing. 13 | 14 | - A `spanned` Cargo feature that adds variants of `error!`, etc. that include 15 | span information. See crate level documentation for more details. 16 | 17 | - Documented that the MSRV of this crate is 1.31 18 | 19 | ## [v0.3.2] - 2018-09-23 20 | 21 | ### Changed 22 | 23 | - Reduced the number of symbols required to keep track of the log levels. This 24 | is technically a breaking change but versions v0.3.0 and v0.3.1 of stlog and 25 | versions v0.2.0 and v0.2.1 have been yanked. 26 | 27 | ### Fixed 28 | 29 | - Fixed the expansion of the `warn!` macro when used with the global logger. 30 | 31 | ## [v0.3.1] - 2018-09-23 - YANKED 32 | 33 | ### Added 34 | 35 | - Add documentation to the `stlog-macros` crate. 36 | 37 | ### Changed 38 | 39 | - The documentation link in the README and in the crate metadata 40 | 41 | - Render README on crates.io 42 | 43 | ## [v0.3.0] - 2018-09-23 - YANKED 44 | 45 | ### Added 46 | 47 | - A `GlobalLog` trait has been added. It's the interface of a global logger. 48 | 49 | - A `global_logger` attribute has been added. It's used to declare the global 50 | logger. 51 | 52 | - Several Cargo features have been added. These can be used to select a 53 | maximum allowed log level. 54 | 55 | ### Changed 56 | 57 | - [breaking-change] The `Logger` trait has been renamed to `Log`. 58 | 59 | - [breaking-change] The signature of the `Log::log` method has changed; it now 60 | takes `&mut self`. 61 | 62 | - This crate now compiles on 1.30-beta. 63 | 64 | ### Removed 65 | 66 | - [breaking-change] The `set_global_logger!` macro has been removed in favor of 67 | the `global_logger` attribute. 68 | 69 | - [breaking-change] The static methods of the `Log` trait that were used to 70 | individually enable / disable log levels has been removed in favor of Cargo 71 | features. 72 | 73 | ## [v0.2.0] - 2017-07-06 74 | 75 | ### Added 76 | 77 | - Support for global logging: a `set_global_logger!` macro was added, and the 78 | `$logger` argument is now optional in all the logging macros. 79 | 80 | - Support for individually disabling log levels: the `Logger` trait gained 81 | several `*_enabled` methods. 82 | 83 | ### Changed 84 | 85 | - [breaking-change] The `Logger::log` method now takes `self` by reference. 86 | 87 | ## v0.1.0 - 2017-06-03 88 | 89 | - Initial release 90 | 91 | [Unreleased]: https://github.com/japaric/stlog/compare/v0.3.3...HEAD 92 | [v0.3.3]: https://github.com/japaric/stlog/compare/v0.3.2...v0.3.3 93 | [v0.3.2]: https://github.com/japaric/stlog/compare/v0.3.1...v0.3.2 94 | [v0.3.1]: https://github.com/japaric/stlog/compare/v0.3.0...v0.3.1 95 | [v0.3.0]: https://github.com/japaric/stlog/compare/v0.2.0...v0.3.0 96 | [v0.2.0]: https://github.com/japaric/stlog/compare/v0.1.0...v0.2.0 97 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["development-tools::debugging", "embedded", "no-std"] 4 | description = "Ultra lightweight logging framework for resource constrained devices" 5 | documentation = "https://japaric.github.io/stlog/stlog/" 6 | edition = "2018" 7 | keywords = ["ELF", "symtab"] 8 | license = "MIT OR Apache-2.0" 9 | name = "stlog" 10 | readme = "README.md" 11 | repository = "https://github.com/japaric/stlog" 12 | version = "0.3.3" 13 | 14 | [features] 15 | max-level-off = [] 16 | max-level-error = [] 17 | max-level-warning = [] 18 | max-level-info = [] 19 | max-level-debug = [] 20 | max-level-trace = [] 21 | spanned = ["stlog-macros/spanned"] 22 | 23 | release-max-level-off = [] 24 | release-max-level-error = [] 25 | release-max-level-warning = [] 26 | release-max-level-info = [] 27 | release-max-level-debug = [] 28 | release-max-level-trace = [] 29 | 30 | [dependencies] 31 | stlog-macros = { path = "macros", version = "0.1.2" } 32 | 33 | [dependencies.void] 34 | default-features = false 35 | version = "1.0.2" 36 | 37 | [workspace] 38 | members = ["macros"] 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2018 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/stlog.svg)](https://crates.io/crates/stlog) 2 | [![crates.io](https://img.shields.io/crates/d/stlog.svg)](https://crates.io/crates/stlog) 3 | 4 | # `stlog` 5 | 6 | > Ultra lightweight logging framework for resource constrained devices 7 | 8 | ## [Documentation](https://docs.rs/stlog/) 9 | 10 | ## License 11 | 12 | Licensed under either of 13 | 14 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 15 | http://www.apache.org/licenses/LICENSE-2.0) 16 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 17 | 18 | at your option. 19 | 20 | ### Contribution 21 | 22 | Unless you explicitly state otherwise, any contribution intentionally submitted 23 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 24 | dual licensed as above, without any additional terms or conditions. 25 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs::File, io::Write, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | // Put the linker script somewhere the linker can find it 5 | let out = PathBuf::from(env::var("OUT_DIR")?); 6 | 7 | File::create(out.join("stlog.x"))?.write_all(include_bytes!("stlog.x"))?; 8 | 9 | println!("cargo:rustc-link-search={}", out.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | rustup target add $T 5 | } 6 | 7 | main 8 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | cargo check --target $T 5 | 6 | if [ $TRAVIS_RUST_VERSION = nightly ]; then 7 | cargo check --features spanned --target $T 8 | fi 9 | 10 | if [ $T = x86_64-unknown-linux-gnu ]; then 11 | cargo build --examples --target $T 12 | 13 | if [ $TRAVIS_RUST_VERSION = nightly ]; then 14 | cargo build --examples --target $T --features spanned 15 | fi 16 | fi 17 | } 18 | 19 | # fake Travis variables to be able to run this on a local machine 20 | if [ -z ${TRAVIS_RUST_VERSION-} ]; then 21 | case $(rustc -V) in 22 | *nightly*) 23 | TRAVIS_RUST_VERSION=nightly 24 | ;; 25 | *beta*) 26 | TRAVIS_RUST_VERSION=beta 27 | ;; 28 | *) 29 | TRAVIS_RUST_VERSION=stable 30 | ;; 31 | esac 32 | fi 33 | 34 | if [ -z ${T-} ]; then 35 | T=$(rustc -Vv | grep host | cut -d ' ' -f2) 36 | fi 37 | 38 | main 39 | -------------------------------------------------------------------------------- /examples/global.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "spanned", feature(proc_macro_hygiene))] 2 | 3 | #[cfg(feature = "spanned")] 4 | use stlog::spanned::{error, info, trace}; 5 | #[cfg(not(feature = "spanned"))] 6 | use stlog::{error, info}; 7 | use stlog::{global_logger, GlobalLog}; 8 | 9 | struct Logger; 10 | 11 | impl GlobalLog for Logger { 12 | fn log(&self, _: u8) {} 13 | } 14 | 15 | #[global_logger] 16 | static LOGGER: Logger = Logger; 17 | 18 | fn main() { 19 | info!("Hello!"); 20 | #[cfg(feature = "spanned")] 21 | trace!("Hello!"); 22 | error!("Bye!"); 23 | } 24 | -------------------------------------------------------------------------------- /examples/local.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "spanned", feature(proc_macro_hygiene))] 2 | 3 | #[cfg(feature = "spanned")] 4 | use stlog::spanned::{error, info, trace}; 5 | use stlog::Log; 6 | #[cfg(not(feature = "spanned"))] 7 | use stlog::{error, info}; 8 | 9 | struct Logger; 10 | 11 | impl Log for Logger { 12 | type Error = (); 13 | 14 | fn log(&mut self, byte: u8) -> Result<(), ()> { 15 | println!("{}", byte); 16 | Ok(()) 17 | } 18 | } 19 | 20 | fn main() { 21 | let mut logger = Logger; 22 | 23 | info!(logger, "Hello!").unwrap(); 24 | #[cfg(feature = "spanned")] 25 | trace!(logger, "Hello!").unwrap(); 26 | error!(logger, "Bye!").unwrap(); 27 | } 28 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | categories = ["development-tools::debugging", "embedded", "no-std"] 4 | description = "Macros part of the stlog logging framework" 5 | documentation = "https://japaric.github.io/stlog/stlog_macros/" 6 | edition = "2018" 7 | keywords = ["log", "symtab", "ELF"] 8 | license = "MIT OR Apache-2.0" 9 | name = "stlog-macros" 10 | repository = "https://github.com/japaric/stlog" 11 | version = "0.1.2" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | quote = "1" 18 | 19 | [dependencies.syn] 20 | features = ["full"] 21 | version = "1" 22 | 23 | [features] 24 | spanned = [] -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Macros part of the stlog logging framework 2 | 3 | #![cfg_attr(feature = "spanned", feature(proc_macro_span))] 4 | #![deny(warnings)] 5 | 6 | extern crate proc_macro; 7 | 8 | use proc_macro::{Span, TokenStream}; 9 | use quote::quote; 10 | use syn::{parse_macro_input, spanned::Spanned, Error, ItemStatic}; 11 | 12 | #[cfg(feature = "spanned")] 13 | mod spanned; 14 | 15 | /// An attribute to declare a global logger 16 | /// 17 | /// This attribute can only be applied to `static` variables that implement the 18 | /// [`GlobalLog`](../stlog/trait.GlobalLog.html) trait. 19 | #[proc_macro_attribute] 20 | pub fn global_logger(args: TokenStream, input: TokenStream) -> TokenStream { 21 | let var = parse_macro_input!(input as ItemStatic); 22 | 23 | if !args.is_empty() { 24 | return Error::new( 25 | Span::call_site().into(), 26 | "`global_logger` attribute takes no arguments", 27 | ) 28 | .to_compile_error() 29 | .into(); 30 | } 31 | 32 | if var.mutability.is_some() { 33 | return Error::new( 34 | var.span(), 35 | "`#[global_logger]` can't be used on `static mut` variables", 36 | ) 37 | .to_compile_error() 38 | .into(); 39 | } 40 | 41 | let attrs = var.attrs; 42 | let vis = var.vis; 43 | let ident = var.ident; 44 | let ty = var.ty; 45 | let expr = var.expr; 46 | 47 | quote!( 48 | #(#attrs)* 49 | #vis static #ident: #ty = { 50 | #[export_name = "stlog::GLOBAL_LOGGER"] 51 | static GLOBAL_LOGGER: &stlog::GlobalLog = &#ident; 52 | 53 | #expr 54 | }; 55 | ) 56 | .into() 57 | } 58 | 59 | #[cfg(feature = "spanned")] 60 | #[proc_macro] 61 | pub fn error(input: TokenStream) -> TokenStream { 62 | spanned::common(input, "error") 63 | } 64 | 65 | #[cfg(feature = "spanned")] 66 | #[proc_macro] 67 | pub fn warning(input: TokenStream) -> TokenStream { 68 | spanned::common(input, "warn") 69 | } 70 | 71 | #[cfg(feature = "spanned")] 72 | #[proc_macro] 73 | pub fn info(input: TokenStream) -> TokenStream { 74 | spanned::common(input, "info") 75 | } 76 | 77 | #[cfg(feature = "spanned")] 78 | #[proc_macro] 79 | pub fn debug(input: TokenStream) -> TokenStream { 80 | spanned::common(input, "debug") 81 | } 82 | 83 | #[cfg(feature = "spanned")] 84 | #[proc_macro] 85 | pub fn trace(input: TokenStream) -> TokenStream { 86 | spanned::common(input, "trace") 87 | } 88 | -------------------------------------------------------------------------------- /macros/src/spanned.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::{Span, TokenStream}; 2 | 3 | use quote::quote; 4 | use syn::{ 5 | parse::{self, Parse, ParseStream}, 6 | parse_macro_input, 7 | spanned::Spanned, 8 | Error, Expr, Lit, Token, 9 | }; 10 | 11 | fn add_span(mut ls: String) -> String { 12 | let span = Span::call_site(); 13 | 14 | let file = span.source_file().path(); 15 | let lc = span.start(); 16 | 17 | ls.push_str(&format!(", loc: {}:{}", file.display(), lc.line)); 18 | 19 | ls 20 | } 21 | 22 | struct Input { 23 | first: Expr, 24 | second: Option<(Token![,], Expr)>, 25 | } 26 | 27 | impl Parse for Input { 28 | fn parse(input: ParseStream) -> parse::Result { 29 | let first = input.parse()?; 30 | 31 | let lookahead = input.lookahead1(); 32 | Ok(if lookahead.peek(Token![,]) { 33 | let comma = input.parse()?; 34 | let expr = input.parse()?; 35 | 36 | Input { 37 | first, 38 | second: Some((comma, expr)), 39 | } 40 | } else { 41 | Input { 42 | first, 43 | second: None, 44 | } 45 | }) 46 | } 47 | } 48 | 49 | fn into_lit_str(e: Expr) -> Result { 50 | match e { 51 | Expr::Lit(e) => match e.lit { 52 | Lit::Str(ls) => Ok(ls.value()), 53 | l => Err(Error::new(l.span(), "expected a string literal")), 54 | }, 55 | e => Err(Error::new(e.span(), "expected a string literal")), 56 | } 57 | } 58 | 59 | pub fn common(input: TokenStream, level: &str) -> TokenStream { 60 | let input = parse_macro_input!(input as Input); 61 | 62 | let (logger, message) = if let Some((_, e)) = input.second { 63 | (Some(input.first), e) 64 | } else { 65 | (None, input.first) 66 | }; 67 | 68 | let symbol = match into_lit_str(message) { 69 | Ok(s) => add_span(s), 70 | Err(e) => return e.to_compile_error().into(), 71 | }; 72 | 73 | let section = format!(".stlog.{}", level); 74 | if let Some(logger) = logger { 75 | quote!(unsafe { 76 | #[export_name = #symbol] 77 | #[link_section = #section] 78 | static SYMBOL: u8 = 0; 79 | 80 | stlog::Log::log(&mut #logger, &SYMBOL as *const u8 as usize as u8) 81 | }) 82 | .into() 83 | } else { 84 | quote!(unsafe { 85 | extern "Rust" { 86 | #[link_name = "stlog::GLOBAL_LOGGER"] 87 | static LOGGER: &'static stlog::GlobalLog; 88 | } 89 | 90 | #[export_name = #symbol] 91 | #[link_section = #section] 92 | static SYMBOL: u8 = 0; 93 | 94 | stlog::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 95 | }) 96 | .into() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Ultra lightweight logging framework for resource constrained devices 2 | //! 3 | //! ![stlog running on a Cortex-M microcontroller](https://i.imgur.com/rPxmAlZ.jpg) 4 | //! 5 | //! **[See stlog in action!](https://streamable.com/nmlx7)** 6 | //! 7 | //! # Features 8 | //! 9 | //! - `O(1)` execution time. Logging a message of arbitrary size is done in a constant number of 10 | //! instructions. 11 | //! 12 | //! - `O(0)` memory usage. The messages are NOT stored in the target device memory (`.rodata`). 13 | //! 14 | //! - Supports different logging levels: error, warning, info, debug and trace, in decreasing level 15 | //! of severity. By default, the `dev` profile logs debug, and more severe, messages and the 16 | //! `release` profile logs info, and more severe, messages, but this can changed using the Cargo 17 | //! features of this crate. 18 | //! 19 | //! - Provides a global logging mode 20 | //! 21 | //! # Non-features 22 | //! 23 | //! - `printf` style or any other kind of formatting 24 | //! 25 | //! # MSRV 26 | //! 27 | //! This crate is guaranteed to compile on stable Rust 1.31 and up. It might compile on older 28 | //! versions but that may change in any new patch release. 29 | //! 30 | //! # Known limitations 31 | //! 32 | //! - The current implementation only supports 256 different log strings. This restriction may be 33 | //! lifted in the future. 34 | //! 35 | //! - The string should not contain the character `@`. Any text that follows this character will be 36 | //! discarded. 37 | //! 38 | //! - The exact same string can't be used in two or more macro invocations. Enabling the `spanned` 39 | //! Cargo feature removes this limitation. 40 | //! 41 | //! ``` ignore 42 | //! use stlog::{error, info}; 43 | //! 44 | //! fn foo() { 45 | //! info!("Hello!"); 46 | //! } 47 | //! 48 | //! fn good() { 49 | //! foo(); 50 | //! foo(); 51 | //! } 52 | //! 53 | //! fn bad() { 54 | //! info!("Hey!"); 55 | //! info!("Hey!"); //~ ERROR symbol `Hey!` is already defined 56 | //! } 57 | //! 58 | //! fn also_bad() { 59 | //! info!("Bye!"); 60 | //! error!("Bye!"); //~ ERROR symbol `Bye!` is already defined 61 | //! } 62 | //! ``` 63 | //! 64 | //! # Requirements 65 | //! 66 | //! The target application must be linked using the `stlog.x` linker script provided by this crate. 67 | //! The easiest way to do this is to append the `-C link-arg` to the other rustc flags using a Cargo 68 | //! configuration file (`.cargo/config`). 69 | //! 70 | //! ``` toml 71 | //! [target.thumbv7m-none-eabi] 72 | //! rustflags = [ 73 | //! "-C", "link-arg=-Tstlog.x", 74 | //! # .. 75 | //! ] 76 | //! ``` 77 | //! 78 | //! To decode the logs on the host you'll need version v0.2.x of the [`stcat`] tool. 79 | //! 80 | //! [`stcat`]: https://crates.io/crates/stcat 81 | //! 82 | //! # Examples 83 | //! 84 | //! ## Local logger 85 | //! 86 | //! - Device side 87 | //! 88 | //! ``` 89 | //! use stlog::{info, warn, Log}; 90 | //! 91 | //! struct Logger { 92 | //! // .. 93 | //! # _0: (), 94 | //! } 95 | //! 96 | //! impl Log for Logger { 97 | //! // .. 98 | //! # type Error = (); 99 | //! # 100 | //! # fn log(&mut self, _: u8) -> Result<(), ()> { 101 | //! # Ok(()) 102 | //! # } 103 | //! } 104 | //! 105 | //! fn main() { 106 | //! let mut logger = Logger { 107 | //! // .. 108 | //! # _0: (), 109 | //! }; 110 | //! 111 | //! info!(logger, "Hello, world!"); 112 | //! warn!(logger, "The quick brown fox jumps over the lazy dog"); 113 | //! } 114 | //! ``` 115 | //! 116 | //! - Host side 117 | //! 118 | //! Assuming that the device is `log`ging through the `/dev/ttyUSB0` interface. 119 | //! 120 | //! ``` text 121 | //! $ flash-and-run /path/to/device/binary 122 | //! 123 | //! $ cat /dev/ttyUSB0 | stcat -e /path/to/device/binary 124 | //! Sept 22 13:00:00.000 INFO Hello, world! 125 | //! Sept 22 13:00:00.001 WARN The quick brown fox jumps over the lazy dog 126 | //! ``` 127 | //! 128 | //! ## Global logger 129 | //! 130 | //! If the first argument is omitted from the logging macros then logging will be done through the 131 | //! global logger. The global logger must be selected using the `global_logger` attribute *in the 132 | //! top crate*. 133 | //! 134 | //! ``` ignore 135 | //! use stlog::{info, GlobalLog}; 136 | //! 137 | //! struct Logger; 138 | //! 139 | //! impl GlobalLog for Logger { .. } 140 | //! 141 | //! #[global_logger] 142 | //! static LOGGER: Logger = Logger; 143 | //! 144 | //! fn main() { 145 | //! info!("Hello"); 146 | //! } 147 | //! 148 | //! #[interrupt] 149 | //! fn SomeInterrupt() { 150 | //! info!("World"); 151 | //! } 152 | //! ``` 153 | //! 154 | //! # Cargo features 155 | //! 156 | //! ## `spanned` 157 | //! 158 | //! Enabling this feature adds variants of the macros, that include span information, under the 159 | //! `spanned` module. For example, `spanned::info!("Hello")` will log the string `"Hello, loc: 160 | //! src/main.rs:12"`, where `src/main.rs:12` is the location of the macro invocation. 161 | //! 162 | //! This feature depends on unstable `proc_macro` features and requires a nightly compiler. 163 | //! 164 | //! ## `[release-]max-level-{off,error,warning,info,debug,trace}` 165 | //! 166 | //! These features can be used to enable / disable logging levels at compile time. 167 | //! 168 | //! - `max-level-off` will disable all levels 169 | //! - `max-level-error` enables the error level and disables everything else 170 | //! - `max-level-warning` enables the error and warning levels and disables everything else 171 | //! - `max-level-info` enables the error, warning and info levels and disables everything else 172 | //! - `max-level-debug` enables everything but the trace level 173 | //! - `max-level-trace` enables all levels 174 | //! 175 | //! The `release-` prefixed features affect the release profile, while the other features only 176 | //! affect the dev profile. 177 | //! 178 | //! If none of these features are enabled the release profile enables the error, warning and info 179 | //! levels, and the dev profile additionally enables the debug level. 180 | //! 181 | //! # Troubleshooting 182 | //! 183 | //! ## Didn't pass `-Tstlog.x` to the linker 184 | //! 185 | //! Symptom: you'll get an error when linking the target application or when calling `stcat`. 186 | //! 187 | //! ``` text 188 | //! $ cargo build 189 | //! error: linking with `rust-lld` failed: exit code: 1 190 | //! | 191 | //! = note: "rust-lld" (..) 192 | //! = note: rust-lld: error: no memory region specified for section '.stlog.info' 193 | //! rust-lld: error: no memory region specified for section '.stlog.error' 194 | //! 195 | //! $ stcat -e /path/to/binary logfile 196 | //! error: symbol `__stlog_error_start__` not found 197 | //! ``` 198 | //! 199 | //! Pass `-Tstlog.x` to the linker as explained in the requirements section. 200 | //! 201 | //! ## Didn't set a `global_logger` 202 | //! 203 | //! Symptom: you'll get an error when linking the program 204 | //! 205 | //! ``` text 206 | //! $ cargo build 207 | //! error: linking with `rust-lld` failed: exit code: 1 208 | //! | 209 | //! = note: "rust-lld" (..) 210 | //! = note: rust-lld: error: undefined symbol: stlog::GLOBAL_LOGGER 211 | //! ``` 212 | 213 | #![deny(rust_2018_compatibility)] 214 | #![deny(rust_2018_idioms)] 215 | #![deny(warnings)] 216 | #![no_std] 217 | 218 | pub use stlog_macros::global_logger; 219 | use void::Void; 220 | 221 | #[cfg(feature = "spanned")] 222 | pub mod spanned; 223 | 224 | /// A logger that does nothing 225 | pub struct NullLogger; 226 | 227 | impl GlobalLog for NullLogger { 228 | fn log(&self, _: u8) {} 229 | } 230 | 231 | impl Log for NullLogger { 232 | type Error = Void; 233 | 234 | fn log(&mut self, _: u8) -> Result<(), Void> { 235 | Ok(()) 236 | } 237 | } 238 | 239 | /// A global version of the [`Log`](trait.Log) trait 240 | /// 241 | /// This is very similar to [`Log`](trait.Log) except that the implementor must ensure that this 242 | /// method is synchronized with other invocations of itself that could occur concurrently. Also, 243 | /// note that there the return type is `()` and not `Result` so errors must be handled by the `log` 244 | /// method. 245 | pub trait GlobalLog: Sync { 246 | fn log(&self, address: u8); 247 | } 248 | 249 | /// A logger that encodes messages using a symbol table 250 | /// 251 | /// # Contract 252 | /// 253 | /// The implementation of the `log` method MUST send its argument as a single byte through some 254 | /// interface. 255 | pub trait Log { 256 | /// Error type of the log operation 257 | type Error; 258 | 259 | /// Sends the `address` of the symbol through some interface 260 | fn log(&mut self, address: u8) -> Result<(), Self::Error>; 261 | } 262 | 263 | /// Logs the given string literal at the ERROR log level 264 | /// 265 | /// `$logger` must be an expression whose type implements the [`Log`](trait.Log.html) trait. 266 | /// 267 | /// If `$logger` is omitted the global logger will be used. 268 | #[macro_export] 269 | macro_rules! error { 270 | ($logger:expr, $string:expr) => {{ 271 | if $crate::max_level() as u8 >= $crate::Level::Error as u8 { 272 | #[export_name = $string] 273 | #[link_section = ".stlog.error"] 274 | static SYMBOL: u8 = 0; 275 | 276 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 277 | } else { 278 | Ok(()) 279 | } 280 | }}; 281 | 282 | ($string:expr) => { 283 | unsafe { 284 | if $crate::max_level() as u8 >= $crate::Level::Error as u8 { 285 | extern "Rust" { 286 | #[link_name = "stlog::GLOBAL_LOGGER"] 287 | static LOGGER: &'static $crate::GlobalLog; 288 | } 289 | 290 | #[export_name = $string] 291 | #[link_section = ".stlog.error"] 292 | static SYMBOL: u8 = 0; 293 | 294 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 295 | } 296 | } 297 | }; 298 | } 299 | 300 | /// Logs the given string literal at the WARNING log level 301 | /// 302 | /// For more details see the [`error!`](macro.error.html) macro. 303 | #[macro_export] 304 | macro_rules! warn { 305 | ($logger:expr, $string:expr) => {{ 306 | if $crate::max_level() as u8 >= $crate::Level::Warn as u8 { 307 | #[export_name = $string] 308 | #[link_section = ".stlog.warn"] 309 | static SYMBOL: u8 = 0; 310 | 311 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 312 | } else { 313 | Ok(()) 314 | } 315 | }}; 316 | 317 | ($string:expr) => { 318 | unsafe { 319 | if $crate::max_level() as u8 >= $crate::Level::Warn as u8 { 320 | extern "Rust" { 321 | #[link_name = "stlog::GLOBAL_LOGGER"] 322 | static LOGGER: &'static $crate::GlobalLog; 323 | } 324 | 325 | #[export_name = $string] 326 | #[link_section = ".stlog.warn"] 327 | static SYMBOL: u8 = 0; 328 | 329 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 330 | } 331 | } 332 | }; 333 | } 334 | 335 | /// Logs the given string literal at the INFO log level 336 | /// 337 | /// For more details see the [`error!`](macro.error.html) macro. 338 | #[macro_export] 339 | macro_rules! info { 340 | ($logger:expr, $string:expr) => {{ 341 | if $crate::max_level() as u8 >= $crate::Level::Info as u8 { 342 | #[export_name = $string] 343 | #[link_section = ".stlog.info"] 344 | static SYMBOL: u8 = 0; 345 | 346 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 347 | } else { 348 | Ok(()) 349 | } 350 | }}; 351 | 352 | ($string:expr) => { 353 | unsafe { 354 | if $crate::max_level() as u8 >= $crate::Level::Info as u8 { 355 | extern "Rust" { 356 | #[link_name = "stlog::GLOBAL_LOGGER"] 357 | static LOGGER: &'static $crate::GlobalLog; 358 | } 359 | 360 | #[export_name = $string] 361 | #[link_section = ".stlog.info"] 362 | static SYMBOL: u8 = 0; 363 | 364 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 365 | } 366 | } 367 | }; 368 | } 369 | 370 | /// Logs the given string literal at the DEBUG log level 371 | /// 372 | /// For more details see the [`error!`](macro.error.html) macro. 373 | #[macro_export] 374 | macro_rules! debug { 375 | ($log:expr, $string:expr) => {{ 376 | if $crate::max_level() as u8 >= $crate::Level::Debug as u8 { 377 | #[export_name = $string] 378 | #[link_section = ".stlog.debug"] 379 | static SYMBOL: u8 = 0; 380 | 381 | $crate::Log::log(&mut $log, &SYMBOL as *const u8 as usize as u8) 382 | } else { 383 | Ok(()) 384 | } 385 | }}; 386 | 387 | ($string:expr) => { 388 | unsafe { 389 | if $crate::max_level() as u8 >= $crate::Level::Debug as u8 { 390 | extern "Rust" { 391 | #[link_name = "stlog::GLOBAL_LOGGER"] 392 | static LOGGER: &'static $crate::GlobalLog; 393 | } 394 | 395 | #[export_name = $string] 396 | #[link_section = ".stlog.debug"] 397 | static SYMBOL: u8 = 0; 398 | 399 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 400 | } 401 | } 402 | }; 403 | } 404 | 405 | /// Logs the given string literal at the TRACE log level 406 | /// 407 | /// For more details see the [`error!`](macro.error.html) macro. 408 | #[macro_export] 409 | macro_rules! trace { 410 | ($logger:expr, $string:expr) => {{ 411 | if $crate::max_level() as u8 >= $crate::Level::Trace as u8 { 412 | #[export_name = $string] 413 | #[link_section = ".stlog.trace"] 414 | static SYMBOL: u8 = 0; 415 | 416 | $crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) 417 | } else { 418 | Ok(()) 419 | } 420 | }}; 421 | 422 | ($string:expr) => { 423 | unsafe { 424 | if $crate::max_level() as u8 >= $crate::Level::Trace as u8 { 425 | extern "Rust" { 426 | #[link_name = "stlog::GLOBAL_LOGGER"] 427 | static LOGGER: &'static $crate::GlobalLog; 428 | } 429 | 430 | #[export_name = $string] 431 | #[link_section = ".stlog.trace"] 432 | static SYMBOL: u8 = 0; 433 | 434 | $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) 435 | } 436 | } 437 | }; 438 | } 439 | 440 | #[doc(hidden)] 441 | pub enum Level { 442 | Off = 0, 443 | Error = 1, 444 | Warn = 2, 445 | Info = 3, 446 | Debug = 4, 447 | Trace = 5, 448 | } 449 | 450 | #[doc(hidden)] 451 | #[inline(always)] 452 | pub fn max_level() -> Level { 453 | match () { 454 | #[cfg(debug_assertions)] 455 | () => { 456 | #[cfg(feature = "max-level-off")] 457 | return Level::Off; 458 | 459 | #[cfg(feature = "max-level-error")] 460 | return Level::Error; 461 | 462 | #[cfg(feature = "max-level-warn")] 463 | return Level::Warn; 464 | 465 | #[cfg(feature = "max-level-info")] 466 | return Level::Info; 467 | 468 | #[cfg(feature = "max-level-debug")] 469 | return Level::Debug; 470 | 471 | #[cfg(feature = "max-level-trace")] 472 | return Level::Trace; 473 | 474 | #[allow(unreachable_code)] 475 | Level::Debug 476 | } 477 | #[cfg(not(debug_assertions))] 478 | () => { 479 | #[cfg(feature = "release-max-level-off")] 480 | return Level::Off; 481 | 482 | #[cfg(feature = "release-max-level-error")] 483 | return Level::Error; 484 | 485 | #[cfg(feature = "release-max-level-warn")] 486 | return Level::Warn; 487 | 488 | #[cfg(feature = "release-max-level-info")] 489 | return Level::Info; 490 | 491 | #[cfg(feature = "release-max-level-debug")] 492 | return Level::Debug; 493 | 494 | #[cfg(feature = "release-max-level-trace")] 495 | return Level::Trace; 496 | 497 | #[allow(unreachable_code)] 498 | Level::Info 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /src/spanned.rs: -------------------------------------------------------------------------------- 1 | pub use stlog_macros::{debug, error, info, trace, warning}; 2 | -------------------------------------------------------------------------------- /stlog.x: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .stlog 0 (INFO) : { 4 | *(.stlog.error); 5 | 6 | __stlog_warning_start__ = .; 7 | *(.stlog.warn); 8 | 9 | __stlog_info_start__ = .; 10 | *(.stlog.info); 11 | 12 | __stlog_debug_start__ = .; 13 | *(.stlog.debug); 14 | 15 | __stlog_trace_start__ = .; 16 | *(.stlog.trace); 17 | } 18 | } 19 | 20 | ASSERT(SIZEOF(.stlog) < 256, " 21 | ERROR(stlog): stlog only supports up to 256 different strings at the moment."); 22 | --------------------------------------------------------------------------------