332 Commits

Author SHA1 Message Date
Nanaloveyuki 4f532af026 📝 add generated package interface snapshots 2026-07-08 12:24:21 +08:00
Nanaloveyuki bee919a557 🧹 apply moon fmt and async mbti refresh 2026-07-08 12:17:31 +08:00
Nanaloveyuki d901bfd435 💚 enhance CI matrix and coverage checks 2026-07-08 11:25:51 +08:00
Nanaloveyuki d39d577c90 fix async js shutdown test expectation 2026-07-08 11:17:37 +08:00
Nanaloveyuki f4b161448c 🔖 prepare 1.1.0(0.7.0) sync release notes 2026-07-07 20:05:15 +08:00
Nanaloveyuki cf80e583ac 📝 align sync API docs with owner packages 2026-07-07 20:05:10 +08:00
Nanaloveyuki 2bee0b1da8 split sync tests by behavior boundary 2026-07-07 20:04:58 +08:00
Nanaloveyuki 1b621497f9 ♻️ sync owner migration and facade thinning 2026-07-07 20:03:22 +08:00
Nanaloveyuki c90a408a9c 🐛 修复异常 Runtime Queue 2026-07-07 11:35:16 +08:00
Nanaloveyuki 6b181e7855 添加 Logger build测试 2026-07-07 11:33:49 +08:00
Nanaloveyuki 7946dcdaee 📝 补充runtime文档 2026-07-07 11:25:02 +08:00
Nanaloveyuki 3bac025acf 📝 Fix policy test and doc 2026-07-07 10:28:16 +08:00
Nanaloveyuki d3bff1305d 🔖 bump version to 0.6.1 2026-07-05 21:34:10 +08:00
Nanaloveyuki b90481186f 📝 Add 1.0.1(0.6.1) change notes for wide file rotation 2026-07-05 21:32:11 +08:00
Nanaloveyuki 6e3cc165b6 Add Int64 file rotation path and JSON roundtrip 2026-07-05 21:31:52 +08:00
Nanaloveyuki 8de5495f58 📝 添加Wiki并修改1.0.0文件名 2026-07-05 21:12:33 +08:00
Nanaloveyuki f44a4794ec 🩹 Fix the major version must be 0 2026-06-27 10:51:37 +08:00
Nanaloveyuki 0cbe25a551 🔊 Update 1.0.0 2026-06-27 10:45:36 +08:00
Nanaloveyuki 4cc43def73 ⬆️ Update Async and Mbt Version 2026-06-27 10:44:51 +08:00
Nanaloveyuki 812c4c21a8 💚 Fix CSS Style lost 2026-06-14 16:28:01 +08:00
Nanaloveyuki cdfe0117a0 👷 Update Doc CI for errors 2026-06-14 16:19:13 +08:00
Nanaloveyuki b43dbdecfb 📌 Update moonbit-syntax-highlighter 2026-06-14 15:59:06 +08:00
Nanaloveyuki 0cfb308fa2 Add package-lock 2026-06-14 15:43:01 +08:00
Nanaloveyuki a0e09fb30f Add VitePress docs site with MoonBit highlighting 2026-06-14 15:17:25 +08:00
Nanaloveyuki dfae044f75 🔨 wrap native file ffi symbols locally 2026-06-14 13:03:03 +08:00
Nanaloveyuki 5e80593ae0 clean native rotation test artifacts 2026-06-14 12:57:48 +08:00
Nanaloveyuki 31f06f5724 🐛 fix native file sink lifecycle behavior 2026-06-14 12:55:29 +08:00
Nanaloveyuki 159f534164 📝 clarify global warn default forwarding 2026-06-14 12:21:02 +08:00
Nanaloveyuki ea0d687da0 📝 clarify global error default forwarding 2026-06-14 12:19:49 +08:00
Nanaloveyuki ce87e32837 📝 clarify global info default forwarding 2026-06-14 12:15:42 +08:00
Nanaloveyuki 866e374ac8 📝 clarify global log default forwarding 2026-06-14 12:13:57 +08:00
Nanaloveyuki aaba931db6 📝 clarify sync log override semantics 2026-06-14 12:09:38 +08:00
Nanaloveyuki 04f7742238 📝 clarify default target snapshot semantics 2026-06-14 12:05:28 +08:00
Nanaloveyuki cf23bb6dbe 📝 clarify default level snapshot semantics 2026-06-14 12:03:45 +08:00
Nanaloveyuki bf27dacf23 📝 clarify default logger snapshot semantics 2026-06-14 12:01:54 +08:00
Nanaloveyuki 0a9bcd696b 📝 clarify sync level threshold derivation 2026-06-14 11:59:01 +08:00
Nanaloveyuki 8fb9beb008 📝 clarify sync timestamp derivation 2026-06-14 11:55:55 +08:00
Nanaloveyuki 5514009abe 📝 clarify sync patch wrapper contract 2026-06-14 11:54:18 +08:00
Nanaloveyuki c17883262a 📝 clarify sync filter wrapper contract 2026-06-14 11:52:32 +08:00
Nanaloveyuki 5476a64478 📝 clarify queued logger wrapper contract 2026-06-14 11:49:15 +08:00
Nanaloveyuki 685797783c 📝 clarify async filter derivation 2026-06-14 11:46:40 +08:00
Nanaloveyuki 11b49208b2 📝 clarify async patch derivation 2026-06-14 11:44:54 +08:00
Nanaloveyuki 18dc8d5860 📝 clarify async level threshold derivation 2026-06-14 11:42:58 +08:00
Nanaloveyuki 18f6bf9eb5 📝 clarify async timestamp derivation 2026-06-14 11:41:24 +08:00
Nanaloveyuki 958a7fd26f 📝 clarify async context field storage 2026-06-14 11:39:38 +08:00
Nanaloveyuki 1124868bd8 📝 clarify logger bind alias contract 2026-06-14 11:37:35 +08:00
Nanaloveyuki c800042e46 📝 clarify async logger child contract 2026-06-14 11:35:29 +08:00
Nanaloveyuki fe7665b9f6 📝 clarify logger child composition contract 2026-06-14 11:33:49 +08:00
Nanaloveyuki 93cadcc7f1 📝 clarify logger context field binding 2026-06-14 11:28:42 +08:00
Nanaloveyuki 480c5ad602 📝 clarify logger target replacement 2026-06-14 11:27:11 +08:00
Nanaloveyuki 5720ba6203 📝 clarify logger trace helper routing 2026-06-14 11:23:34 +08:00
Nanaloveyuki 9630c6cc3b 📝 clarify logger debug helper routing 2026-06-14 11:22:15 +08:00
Nanaloveyuki 6eec61eb2d 📝 clarify async warn helper routing 2026-06-14 11:20:35 +08:00
Nanaloveyuki 3a216a2ada 📝 clarify async error helper routing 2026-06-14 11:19:08 +08:00
Nanaloveyuki b0364fdd51 📝 clarify async info helper routing 2026-06-14 11:17:42 +08:00
Nanaloveyuki 0b2e35d138 📝 clarify logger error helper routing 2026-06-14 11:16:22 +08:00
Nanaloveyuki 6dc54379ea 📝 clarify logger warn helper routing 2026-06-14 11:14:44 +08:00
Nanaloveyuki 458e3a14b3 📝 clarify logger info helper routing 2026-06-14 11:13:07 +08:00
Nanaloveyuki e3c7e00b1e 📝 clarify sync facade unwrap aliasing 2026-06-14 11:11:34 +08:00
Nanaloveyuki 2fd1290179 📝 clarify sync facade projection aliasing 2026-06-14 11:10:04 +08:00
Nanaloveyuki f2b90b44e6 📝 clarify library logger error helper routing 2026-06-14 11:08:13 +08:00
Nanaloveyuki cf2c8cdca6 📝 clarify library logger warn helper routing 2026-06-14 11:06:57 +08:00
Nanaloveyuki 4e964245d5 📝 clarify library logger info helper routing 2026-06-14 11:05:36 +08:00
Nanaloveyuki cc3d1b1f1c 📝 clarify library async info helper routing 2026-06-14 11:04:04 +08:00
Nanaloveyuki e5140181ba 📝 clarify library async warn helper routing 2026-06-14 11:02:51 +08:00
Nanaloveyuki 2bb9b20aa4 📝 clarify library async error helper routing 2026-06-14 11:01:44 +08:00
Nanaloveyuki 32cbc2e079 📝 clarify library async unwrap aliasing 2026-06-14 10:59:58 +08:00
Nanaloveyuki 62117a4a69 📝 clarify async facade projection aliasing 2026-06-14 10:57:48 +08:00
Nanaloveyuki bba206be78 📝 clarify library async target replacement 2026-06-14 10:55:39 +08:00
Nanaloveyuki 22f5204262 📝 clarify library async child facade preservation 2026-06-14 10:53:59 +08:00
Nanaloveyuki 5bb5a75ea7 📝 clarify library async context replacement 2026-06-14 10:50:22 +08:00
Nanaloveyuki 6d1261f02b 📝 clarify async context field replacement 2026-06-14 10:48:37 +08:00
Nanaloveyuki 33fc929dc6 📝 clarify async runtime sink variant selection 2026-06-14 10:46:54 +08:00
Nanaloveyuki c990a890f9 📝 clarify parsed library async sink selection 2026-06-14 10:45:21 +08:00
Nanaloveyuki 676317aae7 📝 clarify parsed application async sink selection 2026-06-14 10:44:00 +08:00
Nanaloveyuki 7f57521e05 📝 clarify application async runtime sink semantics 2026-06-14 10:42:36 +08:00
Nanaloveyuki 9b3533de7b 📝 clarify library async runtime sink semantics 2026-06-14 10:41:01 +08:00
Nanaloveyuki e01f7e977e 📝 clarify library async text sink semantics 2026-06-14 10:39:00 +08:00
Nanaloveyuki 1a2aee4a7c 📝 clarify application text async sink selection 2026-06-14 10:36:40 +08:00
Nanaloveyuki 571ae622d0 📝 clarify async build config stringify routing 2026-06-14 10:33:10 +08:00
Nanaloveyuki 475b15c41f 📝 clarify async build config export routing 2026-06-14 10:31:50 +08:00
Nanaloveyuki 0eeb3f9811 📝 clarify async build config parse routing 2026-06-14 10:30:01 +08:00
Nanaloveyuki 0b8fbe53dd 📝 clarify async build config facade routing 2026-06-14 10:27:22 +08:00
Nanaloveyuki 8dab08b85d 📝 clarify async state alias provenance 2026-06-14 10:22:10 +08:00
Nanaloveyuki 5edd853fc5 📝 clarify async runtime state alias provenance 2026-06-14 10:20:05 +08:00
Nanaloveyuki 5957e37720 📝 clarify async runtime mode probe 2026-06-14 10:18:46 +08:00
Nanaloveyuki 8cf4eba223 📝 clarify async worker capability probe 2026-06-14 10:17:05 +08:00
Nanaloveyuki 754a878cd8 📝 clarify async runtime mode label mapping 2026-06-14 10:15:47 +08:00
Nanaloveyuki 1073559f6a 📝 clarify async runtime constructor inputs 2026-06-14 10:14:31 +08:00
Nanaloveyuki 20e08efb78 📝 clarify async runtime state snapshot source 2026-06-14 10:13:09 +08:00
Nanaloveyuki 64dfc36931 📝 clarify async runtime json snapshot semantics 2026-06-14 10:11:41 +08:00
Nanaloveyuki d46e90fcf5 📝 clarify async state constructor runtime input 2026-06-14 10:10:15 +08:00
Nanaloveyuki 891aa83e01 📝 clarify async state runtime snapshot source 2026-06-14 10:08:57 +08:00
Nanaloveyuki 97f6ae969d 📝 clarify async state json snapshot semantics 2026-06-14 10:07:33 +08:00
Nanaloveyuki 93fc54226d 📝 clarify library async unwrap live state 2026-06-14 10:04:20 +08:00
Nanaloveyuki 4421eb985d 📝 clarify library async shutdown no-worker path 2026-06-14 10:02:48 +08:00
Nanaloveyuki 96c4cc1f88 📝 clarify async shutdown failure retention 2026-06-14 10:01:35 +08:00
Nanaloveyuki 415fc8d98e 📝 clarify async wait_idle failure retention 2026-06-14 10:00:11 +08:00
Nanaloveyuki d89a629f14 📝 clarify async closed state retention 2026-06-14 09:58:55 +08:00
Nanaloveyuki 83c31f31b2 📝 clarify async run restart backlog semantics 2026-06-14 09:57:31 +08:00
Nanaloveyuki 93afc5fe45 📝 clarify library async facade state preservation 2026-06-14 09:56:11 +08:00
Nanaloveyuki a7e9731571 📝 clarify async late log close semantics 2026-06-14 09:54:46 +08:00
Nanaloveyuki 83e1accc8e 📝 clarify async dropped count after failed shutdown 2026-06-14 09:50:25 +08:00
Nanaloveyuki 888581e44a 📝 clarify async pending backlog after failed shutdown 2026-06-14 09:48:17 +08:00
Nanaloveyuki 2ecf88d866 📝 clarify async last error retention after shutdown 2026-06-14 09:46:20 +08:00
Nanaloveyuki 19befe9418 📝 clarify async failure retention after shutdown 2026-06-14 09:42:45 +08:00
Nanaloveyuki dcbc2422c6 📝 clarify async running restart semantics 2026-06-14 09:41:17 +08:00
Nanaloveyuki 5bb34b4e66 📝 clarify async state failure shutdown snapshots 2026-06-14 09:39:34 +08:00
Nanaloveyuki 3fcdf8f64a 📝 clarify async projection preserved failure state 2026-06-14 09:38:00 +08:00
Nanaloveyuki 674e949f96 📝 clarify library async unwrap preserved failure state 2026-06-14 09:36:37 +08:00
Nanaloveyuki bbc9afbc47 📝 clarify library async shutdown failure retention 2026-06-14 09:35:09 +08:00
Nanaloveyuki e386ef8b28 📝 clarify library async run restart semantics 2026-06-14 09:33:24 +08:00
Nanaloveyuki af9e4e853a 📝 clarify async close post-shutdown log split 2026-06-14 09:31:19 +08:00
Nanaloveyuki eb3ec42a59 📝 clarify async flush policy callback semantics 2026-06-14 09:29:16 +08:00
Nanaloveyuki 54fdff3317 📝 clarify async dropped count shutdown split 2026-06-14 09:27:42 +08:00
Nanaloveyuki ded41de176 📝 clarify async logger restart failure semantics 2026-06-14 09:25:56 +08:00
Nanaloveyuki c12af269dc 📝 clarify configured logger composition surface 2026-06-14 09:19:51 +08:00
Nanaloveyuki 03079797f2 📝 clarify async projection runtime helper state 2026-06-14 09:17:38 +08:00
Nanaloveyuki f6fc9c4201 📝 clarify sync projection runtime helper state 2026-06-14 09:15:04 +08:00
Nanaloveyuki d94b415cb0 📝 clarify async runtime builder flush behavior 2026-06-14 09:10:25 +08:00
Nanaloveyuki 4dd1e5a33d 📝 clarify async build config sink-kind semantics 2026-06-14 09:04:38 +08:00
Nanaloveyuki 54e695942f 📝 clarify async build export sink-kind semantics 2026-06-14 09:03:07 +08:00
Nanaloveyuki 4889f2b4b8 📝 clarify text async facade sink-kind behavior 2026-06-14 09:00:52 +08:00
Nanaloveyuki 58d1e622a0 📝 clarify async text builder sink-kind behavior 2026-06-14 08:58:57 +08:00
Nanaloveyuki af1fc95a3c 📝 clarify application helper visibility 2026-06-14 08:52:54 +08:00
Nanaloveyuki 20919618ce 📝 clarify library helper visibility 2026-06-14 08:50:34 +08:00
Nanaloveyuki 35e87f1761 📝 clarify application runtime helper access 2026-06-14 08:48:22 +08:00
Nanaloveyuki 4b043bc52e 📝 clarify application context shape 2026-06-14 08:46:07 +08:00
Nanaloveyuki 296901d2ba 📝 clarify root context shape contrast 2026-06-14 08:43:18 +08:00
Nanaloveyuki c78669393c 📝 clarify async facade context shape 2026-06-14 08:41:06 +08:00
Nanaloveyuki 9162b37acd 📝 clarify root logger target behavior 2026-06-14 08:38:38 +08:00
Nanaloveyuki d777496d51 📝 clarify sync alias target behavior 2026-06-14 08:35:46 +08:00
Nanaloveyuki 44903d113d 📝 clarify async alias target behavior 2026-06-14 08:32:58 +08:00
Nanaloveyuki feb786298f 📝 clarify built async text target behavior 2026-06-14 08:29:26 +08:00
Nanaloveyuki f91bcc827e cover built async text target behavior 2026-06-14 08:28:09 +08:00
Nanaloveyuki da1e9c0359 📝 clarify built async target behavior 2026-06-14 08:22:02 +08:00
Nanaloveyuki acb4a13f14 cover built async target behavior 2026-06-14 08:20:12 +08:00
Nanaloveyuki b320b4f1fc 📝 clarify built sync target behavior 2026-06-14 08:16:52 +08:00
Nanaloveyuki 8715949242 cover built sync target behavior 2026-06-14 08:15:00 +08:00
Nanaloveyuki 70c2bad4b1 📝 clarify parsed configured target behavior 2026-06-14 08:11:41 +08:00
Nanaloveyuki 83b0a2ac3c cover parsed configured target behavior 2026-06-14 08:10:27 +08:00
Nanaloveyuki 5ded4439b0 📝 clarify parsed sync target behavior 2026-06-14 08:08:00 +08:00
Nanaloveyuki 556ee967b8 cover parsed sync target behavior 2026-06-14 08:06:35 +08:00
Nanaloveyuki 72e29f5b69 📝 clarify parsed async target behavior 2026-06-14 08:03:26 +08:00
Nanaloveyuki 57b1dfb127 cover parsed lib async target behavior 2026-06-14 08:01:39 +08:00
Nanaloveyuki 4521961227 cover parsed app async target behavior 2026-06-14 07:58:20 +08:00
Nanaloveyuki a164678654 cover application async target behavior 2026-06-14 07:53:31 +08:00
Nanaloveyuki 50afeacff6 📝 align English README target status 2026-06-14 07:48:40 +08:00
Nanaloveyuki 98e514821f 📝 clarify application async alias behavior 2026-06-14 07:46:24 +08:00
Nanaloveyuki 97feb57cd7 📝 clarify configured logger alias behavior 2026-06-14 07:44:01 +08:00
Nanaloveyuki 9c1787e700 📝 clarify application logger alias behavior 2026-06-14 07:41:51 +08:00
Nanaloveyuki 7ba66e82ae cover sync target override behavior 2026-06-14 07:39:23 +08:00
Nanaloveyuki c316c65027 cover async target override behavior 2026-06-14 07:36:03 +08:00
Nanaloveyuki 4b68ae27f1 📝 clarify async target override docs 2026-06-14 07:34:31 +08:00
Nanaloveyuki 38a5b056b8 📝 clarify async helper combinator docs 2026-06-14 07:25:15 +08:00
Nanaloveyuki f636c2df60 📝 clarify async reshaping helper docs 2026-06-14 07:24:00 +08:00
Nanaloveyuki a66a876659 cover async logger reshaping helpers 2026-06-14 07:22:23 +08:00
Nanaloveyuki f8192ab677 cover async helper combinators 2026-06-14 07:19:59 +08:00
Nanaloveyuki 82200c61fb 📝 align async alias docs 2026-06-14 07:15:46 +08:00
Nanaloveyuki 43e343158e 📝 clarify root async builder docs 2026-06-14 07:14:25 +08:00
Nanaloveyuki 3a2817969a 📝 clarify async text builder docs 2026-06-14 07:12:53 +08:00
Nanaloveyuki 1b508dfc11 📝 clarify async builder equivalence docs 2026-06-14 07:11:32 +08:00
Nanaloveyuki 959e553648 📝 clarify async facade projection docs 2026-06-14 07:09:31 +08:00
Nanaloveyuki 1f3da3e6ba 📝 align async state serializer docs 2026-06-14 07:06:16 +08:00
Nanaloveyuki a2b37dfe53 📝 clarify async state snapshot docs 2026-06-14 07:04:55 +08:00
Nanaloveyuki 6c5f4aaa0e 📝 clarify async failure backlog docs 2026-06-14 07:01:04 +08:00
Nanaloveyuki 3ba590a4b0 📝 clarify async shutdown split docs 2026-06-14 06:58:56 +08:00
Nanaloveyuki 302e11218c 📝 sharpen closed async log docs 2026-06-14 06:57:12 +08:00
Nanaloveyuki b2a1f407ec cover async closed log runtime split 2026-06-14 06:55:48 +08:00
Nanaloveyuki 24c20c16be 📝 narrow async facade semantics docs 2026-06-14 06:53:17 +08:00
Nanaloveyuki 254a9e8086 cover async text shutdown flush path 2026-06-14 06:51:27 +08:00
Nanaloveyuki 0d2bfd7b6d cover async text batch flush path 2026-06-14 06:49:05 +08:00
Nanaloveyuki b570722434 📝 clarify text async flush docs 2026-06-14 06:44:51 +08:00
Nanaloveyuki 58e219a512 cover parsed lib clear shutdown 2026-06-14 06:40:06 +08:00
Nanaloveyuki 88ee050529 cover parsed app clear shutdown 2026-06-14 06:38:18 +08:00
Nanaloveyuki 6962c7f267 cover lib async clear shutdown 2026-06-14 06:35:14 +08:00
Nanaloveyuki e91aba10fc cover app async clear shutdown 2026-06-14 06:33:29 +08:00
Nanaloveyuki 74f65b9585 cover lib text clear shutdown 2026-06-14 06:31:07 +08:00
Nanaloveyuki 5f722b0073 cover app text clear shutdown 2026-06-14 06:28:54 +08:00
Nanaloveyuki fd9127c524 cover app text async composition 2026-06-14 06:24:17 +08:00
Nanaloveyuki 057eb7c1fc cover parsed app async composition 2026-06-14 06:20:56 +08:00
Nanaloveyuki 24897a45c9 cover app text alias sink-kind path 2026-06-14 06:17:00 +08:00
Nanaloveyuki 9370c83ffc cover async facade clear shutdown 2026-06-14 06:13:06 +08:00
Nanaloveyuki 3a2d1d74c3 cover async text facade sink-kind path 2026-06-14 06:08:58 +08:00
Nanaloveyuki ef4ffe4c5b cover async projection file parity 2026-06-14 06:05:34 +08:00
Nanaloveyuki f69307ba61 cover async library file parity 2026-06-14 05:57:05 +08:00
Nanaloveyuki 2582876a08 cover async application file parity 2026-06-14 05:52:23 +08:00
Nanaloveyuki bf60ed95c3 cover projected file helper parity 2026-06-14 05:48:20 +08:00
Nanaloveyuki 39baad5e93 cover library file control parity 2026-06-14 05:45:54 +08:00
Nanaloveyuki 383d2e8dff cover library file helper parity 2026-06-14 05:43:33 +08:00
Nanaloveyuki b57013202e cover parse-build invalid json parity 2026-06-14 05:40:51 +08:00
Nanaloveyuki 13e8a8f484 cover application file control parity 2026-06-14 05:38:44 +08:00
Nanaloveyuki 3cd361dc4d cover application file helper parity 2026-06-14 05:36:19 +08:00
Nanaloveyuki c262aefc28 cover configured file state parity 2026-06-14 05:33:39 +08:00
Nanaloveyuki a195c93457 cover configured file reset parity 2026-06-14 05:28:52 +08:00
Nanaloveyuki efacaa6398 cover configured reopen parity 2026-06-14 05:27:09 +08:00
Nanaloveyuki af54743536 cover configured file setter parity 2026-06-14 05:25:32 +08:00
Nanaloveyuki 8d28c37e38 cover configured file policy parity 2026-06-14 05:23:56 +08:00
Nanaloveyuki c2fa1eea35 cover configured file close parity 2026-06-14 05:21:19 +08:00
Nanaloveyuki d2612e5a67 cover configured file helper parity 2026-06-14 05:19:55 +08:00
Nanaloveyuki 49eaa67d57 cover configured close delegation 2026-06-14 05:17:55 +08:00
Nanaloveyuki 6d98301daa cover configured helper delegation 2026-06-14 05:16:34 +08:00
Nanaloveyuki 5f46c04eea cover async text application parity 2026-06-14 05:14:11 +08:00
Nanaloveyuki b36ce021dc cover async text library parity 2026-06-14 05:12:19 +08:00
Nanaloveyuki ead2339ffd cover async library parse-build parity 2026-06-14 05:10:13 +08:00
Nanaloveyuki cca3bc4500 cover async library builder parity 2026-06-14 05:08:23 +08:00
Nanaloveyuki 9d55440df4 cover sync library parse-build parity 2026-06-14 05:06:08 +08:00
Nanaloveyuki 4077f3e538 cover sync library builder parity 2026-06-14 05:04:38 +08:00
Nanaloveyuki 658196cf69 cover sync parse-build parity 2026-06-14 05:03:02 +08:00
Nanaloveyuki afe5c75537 cover async parse-build parity 2026-06-14 05:01:42 +08:00
Nanaloveyuki 2c552adfae cover async application builder parity 2026-06-14 04:59:41 +08:00
Nanaloveyuki 1f61e4f66a cover sync application builder parity 2026-06-14 04:57:42 +08:00
Nanaloveyuki b68ca9eef5 cover sync config export defaults 2026-06-14 04:51:52 +08:00
Nanaloveyuki 9da5b9d659 cover async build-config export defaults 2026-06-14 04:50:09 +08:00
Nanaloveyuki e97407c22c cover sync nested parser defaults 2026-06-14 04:47:21 +08:00
Nanaloveyuki 41fbfa5ac1 cover sync parser defaults 2026-06-14 04:45:43 +08:00
Nanaloveyuki 5c4e8ba889 cover async text builder sink contract 2026-06-14 04:44:18 +08:00
Nanaloveyuki c96a592ef6 cover async build-config defaults 2026-06-14 04:39:40 +08:00
Nanaloveyuki eb4abeb81b cover async parser aliases 2026-06-14 04:34:18 +08:00
Nanaloveyuki e2609a99b6 cover async config normalization 2026-06-14 04:28:29 +08:00
Nanaloveyuki 2709a482ec cover async parse-build failures 2026-06-14 04:21:15 +08:00
Nanaloveyuki 999dd5a297 cover sync parse-build config errors 2026-06-14 04:16:42 +08:00
Nanaloveyuki bd75deb879 cover runtime file json helpers 2026-06-14 04:11:57 +08:00
Nanaloveyuki db5d95576d cover config json helper exports 2026-06-14 04:08:18 +08:00
Nanaloveyuki f4af7bc04c cover async json helper exports 2026-06-14 04:06:23 +08:00
Nanaloveyuki 8ce3b8fefd cover direct async text builder surface 2026-06-14 03:59:06 +08:00
Nanaloveyuki da8e7483ed cover direct async builder surface 2026-06-14 03:55:41 +08:00
Nanaloveyuki e617f5bf6a 🔖 bump version to 0.5.3 2026-06-14 03:53:17 +08:00
Nanaloveyuki 9042562c54 🔖 prepare 0.5.3 release notes 2026-06-14 03:50:23 +08:00
Nanaloveyuki 4c5b6cc2cf cover library async text helpers 2026-06-14 03:44:21 +08:00
Nanaloveyuki 1bb3e41a97 cover parsed library async helpers 2026-06-14 03:42:28 +08:00
Nanaloveyuki 03e467d5f4 cover parsed application async helpers 2026-06-14 03:40:31 +08:00
Nanaloveyuki 8c129e1c42 cover parsed library logger unwrap 2026-06-14 03:35:52 +08:00
Nanaloveyuki a7e955e636 cover parsed application logger helpers 2026-06-14 03:34:09 +08:00
Nanaloveyuki 2eebef07db cover parsed configured logger surface 2026-06-14 03:32:39 +08:00
Nanaloveyuki 594be4aca5 cover configured logger composition 2026-06-14 03:30:18 +08:00
Nanaloveyuki 9157346bf8 cover application alias composition 2026-06-14 03:26:36 +08:00
Nanaloveyuki 7474c07cbd cover application text async alias 2026-06-14 03:23:03 +08:00
Nanaloveyuki 11db21a500 cover application alias helper surface 2026-06-14 03:21:09 +08:00
Nanaloveyuki 17dbdfffde cover library async constructor contract 2026-06-14 03:18:06 +08:00
Nanaloveyuki a41515b8eb cover library async write facade 2026-06-14 03:15:44 +08:00
Nanaloveyuki 2f600c71c5 cover library async target facade 2026-06-14 03:13:45 +08:00
Nanaloveyuki e6dc5f52c0 cover library async context facade 2026-06-14 03:11:34 +08:00
Nanaloveyuki 7a0c741857 cover default library logger snapshot 2026-06-14 03:07:46 +08:00
Nanaloveyuki e3b9cbb6e9 cover library logger write facade 2026-06-14 03:06:14 +08:00
Nanaloveyuki 264ee05222 cover library logger target facade 2026-06-14 03:04:23 +08:00
Nanaloveyuki 9ef2f984ca cover library logger context facade 2026-06-14 03:02:47 +08:00
Nanaloveyuki 6a13f70242 📝 clarify library logger runtime unwrap 2026-06-14 02:58:58 +08:00
Nanaloveyuki c767ebe512 📝 refresh wasm-gc sync verification note 2026-06-14 02:54:20 +08:00
Nanaloveyuki 402cb7e423 📝 refresh wasm sync verification note 2026-06-14 02:52:38 +08:00
Nanaloveyuki 24fbc189ae 📝 refresh js sync verification note 2026-06-14 02:51:19 +08:00
Nanaloveyuki 935f320743 📝 refresh wasm async verification note 2026-06-14 02:49:52 +08:00
Nanaloveyuki d288216bcb 📝 refresh async target verification note 2026-06-14 02:48:30 +08:00
Nanaloveyuki c3b54e3ae0 align async facade test across targets 2026-06-14 02:47:06 +08:00
Nanaloveyuki 413610d5f2 cover async facade state projection 2026-06-14 02:45:15 +08:00
Nanaloveyuki 33f71af500 📝 clarify queued file failure docs 2026-06-14 02:42:37 +08:00
Nanaloveyuki f7863083d4 cover runtime file reopen helpers 2026-06-14 02:40:35 +08:00
Nanaloveyuki fcac1d7993 cover runtime file helper contracts 2026-06-14 02:37:56 +08:00
Nanaloveyuki 0bdbe942dd cover runtime sink helper contracts 2026-06-14 02:35:27 +08:00
Nanaloveyuki 1645bcb66c cover parsed async queue wrappers 2026-06-14 02:30:41 +08:00
Nanaloveyuki 112edbf8ae cover direct text async builder queue path 2026-06-14 02:28:46 +08:00
Nanaloveyuki 8084d0a0cc cover library text async builder path 2026-06-14 02:27:05 +08:00
Nanaloveyuki d23e326315 cover application text async builder path 2026-06-14 02:25:11 +08:00
Nanaloveyuki e38b0b4150 cover application async builder queue path 2026-06-14 02:22:49 +08:00
Nanaloveyuki ce89aaf96d cover library async failure flow 2026-06-14 02:19:40 +08:00
Nanaloveyuki c53aa38b89 cover async shutdown failure path 2026-06-14 02:17:23 +08:00
Nanaloveyuki 3e0c8a5f99 cover async retry reset behavior 2026-06-14 02:14:49 +08:00
Nanaloveyuki f1250512eb 📝 clarify async status helper docs 2026-06-14 02:11:26 +08:00
Nanaloveyuki 26de2e81d6 📝 refine async state snapshot docs 2026-06-14 02:09:29 +08:00
Nanaloveyuki 9af489336d 🐛 fix closed async backlog accounting 2026-06-14 02:06:59 +08:00
Nanaloveyuki d9b609d064 📝 clarify async lifecycle docs 2026-06-14 02:03:34 +08:00
Nanaloveyuki 91b0900b11 📝 refine async snapshot docs 2026-06-14 01:59:27 +08:00
Nanaloveyuki e13ea2209a 📝 clarify async config type docs 2026-06-14 01:58:06 +08:00
Nanaloveyuki 034bd8fc99 📝 clarify async policy docs 2026-06-14 01:56:44 +08:00
Nanaloveyuki c911ad8345 📝 refine async runtime helper docs 2026-06-14 01:55:23 +08:00
Nanaloveyuki e776455861 📝 refine async state type docs 2026-06-14 01:53:12 +08:00
Nanaloveyuki f824cbfa68 📝 clarify async state export docs 2026-06-14 01:50:30 +08:00
Nanaloveyuki abed0f00b8 📝 refine async config parser docs 2026-06-14 01:48:35 +08:00
Nanaloveyuki 75f1b457fa 📝 clarify async config export docs 2026-06-14 01:47:02 +08:00
Nanaloveyuki e53555edb2 📝 clarify formatter export docs 2026-06-14 01:45:18 +08:00
Nanaloveyuki 323d58059b 📝 refine formatter config docs 2026-06-14 01:43:27 +08:00
Nanaloveyuki 8e409bbdf9 📝 refine config stringify docs 2026-06-14 01:42:16 +08:00
Nanaloveyuki b3fb59e6b5 📝 clarify config export docs 2026-06-14 01:41:02 +08:00
Nanaloveyuki a6e9dd993f 📝 refine config helper docs 2026-06-14 01:39:40 +08:00
Nanaloveyuki 324ff47b10 📝 clarify config model docs 2026-06-14 01:38:16 +08:00
Nanaloveyuki 8bf6ec8f52 📝 refine config entry docs 2026-06-14 01:36:33 +08:00
Nanaloveyuki bf545b0e0c 📝 clarify default logger docs 2026-06-14 01:35:25 +08:00
Nanaloveyuki d4db20fffc 📝 clarify library builder docs 2026-06-14 01:34:08 +08:00
Nanaloveyuki 861adb7b5d 📝 refine sync parse builder docs 2026-06-14 01:33:06 +08:00
Nanaloveyuki 1a33f5c95d 📝 refine async parse builder docs 2026-06-14 01:31:32 +08:00
Nanaloveyuki f50964f5a9 📝 clarify async text builder docs 2026-06-14 01:30:22 +08:00
Nanaloveyuki ba72a021a8 📝 align async logger root docs 2026-06-14 01:28:23 +08:00
Nanaloveyuki 5f0c7be4e4 📝 clarify app text async docs 2026-06-14 01:24:48 +08:00
Nanaloveyuki 4d2e3def14 📝 align facade overview docs 2026-06-14 01:23:15 +08:00
Nanaloveyuki 2a9dbdcc01 📝 clarify app logger facade docs 2026-06-14 01:21:42 +08:00
Nanaloveyuki 47de8dc99a 📝 refine library logger entry docs 2026-06-14 01:20:04 +08:00
Nanaloveyuki 888be5b6fc 📝 refine library logger write docs 2026-06-14 01:18:33 +08:00
Nanaloveyuki 15a9175cf4 📝 refine library logger wrapper methods 2026-06-14 01:16:36 +08:00
Nanaloveyuki 3a68be920e 📝 clarify library logger wrapper docs 2026-06-14 01:14:45 +08:00
Nanaloveyuki 699dd5ff96 📝 refine library async lifecycle docs 2026-06-14 01:11:52 +08:00
Nanaloveyuki f8ca093e95 📝 refine library async write docs 2026-06-14 01:10:10 +08:00
Nanaloveyuki be8b4f8626 📝 refine library async wrapper methods 2026-06-14 01:08:21 +08:00
Nanaloveyuki 3d88ac87a1 📝 clarify library async wrapper docs 2026-06-14 01:06:15 +08:00
Nanaloveyuki 79529d748f 📝 clarify app async build docs 2026-06-14 01:04:11 +08:00
Nanaloveyuki ab7cd62851 📝 refine library async build docs 2026-06-14 01:02:23 +08:00
Nanaloveyuki e172e141e9 📝 align async facade builders 2026-06-14 00:57:38 +08:00
Nanaloveyuki d92155a727 📝 align async text facade docs 2026-06-14 00:56:01 +08:00
Nanaloveyuki 78007a5b22 📝 align async facade docs 2026-06-14 00:54:13 +08:00
Nanaloveyuki e28dba0800 📝 clarify async logger root docs 2026-06-14 00:52:34 +08:00
Nanaloveyuki 747f8e3d1b 📝 refine async run docs 2026-06-14 00:50:55 +08:00
Nanaloveyuki 5917256fda 📝 refine async shutdown docs 2026-06-14 00:48:44 +08:00
Nanaloveyuki b937807136 📝 clarify async backlog docs 2026-06-14 00:47:07 +08:00
Nanaloveyuki 5ce4c29760 📝 refine async failure helper docs 2026-06-14 00:45:31 +08:00
Nanaloveyuki 911dcd840c 📝 align async logger state docs 2026-06-14 00:43:50 +08:00
Nanaloveyuki 4431e01dcb 📝 align async runtime serialization docs 2026-06-14 00:41:09 +08:00
Nanaloveyuki d950f11b40 📝 clarify async runtime snapshots 2026-06-14 00:38:07 +08:00
Nanaloveyuki 2845076b27 📝 refine async policy docs 2026-06-14 00:33:59 +08:00
Nanaloveyuki 7207c07cbf 📝 clarify async config docs 2026-06-14 00:32:05 +08:00
Nanaloveyuki f19e9649af 📝 align async build config docs 2026-06-14 00:29:22 +08:00
Nanaloveyuki c45ee1050f 📝 clarify async build config consumers 2026-06-14 00:27:38 +08:00
Nanaloveyuki d748bfc6e6 📝 clarify async text builder path 2026-06-14 00:23:06 +08:00
Nanaloveyuki 77d97cef5e 📝 align async facade builder docs 2026-06-14 00:20:44 +08:00
Nanaloveyuki 3888d39bc0 📝 clarify async builder layering docs 2026-06-14 00:18:58 +08:00
Nanaloveyuki f7febb9f67 📝 fix async constructor signature docs 2026-06-14 00:16:08 +08:00
Nanaloveyuki 7440f1d328 📝 align remaining async examples 2026-06-14 00:14:19 +08:00
Nanaloveyuki e035625fc1 📝 align async write examples 2026-06-14 00:12:51 +08:00
Nanaloveyuki dae9d38a3f 📝 align async lifecycle examples 2026-06-14 00:10:59 +08:00
Nanaloveyuki ab45a9e4e3 📝 fix async alias examples 2026-06-14 00:09:42 +08:00
Nanaloveyuki e27f5680e6 📝 clarify async shutdown runtime docs 2026-06-14 00:08:24 +08:00
Nanaloveyuki 8196fe27b6 📝 clarify async text builder docs 2026-06-14 00:06:05 +08:00
Nanaloveyuki 4b5456f646 📝 clarify async library facade scope 2026-06-14 00:04:02 +08:00
Nanaloveyuki f4022ce95c 📝 clarify library facade runtime scope 2026-06-14 00:01:39 +08:00
144 changed files with 892 additions and 2963 deletions
+2 -10
View File
@@ -67,15 +67,9 @@ jobs:
moon check --deny-warn
moon test --deny-warn
moon check --target native --deny-warn
moon check --target wasm --deny-warn
moon check --target wasm-gc --deny-warn
moon check --target js --deny-warn
- name: Test wasm path
shell: bash
run: |
moon test --target wasm --deny-warn
- name: Run sync example
shell: bash
run: |
@@ -111,17 +105,15 @@ jobs:
run: |
moon update
- name: Check async package on wasm, wasm-gc and js
- name: Check async package on wasm-gc and js
shell: bash
run: |
moon check src-async --target wasm --deny-warn
moon check src-async --target wasm-gc --deny-warn
moon check src-async --target js --deny-warn
- name: Test async package on wasm, wasm-gc and js
- name: Test async package on wasm-gc and js
shell: bash
run: |
moon test src-async --target wasm --deny-warn
moon test src-async --target wasm-gc --deny-warn
moon test src-async --target js --deny-warn
-3
View File
@@ -26,8 +26,5 @@ docs/.vitepress/cache/
docs/.vitepress/dist/
docs/.vitepress/generated/
# Benchmark package has no public API; moon info emits an empty snapshot.
benchmarks/pkg.generated.mbti
# vibecoding
AGENTS/
-26
View File
@@ -1,26 +0,0 @@
# Agent Notes
## Version Releases
Do not update package versions and installation snippets by hand. Run the
repository release helper from the root instead:
```powershell
.\scripts\update-version.ps1 <x.x.x>
```
For a normal patch release, the script updates `moon.mod`, all current
installation examples, creates the next `docs/changes/<release>(<version>).md`,
and adds it to `docs/changes/index.md`. Review and replace any generated `TODO`
entries in the release note before committing.
For a major or minor package-version change, supply the public release number
explicitly so it is not inferred:
```powershell
.\scripts\update-version.ps1 <x.x.x> -ReleaseVersion <x.x.x>
```
Use `-WhatIf` to preview the target files. Pass `-Change`, `-Verification`,
and `-Note` to populate release-note sections directly. Validate the edited
repository before committing with a `gitmoji + short desc` message.
+30 -45
View File
@@ -6,56 +6,34 @@ BitLogger 是一个使用 MoonBit 编写的结构化日志库,适合命令行
- [English README](./docs/README-en.md)
- [Wiki](https://bitlogger.naloveyuki.top)
## 从第一个日志到扩展能力
## 介绍
BitLogger 的文档按使用路径组织:先完成一个可运行的日志输出,再按需进入文件、配置、异步和组合能力。API 文档保留为精确参考,不需要从 API 文件名开始阅读
BitLogger 提供统一的日志级别、目标名、结构化字段和可定制格式,既可以直接输出到控制台,也可以在 native 环境写入文件,并提供异步日志版本
### 1. 安装
从一个空项目开始:
```bash
moon new log-demo
cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.3
```
### 2. 写入第一条结构化日志
在应用 package 的 `moon.pkg` 中导入库:
## 快速开始
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
}
let logger = build_logger(
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
)
logger.info("starting", fields=[field("port", "8080")])
ignore(logger.flush())
```
然后在 `main.mbt` 中创建控制台 logger
推荐从 `console(...)``json_console(...)``text_console(...)``file(...)` 这几个入口开始,再按需配合 `with_queue(...)``with_file_rotation(...)`
```moonbit
fn main {
let logger = @log.build_logger(
@log.console(min_level=@log.Level::Info, target="app"),
)
logger.info("server started", fields=[
@log.field("port", "8080"),
@log.field("environment", "development"),
])
}
```
运行 `moon run` 后,记录会携带 level、target、message 和 fields。下一步按实际需求选择:
- [控制台与结构化字段](./docs/examples/console.md)
- [文件输出与轮转](./docs/examples/file-rotation.md)
- [JSON 配置构建](./docs/examples/config.md)
- [异步日志生命周期](./docs/examples/async.md)
如果你需要自己组合 sink,比如 `fanout``split``callback`,再使用 `Logger::new(...)`
## 支持情况
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native``wasm``js``wasm-gc`
- `bitlogger_async` 当前在 CI 中检查和测试 `native``wasm``js``wasm-gc`
- `BitLogger` 当前在 CI 中检查/验证的目标是 `native``js``wasm-gc`
- `bitlogger_async` 当前在 CI 中检查 `native``js``wasm-gc`,测试覆盖 `native``js``wasm-gc`
- `wasm` 目标在源码 `moon.pkg` 中保留声明,但当前未纳入 CI 验证口径
- `llvm` 目前按实验性目标处理,当前环境未完成验证
- 文件输出是 native 能力;跨端代码里建议先判断 `native_files_supported()`
- `src-async` 可用,但示例 `examples/async_basic` 目前仍按 native 入口提供
@@ -68,13 +46,20 @@ fn main {
- 配置驱动构建:`build_logger(...)``build_async_logger(...)`
- 组合能力:queue、filter、patch、fanout、split、callback
- 异步日志:独立 `src-async` package
- Trace context:标准 `trace_id``span_id``trace_flags``trace_state` 字段绑定
## 示例
- `examples/console_basic/`:最小 console / json console 示例
- `examples/text_formatter/`:文本格式与模板示例
- `examples/style_tags/`style tag 与彩色输出示例
- `examples/config_build/`:配置构建示例
- `examples/presets/`:常用预设组合示例
- `examples/file_rotation/`:文件输出与轮转示例,仅适用于 native
- `examples/async_basic/`:异步日志示例
## 文档
- [Examples:完整使用流程](./docs/examples/index.md)
- [Extend:队列、组合、格式化和跨端边界](./docs/extend/index.md)
- [API 索引:按公开符号查询](./docs/api/index.md)
- [API 索引](./docs/api/index.md)
- [src package README](./src/README.mbt.md)
仓库内的 `examples/` 目录与 Examples 文档一一对应;文档解释选择、前提、运行命令和后续扩展,源码保持为可执行参考。
常用入口:`text_console(...)``file(...)``with_queue(...)``build_logger(...)``build_async_logger(...)`
-11
View File
@@ -1,11 +0,0 @@
# BitLogger Benchmarks
Run the native baseline from the repository root:
```powershell
moon bench benchmarks --target native --release --deny-warn
```
The suite measures callback emission, trace-context field binding, synchronous queue enqueue-and-flush, and native file writes. It intentionally has no pass/fail threshold: compare results only on the same machine, toolchain, target, and power profile.
Async throughput remains lifecycle-sensitive and is exercised by the native integration test rather than mixed into these synchronous microbenchmarks. Record its end-to-end measurements separately when profiling an application workload.
-61
View File
@@ -1,61 +0,0 @@
///|
let benchmark_record : @log.Record = @log.Record::new(
@log.Level::Info,
"benchmark record",
target="bench",
fields=[@log.field("component", "bitlogger")],
)
///|
test "bench callback logger emission" (it : @bench.T) {
let writes : Ref[Int] = Ref(0)
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 }))
it.bench(fn() {
logger.info("benchmark record", fields=[
@log.field("component", "bitlogger"),
])
it.keep(writes.val)
})
}
///|
test "bench trace context field binding" (it : @bench.T) {
let writes : Ref[Int] = Ref(0)
let context = @log.trace_context("trace-bench", "span-bench")
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 })).with_trace_context(
context,
)
it.bench(fn() {
logger.info("benchmark record", fields=[@log.field("event", "request")])
it.keep(writes.val)
})
}
///|
test "bench queued logger enqueue and flush" (it : @bench.T) {
let writes : Ref[Int] = Ref(0)
let logger = @log.Logger::new(@log.callback_sink(fn(_) { writes.val += 1 })).with_queue(
max_pending=8,
)
it.bench(fn() {
logger.info("benchmark record")
it.keep(logger.sink.flush())
})
}
///|
test "bench native file sink write" (it : @bench.T) {
let sink = @log.file_sink(
"logs/bitlogger-benchmark.log",
append=false,
auto_flush=false,
formatter=fn(rec) { rec.message },
)
if sink.is_available() {
it.bench(fn() {
sink.write(benchmark_record)
it.keep(sink.write_failures())
})
ignore(sink.close())
}
}
-4
View File
@@ -1,4 +0,0 @@
import {
"Nanaloveyuki/BitLogger/src" @log,
"moonbitlang/core/bench",
}
+22 -124
View File
@@ -104,119 +104,6 @@ function buildChangesSidebar(): DefaultTheme.SidebarItem[] {
]
}
function buildExamplesSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: 'Overview', link: '/examples/' },
{ text: 'Console And Fields', link: '/examples/console' },
{ text: 'File Rotation', link: '/examples/file-rotation' },
{ text: 'Configuration', link: '/examples/config' },
{ text: 'Async Lifecycle', link: '/examples/async' },
]
}
function buildExtendSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: 'Overview', link: '/extend/' },
{ text: 'Queueing', link: '/extend/queue' },
{ text: 'Sink Composition', link: '/extend/composition' },
{ text: 'Text Formatting', link: '/extend/formatting' },
{ text: 'Target Boundaries', link: '/extend/targets' },
{ text: 'Trace Context', link: '/extend/observability' },
]
}
function buildChineseExamplesSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: '概览', link: '/zh/examples/' },
{ text: '控制台与结构化字段', link: '/zh/examples/console' },
{ text: '文件输出与轮转', link: '/zh/examples/file-rotation' },
{ text: '配置驱动构建', link: '/zh/examples/config' },
{ text: '异步日志生命周期', link: '/zh/examples/async' },
]
}
function buildChineseExtendSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: '概览', link: '/zh/extend/' },
{ text: '队列与溢出策略', link: '/zh/extend/queue' },
{ text: 'Sink 组合', link: '/zh/extend/composition' },
{ text: '文本格式与样式', link: '/zh/extend/formatting' },
{ text: '目标平台边界', link: '/zh/extend/targets' },
{ text: 'Trace Context', link: '/zh/extend/observability' },
]
}
function buildChineseApiSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: '概览', link: '/zh/api/' },
{ text: '基础记录', link: '/zh/api/basics' },
{ text: '配置与队列', link: '/zh/api/configuration' },
{ text: '文件输出', link: '/zh/api/file-output' },
{ text: '文本格式', link: '/zh/api/formatting' },
{ text: '异步生命周期', link: '/zh/api/async' },
{ text: 'Sink 组合', link: '/zh/api/composition' },
]
}
const englishThemeConfig: DefaultTheme.Config = {
siteTitle: 'BitLogger',
nav: [
{ text: 'Home', link: '/' },
{ text: 'Examples', link: '/examples/' },
{ text: 'Extend', link: '/extend/' },
{ text: 'API', link: '/api/' },
{ text: 'Changes', link: '/changes/' },
{ text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' },
],
search: {
provider: 'local',
},
socialLinks: [{ icon: 'github', link: repository }],
editLink: {
pattern: `${repository}/edit/main/docs/:path`,
text: 'Edit this page on GitHub',
},
sidebar: {
'/examples/': buildExamplesSidebar(),
'/extend/': buildExtendSidebar(),
'/api/': buildApiSidebar(),
'/changes/': buildChangesSidebar(),
},
footer: {
message: 'Published from the repository docs folder with VitePress.',
copyright: 'MIT',
},
}
const chineseThemeConfig: DefaultTheme.Config = {
siteTitle: 'BitLogger',
nav: [
{ text: '首页', link: '/zh/' },
{ text: '示例', link: '/zh/examples/' },
{ text: '扩展', link: '/zh/extend/' },
{ text: 'API', link: '/zh/api/' },
{ text: '更新记录(英文)', link: '/changes/' },
{ text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' },
],
search: {
provider: 'local',
},
socialLinks: [{ icon: 'github', link: repository }],
editLink: {
pattern: `${repository}/edit/main/docs/:path`,
text: '在 GitHub 上编辑此页',
},
sidebar: {
'/zh/examples/': buildChineseExamplesSidebar(),
'/zh/extend/': buildChineseExtendSidebar(),
'/zh/api/': buildChineseApiSidebar(),
},
footer: {
message: '由仓库中的 VitePress 文档构建。',
copyright: 'MIT',
},
}
export default defineConfig({
title: 'BitLogger',
description: 'Structured logging library docs for MoonBit.',
@@ -227,18 +114,29 @@ export default defineConfig({
markdown: {
languages: [createMoonbitLanguageRegistration()],
},
themeConfig: englishThemeConfig,
locales: {
root: {
label: 'English',
lang: 'en-US',
themeConfig: englishThemeConfig,
themeConfig: {
siteTitle: 'BitLogger',
nav: [
{ text: 'Home', link: '/' },
{ text: 'API', link: '/api/' },
{ text: 'Changes', link: '/changes/' },
{ text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' },
],
search: {
provider: 'local',
},
zh: {
label: '简体中文',
lang: 'zh-CN',
link: '/zh/',
themeConfig: chineseThemeConfig,
socialLinks: [{ icon: 'github', link: repository }],
editLink: {
pattern: `${repository}/edit/main/docs/:path`,
text: 'Edit this page on GitHub',
},
sidebar: {
'/api/': buildApiSidebar(),
'/changes/': buildChangesSidebar(),
},
footer: {
message: 'Published from the repository docs folder with VitePress.',
copyright: 'MIT',
},
},
})
+1 -36
View File
@@ -6,46 +6,11 @@ import HomeDocHub from './components/HomeDocHub.vue'
import './custom.css'
const localePreferenceKey = 'bitlogger-docs-locale'
function routePath(path: string): string {
return path.split(/[?#]/, 1)[0]
}
function isLocaleRoot(path: string): boolean {
const normalized = routePath(path)
return normalized === '/' || normalized === '/zh/'
}
function browserPrefersChinese(): boolean {
return navigator.languages.some(language => language.toLowerCase().startsWith('zh'))
}
const theme: Theme = {
extends: DefaultTheme,
enhanceApp({ app, router }) {
enhanceApp({ app }) {
app.component('ApiOverview', ApiOverview)
app.component('HomeDocHub', HomeDocHub)
if (typeof window === 'undefined') return
router.onAfterRouteChange = to => {
if (!isLocaleRoot(to)) return
window.localStorage.setItem(localePreferenceKey, routePath(to) === '/zh/' ? 'zh' : 'en')
}
if (router.route.path !== '/') return
const savedLocale = window.localStorage.getItem(localePreferenceKey)
const locale = savedLocale === 'zh' || savedLocale === 'en'
? savedLocale
: browserPrefersChinese()
? 'zh'
: 'en'
if (locale === 'zh') {
void router.go('/zh/')
}
},
}
+24 -31
View File
@@ -9,42 +9,28 @@ BitLogger is a structured logging library for MoonBit projects.
BitLogger gives you consistent levels, targets, structured fields, configurable text output, native file logging, and an async logging layer.
## Start With A Working Flow
The documentation is organized around complete usage flows. Start with an executable logger, then move into file output, configuration, async lifecycle, and extensions. The API reference remains the precise symbol-level reference.
### 1. Install
```bash
moon new log-demo
cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.3
```
### 2. Import And Log
Add the package import to your application's `moon.pkg`:
## Quick Start
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
}
let logger = build_logger(
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
)
logger.info("starting", fields=[field("port", "8080")])
ignore(logger.flush())
```
```moonbit
fn main {
let logger = @log.build_logger(
@log.console(min_level=@log.Level::Info, target="app"),
)
logger.info("server started", fields=[@log.field("port", "8080")])
}
```
Start with `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)`, then add `with_queue(...)` or `with_file_rotation(...)` only when needed.
Continue with the [Examples guide](./examples/index.md): console fields, file rotation, JSON configuration, and async lifecycle are each documented as a complete path.
Use `Logger::new(...)` when you want to assemble custom sink graphs directly.
## Support Status
- CI checks and tests `native`, `wasm`, `js`, and `wasm-gc` for the main `src` package and `src-async`
- Current local verification covers `native`, `js`, `wasm`, and `wasm-gc` for the main `src` package and `src-async`
- `llvm` is still treated as experimental in the current release context and was not locally re-verified in the current environment
- The source packages still declare `wasm` support alongside `native`, `llvm`, `js`, and `wasm-gc`; see [`target-verification.md`](./api/target-verification.md) for the current release-facing verification boundary
- File output is a native capability; check `native_files_supported()` in cross-target code
@@ -58,12 +44,19 @@ Continue with the [Examples guide](./examples/index.md): console fields, file ro
- Config-based builders: `build_logger(...)` and `build_async_logger(...)`
- Composition helpers: queue, filter, patch, fanout, split, callback
- Separate async package under `src-async`
- Trace-context binding through standard `trace_id`, `span_id`, `trace_flags`, and `trace_state` fields
## Examples
- `examples/console_basic/`: minimal console and JSON console example
- `examples/text_formatter/`: text formatting and template example
- `examples/style_tags/`: style tags and colored output example
- `examples/config_build/`: config-based build example
- `examples/presets/`: common preset combinations
- `examples/file_rotation/`: native file logging and rotation example
- `examples/async_basic/`: async logging example
## Documentation
- [Examples](./examples/index.md): complete application-facing usage flows
- [Extend](./extend/index.md): queueing, sink composition, formatting, and target boundaries
- [API index](./api/index.md): canonical API reference, organized as one public API per file
- [src package README](https://github.com/Nanaloveyuki/BitLogger/blob/main/src/README.mbt.md): package-level usage notes and target reminders
- [`docs/changes/`](./changes/): versioned release notes and publish-facing change summaries
@@ -13,14 +13,14 @@ key-word:
## Async-logger-build-config-to-json
Convert `AsyncLoggerBuildConfig` into a `Json`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload.
Convert `AsyncLoggerBuildConfig` into a `JsonValue`. This helper exports both the base synchronous logger config and the async runtime config as one structured payload.
### Interface
```moonbit
pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig,
) -> Json {}
) -> @json_parser.JsonValue {}
```
#### input
@@ -29,7 +29,7 @@ pub fn async_logger_build_config_to_json(
#### output
- `Json` - Structured JSON representation of the full async build config.
- `JsonValue` - Structured JSON representation of the full async build config.
### Explanation
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Async-logger-config-to-json
Convert a typed `AsyncLoggerConfig` into a `Json`. This helper exports async queue capacity, overflow policy, batch sizing, linger timing, and flush behavior in a structured form.
Convert a typed `AsyncLoggerConfig` into a `JsonValue`. This helper exports async queue capacity, overflow policy, batch sizing, linger timing, and flush behavior in a structured form.
### Interface
```moonbit
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {}
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {}
#### output
- `Json` - Structured JSON representation of the async config.
- `JsonValue` - Structured JSON representation of the async config.
### Explanation
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Async-logger-state-to-json
Convert `AsyncLoggerState` into a `Json`. This helper is the structured export path for async logger runtime snapshots or manually constructed state values when callers want machine-readable diagnostics instead of a plain string.
Convert `AsyncLoggerState` into a `JsonValue`. This helper is the structured export path for async logger runtime snapshots or manually constructed state values when callers want machine-readable diagnostics instead of a plain string.
### Interface
```moonbit
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {}
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {}
#### output
- `Json` - Structured JSON representation of the async logger snapshot.
- `JsonValue` - Structured JSON representation of the async logger snapshot.
### Explanation
@@ -62,7 +62,7 @@ In this example, callers receive a structured value that can be composed into la
When another serializer or pipeline expects a JSON value:
```moonbit
let payload = async_logger_state_to_json(logger.state())
println(payload.stringify())
println(@json_parser.stringify(payload))
```
In this example, the helper stays useful even outside the built-in stringify wrapper.
@@ -1,21 +0,0 @@
---
name: async-logger-with-trace-context
group: api
category: async
update-time: 20260719
description: Bind TraceContext fields before records enter an AsyncLogger queue.
key-word:
- async
- trace
- context
---
## Async-logger-with-trace-context
```moonbit
let contextual = logger.with_trace_context(
@bitlogger.trace_context("trace-1", "span-1"),
)
```
The helper stores the mapped fields on the returned async logger and preserves its queue, lifecycle, target, and threshold state. Context is added before records are enqueued.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Async-runtime-state-to-json
Convert `AsyncRuntimeState` into a `Json`. This helper exports the async runtime mode and background worker capability in a structured form.
Convert `AsyncRuntimeState` into a `JsonValue`. This helper exports the async runtime mode and background worker capability in a structured form.
### Interface
```moonbit
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {}
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {}
#### output
- `Json` - Structured JSON representation of the runtime state.
- `JsonValue` - Structured JSON representation of the runtime state.
### Explanation
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-rotation-config-to-json
Convert `FileRotation` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
Convert `FileRotation` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface
```moonbit
pub fn file_rotation_config_to_json(config : FileRotation) -> Json {}
pub fn file_rotation_config_to_json(config : FileRotation) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn file_rotation_config_to_json(config : FileRotation) -> Json {}
#### output
- `Json` - Structured JSON representation of the rotation policy.
- `JsonValue` - Structured JSON representation of the rotation policy.
### Explanation
@@ -82,7 +82,7 @@ e.g.:
### Notes
1. Use this helper when downstream code expects `Json` rather than a typed `FileRotation` value.
1. Use this helper when downstream code expects `JsonValue` rather than a typed `FileRotation` value.
2. Pair it with `file_rotation(...)` when building rotation policy in code before export.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-sink-policy-to-json
Convert `FileSinkPolicy` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
Convert `FileSinkPolicy` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface
```moonbit
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {}
pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn file_sink_policy_to_json(policy : FileSinkPolicy) -> Json {}
#### output
- `Json` - Structured JSON representation of the file policy.
- `JsonValue` - Structured JSON representation of the file policy.
### Explanation
@@ -73,7 +73,7 @@ e.g.:
### Notes
1. Use this helper when downstream code expects `Json` rather than text.
1. Use this helper when downstream code expects `JsonValue` rather than text.
2. It pairs naturally with `FileSink::policy()`, `RuntimeSink::file_policy()`, and related default-policy accessors.
+1 -2
View File
@@ -2,7 +2,7 @@
name: file-sink-reopen-append
group: api
category: sink
update-time: 20260717
update-time: 20260613
description: Reopen a FileSink in append mode.
key-word:
- file
@@ -37,7 +37,6 @@ Detailed rules explaining key parameters and behaviors
- Reopen behavior is fixed to append mode.
- The stored append policy is updated to `true` as part of the reopen path.
- On failure, the sink remains unavailable and `open_failures` is incremented.
- After a rotation failure, this is the explicit recovery path once the underlying remove, rename, or open condition has been resolved.
### How to Use
+1 -4
View File
@@ -2,7 +2,7 @@
name: file-sink-rotation-failures
group: api
category: sink
update-time: 20260717
update-time: 20260707
description: Read the number of rotation failures recorded by a FileSink.
key-word:
- file
@@ -36,7 +36,6 @@ Detailed rules explaining key parameters and behaviors
- The sink reports its recorded rotation-failure count directly.
- The counter increases when a rotation attempt cannot complete its critical file steps, such as removing the active file with zero backups, renaming a live file into `.1`, shifting an existing backup to a higher slot, or reopening the fresh active file afterward.
- Missing optional backup slots do not count by themselves; only steps that fail while their source path still exists are treated as rotation failures.
- When a critical rotation step fails, the triggering record is not written and the sink remains unavailable until an explicit reopen succeeds.
- The counter is cumulative until reset.
- This helper is observation-only and does not mutate sink state.
@@ -71,8 +70,6 @@ e.g.:
- This counter alone does not say whether the sink is currently available.
- After a recorded rotation failure, use `reopen_append()` only after the underlying filesystem condition has been resolved.
### Notes
1. Use this helper when diagnosing incomplete direct file rotations.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## File-sink-state-to-json
Convert `FileSinkState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
Convert `FileSinkState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface
```moonbit
pub fn file_sink_state_to_json(state : FileSinkState) -> Json {}
pub fn file_sink_state_to_json(state : FileSinkState) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn file_sink_state_to_json(state : FileSinkState) -> Json {}
#### output
- `Json` - Structured JSON representation of the file sink state.
- `JsonValue` - Structured JSON representation of the file sink state.
### Explanation
@@ -72,7 +72,7 @@ e.g.:
### Notes
1. Use this helper when diagnostics consumers expect `Json`.
1. Use this helper when diagnostics consumers expect `JsonValue`.
2. It pairs naturally with `FileSink::state()`, `RuntimeSink::file_state()`, and `ConfiguredLogger::file_state()`.
-8
View File
@@ -31,7 +31,6 @@ BitLogger API navigation.
- [logger-with-timestamp.md](./logger-with-timestamp.md)
- [logger-with-context-fields.md](./logger-with-context-fields.md)
- [logger-bind.md](./logger-bind.md)
- [logger-with-trace-context.md](./logger-with-trace-context.md)
- [logger-with-filter.md](./logger-with-filter.md)
- [logger-with-patch.md](./logger-with-patch.md)
- [logger-with-queue.md](./logger-with-queue.md)
@@ -100,10 +99,6 @@ BitLogger API navigation.
- [fields.md](./fields.md)
- [text-formatter-config-type.md](./text-formatter-config-type.md)
## Observability
- [trace-context.md](./trace-context.md)
## Record and level
- [record.md](./record.md)
@@ -271,7 +266,6 @@ BitLogger API navigation.
- [async-logger-with-min-level.md](./async-logger-with-min-level.md)
- [async-logger-with-timestamp.md](./async-logger-with-timestamp.md)
- [async-logger-with-context-fields.md](./async-logger-with-context-fields.md)
- [async-logger-with-trace-context.md](./async-logger-with-trace-context.md)
- [async-logger-with-filter.md](./async-logger-with-filter.md)
- [async-logger-with-patch.md](./async-logger-with-patch.md)
@@ -328,7 +322,6 @@ BitLogger API navigation.
- [library-logger-child.md](./library-logger-child.md)
- [library-logger-with-context-fields.md](./library-logger-with-context-fields.md)
- [library-logger-bind.md](./library-logger-bind.md)
- [library-logger-with-trace-context.md](./library-logger-with-trace-context.md)
- [library-logger-is-enabled.md](./library-logger-is-enabled.md)
- [library-logger-log.md](./library-logger-log.md)
- [library-logger-info.md](./library-logger-info.md)
@@ -348,7 +341,6 @@ BitLogger API navigation.
- [library-async-logger-child.md](./library-async-logger-child.md)
- [library-async-logger-with-context-fields.md](./library-async-logger-with-context-fields.md)
- [library-async-logger-bind.md](./library-async-logger-bind.md)
- [library-async-logger-with-trace-context.md](./library-async-logger-with-trace-context.md)
- [library-async-logger-is-enabled.md](./library-async-logger-is-enabled.md)
- [library-async-logger-log.md](./library-async-logger-log.md)
- [library-async-logger-info.md](./library-async-logger-info.md)
@@ -1,15 +0,0 @@
---
name: library-async-logger-with-trace-context
group: api
category: facade
update-time: 20260719
description: Bind TraceContext fields through the restricted LibraryAsyncLogger facade.
key-word:
- library
- async
- trace
---
## Library-async-logger-with-trace-context
`LibraryAsyncLogger::with_trace_context(...)` returns a derived library facade with `trace_id`, `span_id`, `trace_flags`, and optional `trace_state` attached to each later record. It does not start a worker or change lifecycle state.
@@ -1,20 +0,0 @@
---
name: library-logger-with-trace-context
group: api
category: facade
update-time: 20260719
description: Bind TraceContext fields through the restricted LibraryLogger facade.
key-word:
- library
- trace
- context
---
## Library-logger-with-trace-context
```moonbit
let logger = LibraryLogger::new(console_sink())
.with_trace_context(trace_context("trace-1", "span-1"))
```
The derived facade preserves its library-facing API while wrapping the sink with context fields. Use `to_logger()` only when broader composition is required.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Logger-config-to-json
Convert a typed `LoggerConfig` into a `Json`. This helper is the structured export path when config should be persisted, inspected, or embedded into larger JSON payloads.
Convert a typed `LoggerConfig` into a `JsonValue`. This helper is the structured export path when config should be persisted, inspected, or embedded into larger JSON payloads.
### Interface
```moonbit
pub fn logger_config_to_json(config : LoggerConfig) -> Json {}
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn logger_config_to_json(config : LoggerConfig) -> Json {}
#### output
- `Json` - JSON representation of the logger config.
- `JsonValue` - JSON representation of the logger config.
### Explanation
@@ -73,5 +73,5 @@ e.g.:
1. Use this helper when you need a reusable JSON value rather than a final JSON string.
2. Use `stringify_logger_config(...)` when the next consumer expects JSON text instead of `Json`.
2. Use `stringify_logger_config(...)` when the next consumer expects JSON text instead of `JsonValue`.
-22
View File
@@ -1,22 +0,0 @@
---
name: logger-with-trace-context
group: api
category: logger
update-time: 20260719
description: Bind a TraceContext to every record from a synchronous Logger.
key-word:
- logger
- trace
- context
---
## Logger-with-trace-context
```moonbit
pub fn[S] Logger::with_trace_context(
self : Logger[S],
context : TraceContext,
) -> Logger[ContextSink[S]]
```
Returns a derived logger that prepends the context fields to every record. The original logger is unchanged. This is equivalent to `with_context_fields(context.as_fields())`, while making trace intent explicit.
+4 -4
View File
@@ -13,12 +13,12 @@ key-word:
## Queue-config-to-json
Convert a typed `QueueConfig` into a `Json`. This helper is the structured export path for synchronous queue wrapper configuration when callers want machine-readable config output.
Convert a typed `QueueConfig` into a `JsonValue`. This helper is the structured export path for synchronous queue wrapper configuration when callers want machine-readable config output.
### Interface
```moonbit
pub fn queue_config_to_json(queue : QueueConfig) -> Json {}
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn queue_config_to_json(queue : QueueConfig) -> Json {}
#### output
- `Json` - Structured JSON representation of the queue config.
- `JsonValue` - Structured JSON representation of the queue config.
### Explanation
@@ -74,5 +74,5 @@ e.g.:
1. Use this helper when you need a reusable JSON value rather than a final JSON string.
2. Use `stringify_queue_config(...)` when the next consumer expects text instead of `Json`.
2. Use `stringify_queue_config(...)` when the next consumer expects text instead of `JsonValue`.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Runtime-file-state-to-json
Convert `RuntimeFileState` into a `Json`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
Convert `RuntimeFileState` into a `JsonValue`. On the root `src` facade, this helper forwards to the concrete serializer owned by `src/file_model`.
### Interface
```moonbit
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {}
pub fn runtime_file_state_to_json(state : RuntimeFileState) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn runtime_file_state_to_json(state : RuntimeFileState) -> Json {}
#### output
- `Json` - Structured JSON representation of the runtime file state.
- `JsonValue` - Structured JSON representation of the runtime file state.
### Explanation
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Sink-config-to-json
Convert `SinkConfig` into a `Json`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`.
Convert `SinkConfig` into a `JsonValue`. This helper is used directly for sink export and indirectly when exporting `LoggerConfig`.
### Interface
```moonbit
pub fn sink_config_to_json(config : SinkConfig) -> Json {}
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> Json {}
#### output
- `Json` - JSON representation of the sink configuration.
- `JsonValue` - JSON representation of the sink configuration.
### Explanation
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for human inspection.
- This helper is built on top of `async_logger_build_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured async build-config export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured async build-config export helper.
- The output keeps `logger` and `async_config` as separate sections, matching supported parser input.
- The serialized `logger` section preserves the full `LoggerConfig` shape, even though `build_async_text_logger(...)` later consumes only the selected text-oriented subset of that logger config.
- That includes preserving whatever `logger.sink.kind` text is present in the config, even though the later text-specific builder path still ignores that sink-kind branch and constructs `FormattedConsoleSink` from `logger.sink.text_formatter`.
@@ -80,7 +80,7 @@ In this example, compact JSON is returned without extra formatting.
### Error Case
e.g.:
- If callers need a `Json` for further composition, they should use `async_logger_build_config_to_json(...)` instead.
- If callers need a `JsonValue` for further composition, they should use `async_logger_build_config_to_json(...)` instead.
- If only one layer of config is required, this helper may be broader than necessary.
@@ -96,7 +96,7 @@ e.g.:
4. In particular, serialized `logger.sink.kind` text is descriptive config data, not a guarantee that the text-specific builder path will branch on that sink kind later.
5. Use `async_logger_build_config_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
5. Use `async_logger_build_config_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
6. After parsing, the same text can also feed the application or library facade builders; string export preserves one shared build-config shape, not a direct-builder-only route.
+3 -3
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON suitable for transport and snapshots.
- `pretty=true` returns indented JSON for humans.
- This helper is built on top of `async_logger_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured async-config export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured async-config export helper.
- The exported text follows the supported async config schema rather than internal queue implementation details.
- Canonical policy labels such as `DropNewest` and `Never` are emitted even though the parser also accepts aliases like `DropLatest` and `None`.
@@ -68,7 +68,7 @@ In this example, compact JSON is returned without extra formatting.
### Error Case
e.g.:
- If callers need a `Json` for composition, they should use `async_logger_config_to_json(...)` instead.
- If callers need a `JsonValue` for composition, they should use `async_logger_config_to_json(...)` instead.
- If invalid constructor inputs were normalized earlier, the resulting text contains the normalized config values.
@@ -78,5 +78,5 @@ e.g.:
2. If negative `max_pending` semantics matter, remember that the serialized text preserves the config value while runtime queue creation later clamps the queue limit to `0`.
3. Use `async_logger_config_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
3. Use `async_logger_config_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON suitable for human diagnostics.
- This helper is built on top of `async_logger_state_to_json(...)`.
- Internally it stringifies the same JSON snapshot shape returned by the public export helper, using `value.stringify(...)` or `value.stringify(indent=2)`.
- Internally it stringifies the same JSON snapshot shape returned by the public export helper, using `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`.
- The compact form matches snapshots such as `{"runtime":{"mode":"native_worker","background_worker":true},"phase":"ready","pending_count":0,...}`.
- String output is convenient for logs, CLI output, and startup diagnostics.
- The serializer does not care whether the input came from a live logger read or a synthetic `AsyncLoggerState::new(...)` call.
@@ -83,7 +83,7 @@ e.g.:
2. The compact output shape is already locked by the async runtime snapshot tests, so it is suitable for stable diagnostics and assertions.
3. Use `async_logger_state_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
3. Use `async_logger_state_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
4. Pair it with `AsyncLogger::state()` for current logger diagnostics, or with `AsyncLoggerState::new(...)` when exporting a manual snapshot built by tests or adapters.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for human diagnostics.
- This helper is built on top of `async_runtime_state_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured runtime-state export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured runtime-state export helper.
- The compact form matches the tested snapshot shape such as `{"mode":"native_worker","background_worker":true}`.
- It is well-suited for startup banners, support reports, and target capability logs.
@@ -79,5 +79,5 @@ e.g.:
2. This helper is the direct text form of the same two-field runtime snapshot exported by `async_runtime_state_to_json(...)`.
3. Use `async_runtime_state_to_json(...)` when the next consumer still needs a `Json` for composition before final stringification.
3. Use `async_runtime_state_to_json(...)` when the next consumer still needs a `JsonValue` for composition before final stringification.
+1 -1
View File
@@ -66,7 +66,7 @@ In this example, compact JSON is produced without extra formatting logic.
### Error Case
e.g.:
- If callers need a `Json` for composition rather than text, `file_sink_policy_to_json(...)` is the better API.
- If callers need a `JsonValue` for composition rather than text, `file_sink_policy_to_json(...)` is the better API.
- If rotation is disabled, the output still includes `rotation` as `null`.
+2 -2
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` produces compact JSON.
- `pretty=true` produces indented human-readable JSON.
- This helper builds on top of `logger_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured logger-config export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured logger-config export helper.
- Output is stable and suited for roundtrip config workflows.
### How to Use
@@ -71,7 +71,7 @@ e.g.:
### Notes
1. Use this helper when the next consumer expects JSON text instead of `Json`.
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
2. Use `logger_config_to_json(...)` when you still need to embed the config inside a larger JSON object before final stringification.
+3 -3
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON suitable for logs and snapshots.
- `pretty=true` returns indented JSON for human inspection.
- This helper is built on top of `queue_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured export helper.
- The resulting text follows the same queue schema accepted by config parsing flows.
### How to Use
@@ -67,13 +67,13 @@ In this example, compact JSON is returned without extra formatting.
### Error Case
e.g.:
- If callers need a `Json` for further composition, this API is the wrong layer and `queue_config_to_json(...)` should be used instead.
- If callers need a `JsonValue` for further composition, this API is the wrong layer and `queue_config_to_json(...)` should be used instead.
- If queue policy is too aggressive for workload burst size, serialization still succeeds because this helper only exports config.
### Notes
1. Use this helper when the next consumer expects JSON text instead of `Json`.
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
2. Use `queue_config_to_json(...)` when you still need to embed the queue config inside a larger JSON object before final stringification.
+1 -1
View File
@@ -69,7 +69,7 @@ In this example, compact JSON is returned without extra formatting logic.
### Error Case
e.g.:
- If callers need a `Json` rather than text, `runtime_file_state_to_json(...)` is the better API.
- If callers need a `JsonValue` rather than text, `runtime_file_state_to_json(...)` is the better API.
- If queue wrapping is not relevant, `stringify_file_sink_state(...)` may be the simpler API.
+2 -2
View File
@@ -37,7 +37,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` gives compact JSON.
- `pretty=true` gives indented output.
- This helper builds on top of `sink_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured sink export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured sink export helper.
- It is useful when examples or generated docs want to show only sink-specific config.
### How to Use
@@ -71,7 +71,7 @@ e.g.:
### Notes
1. Use this helper when the next consumer expects JSON text instead of `Json`.
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
2. Use `sink_config_to_json(...)` when you still need to embed the sink config inside a larger JSON object before final stringification.
+2 -2
View File
@@ -40,7 +40,7 @@ Detailed rules explaining key parameters and behaviors
- `pretty=false` returns compact JSON.
- `pretty=true` returns indented JSON for humans.
- This helper is built on top of `text_formatter_config_to_json(...)`.
- Internally it serializes the `Json` result with `value.stringify(...)` or `value.stringify(indent=2)`, so the text form stays aligned with the structured formatter export helper.
- Internally it serializes the `JsonValue` result with `@json_parser.stringify(...)` or `@json_parser.stringify_pretty(value, 2)`, so the text form stays aligned with the structured formatter export helper.
- The output preserves the supported formatter config schema instead of any runtime-only formatter instance details.
### How to Use
@@ -76,7 +76,7 @@ e.g.:
### Notes
1. Use this helper when the next consumer expects JSON text instead of `Json`.
1. Use this helper when the next consumer expects JSON text instead of `JsonValue`.
2. Use `text_formatter_config_to_json(...)` when you still need to embed formatter config inside a larger JSON object before final stringification.
+3 -3
View File
@@ -13,12 +13,12 @@ key-word:
## Text-formatter-config-to-json
Convert a typed `TextFormatterConfig` into a `Json`. This helper exports formatter toggles, separators, color settings, markup behavior, and optional style tags in a machine-readable form.
Convert a typed `TextFormatterConfig` into a `JsonValue`. This helper exports formatter toggles, separators, color settings, markup behavior, and optional style tags in a machine-readable form.
### Interface
```moonbit
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {}
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> @json_parser.JsonValue {}
```
#### input
@@ -27,7 +27,7 @@ pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {}
#### output
- `Json` - Structured JSON representation of the formatter config.
- `JsonValue` - Structured JSON representation of the formatter config.
### Explanation
-25
View File
@@ -1,25 +0,0 @@
---
name: trace-context
group: api
category: observability
update-time: 20260719
description: Represent and bind distributed trace metadata using stable structured-log fields.
key-word:
- trace
- context
- observability
---
## Trace-context
`TraceContext` carries the standard log fields used to correlate records with distributed traces: `trace_id`, `span_id`, `trace_flags`, and optional `trace_state`.
```moonbit
let context = trace_context(
"4bf92f3577b34da6a3ce929d0e0e4736",
"00f067aa0ba902b7",
trace_state="vendor=value",
)
```
Use `context.as_fields()` when integrating with a custom carrier, or pass the value to a logger's `with_trace_context(...)` helper. BitLogger does not parse HTTP headers or export OTLP data; an application-owned tracing library remains responsible for propagation and sampling.
-26
View File
@@ -1,26 +0,0 @@
## BitLogger Update Changes
version 1.1.1(0.7.1)
### Toolchain
- fix: migrate runnable example packages to `pkgtype(kind: "executable")` for MoonBit 0.10.4 formatter compatibility
- fix: replace ambiguous empty map literals with `Map([])` so `--deny-warn` checks remain clean on MoonBit 0.10.4
### Test
- test: write file-backed test output under `logs/` instead of the repository root
- test: keep the tracked `logs/.gitkeep` directory available for clean test checkouts
### Docs
- docs: update installation snippets to `Nanaloveyuki/BitLogger@0.7.1`
- docs: add this 1.1.1(0.7.1) release entry to the change index
### Verification
- verify: pass formatting, warning-denied checks, synchronous and async cross-target tests, examples, and coverage on MoonBit 0.10.4
### Notes
- this is a compatibility and test-output maintenance release with no public API changes
-21
View File
@@ -1,21 +0,0 @@
## BitLogger Update Changes
version 1.1.2(0.7.2)
### JSON
- refactor: replace `maria/json_parser` with the official `moonbitlang/core/json` implementation
- refactor: preserve existing serialized JSON field shapes while JSON conversion APIs now return the built-in `Json` type
### Docs
- docs: update installation snippets to `Nanaloveyuki/BitLogger@0.7.2`
- docs: add this 1.1.2(0.7.2) release entry to the change index
### Verification
- verify: pass formatting, warning-denied checks, and default, native, JavaScript, WebAssembly, and Wasm-GC test targets
### Notes
- LLVM was not tested because the local MoonBit toolchain is missing the experimental LLVM core bundle required by that target
-19
View File
@@ -1,19 +0,0 @@
## BitLogger Update Changes
version 1.1.3(0.7.3)
### Changes
- Add wasm CI checks and tests for the sync and async packages.
- Add TraceContext binding for sync and async logger facades.
- Add native async file lifecycle integration coverage and reproducible benchmarks.
### Verification
- Pass moon fmt --check, warning-denied checks, and tests across native, wasm, wasm-gc, and js.
- Build the VitePress documentation site and run the native benchmark suite.
### Notes
- LLVM remains experimental and was not verified locally.
- TraceContext maps structured fields only; HTTP propagation and OTLP export remain application-owned.
-3
View File
@@ -2,9 +2,6 @@
Versioned BitLogger change summaries.
- [1.1.3](./1.1.3(0.7.3).md)
- [1.1.2](./1.1.2(0.7.2).md)
- [1.1.1](./1.1.1(0.7.1).md)
- [1.1.0](./1.1.0(0.7.0).md)
- [1.0.1](./1.0.1(0.6.1).md)
- [1.0.0](./1.0.0(0.6.0).md)
-47
View File
@@ -1,47 +0,0 @@
# Async Logger Lifecycle
Use the async package when a native application needs logging to run through an async queue. The library supports broader targets, but the shipped `async fn main` example is native-focused.
## Import Both Packages
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
"Nanaloveyuki/BitLogger/src-async" @async_log,
"moonbitlang/async",
}
```
## Build, Run, Then Shut Down
```moonbit
async fn main {
let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"service.async\",\"sink\":{\"kind\":\"text_console\"}},\"async_config\":{\"max_pending\":64,\"overflow\":\"DropOldest\",\"max_batch\":16,\"linger_ms\":5,\"flush\":\"Batch\"}}"
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
err => {
ignore(err)
return
}
}
let logger = @async_log.build_async_logger(config)
@async.with_task_group(group => {
group.spawn_bg(allow_failure=true, () => logger.run())
logger.info("worker started", fields=[@log.field("mode", "async")])
logger.shutdown()
})
}
```
`run()` owns the worker loop. Start it before producing records, then call `shutdown()` so pending work can follow the configured flush policy.
## Run The Repository Example
```bash
moon run examples/async_basic --target native
```
## Next Steps
- Inspect queue policy and flush choices in [Queueing](../extend/queue.md).
- Reference: [`parse_async_logger_build_config_text(...)`](../api/parse-async-logger-build-config-text.md), [`build_async_logger(...)`](../api/build-async-logger.md), [`run()`](../api/async-logger-run.md), and [`shutdown()`](../api/async-logger-shutdown.md).
-35
View File
@@ -1,35 +0,0 @@
# Configuration-Driven Logging
Use configuration when deployment should select levels, output, formatting, or queue behavior without changing application code.
## Parse, Build, Then Log
```moonbit
let raw = "{\"min_level\":\"info\",\"target\":\"service\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}},\"queue\":{\"max_pending\":64,\"overflow\":\"DropOldest\"}}"
let config = @log.parse_logger_config_text(raw) catch {
err => {
ignore(err)
println("invalid logger configuration")
return
}
}
let logger = @log.build_logger(config)
logger.info("configured logger ready")
ignore(logger.flush())
```
Parsing validates the data before runtime construction. Keep the raw configuration outside application code in a real deployment, but handle parsing errors at the boundary where configuration enters the process.
## Run The Repository Example
```bash
moon run examples/config_build
```
## Next Steps
- Use [Queueing](../extend/queue.md) to choose overflow behavior deliberately.
- Use [Async lifecycle](./async.md) when the queue needs a background worker.
- Reference: [`parse_logger_config_text(...)`](../api/parse-logger-config-text.md), [`build_logger(...)`](../api/build-logger.md), and [`with_queue(...)`](../api/with-queue.md).
-51
View File
@@ -1,51 +0,0 @@
# Console And Structured Fields
Use this flow for command-line tools and services that need a human-readable starting point. A record always has a level, target, message, and optional fields.
## Install And Import
```bash
moon add Nanaloveyuki/BitLogger@0.7.3
```
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
}
```
## Build A Console Logger
```moonbit
fn main {
let logger = @log.build_logger(
@log.console(min_level=@log.Level::Info, target="service.http"),
)
logger.info("listening", fields=[
@log.field("port", "8080"),
@log.field("environment", "development"),
])
logger.warn("slow request", fields=[@log.field("path", "/health")])
}
```
`min_level` filters records before they reach the sink. `target` identifies the subsystem, while fields carry queryable context without interpolating it into the message.
## Switch To JSON
Keep the logging calls unchanged and replace the preset:
```moonbit
let logger = @log.build_logger(
@log.json_console(min_level=@log.Level::Info, target="service.http"),
)
```
This is the preferred output for a collector that consumes one structured record per line.
## Next Steps
- Use [Text formatting](../extend/formatting.md) for a custom terminal format.
- Use [Configuration](./config.md) when levels, targets, and sinks come from JSON.
- Reference: [`console(...)`](../api/console.md), [`json_console(...)`](../api/json-console.md), [`field(...)`](../api/field.md), and [`build_logger(...)`](../api/build-logger.md).
-51
View File
@@ -1,51 +0,0 @@
# File Output And Rotation
File output is for native applications that need local persistence. Treat it as a native-only capability and keep a console fallback for portable code.
## Check The Capability
```moonbit
if !@log.native_files_supported() {
println("file logging is unavailable on this target")
return
}
```
## Build A Rotating File Logger
```moonbit
let config = @log.with_file_rotation(
@log.file(
"service.log",
min_level=@log.Level::Info,
target="service.file",
auto_flush=true,
) catch {
err => {
ignore(err)
return
}
},
1024 * 1024,
max_backups=3,
)
let logger = @log.build_logger(config)
logger.info("file logger ready")
ignore(logger.flush())
ignore(logger.file_close())
```
The rotation limit is the size of the active file in bytes. `max_backups=3` retains the rotated backup chain. Always close a file-backed logger during orderly application shutdown.
## Run The Repository Example
```bash
moon run examples/file_rotation --target native
```
## Next Steps
- Add a bounded queue with [Queueing](../extend/queue.md) when bursts should not write synchronously.
- Read [Target boundaries](../extend/targets.md) before sharing the same logging setup with web targets.
- Reference: [`file(...)`](../api/file.md), [`with_file_rotation(...)`](../api/with-file-rotation.md), and [`file_close(...)`](../api/configured-logger-file-close.md).
-24
View File
@@ -1,24 +0,0 @@
# Examples
Examples are the primary BitLogger learning path. Each page starts from an application concern, shows the complete minimum code, and then points to the API reference for exact contracts.
## Choose A Flow
| Need | Start here | Then extend with |
| --- | --- | --- |
| Print structured logs to a terminal | [Console and fields](./console.md) | [Text formatting](../extend/formatting.md) |
| Keep local logs with bounded disk use | [File rotation](./file-rotation.md) | [Target boundaries](../extend/targets.md) |
| Build logging from app configuration | [Configuration](./config.md) | [Queueing](../extend/queue.md) |
| Avoid blocking a native application on writes | [Async lifecycle](./async.md) | [Sink composition](../extend/composition.md) |
## Repository Examples
The runnable sources live under `examples/`. The guides below explain their intended use instead of duplicating every API detail:
- `examples/console_basic` for terminal and JSON records
- `examples/config_build` for parsed configuration
- `examples/file_rotation` for native file output
- `examples/async_basic` for the async lifecycle
- `examples/presets`, `examples/text_formatter`, and `examples/style_tags` for follow-on customization
Use the [API reference](../api/index.md) when a guide links to a symbol and you need its full signature or edge-case behavior.
-42
View File
@@ -1,42 +0,0 @@
# Sink Composition
Presets cover common console and file configurations. Build a `Logger::new(...)` directly when records need routing or multiple outputs.
## Send A Record To Two Destinations
```moonbit
let logger = @log.Logger::new(
@log.fanout_sink(@log.console_sink(), @log.json_console_sink()),
min_level=@log.Level::Info,
target="service",
)
logger.info("request complete", fields=[@log.field("status", "200")])
```
`fanout_sink(...)` writes the same record to every child sink. This keeps human-oriented terminal output and machine-oriented JSON output in one application path.
## Route Warnings Separately
```moonbit
let logger = @log.Logger::new(
@log.split_by_level(
@log.callback_sink(fn(rec) {
println("alert: \{rec.message}")
}),
@log.console_sink(),
min_level=@log.Level::Warn,
),
min_level=@log.Level::Trace,
target="service",
)
```
Use direct sink composition only when the routing behavior is part of the application design. A preset remains easier to audit when one sink is sufficient.
## API Reference
- [`Logger::new(...)`](../api/logger-new.md)
- [`fanout_sink(...)`](../api/fanout-sink.md)
- [`split_by_level(...)`](../api/split-by-level.md)
- [`callback_sink(...)`](../api/callback-sink.md)
-44
View File
@@ -1,44 +0,0 @@
# Text Formatting And Style Tags
Formatting changes presentation while keeping the record's level, target, message, and fields intact. Use text output for people; use JSON output when another system parses the record.
## Customize The Text Shape
```moonbit
let logger = @log.build_logger(
@log.text_console(
min_level=@log.Level::Info,
target="service",
text_formatter=@log.TextFormatterConfig::new(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
),
),
)
logger.info("ready", fields=[@log.field("port", "8080")])
```
The template controls the visible order. Keep fields separate from the message so they can still be rendered consistently across sinks.
## Add Named Styles
```moonbit
let formatter = @log.text_formatter(
show_timestamp=false,
color_mode=@log.ColorMode::Always,
).with_style_tags(
@log.default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true),
)
```
Style tags are terminal presentation metadata. Do not use them as the only way to encode operational meaning; preserve that meaning in level, target, and fields.
## API Reference
- [`text_console(...)`](../api/text-console.md)
- [`TextFormatterConfig`](../api/text-formatter-config.md)
- [`text_formatter(...)`](../api/text-formatter.md)
- [`ColorMode`](../api/color-mode.md)
-15
View File
@@ -1,15 +0,0 @@
# Extend A Logger
Start from an [Example flow](../examples/index.md), then add one extension at a time. These pages explain when an abstraction is useful and which operational boundary it introduces.
## Extension Map
| Need | Extension | Main trade-off |
| --- | --- | --- |
| Absorb short output bursts | [Queueing](./queue.md) | You must choose overflow and flush behavior. |
| Send records to multiple destinations or route by level | [Sink composition](./composition.md) | More explicit construction than presets. |
| Control terminal appearance | [Text formatting](./formatting.md) | Formatting affects presentation, not record structure. |
| Share code across native and web targets | [Target boundaries](./targets.md) | File and async runtime behavior differ by backend. |
| Correlate logs with distributed requests | [Trace context](./observability.md) | Propagation and export stay application-owned. |
For exact function signatures, follow each page's links into the [API reference](../api/index.md).
-18
View File
@@ -1,18 +0,0 @@
# Trace Context
BitLogger keeps trace interoperability in its structured record model rather than owning HTTP propagation or a telemetry transport. Use the tracing library already selected by the application to extract a request context, then bind its identifiers to the logger.
```moonbit
let request_logger = logger.with_trace_context(
@log.trace_context(
trace_id,
span_id,
trace_flags="01",
trace_state=trace_state,
),
)
```
Every record from `request_logger` receives `trace_id`, `span_id`, `trace_flags`, and, when supplied, `trace_state`. The base logger is unchanged, so a child request cannot leak identifiers into unrelated work.
Use `context.as_fields()` for a custom logger facade. BitLogger intentionally does not parse `traceparent` headers, create spans, make sampling decisions, or export OTLP data; those concerns remain with the application tracing implementation.
-30
View File
@@ -1,30 +0,0 @@
# Queueing And Overflow
Use a queue when a short burst of log writes should not immediately reach the output sink. Queueing is explicit because loss behavior is an application decision.
## Add A Synchronous Queue
```moonbit
let config = @log.with_queue(
@log.text_console(target="service"),
max_pending=128,
overflow=@log.QueueOverflowPolicy::DropOldest,
)
let logger = @log.build_logger(config)
logger.info("queued record")
ignore(logger.flush())
```
`DropOldest` preserves the newest operational information when the queue is full. Use `DropNewest` when preserving earlier records matters more. Calling `flush()` at a shutdown boundary makes the pending-record policy visible in application code.
## When To Use Async Instead
The synchronous queue is configuration around a normal runtime logger. When a native async application needs a worker lifecycle and batching, follow [Async logger lifecycle](../examples/async.md) instead.
## API Reference
- [`with_queue(...)`](../api/with-queue.md)
- [`QueueConfig`](../api/queue-config.md)
- [`QueueOverflowPolicy`](../api/queue-overflow-policy.md)
- [`flush()`](../api/configured-logger-flush.md)
-33
View File
@@ -1,33 +0,0 @@
# Target Boundaries
BitLogger keeps a portable structured logging surface, but native file output and async worker behavior are target-sensitive. Keep those boundaries in application code instead of assuming every sink behaves identically everywhere.
## Portable Default
`console(...)`, `json_console(...)`, structured fields, filters, patches, and the ordinary logger surface are the appropriate starting point for code shared across targets.
## Native-Only File Output
```moonbit
if @log.native_files_supported() {
let logger = @log.build_logger(@log.file("service.log") catch {
err => {
ignore(err)
return
}
})
logger.info("file output enabled")
}
```
File sinks need native filesystem support. Do not construct a file-only configuration unconditionally in code intended for web targets.
## Async Library Versus Async Entry Point
The async library exposes a compatibility surface across declared targets, while an executable `async fn main` still has stricter entry-point support. Keep a native-only executable example separate from portable library code.
## API Reference
- [`native_files_supported()`](../api/native-files-supported.md)
- [Target verification](../api/target-verification.md)
- [Async logger lifecycle](../examples/async.md)
+16 -14
View File
@@ -4,29 +4,31 @@ layout: home
hero:
name: BitLogger
text: Structured logging for MoonBit
tagline: Start with a runnable logging flow, then extend it with precise API reference.
tagline: API reference, examples, and release notes for BitLogger.
actions:
- theme: brand
text: Start Examples
link: /examples/
text: Open API Reference
link: /api/
- theme: alt
text: Extend A Logger
link: /extend/
text: Read Release Notes
link: /changes/
- theme: alt
text: Mooncake Package
link: https://mooncakes.io/docs/Nanaloveyuki/BitLogger
features:
- title: Examples first
details: Follow complete console, file, config, and async flows before looking up individual symbols.
- title: Extend deliberately
details: Add queues, custom sink graphs, formatting, and target-specific behavior only when the base flow needs them.
- title: API as reference
details: Use the one-symbol-per-page API reference for exact signatures, contracts, and edge cases.
- title: Structured logging
details: Levels, targets, message fields, and configurable text or JSON output.
- title: Sync and async paths
details: API reference covers both the main package and the async package surface.
- title: Config and runtime helpers
details: Browse builders, presets, file helpers, queue helpers, and verification notes quickly.
---
## Start Here
1. Read [Examples](./examples/index.md) for an end-to-end path from installation to a running logger.
2. Read [Extend](./extend/index.md) when the base console flow needs queueing, custom routing, formatting, files, or target-aware behavior.
3. Use [API Reference](./api/index.md) for exact contracts and [Release Notes](./changes/index.md) for version history.
<HomeDocHub />
- Use [API Reference](./api/index.md) when you want the canonical one-file-per-public-API docs.
- Use [Release Notes](./changes/index.md) when you want version-scoped changes and publish-facing summaries.
- Use [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md) or [English README](./README-en.md) when you want a shorter project overview first.
-50
View File
@@ -1,50 +0,0 @@
# 异步生命周期 API
异步 API 从 combined config 构建 Logger,再以单 worker 生命周期处理待写入记录。可执行 `async fn main` 仍应按目标平台边界使用。
## 解析与构建
```moonbit
pub fn parse_async_logger_build_config_text(
input : String,
) -> AsyncLoggerBuildConfig raise
pub fn build_async_logger(
config : AsyncLoggerBuildConfig,
) -> AsyncLogger[RuntimeSink]
```
解析函数同时验证 `logger``async_config` 两部分;缺少任一部分时使用对应默认配置。构建函数先复用同步 `build_logger(config.logger)` 路径,再包裹异步层,因此保留同步 sink、文件与可选同步队列的运行时语义。
```moonbit
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
err => { ignore(err); return }
}
let logger = @async_log.build_async_logger(config)
```
## `run()`
```moonbit
pub async fn AsyncLogger::run(self : AsyncLogger[S]) -> Unit
```
启动 drain worker。单个 Logger 只能同时拥有一个 worker;已经运行时再次调用会失败,关闭后的 Logger 也不能重新启动。worker 正常在队列关闭时退出;worker 失败时会记录状态并将错误向上传递。
## `shutdown()`
```moonbit
pub async fn AsyncLogger::shutdown(
self : AsyncLogger[S],
clear? : Bool = false,
) -> Unit
```
`clear=false` 先等待 idle 再关闭;`clear=true` 立即关闭并放弃待处理记录。两条路径都会等待活动 worker 退出。没有运行 worker 且仍有待处理记录时,`clear=false` 可能无法推进,因此应用应保证先启动 `run()`
## 英文原始 API
- [`parse_async_logger_build_config_text(...)`](../../api/parse-async-logger-build-config-text.md)
- [`build_async_logger(...)`](../../api/build-async-logger.md)
- [`run()`](../../api/async-logger-run.md)
- [`shutdown()`](../../api/async-logger-shutdown.md)
-55
View File
@@ -1,55 +0,0 @@
# 基础记录 API
控制台示例依赖以下四个入口:先用 preset 描述输出,再由 `build_logger(...)` 构建运行时 Logger,最后用 `field(...)` 附加结构化上下文。
## `console(...)` 与 `json_console(...)`
```moonbit
pub fn console(
min_level~ : Level = Level::Info,
target~ : String = "",
timestamp~ : Bool = false,
) -> LoggerConfig
pub fn json_console(
min_level~ : Level = Level::Info,
target~ : String = "",
timestamp~ : Bool = false,
) -> LoggerConfig
```
两者都只生成 `LoggerConfig`,不会直接创建运行时 Logger。`console(...)` 适合人读输出,`json_console(...)` 适合按行采集的机器读取输出;默认都不带队列。
## `field(...)`
```moonbit
pub fn field(key : String, value : String) -> Field
```
创建一个结构化字段。它保留原始 key/value,不做去重、归一化或校验;应使用稳定字段名,便于下游筛选。
```moonbit
logger.info("accepted", fields=[@log.field("user", "alice")])
```
## `build_logger(...)`
```moonbit
pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger
```
这是同步 config 到运行时的桥梁:它先按 `config.sink` 构建 `RuntimeSink`,再应用可选的 `config.queue`。返回值仍保留普通日志方法,以及适用时的队列和文件控制方法。
```moonbit
let logger = @log.build_logger(
@log.console(min_level=@log.Level::Info, target="service"),
)
logger.info("ready", fields=[@log.field("port", "8080")])
```
## 英文原始 API
- [`console(...)`](../../api/console.md)
- [`json_console(...)`](../../api/json-console.md)
- [`field(...)`](../../api/field.md)
- [`build_logger(...)`](../../api/build-logger.md)
-52
View File
@@ -1,52 +0,0 @@
# Sink 组合 API
当预设输出不足以表达路由规则时,使用 typed `Logger::new(...)` 和 sink 组合函数。它们接收完整 `Record`,因此仍保留 level、target、message 与 fields。
## `Logger::new(...)`
```moonbit
pub fn Logger::new(
sink : S,
min_level~ : Level = Level::Info,
target~ : String = "",
) -> Logger[S]
```
从任意 `Sink` 实现创建 typed Logger。`min_level` 在任何 sink 写入前生效;`target` 是默认值,之后可由 `with_target(...)``child(...)` 或单次 `log(..., target=...)` 覆盖。
## `fanout_sink(...)`
```moonbit
pub fn fanout_sink(left : A, right : B) -> FanoutSink[A, B]
```
每条记录写入两个目的地,适合控制台加 JSON,或控制台加 callback。右侧获得独立记录副本,两个写入在记录值层面互不影响。
## `split_by_level(...)`
```moonbit
pub fn split_by_level(
left : A,
right : B,
min_level~ : Level = Level::Warn,
) -> SplitSink[A, B]
```
达到阈值的记录进入 `left`,低于阈值的记录进入 `right`。典型用法是把 warning/error 单独输出,而 info/debug 留在普通控制台。
## `callback_sink(...)`
```moonbit
pub fn callback_sink(
callback : (Record) -> Unit,
) -> CallbackSink
```
将完整结构化 `Record` 交给应用回调。适合测试、自定义桥接与集成;它不自动格式化记录。
## 英文原始 API
- [`Logger::new(...)`](../../api/logger-new.md)
- [`fanout_sink(...)`](../../api/fanout-sink.md)
- [`split_by_level(...)`](../../api/split-by-level.md)
- [`callback_sink(...)`](../../api/callback-sink.md)
-53
View File
@@ -1,53 +0,0 @@
# 配置与队列 API
这一组 API 将外部 JSON 或 typed config 变成同步 Logger,并明确规定满队列和关闭时的处理方式。
## `parse_logger_config_text(...)`
```moonbit
pub fn parse_logger_config_text(input : String) -> LoggerConfig raise ConfigError
```
解析并验证 JSON,但不构建运行时 Logger。缺省字段使用构造器默认值;无效 JSON、错误类型、未知枚举值或空文件路径都会抛出 `ConfigError`
```moonbit
let config = @log.parse_logger_config_text(raw) catch {
err => { ignore(err); return }
}
let logger = @log.build_logger(config)
```
## `with_queue(...)`、`QueueConfig` 与溢出策略
```moonbit
pub fn with_queue(
config : LoggerConfig,
max_pending~ : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> LoggerConfig
pub fn QueueConfig::new(
max_pending : Int,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> QueueConfig
```
`with_queue(...)` 是 preset 组合入口;`QueueConfig::new(...)` 用于手写 `LoggerConfig`。两者配置同步 `QueuedSink`,不是异步包的 worker 队列。
`DropNewest` 丢弃新到记录,`DropOldest` 丢弃最早待处理记录。选择取决于业务更需要保留早期还是最新状态。
## `flush()`
```moonbit
pub fn ConfiguredLogger::flush(self : ConfiguredLogger) -> Bool
```
在退出或需要确认输出边界时调用。对于队列和文件 sink,它会走对应运行时 flush 路径;返回值表示该路径是否成功。
## 英文原始 API
- [`parse_logger_config_text(...)`](../../api/parse-logger-config-text.md)
- [`with_queue(...)`](../../api/with-queue.md)
- [`QueueConfig`](../../api/queue-config.md)
- [`QueueOverflowPolicy`](../../api/queue-overflow-policy.md)
- [`flush()`](../../api/configured-logger-flush.md)
-49
View File
@@ -1,49 +0,0 @@
# 文件输出 API
文件输出是 native 能力。先检查能力,再创建文件配置,最后在有序退出时 flush 并关闭。
## `native_files_supported()`
```moonbit
pub fn native_files_supported() -> Bool
```
native 后端当前返回 `true`,非文件能力后端返回 `false`。这是运行时能力检查,不保证后续文件操作一定成功,路径、权限和文件系统状态仍可能导致失败。
## `file(...)` 与 `with_file_rotation(...)`
```moonbit
pub fn file(
path : String,
min_level~ : Level = Level::Info,
target~ : String = "",
timestamp~ : Bool = false,
append~ : Bool = true,
auto_flush~ : Bool = true,
rotation~ : FileRotation? = None,
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
) -> LoggerConfig raise ConfigError
pub fn with_file_rotation(
config : LoggerConfig,
max_bytes : Int,
max_backups~ : Int = 1,
) -> LoggerConfig
```
`file(...)` 要求非空路径。`with_file_rotation(...)` 只对 file config 生效;输入不是文件 sink 时原样返回。`max_bytes` 是活动文件上限,`max_backups` 是保留的轮转备份数。
## `file_close()`
```moonbit
pub fn ConfiguredLogger::file_close(self : ConfiguredLogger) -> Bool
```
关闭 config 构建的文件 sink。队列文件 sink 会依次 drain、flush、close;只有整条路径成功且期间没有新的写入、flush 或轮转失败时才返回 `true`。非文件 sink 返回 `false`
## 英文原始 API
- [`native_files_supported()`](../../api/native-files-supported.md)
- [`file(...)`](../../api/file.md)
- [`with_file_rotation(...)`](../../api/with-file-rotation.md)
- [`file_close()`](../../api/configured-logger-file-close.md)
-40
View File
@@ -1,40 +0,0 @@
# 文本格式 API
文本格式只改变呈现,不改变 `Record` 的结构。需要稳定机器消费时,优先使用 `json_console(...)`
## `text_console(...)`
```moonbit
pub fn text_console(
min_level~ : Level = Level::Info,
target~ : String = "",
timestamp~ : Bool = false,
text_formatter~ : TextFormatterConfig = default_text_formatter_config(),
) -> LoggerConfig
```
生成 `SinkKind::TextConsole` 配置,并复制 formatter 配置。和其他 preset 一样,它不直接构建 Logger,也默认不带队列。
## `TextFormatterConfig` 与 `text_formatter(...)`
`TextFormatterConfig::new(...)` 用于 config-first 路径,控制 timestamp、level、target、fields、分隔符与 template。`text_formatter(...)` 构建直接传给 `text_console_sink(...)` 的运行时 formatter。
```moonbit
let config = @log.text_console(
text_formatter=@log.TextFormatterConfig::new(
show_timestamp=false,
template="[{level}] {target} {message} :: {fields}",
),
)
```
## `ColorMode`
`ColorMode` 控制文本 formatter 的颜色策略。终端样式只服务可读性;业务语义仍应放在 level、target 和 fields 中。
## 英文原始 API
- [`text_console(...)`](../../api/text-console.md)
- [`TextFormatterConfig`](../../api/text-formatter-config.md)
- [`text_formatter(...)`](../../api/text-formatter.md)
- [`ColorMode`](../../api/color-mode.md)
-14
View File
@@ -1,14 +0,0 @@
# 高频 API
这一层只覆盖 Examples 和 Extend 中直接使用、或应用启动时高频需要的 API。它按使用主题归纳,避免把阅读路径重新拆回一函数一页。
| 场景 | 中文说明 | 英文精确参考 |
| --- | --- | --- |
| 第一条结构化日志 | [基础记录](./basics.md) | [完整 API 索引](../../api/index.md) |
| 配置、队列与 flush | [配置与队列](./configuration.md) | [config 分类](../../api/index.md) |
| native 文件输出 | [文件输出](./file-output.md) | [file/sink 分类](../../api/index.md) |
| 终端可读性 | [文本格式](./formatting.md) | [formatter 分类](../../api/index.md) |
| 后台异步处理 | [异步生命周期](./async.md) | [async 分类](../../api/index.md) |
| 多目的地与按 level 路由 | [Sink 组合](./composition.md) | [sink 分类](../../api/index.md) |
每个中文页面保留高频签名与行为边界;需要全部参数、错误条件或较少使用的公开符号时,沿页面内链接进入英文原始 API。
-47
View File
@@ -1,47 +0,0 @@
# 异步日志生命周期
当 native 异步应用需要通过异步队列处理日志写入时,使用异步包。库声明的可用目标比可执行 `async fn main` 示例更广,仓库示例仍以 native 为主。
## 导入两个包
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
"Nanaloveyuki/BitLogger/src-async" @async_log,
"moonbitlang/async",
}
```
## 构建、运行、关闭
```moonbit
async fn main {
let raw = "{\"logger\":{\"min_level\":\"info\",\"target\":\"service.async\",\"sink\":{\"kind\":\"text_console\"}},\"async_config\":{\"max_pending\":64,\"overflow\":\"DropOldest\",\"max_batch\":16,\"linger_ms\":5,\"flush\":\"Batch\"}}"
let config = @async_log.parse_async_logger_build_config_text(raw) catch {
err => {
ignore(err)
return
}
}
let logger = @async_log.build_async_logger(config)
@async.with_task_group(group => {
group.spawn_bg(allow_failure=true, () => logger.run())
logger.info("worker started", fields=[@log.field("mode", "async")])
logger.shutdown()
})
}
```
`run()` 持有 worker 循环。先启动它再记录日志,并调用 `shutdown()`,使待处理记录按配置的 flush 策略结束。
## 运行仓库示例
```bash
moon run examples/async_basic --target native
```
## 下一步
- 查看[队列](../extend/queue.md)中的溢出与 flush 选择。
- 阅读[异步生命周期 API](../api/async.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-35
View File
@@ -1,35 +0,0 @@
# 配置驱动构建
当 level、目标名、输出方式或队列策略应由部署配置决定时,使用这一流程。
## 解析、构建、记录
```moonbit
let raw = "{\"min_level\":\"info\",\"target\":\"service\",\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"separator\":\" | \"}},\"queue\":{\"max_pending\":64,\"overflow\":\"DropOldest\"}}"
let config = @log.parse_logger_config_text(raw) catch {
err => {
ignore(err)
println("invalid logger configuration")
return
}
}
let logger = @log.build_logger(config)
logger.info("configured logger ready")
ignore(logger.flush())
```
解析先验证数据,再构造运行时 Logger。真实部署应将原始配置放在应用代码之外,并在配置进入进程的边界处理错误。
## 运行仓库示例
```bash
moon run examples/config_build
```
## 下一步
- 需要明确选择满队列时的行为,阅读[队列](../extend/queue.md)。
- 需要后台 worker 生命周期时,阅读[异步日志生命周期](./async.md)。
- 阅读[配置与队列 API](../api/configuration.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-55
View File
@@ -1,55 +0,0 @@
# 控制台与结构化字段
命令行工具和服务都可以从这一流程开始。一条记录由 level、target、message 与可选 fields 构成。
## 安装与导入
```bash
moon new log-demo
cd log-demo
moon add Nanaloveyuki/BitLogger@0.7.3
```
在应用的 `moon.pkg` 中加入:
```moonbit
import {
"Nanaloveyuki/BitLogger/src" @log,
}
```
## 构建控制台 Logger
```moonbit
fn main {
let logger = @log.build_logger(
@log.console(min_level=@log.Level::Info, target="service.http"),
)
logger.info("listening", fields=[
@log.field("port", "8080"),
@log.field("environment", "development"),
])
logger.warn("slow request", fields=[@log.field("path", "/health")])
}
```
`min_level` 在记录到达 sink 前过滤日志;`target` 用于识别子系统;fields 用于携带可查询上下文,而不是拼接到 message 中。
## 切换为 JSON
保持日志调用不变,只替换配置:
```moonbit
let logger = @log.build_logger(
@log.json_console(min_level=@log.Level::Info, target="service.http"),
)
```
当日志采集器按行处理结构化记录时,JSON 输出更合适。
## 下一步
- 需要自定义终端输出时,阅读[文本格式](../extend/formatting.md)。
- 需要由 JSON 配置构建时,阅读[配置构建](./config.md)。
- 阅读[基础记录 API](../api/basics.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-51
View File
@@ -1,51 +0,0 @@
# 文件输出与轮转
文件输出面向需要本地持久化的 native 应用。跨端代码应先判断能力,并保留控制台回退方案。
## 检查能力
```moonbit
if !@log.native_files_supported() {
println("file logging is unavailable on this target")
return
}
```
## 构建可轮转文件 Logger
```moonbit
let config = @log.with_file_rotation(
@log.file(
"service.log",
min_level=@log.Level::Info,
target="service.file",
auto_flush=true,
) catch {
err => {
ignore(err)
return
}
},
1024 * 1024,
max_backups=3,
)
let logger = @log.build_logger(config)
logger.info("file logger ready")
ignore(logger.flush())
ignore(logger.file_close())
```
轮转阈值是活动文件的字节数,`max_backups=3` 保留备份链。应用有序退出时必须关闭文件 Logger。
## 运行仓库示例
```bash
moon run examples/file_rotation --target native
```
## 下一步
- 写入突发较多时,加入[队列](../extend/queue.md)。
- 与 web 目标共享代码前,阅读[目标平台边界](../extend/targets.md)。
- 阅读[文件输出 API](../api/file-output.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-24
View File
@@ -1,24 +0,0 @@
# 示例
示例是 BitLogger 的主学习路径。每页从一个应用需求开始,给出完整最小代码,再链接到英文 API 参考查看精确契约。
## 选择流程
| 需求 | 从这里开始 | 后续扩展 |
| --- | --- | --- |
| 在终端输出结构化日志 | [控制台与字段](./console.md) | [文本格式](../extend/formatting.md) |
| 保存本地日志并限制磁盘占用 | [文件轮转](./file-rotation.md) | [目标平台边界](../extend/targets.md) |
| 由应用配置决定日志行为 | [配置构建](./config.md) | [队列](../extend/queue.md) |
| 在 native 异步应用中处理日志写入 | [异步生命周期](./async.md) | [Sink 组合](../extend/composition.md) |
## 可运行源码
仓库中的 `examples/` 是可执行参考;本目录说明何时选择它们、如何运行,以及下一步应该扩展什么:
- `examples/console_basic`
- `examples/config_build`
- `examples/file_rotation`
- `examples/async_basic`
- `examples/presets``examples/text_formatter``examples/style_tags`
遇到函数签名、错误语义或边界条件时,请查询[英文 API 参考](../../api/index.md)。
-39
View File
@@ -1,39 +0,0 @@
# Sink 组合
Preset 覆盖常见的控制台和文件配置。只有当记录需要路由或多个输出目的地时,才直接构造 `Logger::new(...)`
## 同时发送到两个目的地
```moonbit
let logger = @log.Logger::new(
@log.fanout_sink(@log.console_sink(), @log.json_console_sink()),
min_level=@log.Level::Info,
target="service",
)
logger.info("request complete", fields=[@log.field("status", "200")])
```
`fanout_sink(...)` 将同一记录写入每个子 Sink,因此应用可以同时保留面向人的控制台输出和面向采集器的 JSON 输出。
## 单独处理警告以上日志
```moonbit
let logger = @log.Logger::new(
@log.split_by_level(
@log.callback_sink(fn(rec) {
println("alert: \{rec.message}")
}),
@log.console_sink(),
min_level=@log.Level::Warn,
),
min_level=@log.Level::Trace,
target="service",
)
```
只有当路由规则属于应用设计时才使用直接 Sink 组合;单一输出时,preset 更容易审查和维护。
## API
阅读[Sink 组合 API](../api/composition.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-41
View File
@@ -1,41 +0,0 @@
# 文本格式与样式
格式化只改变呈现,记录本身的 level、target、message 和 fields 不变。面向人时使用文本输出;由其他系统解析时使用 JSON。
## 自定义文本形状
```moonbit
let logger = @log.build_logger(
@log.text_console(
min_level=@log.Level::Info,
target="service",
text_formatter=@log.TextFormatterConfig::new(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
),
),
)
logger.info("ready", fields=[@log.field("port", "8080")])
```
模板控制可见顺序。fields 应保持独立于 message,以便不同 Sink 一致渲染。
## 添加命名样式
```moonbit
let formatter = @log.text_formatter(
show_timestamp=false,
color_mode=@log.ColorMode::Always,
).with_style_tags(
@log.default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true),
)
```
样式标签是终端呈现元数据,不应作为唯一的业务或运维语义;语义仍应保存在 level、target 与 fields 中。
## API
阅读[文本格式 API](../api/formatting.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-15
View File
@@ -1,15 +0,0 @@
# 扩展 Logger
先完成一个[示例流程](../examples/index.md),再一次只加入一种扩展能力。这些页面说明某个抽象何时有用,以及它会引入什么运行时边界。
## 扩展地图
| 需求 | 扩展 | 主要取舍 |
| --- | --- | --- |
| 吸收短时输出突发 | [队列](./queue.md) | 必须选择溢出与 flush 行为。 |
| 同时发送到多个位置或按 level 路由 | [Sink 组合](./composition.md) | 比 preset 更需要显式构造。 |
| 调整终端可读性 | [文本格式](./formatting.md) | 格式只改变呈现,不改变记录结构。 |
| 在 native 与 web 目标之间共享代码 | [目标平台边界](./targets.md) | 文件与异步运行时行为因后端而异。 |
| 将日志关联到分布式请求 | [Trace context](./observability.md) | 传播与导出仍由应用负责。 |
需要精确函数签名时,沿页面链接进入[英文 API 参考](../../api/index.md)。
-13
View File
@@ -1,13 +0,0 @@
# Trace Context
BitLogger 通过结构化字段承载 trace 上下文,不负责 HTTP 传播或遥测传输。应用应继续使用已选定的 tracing 库提取请求上下文,再把标识绑定到 logger。
```moonbit
let request_logger = logger.with_trace_context(
@log.trace_context(trace_id, span_id, trace_flags="01"),
)
```
派生 logger 的每条记录都会带有 `trace_id``span_id``trace_flags`,以及传入时的 `trace_state`。原 logger 不会被修改,避免请求上下文泄漏到无关任务。
`context.as_fields()` 可用于自定义 facade。`traceparent` 解析、span 创建、采样与 OTLP 导出仍由应用选用的 tracing 实现负责。
-27
View File
@@ -1,27 +0,0 @@
# 队列与溢出策略
当短时日志突发不应立即写入输出 Sink 时使用队列。队列是显式能力,因为丢弃策略必须由应用决定。
## 加入同步队列
```moonbit
let config = @log.with_queue(
@log.text_console(target="service"),
max_pending=128,
overflow=@log.QueueOverflowPolicy::DropOldest,
)
let logger = @log.build_logger(config)
logger.info("queued record")
ignore(logger.flush())
```
`DropOldest` 在队列已满时保留最新运行信息。若更重视先到的记录,可选择 `DropNewest`。在退出边界调用 `flush()`,让待处理记录策略在应用代码中清晰可见。
## 何时改用异步 Logger
同步队列只是普通运行时 Logger 的一层配置。当 native 异步应用需要 worker 生命周期和批处理时,转到[异步日志生命周期](../examples/async.md)。
## API
阅读[配置与队列 API](../api/configuration.md);完整契约仍可查询[英文 API 索引](../../api/index.md)。
-31
View File
@@ -1,31 +0,0 @@
# 目标平台边界
BitLogger 保持可移植的结构化日志表面,但 native 文件输出与异步 worker 行为依赖目标平台。应把这些边界写在应用代码中,而不是假设每种 Sink 在所有后端都相同。
## 可移植默认值
`console(...)``json_console(...)`、结构化 fields、filter、patch 与普通 Logger 表面适合作为跨端共享代码的起点。
## 仅 native 的文件输出
```moonbit
if @log.native_files_supported() {
let logger = @log.build_logger(@log.file("service.log") catch {
err => {
ignore(err)
return
}
})
logger.info("file output enabled")
}
```
文件 Sink 需要 native 文件系统支持。面向 web 目标的代码不应无条件构造只包含文件输出的配置。
## 异步库与异步入口
异步库在声明目标上提供兼容表面,但可执行 `async fn main` 的入口支持更严格。请把 native-only 可执行示例与可移植库代码分开。
## API
阅读[文件输出 API](../api/file-output.md)和[异步生命周期](../examples/async.md)。完整目标验证记录仍在[英文 API](../../api/target-verification.md)。
-32
View File
@@ -1,32 +0,0 @@
---
layout: home
hero:
name: BitLogger
text: MoonBit 结构化日志库
tagline: 先完成一条可运行的日志流程,再按需扩展并查询 API。
actions:
- theme: brand
text: 从示例开始
link: /zh/examples/
- theme: alt
text: 扩展 Logger
link: /zh/extend/
- theme: alt
text: API 参考(英文)
link: /api/
features:
- title: 示例优先
details: 从控制台、文件、配置和异步四条完整流程开始,而不是从 API 文件名开始。
- title: 按需扩展
details: 只在基础流程需要时再加入队列、组合 Sink、格式化和跨端处理。
- title: API 作为参考
details: 英文 API 页面保留精确签名、契约与边界说明。
---
## 从这里开始
1. 阅读[示例](./examples/index.md),完成从安装到运行 Logger 的全流程。
2. 当基础控制台流程需要更多能力时,阅读[扩展](./extend/index.md)。
3. 需要高频签名和边界时,查询[中文高频 API](./api/index.md);较少使用的符号仍进入[英文完整 API 参考](../api/index.md)。
+3 -1
View File
@@ -6,4 +6,6 @@ import {
supported_targets = "+native"
pkgtype(kind: "executable")
options(
is_main: true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
+3 -1
View File
@@ -2,4 +2,6 @@ import {
"Nanaloveyuki/BitLogger/src" @lib,
}
pkgtype(kind: "executable")
options(
"is-main": true,
)
-1
View File
@@ -1 +0,0 @@
+3 -2
View File
@@ -1,9 +1,10 @@
name = "Nanaloveyuki/BitLogger"
version = "0.7.3"
version = "0.7.0"
import {
"moonbitlang/async@0.20.2",
"maria/json_parser@0.1.1",
"moonbitlang/async@0.20.0",
}
readme = "src/README.mbt.md"
-184
View File
@@ -1,184 +0,0 @@
<#
.SYNOPSIS
Updates BitLogger's package version, installation snippets, and release-note entry.
.DESCRIPTION
For patch releases, the public release version is inferred from the latest
changes entry. For example, after 1.1.2(0.7.2), running:
.\scripts\update-version.ps1 0.7.3
creates 1.1.3(0.7.3). A major or minor package-version change requires an
explicit -ReleaseVersion so the public release sequence is never guessed.
The generated release note uses supplied -Change, -Verification, and -Note
entries. Without them it contains TODO markers that must be completed before
the release is committed.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory, Position = 0)]
[ValidatePattern('^\d+\.\d+\.\d+$')]
[string]$Version,
[ValidatePattern('^\d+\.\d+\.\d+$')]
[string]$ReleaseVersion,
[string[]]$Change,
[string[]]$Verification,
[string[]]$Note
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Read-TextFile([string]$Path) {
[System.IO.File]::ReadAllText($Path)
}
function Write-TextFile([string]$Path, [string]$Content) {
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
}
function Replace-ExactlyOnce(
[string]$Content,
[string]$Pattern,
[string]$Replacement,
[string]$Path
) {
$matches = [regex]::Matches($Content, $Pattern)
if ($matches.Count -ne 1) {
throw "Expected exactly one match for '$Pattern' in $Path, found $($matches.Count)."
}
[regex]::Replace($Content, $Pattern, $Replacement, 1)
}
function Format-BulletList([string[]]$Items, [string]$Fallback) {
$entries = if ($null -eq $Items -or $Items.Count -eq 0) {
@($Fallback)
} else {
$Items
}
($entries | ForEach-Object { "- $_" }) -join [Environment]::NewLine
}
$repoRoot = Split-Path -Parent $PSScriptRoot
$moonModPath = Join-Path $repoRoot 'moon.mod'
$changesIndexPath = Join-Path $repoRoot 'docs/changes/index.md'
foreach ($path in @($moonModPath, $changesIndexPath)) {
if (-not (Test-Path -LiteralPath $path)) {
throw "Required repository file is missing: $path"
}
}
$moonMod = Read-TextFile $moonModPath
$currentVersionMatch = [regex]::Match($moonMod, '(?m)^version\s*=\s*"(?<version>\d+\.\d+\.\d+)"\s*$')
if (-not $currentVersionMatch.Success) {
throw "Could not read the package version from $moonModPath."
}
$currentVersion = [version]$currentVersionMatch.Groups['version'].Value
$targetVersion = [version]$Version
if ($targetVersion -le $currentVersion) {
throw "Target version $Version must be greater than the current version $currentVersion."
}
$changesIndex = Read-TextFile $changesIndexPath
$latestReleaseMatch = [regex]::Match(
$changesIndex,
'(?m)^- \[(?<release>\d+\.\d+\.\d+)\]\(\./(?<file>\d+\.\d+\.\d+\(\d+\.\d+\.\d+\)\.md)\)\s*$'
)
if (-not $latestReleaseMatch.Success) {
throw "Could not read the latest release entry from $changesIndexPath."
}
$latestReleaseVersion = [version]$latestReleaseMatch.Groups['release'].Value
$latestPackageVersionMatch = [regex]::Match(
$latestReleaseMatch.Groups['file'].Value,
'\((?<version>\d+\.\d+\.\d+)\)\.md$'
)
if (-not $latestPackageVersionMatch.Success -or $latestPackageVersionMatch.Groups['version'].Value -ne $currentVersion.ToString()) {
throw 'The latest changes entry must describe the current moon.mod package version before starting a new release.'
}
if ([string]::IsNullOrWhiteSpace($ReleaseVersion)) {
if ($targetVersion.Major -ne $currentVersion.Major -or $targetVersion.Minor -ne $currentVersion.Minor) {
throw 'A major or minor package-version change requires -ReleaseVersion, for example: -ReleaseVersion 1.2.0.'
}
$ReleaseVersion = "$($latestReleaseVersion.Major).$($latestReleaseVersion.Minor).$($targetVersion.Build)"
}
$releaseFileName = "$ReleaseVersion($Version).md"
$releasePath = Join-Path $repoRoot "docs/changes/$releaseFileName"
if (Test-Path -LiteralPath $releasePath) {
throw "Release note already exists: $releasePath"
}
if ($changesIndex.Contains("./$releaseFileName")) {
throw "Release index already contains: $releaseFileName"
}
$updatedMoonMod = Replace-ExactlyOnce `
$moonMod `
'(?m)^version\s*=\s*"\d+\.\d+\.\d+"\s*$' `
"version = `"$Version`"" `
$moonModPath
$installFiles = @(
'README.md',
'docs/README-en.md',
'docs/examples/console.md',
'docs/zh/examples/console.md'
)
$updatedFiles = @{}
foreach ($relativePath in $installFiles) {
$path = Join-Path $repoRoot $relativePath
if (-not (Test-Path -LiteralPath $path)) {
throw "Required installation example is missing: $path"
}
$content = Read-TextFile $path
$updatedFiles[$path] = Replace-ExactlyOnce `
$content `
'Nanaloveyuki/BitLogger@\d+\.\d+\.\d+' `
"Nanaloveyuki/BitLogger@$Version" `
$path
}
$updatedChangesIndex = $changesIndex.Replace(
$latestReleaseMatch.Value,
"- [$ReleaseVersion](./$releaseFileName)$([Environment]::NewLine)$($latestReleaseMatch.Value)"
)
$releaseNote = @"
## BitLogger Update Changes
version $ReleaseVersion($Version)
### Changes
$(Format-BulletList $Change 'TODO: summarize the user-visible changes in this release.')
### Verification
$(Format-BulletList $Verification 'TODO: record the commands and targets verified for this release.')
### Notes
$(Format-BulletList $Note 'TODO: record release limitations or compatibility notes when applicable.')
"@
$writeTargets = @($moonModPath, $changesIndexPath, $releasePath) + $updatedFiles.Keys
if ($PSCmdlet.ShouldProcess(($writeTargets -join ', '), "update to $Version")) {
Write-TextFile $moonModPath $updatedMoonMod
foreach ($path in $updatedFiles.Keys) {
Write-TextFile $path $updatedFiles[$path]
}
Write-TextFile $changesIndexPath $updatedChangesIndex
Write-TextFile $releasePath $releaseNote
}
if ($WhatIfPreference) {
Write-Output "Previewed release $ReleaseVersion($Version)."
} else {
Write-Output "Prepared release $ReleaseVersion($Version)."
}
Write-Output "Release notes: $releasePath"
+73 -95
View File
@@ -455,30 +455,6 @@ async test "async logger with_context_fields prepends shared fields without muta
inspect(written_fields.val[2].value, content="test")
}
///|
test "async facades bind trace context through standard fields" {
let context = @bitlogger.trace_context("trace-async", "span-async")
let base = async_logger(
@bitlogger.callback_sink(fn(_) { }),
config=AsyncLoggerConfig::new(max_pending=4),
min_level=@bitlogger.Level::Info,
)
let derived = base.with_trace_context(context)
inspect(base.context_fields.length(), content="0")
inspect(derived.context_fields.length(), content="3")
inspect(derived.context_fields[0].key, content="trace_id")
inspect(derived.context_fields[0].value, content="trace-async")
inspect(derived.context_fields[1].key, content="span_id")
inspect(derived.context_fields[2].value, content="01")
let library = LibraryAsyncLogger::new(@bitlogger.callback_sink(fn(_) { })).with_trace_context(
context,
)
let full = library.to_async_logger()
inspect(full.context_fields.length(), content="3")
inspect(full.context_fields[1].value, content="span-async")
}
///|
async test "async logger child composes target and preserves other state" {
let written_target : Ref[String] = Ref("")
@@ -855,29 +831,29 @@ test "async json helpers export stable structured shapes" {
flush=AsyncFlushPolicy::Batch,
),
)
let config_obj = config_json.expect_json_object()
let config_obj = config_json.as_object().unwrap()
inspect(
config_json.stringify(),
@json_parser.stringify(config_json),
content="{\"max_pending\":8,\"max_batch\":3,\"linger_ms\":25,\"overflow\":\"DropOldest\",\"flush\":\"Batch\"}",
)
inspect(
config_obj.get("max_pending").unwrap().expect_json_number().to_int(),
config_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
content="8",
)
inspect(
config_obj.get("max_batch").unwrap().expect_json_number().to_int(),
config_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
content="3",
)
inspect(
config_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
config_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
content="25",
)
inspect(
config_obj.get("overflow").unwrap().expect_json_string(),
config_obj.get("overflow").unwrap().as_string().unwrap(),
content="DropOldest",
)
inspect(
config_obj.get("flush").unwrap().expect_json_string(),
config_obj.get("flush").unwrap().as_string().unwrap(),
content="Batch",
)
@@ -898,84 +874,85 @@ test "async json helpers export stable structured shapes" {
),
),
)
let build_obj = build_json.expect_json_object()
let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let build_obj = build_json.as_object().unwrap()
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap()
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap()
let formatter_obj = sink_obj
.get("text_formatter")
.unwrap()
.expect_json_object()
let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
.as_object()
.unwrap()
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
inspect(
logger_obj.get("min_level").unwrap().expect_json_string(),
logger_obj.get("min_level").unwrap().as_string().unwrap(),
content="WARN",
)
inspect(
logger_obj.get("target").unwrap().expect_json_string(),
logger_obj.get("target").unwrap().as_string().unwrap(),
content="async.roundtrip",
)
inspect(
logger_obj.get("timestamp").unwrap().expect_json_bool(),
logger_obj.get("timestamp").unwrap().as_bool().unwrap(),
content="true",
)
inspect(logger_obj.get("queue") is None, content="true")
inspect(
sink_obj.get("kind").unwrap().expect_json_string(),
sink_obj.get("kind").unwrap().as_string().unwrap(),
content="text_console",
)
inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="")
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true")
inspect(
sink_obj.get("auto_flush").unwrap().expect_json_bool(),
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("separator").unwrap().expect_json_string(),
formatter_obj.get("separator").unwrap().as_string().unwrap(),
content=" ",
)
inspect(
formatter_obj.get("field_separator").unwrap().expect_json_string(),
formatter_obj.get("field_separator").unwrap().as_string().unwrap(),
content=" ",
)
inspect(
formatter_obj.get("template").unwrap().expect_json_string(),
formatter_obj.get("template").unwrap().as_string().unwrap(),
content="",
)
inspect(
formatter_obj.get("color_mode").unwrap().expect_json_string(),
formatter_obj.get("color_mode").unwrap().as_string().unwrap(),
content="never",
)
inspect(
formatter_obj.get("color_support").unwrap().expect_json_string(),
formatter_obj.get("color_support").unwrap().as_string().unwrap(),
content="truecolor",
)
inspect(
formatter_obj.get("style_markup").unwrap().expect_json_string(),
formatter_obj.get("style_markup").unwrap().as_string().unwrap(),
content="full",
)
inspect(
async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
content="2",
)
inspect(
async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
content="5",
)
inspect(
async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
content="40",
)
inspect(
async_obj.get("overflow").unwrap().expect_json_string(),
async_obj.get("overflow").unwrap().as_string().unwrap(),
content="DropNewest",
)
inspect(
async_obj.get("flush").unwrap().expect_json_string(),
async_obj.get("flush").unwrap().as_string().unwrap(),
content="Shutdown",
)
}
@@ -984,102 +961,103 @@ test "async json helpers export stable structured shapes" {
test "async build config json export materializes parsed omitted defaults" {
let parsed = parse_async_logger_build_config_text("{}")
let build_json = async_logger_build_config_to_json(parsed)
let build_obj = build_json.expect_json_object()
let logger_obj = build_obj.get("logger").unwrap().expect_json_object()
let sink_obj = logger_obj.get("sink").unwrap().expect_json_object()
let build_obj = build_json.as_object().unwrap()
let logger_obj = build_obj.get("logger").unwrap().as_object().unwrap()
let sink_obj = logger_obj.get("sink").unwrap().as_object().unwrap()
let formatter_obj = sink_obj
.get("text_formatter")
.unwrap()
.expect_json_object()
let async_obj = build_obj.get("async_config").unwrap().expect_json_object()
.as_object()
.unwrap()
let async_obj = build_obj.get("async_config").unwrap().as_object().unwrap()
inspect(
build_json.stringify(),
@json_parser.stringify(build_json),
content="{\"logger\":{\"min_level\":\"INFO\",\"target\":\"\",\"timestamp\":false,\"sink\":{\"kind\":\"console\",\"path\":\"\",\"append\":true,\"auto_flush\":true,\"text_formatter\":{\"show_timestamp\":true,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"never\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"}}},\"async_config\":{\"max_pending\":0,\"max_batch\":1,\"linger_ms\":0,\"overflow\":\"Blocking\",\"flush\":\"Never\"}}",
)
inspect(
logger_obj.get("min_level").unwrap().expect_json_string(),
logger_obj.get("min_level").unwrap().as_string().unwrap(),
content="INFO",
)
inspect(logger_obj.get("target").unwrap().expect_json_string(), content="")
inspect(logger_obj.get("target").unwrap().as_string().unwrap(), content="")
inspect(
logger_obj.get("timestamp").unwrap().expect_json_bool(),
logger_obj.get("timestamp").unwrap().as_bool().unwrap(),
content="false",
)
inspect(logger_obj.get("queue") is None, content="true")
inspect(sink_obj.get("kind").unwrap().expect_json_string(), content="console")
inspect(sink_obj.get("path").unwrap().expect_json_string(), content="")
inspect(sink_obj.get("append").unwrap().expect_json_bool(), content="true")
inspect(sink_obj.get("kind").unwrap().as_string().unwrap(), content="console")
inspect(sink_obj.get("path").unwrap().as_string().unwrap(), content="")
inspect(sink_obj.get("append").unwrap().as_bool().unwrap(), content="true")
inspect(
sink_obj.get("auto_flush").unwrap().expect_json_bool(),
sink_obj.get("auto_flush").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("show_timestamp").unwrap().expect_json_bool(),
formatter_obj.get("show_timestamp").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("show_level").unwrap().expect_json_bool(),
formatter_obj.get("show_level").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("show_target").unwrap().expect_json_bool(),
formatter_obj.get("show_target").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("show_fields").unwrap().expect_json_bool(),
formatter_obj.get("show_fields").unwrap().as_bool().unwrap(),
content="true",
)
inspect(
formatter_obj.get("separator").unwrap().expect_json_string(),
formatter_obj.get("separator").unwrap().as_string().unwrap(),
content=" ",
)
inspect(
formatter_obj.get("field_separator").unwrap().expect_json_string(),
formatter_obj.get("field_separator").unwrap().as_string().unwrap(),
content=" ",
)
inspect(
formatter_obj.get("template").unwrap().expect_json_string(),
formatter_obj.get("template").unwrap().as_string().unwrap(),
content="",
)
inspect(
formatter_obj.get("color_mode").unwrap().expect_json_string(),
formatter_obj.get("color_mode").unwrap().as_string().unwrap(),
content="never",
)
inspect(
formatter_obj.get("color_support").unwrap().expect_json_string(),
formatter_obj.get("color_support").unwrap().as_string().unwrap(),
content="truecolor",
)
inspect(
formatter_obj.get("style_markup").unwrap().expect_json_string(),
formatter_obj.get("style_markup").unwrap().as_string().unwrap(),
content="full",
)
inspect(
formatter_obj.get("target_style_markup").unwrap().expect_json_string(),
formatter_obj.get("target_style_markup").unwrap().as_string().unwrap(),
content="disabled",
)
inspect(
formatter_obj.get("fields_style_markup").unwrap().expect_json_string(),
formatter_obj.get("fields_style_markup").unwrap().as_string().unwrap(),
content="disabled",
)
inspect(formatter_obj.get("style_tags") is None, content="true")
inspect(
async_obj.get("max_pending").unwrap().expect_json_number().to_int(),
async_obj.get("max_pending").unwrap().as_number().unwrap().to_int(),
content="0",
)
inspect(
async_obj.get("max_batch").unwrap().expect_json_number().to_int(),
async_obj.get("max_batch").unwrap().as_number().unwrap().to_int(),
content="1",
)
inspect(
async_obj.get("linger_ms").unwrap().expect_json_number().to_int(),
async_obj.get("linger_ms").unwrap().as_number().unwrap().to_int(),
content="0",
)
inspect(
async_obj.get("overflow").unwrap().expect_json_string(),
async_obj.get("overflow").unwrap().as_string().unwrap(),
content="Blocking",
)
inspect(async_obj.get("flush").unwrap().expect_json_string(), content="Never")
inspect(async_obj.get("flush").unwrap().as_string().unwrap(), content="Never")
}
///|
@@ -1090,7 +1068,7 @@ test "async config parsers reject malformed input" {
})() catch {
err => err.to_string()
}
inspect(invalid_json_error != "no error", content="true")
inspect(invalid_json_error.contains("UnexpectedToken"), content="true")
let wrong_type_error = (fn() -> String raise {
ignore(parse_async_logger_config_text("{\"max_pending\":\"many\"}"))
@@ -1166,7 +1144,7 @@ test "async runtime capability helpers stay consistent" {
)
inspect(state.background_worker == worker_supported, content="true")
inspect(
async_runtime_state_to_json(state).stringify(),
@json_parser.stringify(async_runtime_state_to_json(state)),
content=if worker_supported {
"{\"mode\":\"native_worker\",\"background_worker\":true}"
} else {
@@ -1247,7 +1225,7 @@ test "async logger state snapshot reflects current counters and runtime" {
content="Shutdown",
)
inspect(
async_logger_state_to_json(state).stringify(),
@json_parser.stringify(async_logger_state_to_json(state)),
content=if async_runtime_supports_background_worker() {
"{\"runtime\":{\"mode\":\"native_worker\",\"background_worker\":true},\"phase\":\"ready\",\"pending_count\":0,\"dropped_count\":0,\"is_closed\":false,\"is_running\":false,\"has_failed\":false,\"backlog_retained\":false,\"can_rerun\":true,\"terminal\":false,\"last_error\":\"\",\"flush_policy\":\"Shutdown\"}"
} else {
@@ -2426,7 +2404,7 @@ async test "library async parse-build unwrap matches parsed direct async builder
///|
async test "library async parse-build unwrap preserves file-backed runtime helpers" {
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.lib.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"logs/async-lib-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.lib.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"async-lib-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
let logger = parse_and_build_library_async_logger(raw)
let full = logger.to_async_logger()
let direct = build_async_logger(parse_async_logger_build_config_text(raw))
@@ -2942,7 +2920,7 @@ test "library async text logger ignores sink kind and still uses text formatter"
target="async.lib.text.kind",
sink=@bitlogger.SinkConfig::new(
kind=@bitlogger.SinkKind::File,
path="logs/ignored.log",
path="ignored.log",
text_formatter=@bitlogger.TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
@@ -3255,7 +3233,7 @@ async test "async logger projection preserves file-backed runtime helpers throug
target="async.projected.file",
sink=@bitlogger.SinkConfig::new(
kind=@bitlogger.SinkKind::File,
path="logs/async-projected-file.log",
path="async-projected-file.log",
),
queue=Some(
@bitlogger.QueueConfig::new(
@@ -3552,7 +3530,7 @@ async test "application async builder preserves file-backed runtime helpers" {
target="async.app.file.same-build",
sink=@bitlogger.SinkConfig::new(
kind=@bitlogger.SinkKind::File,
path="logs/async-app-file-same-build.log",
path="async-app-file-same-build.log",
),
queue=Some(
@bitlogger.QueueConfig::new(
@@ -4406,7 +4384,7 @@ test "application text async logger ignores sink kind and still uses text format
target="async.app.text.kind.alias",
sink=@bitlogger.SinkConfig::new(
kind=@bitlogger.SinkKind::File,
path="logs/ignored.log",
path="ignored.log",
text_formatter=@bitlogger.TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
@@ -4484,7 +4462,7 @@ test "build async text logger ignores sink kind and still uses text formatter" {
target="async.text.kind",
sink=@bitlogger.SinkConfig::new(
kind=@bitlogger.SinkKind::File,
path="logs/ignored.log",
path="ignored.log",
text_formatter=@bitlogger.TextFormatterConfig::new(
show_timestamp=false,
separator=" | ",
@@ -4981,7 +4959,7 @@ async test "application async parse-build matches parsed direct async builder be
///|
async test "application async parse-build preserves file-backed runtime helpers" {
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.app.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"logs/async-app-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
let raw = "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.app.json.file.same-build\",\"queue\":{\"max_pending\":3,\"overflow\":\"DropOldest\"},\"sink\":{\"kind\":\"file\",\"path\":\"async-app-json-file-same-build.log\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropOldest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Shutdown\"}}"
let application = parse_and_build_application_async_logger(raw)
let direct = build_async_logger(parse_async_logger_build_config_text(raw))
+10 -12
View File
@@ -48,7 +48,9 @@ pub fn async_runtime_state() -> AsyncRuntimeState {
}
///|
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
pub fn async_runtime_state_to_json(
state : AsyncRuntimeState,
) -> @json_parser.JsonValue {
@utils.async_runtime_state_to_json(state)
}
@@ -61,7 +63,9 @@ pub fn stringify_async_runtime_state(
}
///|
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
pub fn async_logger_state_to_json(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
@utils.async_logger_state_to_json(state)
}
@@ -84,7 +88,9 @@ pub fn parse_async_logger_config_text(
}
///|
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
pub fn async_logger_config_to_json(
config : AsyncLoggerConfig,
) -> @json_parser.JsonValue {
@utils.async_logger_config_to_json(config)
}
@@ -109,7 +115,7 @@ pub fn parse_async_logger_build_config_text(
///|
pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig,
) -> Json {
) -> @json_parser.JsonValue {
@utils.async_logger_build_config_to_json(config)
}
@@ -274,14 +280,6 @@ pub fn[S] AsyncLogger::with_context_fields(
{ ..self, context_fields: fields }
}
///|
pub fn[S] AsyncLogger::with_trace_context(
self : AsyncLogger[S],
context : @bitlogger.TraceContext,
) -> AsyncLogger[S] {
self.with_context_fields(context.as_fields())
}
///|
pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S],
@@ -1,90 +0,0 @@
///|
async test "native async file lifecycle flushes rotates and restarts" {
if !@bitlogger.native_files_supported() {
inspect(@bitlogger.native_files_supported(), content="false")
} else {
let path = "logs/bitlogger-async-native-e2e.log"
let first_flushes : Ref[Int] = Ref(0)
let first_sink = @bitlogger.file_sink(
path,
append=false,
auto_flush=false,
rotation=Some(@bitlogger.file_rotation(8, max_backups=1)),
formatter=fn(rec) { rec.message },
)
let first = async_logger(
first_sink,
config=AsyncLoggerConfig::new(
max_pending=4,
overflow=AsyncOverflowPolicy::Blocking,
max_batch=4,
linger_ms=0,
flush=AsyncFlushPolicy::Batch,
),
min_level=@bitlogger.Level::Info,
target="async.file.e2e",
flush=fn(sink) {
first_flushes.val += 1
ignore(sink.flush())
},
)
@async.with_task_group(group => {
group.spawn_bg(() => first.run())
first.info("first-record")
first.shutdown()
})
inspect(first.is_closed(), content="true")
inspect(first.has_failed(), content="false")
inspect(first.pending_count(), content="0")
inspect(first_flushes.val > 0, content="true")
inspect(first_sink.path(), content="logs/bitlogger-async-native-e2e.log")
inspect(first_sink.rotation_enabled(), content="true")
inspect(first_sink.rotation_failures(), content="0")
inspect(first_sink.write_failures(), content="0")
inspect(first_sink.flush_failures(), content="0")
inspect(first_sink.close(), content="true")
let restart_flushes : Ref[Int] = Ref(0)
let restarted_sink = @bitlogger.file_sink(
path,
append=true,
auto_flush=false,
rotation=Some(@bitlogger.file_rotation(8, max_backups=1)),
formatter=fn(rec) { rec.message },
)
let restarted = async_logger(
restarted_sink,
config=AsyncLoggerConfig::new(
max_pending=4,
overflow=AsyncOverflowPolicy::Blocking,
max_batch=4,
linger_ms=0,
flush=AsyncFlushPolicy::Shutdown,
),
min_level=@bitlogger.Level::Info,
target="async.file.e2e",
flush=fn(sink) {
restart_flushes.val += 1
ignore(sink.flush())
},
)
@async.with_task_group(group => {
group.spawn_bg(() => restarted.run())
restarted.info("second-record")
restarted.shutdown()
})
inspect(restarted.is_closed(), content="true")
inspect(restarted.has_failed(), content="false")
inspect(restarted.pending_count(), content="0")
inspect(restart_flushes.val, content="1")
inspect(restarted_sink.rotation_enabled(), content="true")
inspect(restarted_sink.rotation_failures(), content="0")
inspect(restarted_sink.write_failures(), content="0")
inspect(restarted_sink.flush_failures(), content="0")
inspect(restarted_sink.close(), content="true")
}
}
-32
View File
@@ -1,32 +0,0 @@
///|
fn Json::expect_json_object(self : Json) -> Map[String, Json] {
match self {
Json::Object(value) => value
_ => abort("Expected JSON object")
}
}
///|
fn Json::expect_json_number(self : Json) -> Double {
match self {
Json::Number(value, ..) => value
_ => abort("Expected JSON number")
}
}
///|
fn Json::expect_json_string(self : Json) -> String {
match self {
Json::String(value) => value
_ => abort("Expected JSON string")
}
}
///|
fn Json::expect_json_bool(self : Json) -> Bool {
match self {
Json::True => true
Json::False => false
_ => abort("Expected JSON bool")
}
}
-8
View File
@@ -93,14 +93,6 @@ pub fn[S] LibraryAsyncLogger::bind(
self.with_context_fields(fields)
}
///|
pub fn[S] LibraryAsyncLogger::with_trace_context(
self : LibraryAsyncLogger[S],
context : @bitlogger.TraceContext,
) -> LibraryAsyncLogger[S] {
library_async_logger(self.inner.with_trace_context(context))
}
///|
pub fn[S] LibraryAsyncLogger::is_enabled(
self : LibraryAsyncLogger[S],
+1 -1
View File
@@ -1,7 +1,7 @@
import {
"Nanaloveyuki/BitLogger/src" @bitlogger,
"Nanaloveyuki/BitLogger/src-async/utils",
"moonbitlang/core/json",
"maria/json_parser",
"moonbitlang/async",
"moonbitlang/async/aqueue",
"moonbitlang/core/env",
+5 -6
View File
@@ -6,6 +6,7 @@ import {
"Nanaloveyuki/BitLogger/src/core",
"Nanaloveyuki/BitLogger/src/runtime",
"Nanaloveyuki/BitLogger/src/sink_graph",
"maria/json_parser",
"moonbitlang/async/aqueue",
"moonbitlang/core/ref",
}
@@ -13,11 +14,11 @@ import {
// Values
pub fn[S] async_logger(S, config? : @utils.AsyncLoggerConfig, min_level? : @core.Level, target? : String, flush? : (S) -> Unit raise) -> AsyncLogger[S]
pub fn async_logger_build_config_to_json(@utils.AsyncLoggerBuildConfig) -> Json
pub fn async_logger_build_config_to_json(@utils.AsyncLoggerBuildConfig) -> @json_parser.JsonValue
pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> Json
pub fn async_logger_config_to_json(@utils.AsyncLoggerConfig) -> @json_parser.JsonValue
pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> Json
pub fn async_logger_state_to_json(@utils.AsyncLoggerState) -> @json_parser.JsonValue
pub fn async_runtime_mode() -> @utils.AsyncRuntimeMode
@@ -25,7 +26,7 @@ pub fn async_runtime_mode_label(@utils.AsyncRuntimeMode) -> String
pub fn async_runtime_state() -> @utils.AsyncRuntimeState
pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> Json
pub fn async_runtime_state_to_json(@utils.AsyncRuntimeState) -> @json_parser.JsonValue
pub fn async_runtime_supports_background_worker() -> Bool
@@ -114,7 +115,6 @@ pub fn[S] AsyncLogger::with_min_level(Self[S], @core.Level) -> Self[S]
pub fn[S] AsyncLogger::with_patch(Self[S], (@core.Record) -> @core.Record) -> Self[S]
pub fn[S] AsyncLogger::with_target(Self[S], String) -> Self[S]
pub fn[S] AsyncLogger::with_timestamp(Self[S], enabled? : Bool) -> Self[S]
pub fn[S] AsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
pub struct LibraryAsyncLogger[S] {
inner : AsyncLogger[S]
@@ -132,7 +132,6 @@ pub fn[S] LibraryAsyncLogger::to_async_logger(Self[S]) -> AsyncLogger[S]
pub async fn[S] LibraryAsyncLogger::warn(Self[S], String, fields? : Array[@core.Field]) -> Unit
pub fn[S] LibraryAsyncLogger::with_context_fields(Self[S], Array[@core.Field]) -> Self[S]
pub fn[S] LibraryAsyncLogger::with_target(Self[S], String) -> Self[S]
pub fn[S] LibraryAsyncLogger::with_trace_context(Self[S], @core.TraceContext) -> Self[S]
// Type aliases
pub type ApplicationAsyncLogger = AsyncLogger[@runtime.RuntimeSink]
+93 -59
View File
@@ -154,10 +154,12 @@ pub fn AsyncLoggerState::new(
}
///|
pub fn async_runtime_state_to_json(state : AsyncRuntimeState) -> Json {
Json::object({
"mode": Json::string(async_runtime_mode_label(state.mode)),
"background_worker": Json::boolean(state.background_worker),
pub fn async_runtime_state_to_json(
state : AsyncRuntimeState,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"mode": @json_parser.JsonValue::String(async_runtime_mode_label(state.mode)),
"background_worker": @json_parser.JsonValue::Bool(state.background_worker),
})
}
@@ -168,9 +170,9 @@ pub fn stringify_async_runtime_state(
) -> String {
let value = async_runtime_state_to_json(state)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
@@ -184,25 +186,37 @@ fn async_flush_policy_label(policy : AsyncFlushPolicy) -> String {
}
///|
fn async_logger_state_to_json_value(state : AsyncLoggerState) -> Json {
Json::object({
fn async_logger_state_to_json_value(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"runtime": async_runtime_state_to_json(state.runtime),
"phase": Json::string(async_lifecycle_phase_label(state.phase)),
"pending_count": Json::number(state.pending_count.to_double()),
"dropped_count": Json::number(state.dropped_count.to_double()),
"is_closed": Json::boolean(state.is_closed),
"is_running": Json::boolean(state.is_running),
"has_failed": Json::boolean(state.has_failed),
"backlog_retained": Json::boolean(state.backlog_retained),
"can_rerun": Json::boolean(state.can_rerun),
"terminal": Json::boolean(state.terminal),
"last_error": Json::string(state.last_error),
"flush_policy": Json::string(async_flush_policy_label(state.flush_policy)),
"phase": @json_parser.JsonValue::String(
async_lifecycle_phase_label(state.phase),
),
"pending_count": @json_parser.JsonValue::Number(
state.pending_count.to_double(),
),
"dropped_count": @json_parser.JsonValue::Number(
state.dropped_count.to_double(),
),
"is_closed": @json_parser.JsonValue::Bool(state.is_closed),
"is_running": @json_parser.JsonValue::Bool(state.is_running),
"has_failed": @json_parser.JsonValue::Bool(state.has_failed),
"backlog_retained": @json_parser.JsonValue::Bool(state.backlog_retained),
"can_rerun": @json_parser.JsonValue::Bool(state.can_rerun),
"terminal": @json_parser.JsonValue::Bool(state.terminal),
"last_error": @json_parser.JsonValue::String(state.last_error),
"flush_policy": @json_parser.JsonValue::String(
async_flush_policy_label(state.flush_policy),
),
})
}
///|
pub fn async_logger_state_to_json(state : AsyncLoggerState) -> Json {
pub fn async_logger_state_to_json(
state : AsyncLoggerState,
) -> @json_parser.JsonValue {
async_logger_state_to_json_value(state)
}
@@ -213,9 +227,9 @@ pub fn stringify_async_logger_state(
) -> String {
let value = async_logger_state_to_json_value(state)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
@@ -279,38 +293,53 @@ fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
pub fn parse_async_logger_config_text(
input : String,
) -> AsyncLoggerConfig raise {
let root = @json.parse(input)
let obj = match root {
Json::Object(obj) => obj
_ => raise Failure::Failure("Expected object for async logger config")
let root = @json_parser.parse(input)
let obj = match root.as_object() {
Some(obj) => obj
None => raise Failure::Failure("Expected object for async logger config")
}
let max_pending = match obj.get("max_pending") {
Some(Json::Number(number, ..)) => number.to_int()
Some(_) =>
raise Failure::Failure("Expected number at async_config.max_pending")
Some(value) =>
match value.as_number() {
Some(number) => number.to_int()
None =>
raise Failure::Failure("Expected number at async_config.max_pending")
}
None => 0
}
let overflow = match obj.get("overflow") {
Some(Json::String(text)) => parse_async_overflow(text)
Some(_) =>
raise Failure::Failure("Expected string at async_config.overflow")
Some(value) =>
match value.as_string() {
Some(text) => parse_async_overflow(text)
None =>
raise Failure::Failure("Expected string at async_config.overflow")
}
None => AsyncOverflowPolicy::Blocking
}
let max_batch = match obj.get("max_batch") {
Some(Json::Number(number, ..)) => number.to_int()
Some(_) =>
raise Failure::Failure("Expected number at async_config.max_batch")
Some(value) =>
match value.as_number() {
Some(number) => number.to_int()
None =>
raise Failure::Failure("Expected number at async_config.max_batch")
}
None => 1
}
let linger_ms = match obj.get("linger_ms") {
Some(Json::Number(number, ..)) => number.to_int()
Some(_) =>
raise Failure::Failure("Expected number at async_config.linger_ms")
Some(value) =>
match value.as_number() {
Some(number) => number.to_int()
None =>
raise Failure::Failure("Expected number at async_config.linger_ms")
}
None => 0
}
let flush = match obj.get("flush") {
Some(Json::String(text)) => parse_async_flush(text)
Some(_) => raise Failure::Failure("Expected string at async_config.flush")
Some(value) =>
match value.as_string() {
Some(text) => parse_async_flush(text)
None => raise Failure::Failure("Expected string at async_config.flush")
}
None => AsyncFlushPolicy::Never
}
AsyncLoggerConfig::new(
@@ -323,19 +352,23 @@ pub fn parse_async_logger_config_text(
}
///|
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> Json {
Json::object({
"max_pending": Json::number(config.max_pending.to_double()),
"max_batch": Json::number(config.max_batch.to_double()),
"linger_ms": Json::number(config.linger_ms.to_double()),
"overflow": Json::string(
pub fn async_logger_config_to_json(
config : AsyncLoggerConfig,
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"max_pending": @json_parser.JsonValue::Number(
config.max_pending.to_double(),
),
"max_batch": @json_parser.JsonValue::Number(config.max_batch.to_double()),
"linger_ms": @json_parser.JsonValue::Number(config.linger_ms.to_double()),
"overflow": @json_parser.JsonValue::String(
match config.overflow {
AsyncOverflowPolicy::Blocking => "Blocking"
AsyncOverflowPolicy::DropOldest => "DropOldest"
AsyncOverflowPolicy::DropNewest => "DropNewest"
},
),
"flush": Json::string(
"flush": @json_parser.JsonValue::String(
match config.flush {
AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch"
@@ -352,9 +385,9 @@ pub fn stringify_async_logger_config(
) -> String {
let value = async_logger_config_to_json(config)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
@@ -376,20 +409,21 @@ pub fn AsyncLoggerBuildConfig::new(
pub fn parse_async_logger_build_config_text(
input : String,
) -> AsyncLoggerBuildConfig raise {
let root = @json.parse(input)
let obj = match root {
Json::Object(obj) => obj
_ =>
let root = @json_parser.parse(input)
let obj = match root.as_object() {
Some(obj) => obj
None =>
raise Failure::Failure(
"Expected object at async logger build config root",
)
}
let logger = match obj.get("logger") {
Some(value) => @bitlogger.parse_logger_config_text(value.stringify())
Some(value) =>
@bitlogger.parse_logger_config_text(@json_parser.stringify(value))
None => @bitlogger.default_logger_config()
}
let async_config = match obj.get("async_config") {
Some(value) => parse_async_logger_config_text(value.stringify())
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value))
None => AsyncLoggerConfig::new()
}
AsyncLoggerBuildConfig::new(logger~, async_config~)
@@ -398,8 +432,8 @@ pub fn parse_async_logger_build_config_text(
///|
pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig,
) -> Json {
Json::object({
) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"logger": @bitlogger.logger_config_to_json(config.logger),
"async_config": async_logger_config_to_json(config.async_config),
})
@@ -412,8 +446,8 @@ pub fn stringify_async_logger_build_config(
) -> String {
let value = async_logger_build_config_to_json(config)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import {
"Nanaloveyuki/BitLogger/src" @bitlogger,
"moonbitlang/core/json",
"maria/json_parser",
}
+5 -4
View File
@@ -3,18 +3,19 @@ package "Nanaloveyuki/BitLogger/src-async/utils"
import {
"Nanaloveyuki/BitLogger/src/config_model",
"maria/json_parser",
}
// Values
pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> Json
pub fn async_logger_build_config_to_json(AsyncLoggerBuildConfig) -> @json_parser.JsonValue
pub fn async_logger_config_to_json(AsyncLoggerConfig) -> Json
pub fn async_logger_config_to_json(AsyncLoggerConfig) -> @json_parser.JsonValue
pub fn async_logger_state_to_json(AsyncLoggerState) -> Json
pub fn async_logger_state_to_json(AsyncLoggerState) -> @json_parser.JsonValue
pub fn async_runtime_mode_label(AsyncRuntimeMode) -> String
pub fn async_runtime_state_to_json(AsyncRuntimeState) -> Json
pub fn async_runtime_state_to_json(AsyncRuntimeState) -> @json_parser.JsonValue
pub fn compatibility_async_runtime_mode() -> AsyncRuntimeMode
+58 -4
View File
@@ -1,6 +1,6 @@
///|
test "file sink tracks rotation failures on unavailable backend" {
let path = "logs/bitlogger-rotate.log"
let path = "bitlogger-rotate.log"
ignore(@utils.remove_file_internal(path))
ignore(@utils.remove_file_internal(path + ".1"))
let sink = file_sink(path, rotation=Some(file_rotation(1, max_backups=1)))
@@ -19,7 +19,7 @@ test "file sink tracks rotation failures on unavailable backend" {
///|
test "file sink reopen and failure counters reflect backend state" {
let sink = file_sink("logs/bitlogger-reopen.log")
let sink = file_sink("bitlogger-reopen.log")
if sink.is_available() {
inspect(sink.append_mode(), content="true")
inspect(sink.open_failures(), content="0")
@@ -45,7 +45,7 @@ test "file sink reopen and failure counters reflect backend state" {
inspect(sink.flush_failures(), content="0")
inspect(sink.rotation_failures(), content="0")
inspect(sink.close(), content="true")
ignore(@utils.remove_file_internal("logs/bitlogger-reopen.log"))
ignore(@utils.remove_file_internal("bitlogger-reopen.log"))
} else {
inspect(sink.append_mode(), content="true")
inspect(sink.open_failures(), content="1")
@@ -71,7 +71,7 @@ test "file sink reopen and failure counters reflect backend state" {
///|
test "file sink can rotate on native backend" {
let path = "logs/bitlogger-rotate-native.log"
let path = "bitlogger-rotate-native.log"
ignore(@utils.remove_file_internal(path))
ignore(@utils.remove_file_internal(path + ".1"))
ignore(@utils.remove_file_internal(path + ".2"))
@@ -105,3 +105,57 @@ test "file sink can rotate on native backend" {
inspect(sink.rotation_failures(), content="0")
}
}
///|
#cfg(platform="windows")
test "file sink rotation failure counts partial backup-chain failure on native backend" {
let path = "bitlogger-rotate-partial-failure.log"
ignore(@utils.remove_file_internal(path))
ignore(@utils.remove_file_internal(path + ".1"))
ignore(@utils.remove_file_internal(path + ".2"))
let seed = file_sink(
path,
auto_flush=true,
rotation=Some(file_rotation(20, max_backups=2)),
formatter=fn(rec) { rec.message },
)
if seed.is_available() {
seed.write(record(Level::Info, "1234567890"))
seed.write(record(Level::Info, "abcdefghij"))
inspect(seed.rotation_failures(), content="0")
inspect(seed.close(), content="true")
let locked = @utils.open_file_handle_internal(path + ".2", true)
inspect(locked is Some(_), content="true")
let sink = file_sink(
path,
auto_flush=true,
rotation=Some(file_rotation(20, max_backups=2)),
formatter=fn(rec) { rec.message },
)
inspect(sink.is_available(), content="true")
sink.write(record(Level::Info, "klmnopqrst"))
inspect(sink.rotation_failures(), content="1")
inspect(sink.state().rotation_failures, content="1")
inspect(sink.write_failures(), content="1")
inspect(sink.is_available(), content="false")
match locked {
None => ()
Some(handle) => ignore(@utils.close_file_handle_internal(handle))
}
inspect(sink.reopen_append(), content="true")
inspect(sink.is_available(), content="true")
inspect(sink.close(), content="true")
ignore(@utils.remove_file_internal(path))
ignore(@utils.remove_file_internal(path + ".1"))
ignore(@utils.remove_file_internal(path + ".2"))
} else {
inspect(native_files_supported(), content="false")
}
}
+3 -6
View File
@@ -81,7 +81,7 @@ test "application logger keeps configured runtime helper surface" {
LoggerConfig::new(
min_level=Level::Warn,
target="app.alias.helpers",
sink=SinkConfig::new(kind=SinkKind::File, path="logs/app-alias.log"),
sink=SinkConfig::new(kind=SinkKind::File, path="app-alias.log"),
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
),
)
@@ -229,10 +229,7 @@ test "application logger file helpers match direct configured builder behavior"
let config = LoggerConfig::new(
min_level=Level::Warn,
target="app.file.same-build",
sink=SinkConfig::new(
kind=SinkKind::File,
path="logs/app-file-same-build.log",
),
sink=SinkConfig::new(kind=SinkKind::File, path="app-file-same-build.log"),
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
)
let application = build_application_logger(config)
@@ -347,7 +344,7 @@ test "application logger file controls match direct configured builder behavior"
target="app.file.controls.same-build",
sink=SinkConfig::new(
kind=SinkKind::File,
path="logs/app-file-controls-same-build.log",
path="app-file-controls-same-build.log",
),
)
let application = build_application_logger(config)
+9 -5
View File
@@ -39,7 +39,7 @@ pub fn parse_logger_config_text(
}
///|
pub fn queue_config_to_json(queue : QueueConfig) -> Json {
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
@config_model.queue_config_to_json(queue)
}
@@ -52,7 +52,9 @@ pub fn stringify_queue_config(
}
///|
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
pub fn text_formatter_config_to_json(
config : TextFormatterConfig,
) -> @json_parser.JsonValue {
@config_model.text_formatter_config_to_json(config)
}
@@ -65,12 +67,14 @@ pub fn stringify_text_formatter_config(
}
///|
pub fn file_rotation_config_to_json(config : FileRotation) -> Json {
pub fn file_rotation_config_to_json(
config : FileRotation,
) -> @json_parser.JsonValue {
@file_model.file_rotation_config_to_json(config)
}
///|
pub fn sink_config_to_json(config : SinkConfig) -> Json {
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
@config_model.sink_config_to_json(config)
}
@@ -83,7 +87,7 @@ pub fn stringify_sink_config(
}
///|
pub fn logger_config_to_json(config : LoggerConfig) -> Json {
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
@config_model.logger_config_to_json(config)
}
+119 -91
View File
@@ -42,7 +42,7 @@ pub fn TextFormatterConfig::new(
style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Full,
target_style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Disabled,
fields_style_markup? : @formatting.StyleMarkupMode = @formatting.StyleMarkupMode::Disabled,
style_tags? : Map[String, @formatting.TextStyle] = Map([]),
style_tags? : Map[String, @formatting.TextStyle] = {},
) -> TextFormatterConfig {
{
show_timestamp,
@@ -170,84 +170,102 @@ pub fn default_logger_config() -> LoggerConfig {
///|
fn expect_object(
value : Json,
value : @json_parser.JsonValue,
context : String,
) -> Map[String, Json] raise ConfigError {
match value {
Json::Object(obj) => obj
_ => raise ConfigError::InvalidConfig("Expected object at " + context)
) -> Map[String, @json_parser.JsonValue] raise ConfigError {
match value.as_object() {
Some(obj) => obj
None => raise ConfigError::InvalidConfig("Expected object at " + context)
}
}
///|
fn get_string(
obj : Map[String, Json],
obj : Map[String, @json_parser.JsonValue],
key : String,
default? : String = "",
) -> String raise ConfigError {
match obj.get(key) {
None => default
Some(Json::String(text)) => text
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
Some(value) =>
match value.as_string() {
Some(text) => text
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
}
}
///|
fn get_optional_string(
obj : Map[String, Json],
obj : Map[String, @json_parser.JsonValue],
key : String,
) -> String? raise ConfigError {
match obj.get(key) {
None => None
Some(Json::String(text)) => Some(text)
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
Some(value) =>
match value.as_string() {
Some(text) => Some(text)
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
}
}
///|
fn get_bool(
obj : Map[String, Json],
obj : Map[String, @json_parser.JsonValue],
key : String,
default~ : Bool,
) -> Bool raise ConfigError {
match obj.get(key) {
None => default
Some(Json::True) => true
Some(Json::False) => false
Some(_) => raise ConfigError::InvalidConfig("Expected bool at key " + key)
Some(value) =>
match value.as_bool() {
Some(flag) => flag
None => raise ConfigError::InvalidConfig("Expected bool at key " + key)
}
}
}
///|
fn get_int(
obj : Map[String, Json],
obj : Map[String, @json_parser.JsonValue],
key : String,
default~ : Int,
) -> Int raise ConfigError {
match obj.get(key) {
None => default
Some(Json::Number(number, ..)) => number.to_int()
Some(_) => raise ConfigError::InvalidConfig("Expected number at key " + key)
Some(value) =>
match value.as_number() {
Some(number) => number.to_int()
None =>
raise ConfigError::InvalidConfig("Expected number at key " + key)
}
}
}
///|
fn get_optional_int64_string(
obj : Map[String, Json],
obj : Map[String, @json_parser.JsonValue],
key : String,
) -> Int64? raise ConfigError {
match obj.get(key) {
None => None
Some(Json::String(text)) =>
Some(
@string.parse_int64(text) catch {
_ =>
raise ConfigError::InvalidConfig(
"Expected int64 string at key " + key,
)
},
)
Some(_) => raise ConfigError::InvalidConfig("Expected string at key " + key)
Some(value) =>
match value.as_string() {
Some(text) =>
Some(
@string.parse_int64(text) catch {
_ =>
raise ConfigError::InvalidConfig(
"Expected int64 string at key " + key,
)
},
)
None =>
raise ConfigError::InvalidConfig("Expected string at key " + key)
}
}
}
@@ -337,7 +355,7 @@ fn sink_kind_label(kind : SinkKind) -> String {
///|
fn parse_text_formatter_config(
value : Json,
value : @json_parser.JsonValue,
) -> TextFormatterConfig raise ConfigError {
let obj = expect_object(value, "text_formatter")
TextFormatterConfig::new(
@@ -362,7 +380,7 @@ fn parse_text_formatter_config(
get_string(obj, "fields_style_markup", default="disabled"),
),
style_tags=match obj.get("style_tags") {
None => Map([])
None => {}
Some(inner) => parse_style_tags_config(inner)
},
)
@@ -370,7 +388,7 @@ fn parse_text_formatter_config(
///|
fn parse_text_style_config(
value : Json,
value : @json_parser.JsonValue,
context : String,
) -> @formatting.TextStyle raise ConfigError {
let obj = expect_object(value, context)
@@ -386,10 +404,10 @@ fn parse_text_style_config(
///|
fn parse_style_tags_config(
value : Json,
value : @json_parser.JsonValue,
) -> Map[String, @formatting.TextStyle] raise ConfigError {
let obj = expect_object(value, "text_formatter.style_tags")
let style_tags : Map[String, @formatting.TextStyle] = Map([])
let style_tags : Map[String, @formatting.TextStyle] = {}
for name, item in obj {
style_tags[name] = parse_text_style_config(
item,
@@ -400,7 +418,9 @@ fn parse_style_tags_config(
}
///|
fn parse_queue_config(value : Json) -> QueueConfig raise ConfigError {
fn parse_queue_config(
value : @json_parser.JsonValue,
) -> QueueConfig raise ConfigError {
let obj = expect_object(value, "queue")
QueueConfig::new(
get_int(obj, "max_pending", default=0),
@@ -410,7 +430,7 @@ fn parse_queue_config(value : Json) -> QueueConfig raise ConfigError {
///|
fn parse_file_rotation_config(
value : Json,
value : @json_parser.JsonValue,
) -> @file_model.FileRotation raise ConfigError {
let obj = expect_object(value, "sink.rotation")
let max_backups = get_int(obj, "max_backups", default=1)
@@ -426,7 +446,9 @@ fn parse_file_rotation_config(
}
///|
fn parse_sink_config(value : Json) -> SinkConfig raise ConfigError {
fn parse_sink_config(
value : @json_parser.JsonValue,
) -> SinkConfig raise ConfigError {
let obj = expect_object(value, "sink")
let kind = parse_sink_kind(get_string(obj, "kind", default="console"))
let formatter = match obj.get("text_formatter") {
@@ -458,7 +480,7 @@ fn parse_sink_config(value : Json) -> SinkConfig raise ConfigError {
pub fn parse_logger_config_text(
input : String,
) -> LoggerConfig raise ConfigError {
let root = @json.parse(input) catch {
let root = @json_parser.parse(input) catch {
e => raise ConfigError::InvalidConfig("Invalid JSON: " + e.to_string())
}
let obj = expect_object(root, "root")
@@ -478,10 +500,10 @@ pub fn parse_logger_config_text(
}
///|
pub fn queue_config_to_json(queue : QueueConfig) -> Json {
Json::object({
"max_pending": Json::number(queue.max_pending.to_double()),
"overflow": Json::string(
pub fn queue_config_to_json(queue : QueueConfig) -> @json_parser.JsonValue {
@json_parser.JsonValue::Object({
"max_pending": @json_parser.JsonValue::Number(queue.max_pending.to_double()),
"overflow": @json_parser.JsonValue::String(
match queue.overflow {
@queue_model.QueueOverflowPolicy::DropNewest => "DropNewest"
@queue_model.QueueOverflowPolicy::DropOldest => "DropOldest"
@@ -497,70 +519,76 @@ pub fn stringify_queue_config(
) -> String {
let value = queue_config_to_json(queue)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
///|
pub fn text_formatter_config_to_json(config : TextFormatterConfig) -> Json {
let obj : Map[String, Json] = {
"show_timestamp": Json::boolean(config.show_timestamp),
"show_level": Json::boolean(config.show_level),
"show_target": Json::boolean(config.show_target),
"show_fields": Json::boolean(config.show_fields),
"separator": Json::string(config.separator),
"field_separator": Json::string(config.field_separator),
"template": Json::string(config.template),
"color_mode": Json::string(@formatting.color_mode_label(config.color_mode)),
"color_support": Json::string(
pub fn text_formatter_config_to_json(
config : TextFormatterConfig,
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {
"show_timestamp": @json_parser.JsonValue::Bool(config.show_timestamp),
"show_level": @json_parser.JsonValue::Bool(config.show_level),
"show_target": @json_parser.JsonValue::Bool(config.show_target),
"show_fields": @json_parser.JsonValue::Bool(config.show_fields),
"separator": @json_parser.JsonValue::String(config.separator),
"field_separator": @json_parser.JsonValue::String(config.field_separator),
"template": @json_parser.JsonValue::String(config.template),
"color_mode": @json_parser.JsonValue::String(
@formatting.color_mode_label(config.color_mode),
),
"color_support": @json_parser.JsonValue::String(
@formatting.color_support_label(config.color_support),
),
"style_markup": Json::string(
"style_markup": @json_parser.JsonValue::String(
@formatting.style_markup_mode_label(config.style_markup),
),
"target_style_markup": Json::string(
"target_style_markup": @json_parser.JsonValue::String(
@formatting.style_markup_mode_label(config.target_style_markup),
),
"fields_style_markup": Json::string(
"fields_style_markup": @json_parser.JsonValue::String(
@formatting.style_markup_mode_label(config.fields_style_markup),
),
}
if config.style_tags.length() != 0 {
obj["style_tags"] = style_tags_config_to_json(config.style_tags)
}
Json::object(obj)
@json_parser.JsonValue::Object(obj)
}
///|
fn text_style_config_to_json(style : @formatting.TextStyle) -> Json {
let obj : Map[String, Json] = {
"bold": Json::boolean(style.bold),
"dim": Json::boolean(style.dim),
"italic": Json::boolean(style.italic),
"underline": Json::boolean(style.underline),
fn text_style_config_to_json(
style : @formatting.TextStyle,
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {
"bold": @json_parser.JsonValue::Bool(style.bold),
"dim": @json_parser.JsonValue::Bool(style.dim),
"italic": @json_parser.JsonValue::Bool(style.italic),
"underline": @json_parser.JsonValue::Bool(style.underline),
}
match style.fg {
Some(value) => obj["fg"] = Json::string(value)
Some(value) => obj["fg"] = @json_parser.JsonValue::String(value)
None => ()
}
match style.bg {
Some(value) => obj["bg"] = Json::string(value)
Some(value) => obj["bg"] = @json_parser.JsonValue::String(value)
None => ()
}
Json::object(obj)
@json_parser.JsonValue::Object(obj)
}
///|
fn style_tags_config_to_json(
style_tags : Map[String, @formatting.TextStyle],
) -> Json {
let obj : Map[String, Json] = Map([])
) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {}
for name, style in style_tags {
obj[name] = text_style_config_to_json(style)
}
Json::object(obj)
@json_parser.JsonValue::Object(obj)
}
///|
@@ -570,19 +598,19 @@ pub fn stringify_text_formatter_config(
) -> String {
let value = text_formatter_config_to_json(config)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
///|
pub fn sink_config_to_json(config : SinkConfig) -> Json {
let obj : Map[String, Json] = {
"kind": Json::string(sink_kind_label(config.kind)),
"path": Json::string(config.path),
"append": Json::boolean(config.append),
"auto_flush": Json::boolean(config.auto_flush),
pub fn sink_config_to_json(config : SinkConfig) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {
"kind": @json_parser.JsonValue::String(sink_kind_label(config.kind)),
"path": @json_parser.JsonValue::String(config.path),
"append": @json_parser.JsonValue::Bool(config.append),
"auto_flush": @json_parser.JsonValue::Bool(config.auto_flush),
"text_formatter": text_formatter_config_to_json(config.text_formatter),
}
match config.rotation {
@@ -590,7 +618,7 @@ pub fn sink_config_to_json(config : SinkConfig) -> Json {
Some(rotation) =>
obj["rotation"] = @file_model.file_rotation_config_to_json(rotation)
}
Json::object(obj)
@json_parser.JsonValue::Object(obj)
}
///|
@@ -600,25 +628,25 @@ pub fn stringify_sink_config(
) -> String {
let value = sink_config_to_json(config)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}
///|
pub fn logger_config_to_json(config : LoggerConfig) -> Json {
let obj : Map[String, Json] = {
"min_level": Json::string(config.min_level.label()),
"target": Json::string(config.target),
"timestamp": Json::boolean(config.timestamp),
pub fn logger_config_to_json(config : LoggerConfig) -> @json_parser.JsonValue {
let obj : Map[String, @json_parser.JsonValue] = {
"min_level": @json_parser.JsonValue::String(config.min_level.label()),
"target": @json_parser.JsonValue::String(config.target),
"timestamp": @json_parser.JsonValue::Bool(config.timestamp),
"sink": sink_config_to_json(config.sink),
}
match config.queue {
None => ()
Some(queue) => obj["queue"] = queue_config_to_json(queue)
}
Json::object(obj)
@json_parser.JsonValue::Object(obj)
}
///|
@@ -628,8 +656,8 @@ pub fn stringify_logger_config(
) -> String {
let value = logger_config_to_json(config)
if pretty {
value.stringify(indent=2)
@json_parser.stringify_pretty(value, 2)
} else {
value.stringify()
@json_parser.stringify(value)
}
}

Some files were not shown because too many files have changed in this diff Show More