├── .circleci └── config.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── log.rs └── million.rs ├── ndjson.png ├── pretty.png └── src ├── lib.rs ├── ndjson.rs ├── pretty.rs └── wasm.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | # Circle CI dependencies. 4 | # Rust Orb: https://github.com/CircleCI-Public/rust-orb 5 | # Github Orb: https://github.com/CircleCI-Public/github-cli-orb 6 | orbs: 7 | rust: circleci/rust@1.6.0 8 | gh: circleci/github-cli@2.0.0 9 | win: circleci/windows@2.2.0 10 | 11 | # We run jobs on the following platforms: linux, macos and windows. 12 | # These are their specifications: 13 | executors: 14 | linux: &linux 15 | machine: 16 | image: ubuntu-2004:202010-01 17 | macos: &macos 18 | macos: 19 | xcode: "12.5.1" 20 | windows: &windows 21 | machine: 22 | image: 'windows-server-2019-vs2019:stable' 23 | resource_class: windows.medium 24 | shell: powershell.exe -ExecutionPolicy Bypass 25 | 26 | # There are two workflows: lint and test. 27 | 28 | # We run `cargo clippy` for linting on 29 | # linux on stable rust. 30 | 31 | # Tests are run on three platforms: linux, macos and 32 | # windows. They all get run through stable and nightly rust so we are aware of 33 | # any breaking changes that might be happening in the near future. 34 | workflows: 35 | lint: 36 | jobs: 37 | - lint: 38 | name: Lint 39 | matrix: 40 | parameters: 41 | platform: [linux] 42 | rust_channel: [stable] 43 | wasm: 44 | jobs: 45 | - wasm: 46 | name: Wasm compilation 47 | matrix: 48 | parameters: 49 | platform: [linux] 50 | rust_channel: [stable] 51 | test: 52 | jobs: 53 | - test: 54 | name: Test (<< matrix.rust_channel >> rust on << matrix.platform >>) 55 | matrix: 56 | parameters: 57 | platform: [linux, macos, windows] 58 | rust_channel: [stable, nightly] 59 | 60 | 61 | 62 | # Details of the two jobs: lint and test. 63 | jobs: 64 | lint: 65 | parameters: 66 | rust_channel: 67 | type: enum 68 | enum: ["stable", "nightly"] 69 | default: stable 70 | platform: 71 | type: executor 72 | executor: << parameters.platform >> 73 | steps: 74 | - checkout 75 | - install_system_deps: 76 | rust_channel: << parameters.rust_channel >> 77 | platform: << parameters.platform >> 78 | - run: 79 | name: Run cargo clippy 80 | command: cargo clippy --all-targets --all-features -- -D warnings && cargo clippy --benches 81 | 82 | wasm: 83 | parameters: 84 | rust_channel: 85 | type: enum 86 | enum: ["stable"] 87 | default: stable 88 | platform: 89 | type: executor 90 | executor: << parameters.platform >> 91 | steps: 92 | - checkout 93 | - install_system_deps: 94 | rust_channel: << parameters.rust_channel >> 95 | platform: << parameters.platform >> 96 | - run: 97 | name: Download wasm target 98 | command: rustup target add wasm32-unknown-unknown 99 | - run: 100 | name: Run cargo build on wasm32-unknown-unknown 101 | command: cargo build --target wasm32-unknown-unknown 102 | 103 | test: 104 | parameters: 105 | rust_channel: 106 | type: enum 107 | enum: ["stable", "nightly"] 108 | default: stable 109 | platform: 110 | type: executor 111 | executor: << parameters.platform >> 112 | steps: 113 | - checkout 114 | - install_system_deps: 115 | rust_channel: << parameters.rust_channel >> 116 | platform: << parameters.platform >> 117 | - run: 118 | name: Run cargo test 119 | command: cargo test 120 | 121 | # The folowing are reusable command snippets can be referred to in any `steps`. 122 | # Commands we currently have: install_system_deps, install_rust_toolchain. 123 | commands: 124 | install_system_deps: 125 | parameters: 126 | platform: 127 | type: executor 128 | rust_channel: 129 | type: enum 130 | enum: ["stable", "nightly"] 131 | steps: 132 | - when: 133 | condition: 134 | equal: [ *linux, << parameters.platform >> ] 135 | steps: 136 | - run: 137 | name: Update apt repositories 138 | command: sudo apt-get update 139 | - run: 140 | name: Check glibc version 141 | command: ldd --version 142 | - run: 143 | name: Install OpenSSL 144 | command: sudo apt-get install -y libssl-dev 145 | 146 | - when: 147 | condition: 148 | equal: [ *macos, << parameters.platform >> ] 149 | steps: 150 | - run: 151 | name: Skip homebrew update 152 | command: echo "HOMEBREW_NO_AUTO_UPDATE=1" >> $BASH_ENV 153 | - run: 154 | name: Install OpenSSL@1.1 155 | command: brew install openssl@1.1 156 | 157 | - install_rust_toolchain: 158 | rust_channel: << parameters.rust_channel >> 159 | platform: << parameters.platform >> 160 | 161 | install_rust_toolchain: 162 | parameters: 163 | rust_channel: 164 | type: enum 165 | enum: ["stable", "nightly"] 166 | platform: 167 | type: executor 168 | steps: 169 | - unless: 170 | condition: 171 | equal: [ *windows, << parameters.platform >> ] 172 | steps: 173 | - rust/install: 174 | version: << parameters.rust_channel >> 175 | 176 | - when: 177 | condition: 178 | equal: [ *windows, << parameters.platform >> ] 179 | steps: 180 | - run: 181 | name: Install rustup 182 | environment: 183 | # Override auto-detection of RAM for rustc install. 184 | # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 185 | RUSTUP_UNPACK_RAM: "21474836480" 186 | command: | 187 | $installer_dir = "$Env:TEMP" 188 | echo "Downloading rustup" 189 | (New-Object System.Net.WebClient).DownloadFile("https://win.rustup.rs/x86_64", "$installer_dir\rustup-init.exe") 190 | echo "Installing rustup" 191 | & $installer_dir\rustup-init.exe --profile minimal -y 192 | exit $LASTEXITCODE 193 | - run: 194 | name: Configure cargo for Windows 195 | command: | 196 | Add-Content -path "${Env:USERPROFILE}\.cargo\config.toml" @" 197 | [net] 198 | git-fetch-with-cli = true 199 | "@ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | *.log 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "backtrace" 7 | version = "0.3.32" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "18b50f5258d1a9ad8396d2d345827875de4261b158124d4c819d9b351454fae5" 10 | dependencies = [ 11 | "backtrace-sys", 12 | "cfg-if 0.1.9", 13 | "libc", 14 | "rustc-demangle", 15 | ] 16 | 17 | [[package]] 18 | name = "backtrace-sys" 19 | version = "0.1.30" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "5b3a000b9c543553af61bc01cbfc403b04b5caa9e421033866f2e98061eb3e61" 22 | dependencies = [ 23 | "cc", 24 | "libc", 25 | ] 26 | 27 | [[package]] 28 | name = "bumpalo" 29 | version = "2.5.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "2cd43d82f27d68911e6ee11ee791fb248f138f5d69424dc02e098d4f152b0b05" 32 | 33 | [[package]] 34 | name = "cc" 35 | version = "1.0.37" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" 38 | 39 | [[package]] 40 | name = "cfg-if" 41 | version = "0.1.9" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" 44 | 45 | [[package]] 46 | name = "cfg-if" 47 | version = "1.0.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 50 | 51 | [[package]] 52 | name = "ctor" 53 | version = "0.1.22" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" 56 | dependencies = [ 57 | "quote 1.0.17", 58 | "syn 1.0.90", 59 | ] 60 | 61 | [[package]] 62 | name = "erased-serde" 63 | version = "0.3.20" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" 66 | dependencies = [ 67 | "serde", 68 | ] 69 | 70 | [[package]] 71 | name = "failure" 72 | version = "0.1.5" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" 75 | dependencies = [ 76 | "backtrace", 77 | "failure_derive", 78 | ] 79 | 80 | [[package]] 81 | name = "failure_derive" 82 | version = "0.1.5" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" 85 | dependencies = [ 86 | "proc-macro2 0.4.30", 87 | "quote 0.6.13", 88 | "syn 0.15.39", 89 | "synstructure", 90 | ] 91 | 92 | [[package]] 93 | name = "femme" 94 | version = "2.2.0" 95 | dependencies = [ 96 | "cfg-if 1.0.0", 97 | "js-sys", 98 | "kv-log-macro", 99 | "log", 100 | "serde", 101 | "serde_derive", 102 | "serde_json", 103 | "wasm-bindgen", 104 | "web-sys", 105 | ] 106 | 107 | [[package]] 108 | name = "heck" 109 | version = "0.3.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 112 | dependencies = [ 113 | "unicode-segmentation", 114 | ] 115 | 116 | [[package]] 117 | name = "itoa" 118 | version = "0.4.4" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 121 | 122 | [[package]] 123 | name = "js-sys" 124 | version = "0.3.25" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "da3ea71161651a4cd97d999b2da139109c537b15ab33abc8ae4ead38deac8a03" 127 | dependencies = [ 128 | "wasm-bindgen", 129 | ] 130 | 131 | [[package]] 132 | name = "kv-log-macro" 133 | version = "1.0.5" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51" 136 | dependencies = [ 137 | "log", 138 | ] 139 | 140 | [[package]] 141 | name = "lazy_static" 142 | version = "1.3.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" 145 | 146 | [[package]] 147 | name = "libc" 148 | version = "0.2.60" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" 151 | 152 | [[package]] 153 | name = "log" 154 | version = "0.4.16" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" 157 | dependencies = [ 158 | "cfg-if 1.0.0", 159 | "serde", 160 | "value-bag", 161 | ] 162 | 163 | [[package]] 164 | name = "memchr" 165 | version = "2.2.1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" 168 | 169 | [[package]] 170 | name = "nom" 171 | version = "4.2.3" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 174 | dependencies = [ 175 | "memchr", 176 | "version_check 0.1.5", 177 | ] 178 | 179 | [[package]] 180 | name = "proc-macro2" 181 | version = "0.4.30" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 184 | dependencies = [ 185 | "unicode-xid 0.1.0", 186 | ] 187 | 188 | [[package]] 189 | name = "proc-macro2" 190 | version = "1.0.36" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 193 | dependencies = [ 194 | "unicode-xid 0.2.2", 195 | ] 196 | 197 | [[package]] 198 | name = "quote" 199 | version = "0.6.13" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 202 | dependencies = [ 203 | "proc-macro2 0.4.30", 204 | ] 205 | 206 | [[package]] 207 | name = "quote" 208 | version = "1.0.17" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" 211 | dependencies = [ 212 | "proc-macro2 1.0.36", 213 | ] 214 | 215 | [[package]] 216 | name = "rustc-demangle" 217 | version = "0.1.15" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" 220 | 221 | [[package]] 222 | name = "ryu" 223 | version = "1.0.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" 226 | 227 | [[package]] 228 | name = "serde" 229 | version = "1.0.114" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 232 | 233 | [[package]] 234 | name = "serde_derive" 235 | version = "1.0.97" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "c22a0820adfe2f257b098714323563dd06426502abbbce4f51b72ef544c5027f" 238 | dependencies = [ 239 | "proc-macro2 0.4.30", 240 | "quote 0.6.13", 241 | "syn 0.15.39", 242 | ] 243 | 244 | [[package]] 245 | name = "serde_fmt" 246 | version = "1.0.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "2963a69a2b3918c1dc75a45a18bd3fcd1120e31d3f59deb1b2f9b5d5ffb8baa4" 249 | dependencies = [ 250 | "serde", 251 | ] 252 | 253 | [[package]] 254 | name = "serde_json" 255 | version = "1.0.56" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" 258 | dependencies = [ 259 | "itoa", 260 | "ryu", 261 | "serde", 262 | ] 263 | 264 | [[package]] 265 | name = "sourcefile" 266 | version = "0.1.4" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" 269 | 270 | [[package]] 271 | name = "sval" 272 | version = "1.0.0-alpha.5" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08" 275 | dependencies = [ 276 | "serde", 277 | ] 278 | 279 | [[package]] 280 | name = "syn" 281 | version = "0.15.39" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "b4d960b829a55e56db167e861ddb43602c003c7be0bee1d345021703fac2fb7c" 284 | dependencies = [ 285 | "proc-macro2 0.4.30", 286 | "quote 0.6.13", 287 | "unicode-xid 0.1.0", 288 | ] 289 | 290 | [[package]] 291 | name = "syn" 292 | version = "1.0.90" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" 295 | dependencies = [ 296 | "proc-macro2 1.0.36", 297 | "quote 1.0.17", 298 | "unicode-xid 0.2.2", 299 | ] 300 | 301 | [[package]] 302 | name = "synstructure" 303 | version = "0.10.2" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" 306 | dependencies = [ 307 | "proc-macro2 0.4.30", 308 | "quote 0.6.13", 309 | "syn 0.15.39", 310 | "unicode-xid 0.1.0", 311 | ] 312 | 313 | [[package]] 314 | name = "unicode-segmentation" 315 | version = "1.3.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" 318 | 319 | [[package]] 320 | name = "unicode-xid" 321 | version = "0.1.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 324 | 325 | [[package]] 326 | name = "unicode-xid" 327 | version = "0.2.2" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 330 | 331 | [[package]] 332 | name = "value-bag" 333 | version = "1.0.0-alpha.8" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" 336 | dependencies = [ 337 | "ctor", 338 | "erased-serde", 339 | "serde", 340 | "serde_fmt", 341 | "sval", 342 | "version_check 0.9.4", 343 | ] 344 | 345 | [[package]] 346 | name = "version_check" 347 | version = "0.1.5" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 350 | 351 | [[package]] 352 | name = "version_check" 353 | version = "0.9.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 356 | 357 | [[package]] 358 | name = "wasm-bindgen" 359 | version = "0.2.48" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "4de97fa1806bb1a99904216f6ac5e0c050dc4f8c676dc98775047c38e5c01b55" 362 | dependencies = [ 363 | "serde", 364 | "serde_json", 365 | "wasm-bindgen-macro", 366 | ] 367 | 368 | [[package]] 369 | name = "wasm-bindgen-backend" 370 | version = "0.2.48" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "5d82c170ef9f5b2c63ad4460dfcee93f3ec04a9a36a4cc20bc973c39e59ab8e3" 373 | dependencies = [ 374 | "bumpalo", 375 | "lazy_static", 376 | "log", 377 | "proc-macro2 0.4.30", 378 | "quote 0.6.13", 379 | "syn 0.15.39", 380 | "wasm-bindgen-shared", 381 | ] 382 | 383 | [[package]] 384 | name = "wasm-bindgen-macro" 385 | version = "0.2.48" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "f07d50f74bf7a738304f6b8157f4a581e1512cd9e9cdb5baad8c31bbe8ffd81d" 388 | dependencies = [ 389 | "quote 0.6.13", 390 | "wasm-bindgen-macro-support", 391 | ] 392 | 393 | [[package]] 394 | name = "wasm-bindgen-macro-support" 395 | version = "0.2.48" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "95cf8fe77e45ba5f91bc8f3da0c3aa5d464b3d8ed85d84f4d4c7cc106436b1d7" 398 | dependencies = [ 399 | "proc-macro2 0.4.30", 400 | "quote 0.6.13", 401 | "syn 0.15.39", 402 | "wasm-bindgen-backend", 403 | "wasm-bindgen-shared", 404 | ] 405 | 406 | [[package]] 407 | name = "wasm-bindgen-shared" 408 | version = "0.2.48" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "d9c2d4d4756b2e46d3a5422e06277d02e4d3e1d62d138b76a4c681e925743623" 411 | 412 | [[package]] 413 | name = "wasm-bindgen-webidl" 414 | version = "0.2.48" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "24e47859b4eba3d3b9a5c2c299f9d6f8d0b613671315f6f0c5c7f835e524b36a" 417 | dependencies = [ 418 | "failure", 419 | "heck", 420 | "log", 421 | "proc-macro2 0.4.30", 422 | "quote 0.6.13", 423 | "syn 0.15.39", 424 | "wasm-bindgen-backend", 425 | "weedle", 426 | ] 427 | 428 | [[package]] 429 | name = "web-sys" 430 | version = "0.3.25" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "86d515d2f713d3a6ab198031d2181b7540f8e319e4637ec2d4a41a208335ef29" 433 | dependencies = [ 434 | "failure", 435 | "js-sys", 436 | "sourcefile", 437 | "wasm-bindgen", 438 | "wasm-bindgen-webidl", 439 | ] 440 | 441 | [[package]] 442 | name = "weedle" 443 | version = "0.10.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" 446 | dependencies = [ 447 | "nom", 448 | ] 449 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "femme" 3 | version = "2.2.1" 4 | license = "MIT OR Apache-2.0" 5 | authors = [ 6 | "lrlna ", 7 | "yoshuawuyts ", 8 | ] 9 | repository = "https://github.com/lrlna/femme" 10 | documentation = "https://docs.rs/femme" 11 | description = "Not just a pretty (inter)face: pretty-printer and ndjson logger for log crate." 12 | keywords = ["pretty-printer", "ndjson", "femme", "log", "logger"] 13 | readme = "README.md" 14 | edition = "2018" 15 | 16 | [dependencies] 17 | log = { version = "0.4.14", features = [ 18 | "kv_unstable", 19 | "std", 20 | "kv_unstable_serde", 21 | ] } 22 | serde = "1.0.97" 23 | serde_derive = "1.0.97" 24 | serde_json = "1.0.56" 25 | cfg-if = "1.0.0" 26 | 27 | [target.'cfg(target_arch = "wasm32")'.dependencies] 28 | web-sys = { version = "0.3.25", features = ["console"] } 29 | js-sys = "0.3.25" 30 | wasm-bindgen = { version = "0.2.48", features = ["serde-serialize"] } 31 | 32 | [dev-dependencies] 33 | kv-log-macro = "1.0.5" 34 | -------------------------------------------------------------------------------- /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 2020 Irina Shestak. 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 | MIT License 2 | 3 | Copyright (c) Irina Shestak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # femme 2 | [![crates.io version][1]][2] [![build status][3]][4] 3 | [![downloads][5]][6] [![docs.rs docs][7]][8] 4 | 5 | Not just a pretty (inter)face. 6 | 7 | A pretty-printer and [ndjson](http://ndjson.org/) logger for the [log](https://docs.rs/log) crate. 8 | 9 | - [Documentation][8] 10 | - [Crates.io][2] 11 | - [Releases][releases] 12 | 13 | ## Examples 14 | ```rust 15 | use kv_log_macro as log; 16 | 17 | femme::with_level(femme::LevelFilter::Trace); 18 | log::warn!("Unauthorized access attempt on /login"); 19 | log::info!("Listening on port 8080"); 20 | ``` 21 | 22 | Prettified output will be displayed when debugging. In release mode, this logger 23 | will output to ndjson. 24 | 25 | When using Wasm with `#[cfg(target_arch = "wasm32")]`, Wasm logger will be used. 26 | Wasm logger uses `web_sys` crate to send `console.log()` to JavaScript. 27 | 28 | ## Screenshots 29 | ### Pretty Output 30 | pretty printed logs 31 | 32 | ### Newline Delimited JSON 33 | ndjson 34 | 35 | 36 | ## Installation 37 | ```sh 38 | $ cargo add femme 39 | ``` 40 | 41 | ## License 42 | [MIT](./LICENSE-MIT) OR [Apache-2.0](./LICENSE-APACHE) 43 | 44 | [1]: https://img.shields.io/crates/v/femme.svg?style=flat-square 45 | [2]: https://crates.io/crates/femme 46 | [3]: https://img.shields.io/circleci/build/github/lrlna/femme 47 | [4]: https://app.circleci.com/pipelines/github/lrlna/femme 48 | [5]: https://img.shields.io/crates/d/femme.svg?style=flat-square 49 | [6]: https://crates.io/crates/femme 50 | [7]: https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square 51 | [8]: https://docs.rs/femme 52 | 53 | [releases]: https://github.com/lrlna/femme/releases 54 | -------------------------------------------------------------------------------- /examples/log.rs: -------------------------------------------------------------------------------- 1 | use kv_log_macro as log; 2 | 3 | fn main() { 4 | femme::with_level(femme::LevelFilter::Trace); 5 | log::error!("Buffer has to be 16 bytes in length"); 6 | log::warn!("Unauthorized access attempt", { route: "/login", user_id: "827756627", }); 7 | log::info!("Server listening", { port: "8080" }); 8 | log::info!("Request handled", { method: "GET", path: "/foo/bar", status: 200, elapsed: "4ms" }); 9 | log::debug!("Getting String as bson value type"); 10 | log::trace!("Task spawned", {task_id: "567", thread_id: "12"}); 11 | log::info!(r#"raw " fun with JSON"#); 12 | log::info!("n\ne\nw\nl\ni\nn\ne\n"); 13 | } 14 | -------------------------------------------------------------------------------- /examples/million.rs: -------------------------------------------------------------------------------- 1 | use kv_log_macro as log; 2 | use std::time::Instant; 3 | 4 | fn main() { 5 | femme::with_level(femme::LevelFilter::Trace); 6 | 7 | let start = Instant::now(); 8 | for n in 0..1_000_000 { 9 | log::info!("logging no. {}", n); 10 | } 11 | 12 | eprintln!("time elapsed: {:?}", Instant::now().duration_since(start)); 13 | } 14 | -------------------------------------------------------------------------------- /ndjson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lrlna/femme/3d438b294c5d71773f08ff6e1d60d29c13e541ac/ndjson.png -------------------------------------------------------------------------------- /pretty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lrlna/femme/3d438b294c5d71773f08ff6e1d60d29c13e541ac/pretty.png -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Not just a pretty (inter)face. 2 | //! 3 | //! A pretty-printer and [ndjson](http://ndjson.org/) logger for the [log](https://docs.rs/log) crate. 4 | //! 5 | //! ## Examples 6 | //! ``` 7 | //! femme::start(); 8 | //! log::warn!("Unauthorized access attempt on /login"); 9 | //! log::info!("Listening on port 8080"); 10 | //! ``` 11 | 12 | pub use log::LevelFilter; 13 | 14 | #[cfg(not(target_arch = "wasm32"))] 15 | mod ndjson; 16 | 17 | #[cfg(not(target_arch = "wasm32"))] 18 | mod pretty; 19 | 20 | #[cfg(target_arch = "wasm32")] 21 | mod wasm; 22 | 23 | /// Starts logging depending on current environment. 24 | /// 25 | /// Always logs with 'Info' LevelFilter. 26 | /// For other filters use with_level. 27 | /// 28 | /// # Log output 29 | /// 30 | /// - when compiling with `--release` uses ndjson. 31 | /// - pretty-prints otherwise. 32 | /// - works in WASM out of the box. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// femme::start(); 38 | /// log::warn!("Unauthorized access attempt on /login"); 39 | /// log::info!("Listening on port 8080"); 40 | /// ``` 41 | pub fn start() { 42 | with_level(LevelFilter::Info); 43 | } 44 | 45 | /// Start logging with a log level. 46 | /// 47 | /// All messages under the specified log level will statically be filtered out. 48 | /// 49 | /// # Examples 50 | /// ``` 51 | /// femme::with_level(log::LevelFilter::Trace); 52 | /// ``` 53 | pub fn with_level(level: log::LevelFilter) { 54 | #[cfg(target_arch = "wasm32")] 55 | wasm::start(level); 56 | 57 | #[cfg(not(target_arch = "wasm32"))] 58 | { 59 | // Use ndjson in release mode, pretty logging while debugging. 60 | if cfg!(debug_assertions) { 61 | pretty::start(level); 62 | } else { 63 | ndjson::start(level); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ndjson.rs: -------------------------------------------------------------------------------- 1 | //! Print logs as ndjson. 2 | 3 | use log::{kv, LevelFilter, Log, Metadata, Record}; 4 | use std::io::{self, StdoutLock, Write}; 5 | use std::time; 6 | 7 | /// Start logging. 8 | pub fn start(level: LevelFilter) { 9 | let logger = Box::new(Logger {}); 10 | log::set_boxed_logger(logger).expect("Could not start logging"); 11 | log::set_max_level(level); 12 | } 13 | 14 | #[derive(Debug)] 15 | pub(crate) struct Logger {} 16 | 17 | impl Log for Logger { 18 | fn enabled(&self, metadata: &Metadata<'_>) -> bool { 19 | metadata.level() <= log::max_level() 20 | } 21 | 22 | fn log(&self, record: &Record<'_>) { 23 | if self.enabled(record.metadata()) { 24 | let stdout = io::stdout(); 25 | let mut handle = stdout.lock(); 26 | let level = get_level(record.level()); 27 | let time = time::UNIX_EPOCH.elapsed().unwrap().as_millis(); 28 | write!( 29 | &mut handle, 30 | "{{\"level\":{},\"time\":{},\"msg\":", 31 | level, time 32 | ) 33 | .unwrap(); 34 | serde_json::to_writer(&mut handle, record.args()).unwrap(); 35 | format_kv_pairs(&mut handle, record); 36 | writeln!(&mut handle, "}}").unwrap(); 37 | } 38 | } 39 | fn flush(&self) {} 40 | } 41 | 42 | fn get_level(level: log::Level) -> u8 { 43 | use log::Level::*; 44 | match level { 45 | Trace => 10, 46 | Debug => 20, 47 | Info => 30, 48 | Warn => 40, 49 | Error => 50, 50 | } 51 | } 52 | 53 | fn format_kv_pairs<'b>(out: &mut StdoutLock<'b>, record: &Record) { 54 | struct Visitor<'a, 'b> { 55 | string: &'a mut StdoutLock<'b>, 56 | } 57 | 58 | impl<'kvs, 'a, 'b> kv::Visitor<'kvs> for Visitor<'a, 'b> { 59 | fn visit_pair( 60 | &mut self, 61 | key: kv::Key<'kvs>, 62 | val: kv::Value<'kvs>, 63 | ) -> Result<(), kv::Error> { 64 | if let Ok(value_str) = serde_json::to_string(&val) { 65 | write!(self.string, ",\"{}\":{}", key, value_str)?; 66 | } else { 67 | write!(self.string, ",\"{}\":\"{}\"", key, val)?; 68 | } 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | let mut visitor = Visitor { string: out }; 75 | record.key_values().visit(&mut visitor).unwrap(); 76 | } 77 | -------------------------------------------------------------------------------- /src/pretty.rs: -------------------------------------------------------------------------------- 1 | //! Pretty print logs. 2 | 3 | use log::{kv, Level, LevelFilter, Log, Metadata, Record}; 4 | use std::io::{self, StdoutLock, Write}; 5 | 6 | // ANSI term codes. 7 | const RESET: &str = "\x1b[0m"; 8 | const BOLD: &str = "\x1b[1m"; 9 | const RED: &str = "\x1b[31m"; 10 | const GREEN: &str = "\x1b[32m"; 11 | const YELLOW: &str = "\x1b[33m"; 12 | 13 | /// Start logging. 14 | pub fn start(level: LevelFilter) { 15 | let logger = Box::new(Logger {}); 16 | log::set_boxed_logger(logger).expect("Could not start logging"); 17 | log::set_max_level(level); 18 | } 19 | 20 | #[derive(Debug)] 21 | pub(crate) struct Logger {} 22 | 23 | impl Log for Logger { 24 | fn enabled(&self, metadata: &Metadata<'_>) -> bool { 25 | metadata.level() <= log::max_level() 26 | } 27 | 28 | fn log(&self, record: &Record<'_>) { 29 | if self.enabled(record.metadata()) { 30 | let stdout = io::stdout(); 31 | let mut handle = stdout.lock(); 32 | format_src(&mut handle, record); 33 | write!(handle, " {}", &record.args()).unwrap(); 34 | format_kv_pairs(&mut handle, record); 35 | writeln!(&mut handle).unwrap(); 36 | } 37 | } 38 | fn flush(&self) {} 39 | } 40 | 41 | fn format_kv_pairs<'b>(out: &mut StdoutLock<'b>, record: &Record) { 42 | struct Visitor<'a, 'b> { 43 | stdout: &'a mut StdoutLock<'b>, 44 | } 45 | 46 | impl<'kvs, 'a, 'b> kv::Visitor<'kvs> for Visitor<'a, 'b> { 47 | fn visit_pair( 48 | &mut self, 49 | key: kv::Key<'kvs>, 50 | val: kv::Value<'kvs>, 51 | ) -> Result<(), kv::Error> { 52 | write!(self.stdout, "\n {}{}{} {}", BOLD, key, RESET, val)?; 53 | Ok(()) 54 | } 55 | } 56 | 57 | let mut visitor = Visitor { stdout: out }; 58 | record.key_values().visit(&mut visitor).unwrap(); 59 | } 60 | 61 | fn format_src(out: &mut StdoutLock<'_>, record: &Record<'_>) { 62 | let msg = record.target(); 63 | match record.level() { 64 | Level::Trace | Level::Debug | Level::Info => { 65 | write!(out, "{}{}{}{}", GREEN, BOLD, msg, RESET).unwrap(); 66 | } 67 | Level::Warn => write!(out, "{}{}{}{}", YELLOW, BOLD, msg, RESET).unwrap(), 68 | Level::Error => write!(out, "{}{}{}{}", RED, BOLD, msg, RESET).unwrap(), 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | //! Print logs as ndjson. 2 | 3 | use js_sys::Object; 4 | use log::{kv, Level, LevelFilter, Log, Metadata, Record}; 5 | use wasm_bindgen::prelude::*; 6 | 7 | use std::collections::HashMap; 8 | 9 | /// Start logging. 10 | pub fn start(level: LevelFilter) { 11 | let logger = Box::new(Logger {}); 12 | log::set_boxed_logger(logger).expect("Could not start logging"); 13 | log::set_max_level(level); 14 | } 15 | 16 | #[derive(Debug)] 17 | pub(crate) struct Logger {} 18 | 19 | impl Log for Logger { 20 | fn enabled(&self, metadata: &Metadata<'_>) -> bool { 21 | metadata.level() <= log::max_level() 22 | } 23 | 24 | fn log(&self, record: &Record<'_>) { 25 | if self.enabled(record.metadata()) { 26 | let args = format!("{}", record.args()).into(); 27 | let line = format_line(&record).into(); 28 | 29 | match format_kv_pairs(&record) { 30 | Some(obj) => match record.level() { 31 | Level::Error => web_sys::console::error_3(&args, &obj, &line), 32 | Level::Warn => web_sys::console::warn_3(&args, &obj, &line), 33 | Level::Info => web_sys::console::info_3(&args, &obj, &line), 34 | _ => web_sys::console::debug_3(&args, &obj, &line), 35 | }, 36 | None => match record.level() { 37 | Level::Error => web_sys::console::error_2(&args, &line), 38 | Level::Warn => web_sys::console::warn_2(&args, &line), 39 | Level::Info => web_sys::console::info_2(&args, &line), 40 | _ => web_sys::console::debug_2(&args, &line), 41 | }, 42 | } 43 | } 44 | } 45 | fn flush(&self) {} 46 | } 47 | 48 | fn format_line(record: &Record<'_>) -> String { 49 | match (record.file(), record.line()) { 50 | (Some(file), Some(line)) => format!("({}:{})", file, line), 51 | _ => String::new(), 52 | } 53 | } 54 | 55 | fn format_kv_pairs(record: &Record) -> Option { 56 | struct Visitor { 57 | hashmap: Option>, 58 | } 59 | 60 | impl<'kvs> kv::Visitor<'kvs> for Visitor { 61 | fn visit_pair( 62 | &mut self, 63 | key: kv::Key<'kvs>, 64 | val: kv::Value<'kvs>, 65 | ) -> Result<(), kv::Error> { 66 | if self.hashmap.is_none() { 67 | self.hashmap = Some(HashMap::new()) 68 | } 69 | let hm = self.hashmap.as_mut().unwrap(); 70 | hm.insert(key.to_string(), val.to_string()); 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl Visitor { 76 | fn new() -> Self { 77 | Self { hashmap: None } 78 | } 79 | } 80 | 81 | let mut visitor = Visitor::new(); 82 | record.key_values().visit(&mut visitor).unwrap(); 83 | 84 | match visitor.hashmap.as_ref() { 85 | Some(hashmap) => { 86 | let val = JsValue::from_serde(hashmap).unwrap(); 87 | Some(Object::from(val)) 88 | } 89 | None => None, 90 | } 91 | } 92 | --------------------------------------------------------------------------------