454 Commits

Author SHA1 Message Date
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
Nanaloveyuki 7d2d20c31e 📝 refine configured runtime docs 2026-06-13 23:58:00 +08:00
Nanaloveyuki 1e568f55cd 📝 refine configured file setters docs 2026-06-13 23:53:41 +08:00
Nanaloveyuki 4e00c5eac5 📝 refine configured file status docs 2026-06-13 23:51:37 +08:00
Nanaloveyuki 01eb5c6f8d 📝 refine configured file control docs 2026-06-13 23:49:45 +08:00
Nanaloveyuki dddad3d3d0 📝 refine configured file policy docs 2026-06-13 23:47:23 +08:00
Nanaloveyuki 8427cc6cf3 📝 refine configured runtime file state docs 2026-06-13 23:45:46 +08:00
Nanaloveyuki ce3eb06c1c 📝 clarify runtime file export examples 2026-06-13 23:44:05 +08:00
Nanaloveyuki 61094732a8 📝 align runtime file examples 2026-06-13 23:42:09 +08:00
Nanaloveyuki 342d08377f 📝 refresh runtime file constructors docs 2026-06-13 23:39:24 +08:00
Nanaloveyuki 56ba583680 📝 refresh runtime file export docs 2026-06-13 23:37:55 +08:00
Nanaloveyuki 06541a578c 📝 refresh runtime file model docs 2026-06-13 23:36:05 +08:00
Nanaloveyuki d47109aa54 📝 remove duplicate constructor docs 2026-06-13 23:31:41 +08:00
Nanaloveyuki bb39091830 📝 document formatter config constructor 2026-06-13 23:25:56 +08:00
Nanaloveyuki c4ebf09a7b 📝 document config constructors 2026-06-13 23:23:58 +08:00
Nanaloveyuki 78fd2124da 📝 document runtime sink policy setter 2026-06-13 23:21:24 +08:00
Nanaloveyuki 10555b3580 📝 document runtime sink file state 2026-06-13 23:19:03 +08:00
Nanaloveyuki 793e9bfc83 📝 document runtime sink file policies 2026-06-13 23:16:37 +08:00
Nanaloveyuki 419b79ac50 📝 document runtime sink failure counters 2026-06-13 23:14:22 +08:00
Nanaloveyuki c8e4cee9d6 📝 document runtime sink file flush methods 2026-06-13 23:11:39 +08:00
Nanaloveyuki a3ecb6676d 📝 document runtime sink rotation methods 2026-06-13 23:09:40 +08:00
Nanaloveyuki f1b7a86cf6 📝 document runtime sink reopen methods 2026-06-13 23:05:08 +08:00
Nanaloveyuki 65605f6b59 📝 document runtime sink append path methods 2026-06-13 22:59:12 +08:00
Nanaloveyuki bf7512d113 📝 document runtime sink auto flush methods 2026-06-13 22:56:51 +08:00
Nanaloveyuki 6b59398bc6 📝 document text formatter config conversion 2026-06-13 22:54:34 +08:00
Nanaloveyuki 1c83cf2ba1 📝 document runtime file state constructor 2026-06-13 22:52:29 +08:00
Nanaloveyuki c5f7dda2aa 📝 document file sink policy constructor 2026-06-13 22:50:45 +08:00
Nanaloveyuki 18bc3dbdb0 📝 document file sink state constructor 2026-06-13 22:48:43 +08:00
Nanaloveyuki e1ba2fb835 📝 document async state constructors 2026-06-13 22:46:47 +08:00
Nanaloveyuki 45a596cce1 📝 document async logger build config constructor 2026-06-13 22:44:36 +08:00
Nanaloveyuki 0df565d454 📝 document queued sink methods 2026-06-13 22:42:41 +08:00
Nanaloveyuki 86b99fe004 📝 document buffered sink methods 2026-06-13 22:40:26 +08:00
Nanaloveyuki 1ebf31945c 📝 document runtime sink state surface 2026-06-13 22:38:14 +08:00
Nanaloveyuki a17f50db88 📝 document runtime sink control methods 2026-06-13 22:35:36 +08:00
Nanaloveyuki 78ca9e60a9 📝 document file sink policy rotation methods 2026-06-13 22:32:45 +08:00
Nanaloveyuki 138461871a 📝 document file sink counters and resets 2026-06-13 22:29:11 +08:00
Nanaloveyuki afce0c7ce2 📝 document file sink reopen methods 2026-06-13 22:26:05 +08:00
Nanaloveyuki e34065441f 📝 document file sink policy state methods 2026-06-13 22:23:35 +08:00
Nanaloveyuki 78010b2524 📝 document file sink policy mutators 2026-06-13 22:18:35 +08:00
Nanaloveyuki 8373c9c6c1 📝 document file sink policy accessors 2026-06-13 22:16:32 +08:00
Nanaloveyuki 9a3fbcc793 📝 document file sink lifecycle methods 2026-06-13 22:14:13 +08:00
Nanaloveyuki 038c37992f 📝 document formatter color and registry methods 2026-06-13 22:11:32 +08:00
Nanaloveyuki 3dc5759a73 📝 document formatter target and field markup methods 2026-06-13 22:09:02 +08:00
Nanaloveyuki 1c8a8c24a9 📝 document formatter message markup methods 2026-06-13 22:06:30 +08:00
Nanaloveyuki 0f0b4c4321 📝 document style tag registry methods 2026-06-13 22:02:04 +08:00
Nanaloveyuki 248406ce75 🐛 hide internal file backend wrappers 2026-06-13 21:58:38 +08:00
Nanaloveyuki 212bb76b56 📝 document formatter rendering helpers 2026-06-13 21:55:22 +08:00
Nanaloveyuki 0dd4c6b080 📝 document remaining sink root types 2026-06-13 21:49:54 +08:00
Nanaloveyuki 27886e1eba 📝 document wrapper sink types 2026-06-13 21:47:40 +08:00
Nanaloveyuki fd7defbb93 📝 document routing sink types 2026-06-13 21:45:17 +08:00
Nanaloveyuki 1a30562a28 📝 document callback sink types 2026-06-13 21:42:30 +08:00
Nanaloveyuki 0418fcab60 📝 document console sink types 2026-06-13 21:40:40 +08:00
Nanaloveyuki 6ab828c4ce 📝 document formatted sink constructors 2026-06-13 21:37:24 +08:00
Nanaloveyuki a9725a8d33 📝 document root logger types 2026-06-13 21:34:56 +08:00
Nanaloveyuki b60df648ff 📝 document library logger facade types 2026-06-13 21:32:37 +08:00
Nanaloveyuki a983af2e0b 📝 document configured logger alias 2026-06-13 21:29:39 +08:00
Nanaloveyuki 041385712b 📝 document file capability and rotation json helpers 2026-06-13 21:27:21 +08:00
Nanaloveyuki 0566ebbabd 📝 document field and file rotation aliases 2026-06-13 21:24:34 +08:00
Nanaloveyuki 839c03ec57 📝 document sync config model aliases 2026-06-13 21:21:20 +08:00
Nanaloveyuki 9c00ee9341 📝 document formatter model aliases 2026-06-13 21:18:25 +08:00
Nanaloveyuki dea63b1ed6 📝 document async config model aliases 2026-06-13 21:16:18 +08:00
Nanaloveyuki d4ec733b64 📝 document async state model aliases 2026-06-13 21:13:59 +08:00
Nanaloveyuki b47f95c918 📝 document remaining record helpers 2026-06-13 21:10:58 +08:00
Nanaloveyuki e01eed5c44 📝 document record transformation apis 2026-06-13 21:08:25 +08:00
Nanaloveyuki 17573b9f9e 📝 document record alias 2026-06-13 21:05:49 +08:00
Nanaloveyuki 3321ebef8b 📝 document file runtime model aliases 2026-06-13 21:03:32 +08:00
Nanaloveyuki 12f6ce4c2b 📝 document formatter mode aliases 2026-06-13 21:00:34 +08:00
Nanaloveyuki 17c1b95a61 📝 document public config and level aliases 2026-06-13 20:56:46 +08:00
Nanaloveyuki 3881ca505a 📝 document public function type aliases 2026-06-13 20:51:03 +08:00
Nanaloveyuki 554318a7b0 📝 document application logger aliases 2026-06-13 20:48:22 +08:00
Nanaloveyuki 00e06c302f 📝 document async library facade write apis 2026-06-13 20:45:41 +08:00
Nanaloveyuki cff282db2f 📝 document sync library facade write apis 2026-06-13 20:42:12 +08:00
Nanaloveyuki 13f0a24336 📝 document async library facade composition apis 2026-06-13 20:39:24 +08:00
Nanaloveyuki 4e32a7350a 📝 document sync library facade composition apis 2026-06-13 20:36:45 +08:00
Nanaloveyuki 76dbc421eb 📝 document library facade conversion apis 2026-06-13 20:05:22 +08:00
Nanaloveyuki fbd63b5e4a 🔖 bump version to 0.5.2 2026-06-07 20:49:10 +08:00
Nanaloveyuki a3d0a695de 🔖 prepare 0.5.2 release notes 2026-06-07 20:34:25 +08:00
Nanaloveyuki c0fe7999c4 🔖 prepare 0.5.1 release notes 2026-05-21 14:56:04 +08:00
Nanaloveyuki 5c8067d009 📝 simplify bilingual readme copy 2026-05-21 14:22:49 +08:00
Nanaloveyuki 6c19708d7b 📝 clarify api target verification status 2026-05-21 14:14:15 +08:00
Nanaloveyuki 3eee5893f5 tighten cross-target capability contracts 2026-05-21 14:13:17 +08:00
Nanaloveyuki 3e8d4c50a9 📝 align portable examples and release messaging 2026-05-21 14:12:56 +08:00
Nanaloveyuki d4f83f5ccb 🩹 FIx Github Action No such directory 2026-05-20 19:56:10 +08:00
Nanaloveyuki 73bcf78d89 🔖 Prepare 0.5.0 release metadata 2026-05-20 11:40:52 +08:00
Nanaloveyuki 1ca5ab0835 Add coverage for release helper APIs 2026-05-20 11:40:37 +08:00
Nanaloveyuki e019db11d6 📝 Polish onboarding and add feature examples 2026-05-20 11:40:23 +08:00
Nanaloveyuki 5f12991592 ♻️ Extract file sink implementation 2026-05-20 11:40:02 +08:00
Nanaloveyuki 25a6a973d2 📝 Update More API Document 2026-05-20 11:37:49 +08:00
Nanaloveyuki 55af0b664f 🙈 Update Vibecoding gitignore 2026-05-20 11:37:03 +08:00
Nanaloveyuki 0a098915af 📝 Add agent handoff note templates 2026-05-20 10:08:11 +08:00
Nanaloveyuki 4860d1e08b 📝 Document logger config presets 2026-05-20 10:07:52 +08:00
Nanaloveyuki dca34bc114 ♻️ Split global logger emitters 2026-05-20 10:07:08 +08:00
Nanaloveyuki 4c25a81b03 Add logger config presets 2026-05-20 10:06:51 +08:00
Nanaloveyuki 3c15d8ed13 Add logger config presets 2026-05-20 09:40:21 +08:00
Nanaloveyuki 6998df0ee4 ♻️ Split logger emission helpers 2026-05-20 09:40:15 +08:00
Nanaloveyuki 414d6a0ee8 ♻️ Split runtime file control helpers 2026-05-20 09:34:07 +08:00
Nanaloveyuki 2823a67d53 ♻️ Extract async config and state models 2026-05-20 09:22:56 +08:00
Nanaloveyuki a766dd09ac ♻️ Extract runtime file state helpers 2026-05-20 09:12:02 +08:00
Nanaloveyuki d0989d6308 ♻️ Extract config into utils subpackage 2026-05-20 09:02:44 +08:00
Nanaloveyuki 8b752a4b4d ♻️ Extract sink models into utils subpackage 2026-05-20 08:51:48 +08:00
Nanaloveyuki ac45ec2b03 ♻️ Extract file backend into utils subpackage 2026-05-20 08:44:54 +08:00
Nanaloveyuki 41d221af46 ♻️ Extract formatter into utils subpackage 2026-05-20 08:31:55 +08:00
Nanaloveyuki d6e47d4bb8 ♻️ Extract core and utils subpackages 2026-05-20 08:15:31 +08:00
Nanaloveyuki 91096a9e0d ⬆️ Update MoonBit Version to v0.9.2 and Fix warns 2026-05-20 07:55:14 +08:00
Nanaloveyuki 78423384ea Clarify application and library logger entries 2026-05-15 11:42:46 +08:00
Nanaloveyuki 1b56e1e20a Add library logger facades 2026-05-15 11:15:34 +08:00
Nanaloveyuki 91d778d92e ♻️ Split runtime and async shared layers 2026-05-15 11:15:20 +08:00
Nanaloveyuki 1c75c98e3c 🚚 Move bitlogger&bitlogger-async to src& src-async 2026-05-15 10:13:36 +08:00
Nanaloveyuki 02c40f26f9 ⬆️ 升级 Moonbit 版本和 Async 版本 2026-05-15 09:55:29 +08:00
Nanaloveyuki 640b717c70 📝 Add record and level API docs 2026-05-12 16:13:50 +08:00
Nanaloveyuki bd1ef99189 📝 Add global logger API docs 2026-05-12 16:10:24 +08:00
Nanaloveyuki 661c17e093 📝 Add logger write API docs 2026-05-12 16:06:00 +08:00
Nanaloveyuki 474a1931c2 📝 Add record patch API docs 2026-05-12 16:00:17 +08:00
Nanaloveyuki dd895ac211 📝 Add filter predicate API docs 2026-05-12 15:56:30 +08:00
Nanaloveyuki 9248135b9c 📝 Expand API index coverage 2026-05-12 15:51:54 +08:00
Nanaloveyuki 694414e068 📝 Add file state export API docs 2026-05-12 15:05:58 +08:00
Nanaloveyuki bab9864f1f 📝 Add configured logger file state API docs 2026-05-12 14:59:59 +08:00
Nanaloveyuki d1260c5deb 📝 Add configured logger file policy API docs 2026-05-12 14:58:22 +08:00
Nanaloveyuki cfde700526 📝 Add configured logger file control API docs 2026-05-12 14:52:23 +08:00
Nanaloveyuki 87cf651c4c 📝 Update README with remove async summary 2026-05-12 14:49:18 +08:00
Nanaloveyuki 1e487f2ffc 📝 Add API index and simplify README links 2026-05-12 14:30:49 +08:00
Nanaloveyuki f21aca670a 📝 Refocus English README on entry points 2026-05-12 14:26:38 +08:00
Nanaloveyuki 6f1d6086d6 📝 Refocus READMEs on features and entry points 2026-05-12 14:25:11 +08:00
Nanaloveyuki 0ab3b95959 📝 Add configured logger file runtime API docs 2026-05-12 14:18:54 +08:00
Nanaloveyuki bd3a1c24d0 📝 Add configured logger runtime control API docs 2026-05-12 14:17:19 +08:00
Nanaloveyuki d7f93e9f9c 📝 Add async logger write API docs 2026-05-12 14:04:44 +08:00
Nanaloveyuki dad55a241f 📝 Add async logger lifecycle API docs 2026-05-12 13:55:05 +08:00
Nanaloveyuki f172305453 📝 Add async logger composition API docs 2026-05-12 13:51:08 +08:00
Nanaloveyuki 80f15cd455 📝 Polish API doc consistency and scope 2026-05-12 13:47:04 +08:00
Nanaloveyuki c6c153cf53 📝 Refine API doc notes and guidance 2026-05-12 13:32:15 +08:00
Nanaloveyuki 4d913aa642 📝 Align API docs with updated interface template 2026-05-12 13:20:36 +08:00
Nanaloveyuki 8dbadd5938 📝 Add async state and config export API docs 2026-05-12 13:08:27 +08:00
Nanaloveyuki 3dbf848a53 📝 Document logger composition APIs 2026-05-12 13:08:21 +08:00
Nanaloveyuki 0b93af9261 📝 Add sync config export API docs 2026-05-12 13:08:15 +08:00
Nanaloveyuki 6e6c14d28c 📝 Document async logger state diagnostics 2026-05-12 10:57:38 +08:00
Nanaloveyuki f1b7d1d21c Add async logger runtime snapshots 2026-05-12 10:50:42 +08:00
Nanaloveyuki 8584fc7e01 💚 Expand cross-target CI coverage 2026-05-12 10:49:06 +08:00
Nanaloveyuki a328414087 Prepare 0.4.1 release documentation 2026-05-12 10:37:43 +08:00
Nanaloveyuki f609b02377 Add cross-target async compatibility runtime 2026-05-12 10:37:15 +08:00
464 changed files with 51845 additions and 5301 deletions
+32 -4
View File
@@ -29,19 +29,47 @@ jobs:
- name: Check bitlogger
run: |
moon check bitlogger
moon check
- name: Test bitlogger
run: |
moon test bitlogger
moon test
- name: Check bitlogger native
run: |
moon check bitlogger --target native
moon check --target native
- name: Check bitlogger wasm-gc
run: |
moon check --target wasm-gc
- name: Check bitlogger js
run: |
moon check --target js
- name: Check bitlogger_async native
run: |
moon check bitlogger_async --target native
moon check src-async --target native
- name: Check bitlogger_async wasm-gc
run: |
moon check src-async --target wasm-gc
- name: Check bitlogger_async js
run: |
moon check src-async --target js
- name: Test bitlogger_async native
run: |
moon test src-async --target native
- name: Test bitlogger_async wasm-gc
run: |
moon test src-async --target wasm-gc
- name: Test bitlogger_async js
run: |
moon test src-async --target js
- name: Run basic example
run: |
+61
View File
@@ -0,0 +1,61 @@
name: Docs
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: docs-pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Build docs site
env:
DOCS_BASE: /
run: npm run docs:build
- name: Configure Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/configure-pages@v5
- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+9
View File
@@ -16,6 +16,15 @@ desktop.ini
*.log
*.tmp
*.bak
node_modules/
# Dev Documentations
docs/dev/*
# Docs site build artifacts
docs/.vitepress/cache/
docs/.vitepress/dist/
docs/.vitepress/generated/
# vibecoding
AGENTS/
+54 -356
View File
@@ -1,366 +1,64 @@
<html>
<div style="display: flex; justify-content: center; align-items: center; height: 15vh;">
<h3 title="https://moonbitlang.github.io/OSC2026/index.html#top">2026 MoonBit 国产基础软件生态开源大赛参赛作品</h3>
</div>
<div style="display: flex; justify-content: center; align-items: center; height: 8vh;">
<a href="https://mooncakes.io/docs/Nanaloveyuki/BitLogger" title="点击前往 Mooncake 页面"><b>Mooncake@Nanaloveyuki/BitLogger</b></a>
</div>
<div style="display: flex; justify-content: center; align-items: center; height: 2vh;">
<b>中文 | <a href="./docs/README-en.md">English</a></b>
</div>
</html>
# BitLogger
## 📖 介绍
BitLogger 是一个使用 MoonBit 编写的结构化日志库
## ❇️ 特点
- 🧩 基础能力: 支持 level, formatter, sink, context field 和全局 logger.
- 🏗️ 结构清晰: 按 `level / record / formatter / sinks / logger / global` 拆分文件, 便于维护.
- 🔌 可扩展: 支持 `fanout_sink(...)``callback_sink(...)`, 方便接入文件, 指标或外部系统.
- 🔀 可分流: 支持 `split_sink(...)` / `split_by_level(...)`, 可按谓词或 level 将日志路由到不同 sink.
- 🧱 可组合: 支持 `buffered_sink(...)``filter_sink(...)`, 可以组合不同输出策略.
- 🔎 可复用过滤: 提供 `target_has_prefix(...)`, `message_contains(...)`, `level_at_least(...)`, `field_equals(...)` 等过滤辅助函数.
- 🩹 可变换 Record: 支持 `with_patch(...)`, `patch_sink(...)` 以及脱敏, 补字段, message 变换 helper.
- 🧷 可绑定上下文: 支持 `bind(...)``fields(...)`, 便于复用上下文字段.
- 📮 显式队列: 支持 `queued_sink(...)` / `with_queue(...)`, 支持有界积压和溢出策略.
- 🧾 可配置文本格式: 支持 `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)`, `formatted_callback_sink(...)` 和模板化 `template` 输出.
- 🎨 轻量样式标签: 支持 `color_mode`, inline markup, `TextStyle`, `StyleTagRegistry`, 自定义标签与内置标签覆盖.
- 💾 Native 文件输出: 支持 `file_sink(...)`, 基础 size rotation / backup retention, 显式 `reopen()` / `reopen_with_current_policy()` / `reopen_append()` / `reopen_truncate()` 与失败计数, 仅在 `native/llvm` backend 可用.
- 📦 MoonBit 适配: API 和工程结构与 MoonBit 的 package / visibility / toolchain 模型保持一致.
## 🚀 快速开始
```moonbit
let logger = Logger::new(console_sink(), min_level=Level::Info, target="demo")
.with_timestamp()
.with_context_fields([field("service", "bitlogger")])
logger.info("starting", fields=[field("port", "8080")])
```
<details><summary>层级 target 示例</summary>
```moonbit
let worker = Logger::new(console_sink(), target="app").child("worker")
worker.info("job ready")
```
</details>
<details><summary>自定义 callback sink 示例</summary>
```moonbit
let hook = Logger::new(
callback_sink(fn(rec) {
println("callback saw [\{rec.target}] \{rec.message}")
}),
target="hook",
)
hook.info("hello")
```
</details>
<details><summary>基础 buffered sink 示例</summary>
```moonbit
let sink = buffered_sink(console_sink(), flush_limit=2)
let logger = Logger::new(sink, target="buffered")
logger.info("one")
logger.info("two")
sink.flush()
```
</details>
<details><summary>基础 filter sink 示例</summary>
```moonbit
let sink = filter_sink(console_sink(), fn(rec) {
rec.target == "kept"
})
let kept = Logger::new(sink, target="kept")
let dropped = Logger::new(sink, target="dropped")
kept.info("visible")
dropped.info("hidden")
```
</details>
<details><summary>Logger 链式 filter 示例</summary>
```moonbit
let logger = Logger::new(console_sink(), target="service")
.with_filter(all_of([
target_has_prefix("service"),
message_contains("visible"),
]))
logger.info("hidden")
logger.child("api").info("visible")
```
</details>
<details><summary>Record patch 示例</summary>
```moonbit
let logger = Logger::new(console_sink(), target="auth")
.with_patch(compose_patches([
prefix_message("[safe] "),
redact_fields(["token"]),
append_fields([field("service", "bitlogger")]),
]))
logger.info("login", fields=[field("user", "alice"), field("token", "secret")])
```
</details>
<details><summary>bind 上下文示例</summary>
```moonbit
let logger = Logger::new(console_sink(), target="audit")
.bind(fields([("service", "bitlogger"), ("scope", "login")]))
logger.info("accepted", fields=[field("user", "alice")])
```
</details>
<details><summary>显式 queue sink 示例</summary>
```moonbit
let logger = Logger::new(console_sink(), target="queue")
.with_queue(max_pending=2, overflow=QueueOverflowPolicy::DropOldest)
logger.info("one")
logger.info("two")
logger.info("three")
ignore(logger.sink.flush())
```
</details>
<details><summary>按 level 分流 sink 示例</summary>
```moonbit
let logger = Logger::new(
split_by_level(
callback_sink(fn(rec) {
println("high priority: \{rec.level.label()} \{rec.message}")
}),
console_sink(),
min_level=Level::Warn,
),
min_level=Level::Trace,
target="split",
)
logger.info("normal output")
logger.warn("warning output")
```
</details>
<details><summary>自定义文本 formatter 示例</summary>
```moonbit
let formatter = text_formatter(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
color_mode=ColorMode::Always,
)
let logger = Logger::new(text_console_sink(formatter), target="pretty")
logger.info("hello", fields=[field("mode", "pretty")])
```
</details>
<details><summary>inline style tag 示例</summary>
```moonbit
let formatter = text_formatter(
show_timestamp=false,
color_mode=ColorMode::Always,
).with_style_tags(
default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
.define_alias("danger", "red"),
)
let logger = Logger::new(text_console_sink(formatter), target="styled")
logger.info("<accent>styled</> output and <danger>alert</>")
```
</details>
<details><summary>关闭 style markup 解析示例</summary>
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
).without_style_markup()
let logger = Logger::new(text_console_sink(formatter), target="raw")
logger.info("<red>kept as raw text</>")
```
</details>
<details><summary>JSON 配置加载示例</summary>
```moonbit
let config = parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\",\"color_mode\":\"always\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
)
let logger = build_logger(config)
logger.info("configured from json")
ignore(logger.flush())
```
</details>
<details><summary>JSON style_tags 示例</summary>
```moonbit
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
)
let logger = build_logger(config)
logger.info("<accent>styled from json</>")
```
</details>
<details><summary>JSON style_markup 模式示例</summary>
```moonbit
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
)
let logger = build_logger(config)
logger.info("<red>still raw</>")
```
</details>
<details><summary>native 文件 sink 示例</summary>
```moonbit
if native_files_supported() {
let logger = Logger::new(
file_sink("bitlogger.log", rotation=Some(file_rotation(128, max_backups=2))),
target="file",
)
logger.info("hello", fields=[field("kind", "file")])
ignore(logger.sink.flush())
ignore(logger.sink.close())
}
```
</details>
<details><summary>file runtime state dump 示例</summary>
```moonbit
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="bitlogger-runtime.log"),
queue=Some(QueueConfig::new(16)),
),
)
logger.info("queued hello")
match logger.file_runtime_state() {
Some(snapshot) => println(stringify_runtime_file_state(snapshot, pretty=true))
None => ()
}
```
</details>
## 📂 仓库结构
- `bitlogger/`: MoonBit 库 package, 包含本体实现, 测试与 Mooncake README
- `examples/basic/`: 最小可运行示例
- `examples/async_basic/`: 基于 `moonbitlang/async` 的异步 logger 示例
## 🔗 相关文档
BitLogger 是一个使用 MoonBit 编写的结构化日志库,适合命令行工具、服务和需要统一日志输出的项目。
- [Mooncake 文档页](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [English README](./docs/README-en.md)
## 📝 配置说明
## 介绍
- 提供 JSON 配置层: `parse_logger_config_text(...)`, `stringify_logger_config(...)`, `build_logger(...)`
- `QueueConfig`, `TextFormatterConfig`, `SinkConfig` 可分别通过 `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, `sink_config_to_json(...)` / `stringify_sink_config(...)` 单独导出 JSON
- 支持字段: `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, `queue`
- `TextFormatter` / `TextFormatterConfig` 提供 `color_mode = Never | Auto | Always`, 可控制 ANSI 文本着色
- `TextFormatter` / `TextFormatterConfig` 提供 `color_support = basic | truecolor`, 可控制 hex / RGB 样式是否降级到基础 ANSI 色
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
- `target_style_markup``fields_style_markup` 可独立控制 `target``fields` 是否解析 style markup
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
- 闭合同时支持简写 `</>` 与具名闭合 `</red>`, `</danger>`, `</b>`
- 内置语义标签包括: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
- 运行期样式标签 API: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
- 样式标签优先级: formatter 局部 `style_tags` > 全局 style tag registry > 内置标签
- `sink.text_formatter.style_tags` 现支持最小对象映射配置, 可声明 `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
- `define_alias(...)` 仍为运行期 API, 当前不在 JSON schema 中
- `sink.rotation` 支持 `max_bytes``max_backups`, 用于基础 size-based rotation 和 backup retention
- `file_sink(...)` 提供 `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, `rotation_failures()`, 用于基础可观测性
- `file_sink(...)` 提供 `append_mode()`. 显式传入 `reopen(append=...)` 时, 会更新后续 reopen 使用的 append 策略. `reopen_with_current_policy()` 使用当前保存的策略重开文件, `reopen_append()` / `reopen_truncate()` 提供常见的 append 和 truncate 模式
- `file_sink(...)` 支持 `set_append_mode(...)`, 用于修改后续 reopen 使用的 append 策略
- `file_sink(...)` 可读取 `path()``auto_flush_enabled()` 等基础 file 策略状态
- `file_sink(...)` 提供 `rotation_enabled()``rotation_config()`, 可查询 rotation 是否启用及其参数
- `file_sink(...)` 提供 `state()`, 可一次性读取 path, available, append, auto_flush, rotation 与各类 failure counter 快照
- `file_sink(...)` 提供 `policy()``default_policy()`, 可分别读取当前策略与创建时默认策略
- `file_sink(...)` 提供 `policy_matches_default()`, 可判断当前运行期策略是否偏离默认策略
- `file_sink(...)` 支持 `set_policy(...)`, 可一次性写回 append / auto_flush / rotation 三项策略
- `file_sink(...)` 提供 `reset_failure_counters()`, 可清空 open / write / flush / rotation 失败计数
- `file_sink(...)` 提供 `reset_policy()`, 可将 append / auto_flush / rotation 恢复到创建 sink 时的默认策略
- `file_sink(...)` 支持 `set_auto_flush(...)`, `set_rotation(...)`, `clear_rotation()`, 可在运行期调整写出策略
- `build_logger(...)` 产出的 `ConfiguredLogger` 也提供 `file_reopen()`, `file_reopen_with_current_policy()`, `file_reopen_append()`, `file_reopen_truncate()`, `file_flush()`, `file_close()`, `file_append_mode()`, `file_path()`, `file_auto_flush()`, `file_rotation_enabled()`, `file_rotation_config()`, `file_state()`, 以及 `file_set_append_mode(...)`, `file_set_auto_flush(...)`, `file_set_rotation(...)`, `file_clear_rotation()` 和对应的 file failure 计数访问器
- `ConfiguredLogger` 提供 `file_runtime_state()`, 可在 file sink 被 queue 包装时同时读取底层 file 快照, queue 状态, pending 计数与 dropped 计数
- `ConfiguredLogger` 提供 `file_policy()``file_default_policy()`, 可分别读取当前 file 策略与初始配置策略
- `ConfiguredLogger` 提供 `file_policy_matches_default()`, 可判断当前配置式 file 策略是否偏离默认值
- `ConfiguredLogger` 支持 `file_set_policy(...)`, 可一次性改写配置式 file sink 的当前运行期策略
- `ConfiguredLogger` 支持 `file_reset_failure_counters()`, 可在配置式 file sink 上统一清空失败计数
- `ConfiguredLogger` 支持 `file_reset_policy()`, 可将配置式 file sink 的运行期策略恢复到初始配置
- `file_sink_policy_to_json(...)`, `stringify_file_sink_policy(...)` 可将独立 file policy 直接导出为 JSON, 便于策略快照, 配置对比或诊断上报
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, `stringify_runtime_file_state(...)` 可直接把 file / queued-file 快照导出为 JSON, 便于排障或上报
- `sink.text_formatter.template` 支持固定 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
- `sink.text_formatter.color_mode` 支持 `never`, `auto`, `always`
- `sink.text_formatter.color_support` 支持 `basic`, `truecolor`
- `sink.text_formatter.style_markup` 支持 `disabled`, `builtin`, `full`
- `sink.text_formatter.target_style_markup``sink.text_formatter.fields_style_markup` 支持 `disabled`, `builtin`, `full`
- `sink.text_formatter.style_tags.<name>` 支持 `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
- `fields_style_markup` 当前只解析 field value, 不解析 field key
- 可由配置直接组装的 sink 类型: `console`, `json_console`, `text_console`, `file`
- `queue` 作为显式包装层附着在最终 sink 外侧. 这仍然是同步 drain 模型, 不是 async runtime
BitLogger 提供统一的日志级别、目标名、结构化字段和可定制格式,既可以直接输出到控制台,也可以在 native 环境写入文件,并提供异步日志版本。
## 🧵 异步层
## 快速开始
- 提供独立 package: `bitlogger_async/`
- 异步层基于 `moonbitlang/async`, 提供 `AsyncLogger`, `async_logger(...)`, 后台 `run()` worker 与有界 async queue
- async API 支持 `with_context_fields(...)`, `with_filter(...)`, `with_patch(...)`, `with_target(...)`, `child(...)`
- 建议使用 `shutdown()` 停止 worker. 默认模式下, 它会先等待队列清空, 再关闭 queue, 最后等待 worker 退出
- 提供基础生命周期观测: `is_closed()`, `is_running()`, `has_failed()`, `last_error()`
- async worker 支持 `max_batch` 批量消费, 以及 `flush=Never|Batch|Shutdown` 的基础 flush 策略
- 示例见 [examples/async_basic/main.mbt](/E:/repo/MooLiteyukiBot/examples/async_basic/main.mbt:1)
- 仅支持 `native/llvm` backend, 与同步 core 分开维护
```moonbit
let logger = build_logger(
text_console(
min_level=Level::Info,
target="demo",
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
)
### Async Config
logger.info("starting", fields=[field("port", "8080")])
ignore(logger.flush())
```
- 提供 `parse_async_logger_config_text(...)`, `stringify_async_logger_config(...)`, `parse_async_logger_build_config_text(...)`, `build_async_logger(...)`
- JSON 顶层结构分为两个字段: `logger``async_config`
- `logger` 复用同步 `LoggerConfig` 的 schema, `async_config` 支持 `max_pending`, `overflow`, `max_batch`, `flush`
- 用法可参考 [examples/async_basic/main.mbt](/E:/repo/MooLiteyukiBot/examples/async_basic/main.mbt:1)
推荐从 `console(...)``json_console(...)``text_console(...)``file(...)` 这几个入口开始,再按需配合 `with_queue(...)` `with_file_rotation(...)`
如果你需要自己组合 sink,比如 `fanout``split``callback`,再使用 `Logger::new(...)`
## 支持情况
- `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 入口提供
## 主要能力
- 结构化日志:level、target、message、fields
- 多种输出方式:console、json console、text console、file
- 可定制文本格式:模板、style tag、颜色控制
- 配置驱动构建:`build_logger(...)``build_async_logger(...)`
- 组合能力:queue、filter、patch、fanout、split、callback
- 异步日志:独立 `src-async` package
## 示例
- `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/`:异步日志示例
## 文档
- [API 索引](./docs/api/index.md)
- [src package README](./src/README.mbt.md)
常用入口:`text_console(...)``file(...)``with_queue(...)``build_logger(...)``build_async_logger(...)`
-772
View File
@@ -1,772 +0,0 @@
test "level filter works" {
let logger = Logger::new(console_sink(), min_level=Level::Warn, target="test")
inspect(logger.is_enabled(Level::Error), content="true")
inspect(logger.is_enabled(Level::Info), content="false")
}
test "context sink merges fields" {
let logger = Logger::new(console_sink(), min_level=Level::Info, target="ctx")
.with_context_fields([field("service", "bitlogger")])
let merged = [field("service", "bitlogger"), field("mode", "test")]
inspect(merged.length(), content="2")
inspect(merged[0].key, content="service")
inspect(merged[1].key, content="mode")
logger.info("hello", fields=[field("mode", "test")])
}
test "fields helper builds field arrays ergonomically" {
let items = fields([("service", "bitlogger"), ("mode", "test")])
inspect(items.length(), content="2")
inspect(items[0].key, content="service")
inspect(items[0].value, content="bitlogger")
inspect(items[1].key, content="mode")
inspect(items[1].value, content="test")
}
test "fanout sink can write to plain and json outputs" {
let logger = Logger::new(
fanout_sink(console_sink(), json_console_sink()),
min_level=Level::Info,
target="fanout",
)
inspect(logger.is_enabled(Level::Info), content="true")
logger.info("hello", fields=[field("kind", "dual")])
}
test "child logger composes target path" {
let logger = Logger::new(console_sink(), min_level=Level::Info, target="app")
.child("worker")
inspect(logger.target, content="app.worker")
}
test "logger config parser reads core options" {
let config = parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"service\",\"timestamp\":true}",
)
inspect(config.min_level.label(), content="DEBUG")
inspect(config.target, content="service")
inspect(config.timestamp, content="true")
}
test "logger config parser reads formatter and queue options" {
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"separator\":\" | \",\"show_timestamp\":false,\"template\":\"[{level}] {message}\",\"color_mode\":\"always\"}},\"queue\":{\"max_pending\":32,\"overflow\":\"DropOldest\"}}",
)
inspect(match config.sink.kind {
SinkKind::TextConsole => "TextConsole"
_ => "other"
}, content="TextConsole")
inspect(config.sink.text_formatter.separator, content=" | ")
inspect(config.sink.text_formatter.show_timestamp, content="false")
inspect(config.sink.text_formatter.template, content="[{level}] {message}")
inspect(color_mode_label(config.sink.text_formatter.color_mode), content="always")
match config.queue {
Some(queue) => {
inspect(queue.max_pending, content="32")
inspect(match queue.overflow {
QueueOverflowPolicy::DropNewest => "DropNewest"
QueueOverflowPolicy::DropOldest => "DropOldest"
}, content="DropOldest")
}
None => inspect(false, content="true")
}
}
test "logger config parser reads formatter style tags" {
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"color_support\":\"basic\",\"style_markup\":\"builtin\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true},\"panel\":{\"bg\":\"#202020\",\"underline\":true}}}}}",
)
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
inspect(color_support_label(config.sink.text_formatter.color_support), content="basic")
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
inspect(config.sink.text_formatter.style_tags.length(), content="2")
match config.sink.text_formatter.style_tags.get("accent") {
Some(style) => {
inspect(style.fg.unwrap(), content="#4cc9f0")
inspect(style.bold, content="true")
inspect(style.bg is None, content="true")
}
None => inspect(false, content="true")
}
match config.sink.text_formatter.style_tags.get("panel") {
Some(style) => {
inspect(style.bg.unwrap(), content="#202020")
inspect(style.underline, content="true")
}
None => inspect(false, content="true")
}
}
test "logger config parser reads file rotation options" {
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"file\",\"path\":\"bitlogger.log\",\"rotation\":{\"max_bytes\":128,\"max_backups\":3}}}",
)
inspect(match config.sink.kind {
SinkKind::File => "File"
_ => "other"
}, content="File")
inspect(config.sink.path, content="bitlogger.log")
match config.sink.rotation {
Some(rotation) => {
inspect(rotation.max_bytes, content="128")
inspect(rotation.max_backups, content="3")
}
None => inspect(false, content="true")
}
}
test "logger config stringify roundtrips stable fields" {
let text = stringify_logger_config(
LoggerConfig::new(
min_level=Level::Warn,
target="api",
timestamp=true,
sink=SinkConfig::new(
kind=SinkKind::TextConsole,
text_formatter=TextFormatterConfig::new(
show_timestamp=false,
show_level=true,
show_target=true,
show_fields=true,
separator=" | ",
field_separator=",",
template="[{level}] {target} {message}",
),
),
queue=Some(QueueConfig::new(8, overflow=QueueOverflowPolicy::DropNewest)),
),
)
let config = parse_logger_config_text(text)
inspect(config.min_level.label(), content="WARN")
inspect(config.target, content="api")
inspect(config.timestamp, content="true")
inspect(config.sink.text_formatter.separator, content=" | ")
inspect(config.sink.text_formatter.template, content="[{level}] {target} {message}")
}
test "logger config stringify roundtrips formatter style tags" {
let text = stringify_logger_config(
LoggerConfig::new(
sink=SinkConfig::new(
kind=SinkKind::TextConsole,
text_formatter=TextFormatterConfig::new(
color_mode=ColorMode::Always,
color_support=ColorSupport::Basic,
style_markup=StyleMarkupMode::Builtin,
target_style_markup=StyleMarkupMode::Builtin,
fields_style_markup=StyleMarkupMode::Disabled,
style_tags={
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
"panel": text_style(bg=Some("#202020"), dim=true),
},
),
),
),
)
let config = parse_logger_config_text(text)
inspect(color_mode_label(config.sink.text_formatter.color_mode), content="always")
inspect(color_support_label(config.sink.text_formatter.color_support), content="basic")
inspect(style_markup_mode_label(config.sink.text_formatter.style_markup), content="builtin")
inspect(style_markup_mode_label(config.sink.text_formatter.target_style_markup), content="builtin")
inspect(style_markup_mode_label(config.sink.text_formatter.fields_style_markup), content="disabled")
inspect(config.sink.text_formatter.style_tags.length(), content="2")
inspect(config.sink.text_formatter.style_tags.get("accent").unwrap().fg.unwrap(), content="#4cc9f0")
inspect(config.sink.text_formatter.style_tags.get("accent").unwrap().bold, content="true")
inspect(config.sink.text_formatter.style_tags.get("panel").unwrap().bg.unwrap(), content="#202020")
inspect(config.sink.text_formatter.style_tags.get("panel").unwrap().dim, content="true")
}
test "logger config stringify roundtrips file rotation fields" {
let text = stringify_logger_config(
LoggerConfig::new(
sink=SinkConfig::new(
kind=SinkKind::File,
path="bitlogger.log",
rotation=Some(file_rotation(256, max_backups=2)),
),
),
)
let config = parse_logger_config_text(text)
inspect(config.sink.path, content="bitlogger.log")
match config.sink.rotation {
Some(rotation) => {
inspect(rotation.max_bytes, content="256")
inspect(rotation.max_backups, content="2")
}
None => inspect(false, content="true")
}
}
test "config subtype json helpers stringify stable shapes" {
inspect(
stringify_queue_config(
QueueConfig::new(8, overflow=QueueOverflowPolicy::DropOldest),
),
content="{\"max_pending\":8,\"overflow\":\"DropOldest\"}",
)
inspect(
stringify_text_formatter_config(
TextFormatterConfig::new(
show_timestamp=false,
show_level=true,
show_target=false,
show_fields=true,
separator=" | ",
field_separator=",",
template="[{level}] {message} :: {fields}",
color_mode=ColorMode::Always,
color_support=ColorSupport::Basic,
style_markup=StyleMarkupMode::Builtin,
target_style_markup=StyleMarkupMode::Builtin,
fields_style_markup=StyleMarkupMode::Disabled,
style_tags={
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
},
),
),
content="{\"show_timestamp\":false,\"show_level\":true,\"show_target\":false,\"show_fields\":true,\"separator\":\" | \",\"field_separator\":\",\",\"template\":\"[{level}] {message} :: {fields}\",\"color_mode\":\"always\",\"color_support\":\"basic\",\"style_markup\":\"builtin\",\"target_style_markup\":\"builtin\",\"fields_style_markup\":\"disabled\",\"style_tags\":{\"accent\":{\"bold\":true,\"dim\":false,\"italic\":false,\"underline\":false,\"fg\":\"#4cc9f0\"}}}",
)
inspect(
stringify_sink_config(
SinkConfig::new(
kind=SinkKind::File,
path="demo.log",
append=false,
auto_flush=false,
rotation=Some(file_rotation(128, max_backups=2)),
text_formatter=TextFormatterConfig::new(show_timestamp=false, color_mode=ColorMode::Auto),
),
),
content="{\"kind\":\"file\",\"path\":\"demo.log\",\"append\":false,\"auto_flush\":false,\"text_formatter\":{\"show_timestamp\":false,\"show_level\":true,\"show_target\":true,\"show_fields\":true,\"separator\":\" \",\"field_separator\":\" \",\"template\":\"\",\"color_mode\":\"auto\",\"color_support\":\"truecolor\",\"style_markup\":\"full\",\"target_style_markup\":\"disabled\",\"fields_style_markup\":\"disabled\"},\"rotation\":{\"max_bytes\":128,\"max_backups\":2}}",
)
}
test "config basic color support downgrades hex colors" {
let formatter = TextFormatterConfig::new(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
color_support=ColorSupport::Basic,
)
let rendered = format_text(
Record::new(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>"),
formatter=formatter.to_formatter(),
)
inspect(rendered, content="\u{001b}[31mhot\u{001b}[0m \u{001b}[100mbg\u{001b}[0m")
}
test "config formatter style tags render in built logger" {
let formatter = TextFormatterConfig::new(
show_timestamp=false,
show_target=false,
color_mode=ColorMode::Always,
style_tags={
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
},
)
let rendered = format_text(
Record::new(Level::Info, "<accent>tag</>"),
formatter=formatter.to_formatter(),
)
inspect(rendered, content="[\u{001b}[32mINFO\u{001b}[0m] \u{001b}[38;2;76;201;240;1mtag\u{001b}[0m")
}
test "config builtin style markup ignores custom tags" {
let formatter = TextFormatterConfig::new(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
style_markup=StyleMarkupMode::Builtin,
style_tags={
"brand": text_style(fg=Some("#4cc9f0"), bold=true),
},
)
let rendered = format_text(
Record::new(Level::Info, "<brand>custom</> <red>builtin</>"),
formatter=formatter.to_formatter(),
)
inspect(rendered, content="<brand>custom</> \u{001b}[31mbuiltin\u{001b}[0m")
}
test "config disabled style markup keeps raw tags" {
let formatter = TextFormatterConfig::new(
show_level=false,
show_target=false,
color_mode=ColorMode::Always,
style_markup=StyleMarkupMode::Disabled,
style_tags={
"accent": text_style(fg=Some("#4cc9f0"), bold=true),
},
)
let rendered = format_text(
Record::new(Level::Info, "<accent>raw</> <red>tag</>"),
formatter=formatter.to_formatter(),
)
inspect(rendered, content="<accent>raw</> <red>tag</>")
}
test "config target and fields markup modes render separately" {
let formatter = TextFormatterConfig::new(
show_level=false,
color_mode=ColorMode::Always,
style_markup=StyleMarkupMode::Disabled,
target_style_markup=StyleMarkupMode::Builtin,
fields_style_markup=StyleMarkupMode::Builtin,
)
let rendered = format_text(
Record::new(
Level::Info,
"<danger>message stays raw</>",
target="<danger>svc</>",
fields=[field("status", "<success>ok</>")],
),
formatter=formatter.to_formatter(),
)
inspect(
rendered,
content="[\u{001b}[34m\u{001b}[31;1msvc\u{001b}[0m\u{001b}[0m] <danger>message stays raw</> \u{001b}[35mstatus=\u{001b}[32;1mok\u{001b}[0m\u{001b}[0m",
)
}
test "build logger from config supports queued text console" {
let logger = build_logger(
LoggerConfig::new(
min_level=Level::Debug,
target="config.runtime",
timestamp=true,
sink=SinkConfig::new(
kind=SinkKind::TextConsole,
text_formatter=TextFormatterConfig::new(show_timestamp=false, separator=" | "),
),
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
),
)
logger.info("one")
logger.info("two")
logger.info("three")
inspect(logger.pending_count(), content="2")
inspect(logger.flush(), content="2")
}
test "configured logger drain supports partial queue draining" {
let logger = build_logger(
LoggerConfig::new(
min_level=Level::Info,
target="config.partial",
sink=SinkConfig::new(kind=SinkKind::Console),
queue=Some(QueueConfig::new(4, overflow=QueueOverflowPolicy::DropNewest)),
),
)
logger.info("one")
logger.info("two")
logger.info("three")
inspect(logger.pending_count(), content="3")
inspect(logger.drain(max_items=2), content="2")
inspect(logger.pending_count(), content="1")
inspect(logger.flush(), content="1")
}
test "configured logger reports dropped_count for bounded queue" {
let logger = build_logger(
LoggerConfig::new(
min_level=Level::Info,
target="config.drop",
sink=SinkConfig::new(kind=SinkKind::TextConsole),
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropOldest)),
),
)
logger.info("one")
logger.info("two")
logger.info("three")
logger.info("four")
inspect(logger.pending_count(), content="2")
inspect(logger.dropped_count(), content="2")
inspect(logger.flush(), content="2")
}
test "configured logger exposes file sink observability helpers" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(
kind=SinkKind::File,
path="config-file.log",
auto_flush=false,
rotation=Some(file_rotation(64, max_backups=3)),
),
),
)
let state = logger.file_state()
let runtime_state = logger.file_runtime_state()
inspect(logger.file_available() == native_files_supported(), content="true")
inspect(logger.file_path(), content="config-file.log")
inspect(state.path, content="config-file.log")
inspect(state.available == logger.file_available(), content="true")
inspect(state.append == logger.file_append_mode(), content="true")
inspect(state.auto_flush == logger.file_auto_flush(), content="true")
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_auto_flush(), content="false")
match runtime_state {
Some(snapshot) => {
inspect(snapshot.queued, content="false")
inspect(snapshot.pending_count, content="0")
inspect(snapshot.dropped_count, content="0")
inspect(snapshot.file.path, content="config-file.log")
}
None => inspect(false, content="true")
}
inspect(logger.file_rotation_enabled(), content="true")
match logger.file_rotation_config() {
Some(rotation) => {
inspect(rotation.max_bytes, content="64")
inspect(rotation.max_backups, content="3")
}
None => inspect(false, content="true")
}
inspect(logger.file_open_failures(), content=if logger.file_available() { "0" } else { "1" })
inspect(logger.file_write_failures(), content="0")
inspect(logger.file_flush_failures(), content="0")
inspect(logger.file_rotation_failures(), content="0")
ignore(logger.close())
}
test "file state json helpers stringify stable snapshots" {
let plain = file_sink_state_to_json(
FileSinkState::new(
"demo.log",
available=true,
append=false,
auto_flush=true,
rotation=Some(file_rotation(64, max_backups=2)),
open_failures=1,
write_failures=2,
flush_failures=3,
rotation_failures=4,
),
)
inspect(
@json_parser.stringify(plain),
content="{\"path\":\"demo.log\",\"available\":true,\"append\":false,\"auto_flush\":true,\"open_failures\":1,\"write_failures\":2,\"flush_failures\":3,\"rotation_failures\":4,\"rotation\":{\"max_bytes\":64,\"max_backups\":2}}",
)
inspect(
stringify_file_sink_state(
FileSinkState::new(
"plain.log",
available=false,
append=true,
auto_flush=false,
rotation=None,
open_failures=0,
write_failures=0,
flush_failures=0,
rotation_failures=0,
),
),
content="{\"path\":\"plain.log\",\"available\":false,\"append\":true,\"auto_flush\":false,\"open_failures\":0,\"write_failures\":0,\"flush_failures\":0,\"rotation_failures\":0,\"rotation\":null}",
)
}
test "runtime file state json helpers stringify queue snapshots" {
let json = stringify_runtime_file_state(
RuntimeFileState::new(
FileSinkState::new(
"queue.log",
available=true,
append=true,
auto_flush=false,
rotation=None,
open_failures=0,
write_failures=1,
flush_failures=2,
rotation_failures=3,
),
queued=true,
pending_count=7,
dropped_count=5,
),
)
inspect(
json,
content="{\"file\":{\"path\":\"queue.log\",\"available\":true,\"append\":true,\"auto_flush\":false,\"open_failures\":0,\"write_failures\":1,\"flush_failures\":2,\"rotation_failures\":3,\"rotation\":null},\"queued\":true,\"pending_count\":7,\"dropped_count\":5}",
)
}
test "file sink policy json helpers stringify stable policies" {
inspect(
@json_parser.stringify(
file_sink_policy_to_json(
FileSinkPolicy::new(
append=false,
auto_flush=true,
rotation=Some(file_rotation(96, max_backups=3)),
),
),
),
content="{\"append\":false,\"auto_flush\":true,\"rotation\":{\"max_bytes\":96,\"max_backups\":3}}",
)
inspect(
stringify_file_sink_policy(
FileSinkPolicy::new(append=true, auto_flush=false, rotation=None),
),
content="{\"append\":true,\"auto_flush\":false,\"rotation\":null}",
)
}
test "configured logger reports disabled rotation when file sink has none" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-no-rotation.log"),
),
)
inspect(logger.file_rotation_enabled(), content="false")
inspect(logger.file_rotation_config() is None, content="true")
match logger.file_runtime_state() {
Some(snapshot) => inspect(snapshot.queued, content="false")
None => inspect(false, content="true")
}
ignore(logger.close())
}
test "configured non-file logger has no file runtime state" {
let logger = build_logger(
LoggerConfig::new(sink=SinkConfig::new(kind=SinkKind::Console)),
)
inspect(logger.file_runtime_state() is None, content="true")
}
test "configured logger file setters update file sink policy state" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-setters.log"),
queue=Some(QueueConfig::new(2, overflow=QueueOverflowPolicy::DropNewest)),
),
)
let default_policy = logger.file_default_policy()
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_auto_flush(), content="true")
inspect(logger.file_rotation_enabled(), content="false")
inspect(default_policy.append, content="true")
inspect(default_policy.auto_flush, content="true")
inspect(default_policy.rotation is None, content="true")
inspect(logger.file_policy_matches_default(), content="true")
inspect(logger.file_set_append_mode(false), content="true")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_set_auto_flush(false), content="true")
inspect(logger.file_auto_flush(), content="false")
inspect(logger.file_set_rotation(Some(file_rotation(48, max_backups=4))), content="true")
inspect(logger.file_rotation_enabled(), content="true")
match logger.file_rotation_config() {
Some(rotation) => {
inspect(rotation.max_bytes, content="48")
inspect(rotation.max_backups, content="4")
}
None => inspect(false, content="true")
}
inspect(logger.file_clear_rotation(), content="true")
inspect(logger.file_rotation_enabled(), content="false")
inspect(logger.file_rotation_config() is None, content="true")
inspect(logger.file_reopen(), content=if logger.file_available() { "true" } else { "false" })
inspect(logger.file_append_mode(), content="false")
let state = logger.file_state()
let policy = logger.file_policy()
inspect(state.append, content="false")
inspect(state.auto_flush, content="false")
inspect(state.rotation is None, content="true")
inspect(policy.append, content="false")
inspect(policy.auto_flush, content="false")
inspect(policy.rotation is None, content="true")
inspect(logger.file_policy_matches_default(), content="false")
inspect(logger.file_reset_policy(), content="true")
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_auto_flush(), content="true")
inspect(logger.file_rotation_config() is None, content="true")
inspect(logger.file_policy_matches_default(), content="true")
ignore(logger.close())
}
test "configured logger reset policy restores configured file defaults" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(
kind=SinkKind::File,
path="config-reset-policy.log",
append=false,
auto_flush=false,
rotation=Some(file_rotation(36, max_backups=2)),
),
),
)
let default_policy = logger.file_default_policy()
inspect(default_policy.append, content="false")
inspect(default_policy.auto_flush, content="false")
inspect(logger.file_policy_matches_default(), content="true")
inspect(logger.file_set_append_mode(true), content="true")
inspect(logger.file_set_auto_flush(true), content="true")
inspect(logger.file_clear_rotation(), content="true")
inspect(logger.file_reset_policy(), content="true")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_auto_flush(), content="false")
inspect(logger.file_policy_matches_default(), content="true")
match logger.file_rotation_config() {
Some(rotation) => {
inspect(rotation.max_bytes, content="36")
inspect(rotation.max_backups, content="2")
}
None => inspect(false, content="true")
}
ignore(logger.close())
}
test "configured logger set policy applies bundled runtime file policy" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-set-policy.log"),
),
)
let default_policy = logger.file_default_policy()
inspect(
logger.file_set_policy(
FileSinkPolicy::new(
append=false,
auto_flush=false,
rotation=Some(file_rotation(22, max_backups=3)),
),
),
content="true",
)
let policy = logger.file_policy()
inspect(policy.append, content="false")
inspect(policy.auto_flush, content="false")
match policy.rotation {
Some(rotation) => {
inspect(rotation.max_bytes, content="22")
inspect(rotation.max_backups, content="3")
}
None => inspect(false, content="true")
}
inspect(default_policy.append, content="true")
inspect(default_policy.auto_flush, content="true")
inspect(default_policy.rotation is None, content="true")
inspect(logger.file_policy_matches_default(), content="false")
ignore(logger.close())
}
test "configured non-file logger cannot reset file policy" {
let logger = build_logger(LoggerConfig::new(sink=SinkConfig::new(kind=SinkKind::Console)))
inspect(logger.file_reset_policy(), content="false")
inspect(logger.file_policy().append, content="false")
inspect(logger.file_default_policy().append, content="false")
inspect(logger.file_set_policy(FileSinkPolicy::new()), content="false")
inspect(logger.file_policy_matches_default(), content="false")
}
test "configured logger can reopen built file sink" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-reopen.log"),
),
)
if logger.file_available() {
inspect(logger.close(), content="true")
inspect(logger.file_reopen_append(), content="true")
inspect(logger.file_available(), content="true")
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_open_failures(), content="0")
logger.info("reopened from config")
inspect(logger.file_write_failures(), content="0")
inspect(logger.file_reopen_truncate(), content="true")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_reopen(), content="true")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_reopen_with_current_policy(), content="true")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_reopen_append(), content="true")
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_reset_failure_counters(), content="true")
inspect(logger.file_open_failures(), content="0")
inspect(logger.file_write_failures(), content="0")
inspect(logger.file_flush_failures(), content="0")
inspect(logger.file_rotation_failures(), content="0")
inspect(logger.close(), content="true")
} else {
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_open_failures(), content="1")
logger.info("dropped")
inspect(logger.file_write_failures(), content="1")
inspect(logger.file_reopen_append(), content="false")
inspect(logger.file_open_failures(), content="2")
inspect(logger.file_reopen_truncate(), content="false")
inspect(logger.file_append_mode(), content="false")
inspect(logger.file_reopen_with_current_policy(), content="false")
inspect(logger.file_open_failures(), content="4")
inspect(logger.file_reopen_append(), content="false")
inspect(logger.file_append_mode(), content="true")
inspect(logger.file_open_failures(), content="5")
inspect(logger.file_reset_failure_counters(), content="true")
inspect(logger.file_open_failures(), content="0")
inspect(logger.file_write_failures(), content="0")
inspect(logger.file_flush_failures(), content="0")
inspect(logger.file_rotation_failures(), content="0")
}
}
test "configured non-file logger cannot reset file failure counters" {
let logger = build_logger(LoggerConfig::new(sink=SinkConfig::new(kind=SinkKind::Console)))
inspect(logger.file_reset_failure_counters(), content="false")
}
test "configured logger exposes file flush and close helpers" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-control.log"),
),
)
if logger.file_available() {
logger.info("before flush")
inspect(logger.file_flush(), content="true")
inspect(logger.file_close(), content="true")
inspect(logger.file_available(), content="false")
inspect(logger.file_flush(), content="false")
} else {
inspect(logger.file_flush(), content="false")
inspect(logger.file_close(), content="false")
}
}
test "configured queued file logger flushes queue through file helper" {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="config-queued-file.log"),
queue=Some(QueueConfig::new(4, overflow=QueueOverflowPolicy::DropNewest)),
),
)
logger.info("one")
logger.info("two")
match logger.file_runtime_state() {
Some(snapshot) => {
inspect(snapshot.queued, content="true")
inspect(snapshot.pending_count, content="2")
inspect(snapshot.dropped_count, content="0")
}
None => inspect(false, content="true")
}
if logger.file_available() {
inspect(logger.pending_count(), content="2")
inspect(logger.file_flush(), content="true")
inspect(logger.pending_count(), content="0")
match logger.file_runtime_state() {
Some(snapshot) => inspect(snapshot.pending_count, content="0")
None => inspect(false, content="true")
}
inspect(logger.file_close(), content="true")
} else {
inspect(logger.pending_count(), content="2")
inspect(logger.file_flush(), content="false")
inspect(logger.pending_count(), content="0")
match logger.file_runtime_state() {
Some(snapshot) => inspect(snapshot.pending_count, content="0")
None => inspect(false, content="true")
}
inspect(logger.file_close(), content="false")
}
}
-325
View File
@@ -1,325 +0,0 @@
# BitLogger
BitLogger is a minimal structured logger for MoonBit.
BitLogger 是一个使用 MoonBit 编写的结构化日志库.
## Features / 特性
- log levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`
- 日志级别: `Trace`, `Debug`, `Info`, `Warn`, `Error`
- structured key-value fields
- 结构化字段: `field("key", "value")`
- sink abstraction
- sink 抽象与组合接口
- default global console logger
- 默认全局 logger 辅助函数
- context fields via `with_context_fields(...)`
- 通过 `with_context_fields(...)` 添加上下文字段
- child target composition via `child(...)`
- 通过 `child(...)` 组合层级 target
- optional timestamps via `with_timestamp()`
- 通过 `with_timestamp()` 启用时间戳
- JSON console output via `json_console_sink()`
- `json_console_sink()` 提供 JSON 控制台输出
- sink composition via `fanout_sink(...)`
- `fanout_sink(...)` 支持多 sink 组合
- sink routing via `split_sink(...)` and `split_by_level(...)`
- `split_sink(...)`, `split_by_level(...)` 支持按谓词或 level 将日志路由到不同 sink
- custom callback sink via `callback_sink(...)`
- `callback_sink(...)` 支持自定义外部集成
- buffered sink via `buffered_sink(...)`
- `buffered_sink(...)` 支持内存缓冲与 flush
- filter sink via `filter_sink(...)`
- `filter_sink(...)` 支持按 `Record` 条件筛选输出
- reusable filter helpers such as `target_has_prefix(...)`, `message_contains(...)`, and `field_equals(...)`
- 提供 `target_has_prefix(...)`, `message_contains(...)`, `field_equals(...)` 等可复用过滤辅助函数
- record patching via `with_patch(...)` and `patch_sink(...)`
- 支持 `with_patch(...)`, `patch_sink(...)` 以及常见 record patch helper
- context binding via `bind(...)` and `fields(...)`
- 支持 `bind(...)`, `fields(...)`, 更方便封装上下文字段
- explicit queued delivery via `queued_sink(...)` and `with_queue(...)`
- 支持 `queued_sink(...)`, `with_queue(...)`, 有界积压与溢出策略
- configurable text formatting via `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)`, and template-driven `template` output
- 支持 `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)` 以及模板化 `template` 文本输出
- lightweight style tags via `color_mode`, inline markup, `TextStyle`, `StyleTagRegistry`, custom tags, and builtin-tag overrides
- 支持 `color_mode`, inline markup, `TextStyle`, `StyleTagRegistry`, 自定义标签与内置标签覆盖
- JSON config parsing via `parse_logger_config_text(...)` and `stringify_logger_config(...)`
- 支持 `parse_logger_config_text(...)`, `stringify_logger_config(...)` 进行最小 JSON 配置读写
- `TextFormatter` / `TextFormatterConfig` now support `color_mode = Never | Auto | Always`
- `TextFormatter` / `TextFormatterConfig` 现支持 `color_mode = Never | Auto | Always`
- `QueueConfig` / `TextFormatterConfig` / `SinkConfig` can also be exported independently through dedicated JSON helpers
- `QueueConfig` / `TextFormatterConfig` / `SinkConfig` 也可分别通过专用 JSON helper 单独导出
- config-driven logger assembly via `build_logger(...)`
- 支持 `build_logger(...)` 将配置组装为可直接使用的 logger
- native-only file output via `file_sink(...)`, with basic size rotation, backup retention, explicit `reopen()` / `reopen_with_current_policy()` / `reopen_append()` / `reopen_truncate()`, and failure counters
- 支持 `file_sink(...)`, 基础 size rotation / backup retention, 显式 `reopen()` / `reopen_with_current_policy()` / `reopen_append()` / `reopen_truncate()` 与失败计数, 仅在 `native/llvm` backend 可用
## Example / 示例
```mbt check
test {
let logger = Logger::new(console_sink(), min_level=Level::Debug, target="demo")
.with_timestamp()
logger.info("starting", fields=[field("port", "8080")])
}
```
```mbt check
test {
let logger = Logger::new(console_sink(), target="app").child("worker")
logger.info("ready")
}
```
```mbt check
test {
let logger = Logger::new(
fanout_sink(console_sink(), json_console_sink()),
min_level=Level::Info,
target="demo",
)
logger.info("ready", fields=[field("mode", "fanout")])
}
```
```mbt check
test {
let logger = Logger::new(
callback_sink(fn(rec) {
println("callback saw [\{rec.target}] \{rec.message}")
}),
target="hook",
)
logger.info("hello")
}
```
```mbt check
test {
let sink = buffered_sink(console_sink(), flush_limit=2)
let logger = Logger::new(sink, target="buffered")
logger.info("one")
logger.info("two")
sink.flush()
}
```
```mbt check
test {
let sink = filter_sink(console_sink(), fn(rec) {
rec.target == "kept"
})
let kept = Logger::new(sink, target="kept")
let dropped = Logger::new(sink, target="dropped")
kept.info("visible")
dropped.info("hidden")
}
```
```mbt check
test {
let logger = Logger::new(console_sink(), target="service")
.with_filter(all_of([
target_has_prefix("service"),
message_contains("visible"),
]))
logger.info("hidden")
logger.child("api").info("visible")
}
```
```mbt check
test {
let logger = Logger::new(console_sink(), target="auth")
.with_patch(compose_patches([
prefix_message("[safe] "),
redact_fields(["token"]),
append_fields([field("service", "bitlogger")]),
]))
logger.info("login", fields=[field("user", "alice"), field("token", "secret")])
}
```
```mbt check
test {
let logger = Logger::new(console_sink(), target="audit")
.bind(fields([("service", "bitlogger"), ("scope", "login")]))
logger.info("accepted", fields=[field("user", "alice")])
}
```
```mbt check
test {
let logger = Logger::new(console_sink(), target="queue")
.with_queue(max_pending=2, overflow=QueueOverflowPolicy::DropOldest)
logger.info("one")
logger.info("two")
logger.info("three")
ignore(logger.sink.flush())
}
```
```mbt check
test {
let logger = Logger::new(
split_by_level(
callback_sink(fn(rec) {
println("high priority: \{rec.level.label()} \{rec.message}")
}),
console_sink(),
min_level=Level::Warn,
),
min_level=Level::Trace,
target="split",
)
logger.info("normal output")
logger.warn("warning output")
}
```
```mbt check
test {
let formatter = text_formatter(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
color_mode=ColorMode::Always,
)
let logger = Logger::new(text_console_sink(formatter), target="pretty")
logger.info("hello", fields=[field("mode", "pretty")])
}
```
```mbt check
test {
let formatter = text_formatter(
show_timestamp=false,
color_mode=ColorMode::Always,
).with_style_tags(
default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
.define_alias("danger", "red"),
)
let logger = Logger::new(text_console_sink(formatter), target="styled")
logger.info("<accent>styled</> output and <danger>alert</>")
}
```
```mbt check
test {
let formatter = text_formatter(
color_mode=ColorMode::Always,
).without_style_markup()
let logger = Logger::new(text_console_sink(formatter), target="raw")
logger.info("<red>kept as raw text</>")
}
```
```mbt check
test {
let config = parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\",\"color_mode\":\"always\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
)
let logger = build_logger(config)
logger.info("configured from json")
ignore(logger.flush())
}
```
```mbt check
test {
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
)
let logger = build_logger(config)
logger.info("<accent>styled from json</>")
}
```
```mbt check
test {
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
)
let logger = build_logger(config)
logger.info("<red>still raw</>")
}
```
## Formatter Template / 模板格式
- supported tokens / 支持的 token: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, `{fields}`
- `color_mode` / `color_mode`: `never`, `auto`, `always`
- `color_support` / `color_support`: `basic`, `truecolor`
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
- closing tags / 闭合标签: `</>` 以及具名闭合 `</red>`, `</danger>`, `</b>`
- builtin semantic tags / 内置语义标签: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
- `sink.text_formatter.style_tags` / `sink.text_formatter.style_tags` 现支持最小对象映射: `fg`, `bg`, `bold`, `dim`, `italic`, `underline`
- `fields_style_markup` currently affects values only / `fields_style_markup` 当前仅影响 field value, 不影响 field key
- `define_alias(...)` is still runtime-only / `define_alias(...)` 目前仍为运行期 API
- disabled or missing parts render as empty text / 被关闭或缺失的部分会渲染为空字符串
- `template` is intentionally a simple token replacement layer, not a full DSL / `template` 使用轻量 token 替换方式, 不是完整 DSL
```mbt check
test {
if native_files_supported() {
let logger = Logger::new(
file_sink("bitlogger.log", rotation=Some(file_rotation(128, max_backups=2))),
target="file",
)
logger.info("hello", fields=[field("kind", "file")])
ignore(logger.sink.flush())
ignore(logger.sink.close())
}
}
```
```mbt check
test {
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="bitlogger-runtime.log"),
queue=Some(QueueConfig::new(16)),
),
)
logger.info("queued hello")
match logger.file_runtime_state() {
Some(snapshot) => println(stringify_runtime_file_state(snapshot, pretty=true))
None => ()
}
}
```
## File Rotation / 文件轮转
- basic rotation is size-based / 基础轮转按文件大小触发
- `file_rotation(max_bytes, max_backups=...)` controls threshold and retained backups / `file_rotation(max_bytes, max_backups=...)` 控制触发阈值和保留备份数
- JSON config uses `sink.rotation.max_bytes` and `sink.rotation.max_backups` / JSON 配置使用 `sink.rotation.max_bytes` 与 `sink.rotation.max_backups`
- `FileSink::reopen()` can explicitly reopen the current file handle, `FileSink::reopen_with_current_policy()` makes the stored-policy reopen path explicit, and `FileSink::reopen_append()` / `FileSink::reopen_truncate()` cover the two common reopen modes directly / `FileSink::reopen()` 可显式重开当前文件句柄, `FileSink::reopen_with_current_policy()` 会按当前保存的策略重开文件, `FileSink::reopen_append()` / `FileSink::reopen_truncate()` 提供常见的 append 与 truncate 模式
- `append_mode()` exposes the current append policy, and `reopen(append=...)` updates that policy for later reopen calls / `append_mode()` 可读取当前 append 策略, `reopen(append=...)` 会更新后续 reopen 复用的 append 策略
- `set_append_mode(...)` updates the stored append policy without forcing an immediate reopen / `set_append_mode(...)` 可直接更新保存的 append 策略, 不会强制立即 reopen
- `path()` and `auto_flush_enabled()` expose core file sink policy state / `path()` 与 `auto_flush_enabled()` 可读取 file sink 的基础策略状态
- `rotation_enabled()` and `rotation_config()` expose whether rotation is active and which settings are currently applied / `rotation_enabled()` 与 `rotation_config()` 可读取 rotation 是否启用及当前配置参数
- `state()` exposes a single file-sink snapshot including path, availability, append policy, auto-flush, rotation config, and failure counters / `state()` 可一次性读取包含 path, availability, append, auto_flush, rotation 配置与失败计数的 file sink 快照
- `policy()` and `default_policy()` expose the current runtime policy and the sink's original defaults separately / `policy()` 与 `default_policy()` 可分别读取当前运行期策略与 sink 初始默认策略
- `policy_matches_default()` explicitly tells whether the current runtime policy has drifted from the defaults / `policy_matches_default()` 可显式判断当前运行期策略是否已偏离默认策略
- `set_policy(...)` applies append, auto-flush, and rotation as a bundled runtime update / `set_policy(...)` 可将 append, auto_flush, rotation 作为一组运行期策略一次性写回
- `reset_failure_counters()` clears the open/write/flush/rotation counters after diagnostics or recovery / `reset_failure_counters()` 可在排障或恢复后清空 open/write/flush/rotation 失败计数
- `reset_policy()` restores append, auto-flush, and rotation back to the sink's original defaults / `reset_policy()` 可将 append, auto_flush, rotation 恢复到 sink 初始默认策略
- `file_sink_policy_to_json(...)` / `stringify_file_sink_policy(...)` also export standalone file policy snapshots as JSON / `file_sink_policy_to_json(...)` / `stringify_file_sink_policy(...)` 也可将独立 file policy 快照直接导出为 JSON
- `file_sink_state_to_json(...)` / `stringify_file_sink_state(...)` and `runtime_file_state_to_json(...)` / `stringify_runtime_file_state(...)` export snapshots as JSON / `file_sink_state_to_json(...)` / `stringify_file_sink_state(...)` 与 `runtime_file_state_to_json(...)` / `stringify_runtime_file_state(...)` 可将快照直接导出为 JSON
- `set_auto_flush(...)`, `set_rotation(...)`, and `clear_rotation()` allow runtime tuning of core file sink policies / `set_auto_flush(...)`, `set_rotation(...)`, `clear_rotation()` 可在运行期调整 file sink 的基础策略
- `open_failures()`, `write_failures()`, `flush_failures()`, `rotation_failures()` expose basic sink health counters / `open_failures()`, `write_failures()`, `flush_failures()`, `rotation_failures()` 可用于观察基础 sink 健康状态
- `ConfiguredLogger` also forwards file reopen, flush, close, append-mode, path, auto-flush, rotation-config, state snapshot, append setter, policy setter, and failure-counter helpers for config-built file sinks / `ConfiguredLogger` 也会为配置生成的 file sink 转发 reopen, flush, close, append-mode, path, auto-flush, rotation 配置, state 快照, append setter, 策略 setter 与失败计数访问器
- `ConfiguredLogger::file_runtime_state()` also reports whether a file sink is queue-wrapped and exposes the outer queue pending/drop counters together with the inner file snapshot / `ConfiguredLogger::file_runtime_state()` 还可报告 file sink 是否被 queue 包装, 并将外层 queue 的 pending/drop 计数与内层 file 快照一起返回
- `ConfiguredLogger::file_policy()` and `ConfiguredLogger::file_default_policy()` also expose current runtime policy and initial config policy separately / `ConfiguredLogger::file_policy()` 与 `ConfiguredLogger::file_default_policy()` 也可分别读取当前运行期策略与初始配置策略
- `ConfiguredLogger::file_policy_matches_default()` also tells whether the current runtime file policy has drifted from the default config / `ConfiguredLogger::file_policy_matches_default()` 也可显式判断当前运行期 file 策略是否已偏离默认配置
- `ConfiguredLogger::file_set_policy()` also applies a bundled runtime file policy through the config-built control surface / `ConfiguredLogger::file_set_policy()` 也可通过配置式控制面一次性写回 bundled file 策略
- `ConfiguredLogger::file_reset_failure_counters()` also clears file failure counters through the config-built control surface / `ConfiguredLogger::file_reset_failure_counters()` 也可通过配置式控制面清空 file 失败计数
- `ConfiguredLogger::file_reset_policy()` also restores runtime file policy back to the initial config values / `ConfiguredLogger::file_reset_policy()` 也可将运行期 file 策略恢复到初始配置值
- current scope is observability-first, not a full self-healing sink runtime / 当前以可观测性为主, 不提供完整的自恢复 sink runtime.
-1197
View File
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
let default_console_sink : ConsoleSink = console_sink()
let default_min_level_ref : Ref[Level] = Ref::new(Level::Info)
let default_target_ref : Ref[String] = Ref::new("")
pub fn set_default_min_level(level : Level) -> Unit {
default_min_level_ref.val = level
}
pub fn set_default_target(target : String) -> Unit {
default_target_ref.val = target
}
pub fn default_logger() -> Logger[ConsoleSink] {
Logger::new(default_console_sink, min_level=default_min_level_ref.val, target=default_target_ref.val)
}
pub fn log(level : Level, message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().log(level, message, fields=fields)
}
pub fn trace(message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().trace(message, fields=fields)
}
pub fn debug(message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().debug(message, fields=fields)
}
pub fn info(message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().info(message, fields=fields)
}
pub fn warn(message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().warn(message, fields=fields)
}
pub fn error(message : String, fields~ : Array[Field] = []) -> Unit {
default_logger().error(message, fields=fields)
}
-122
View File
@@ -1,122 +0,0 @@
pub struct Logger[S] {
min_level : Level
sink : S
target : String
timestamp : Bool
}
pub fn[S] Logger::new(sink : S, min_level~ : Level = Level::Info, target~ : String = "") -> Logger[S] {
{ min_level, sink, target, timestamp: false }
}
pub fn[S] Logger::with_target(self : Logger[S], target : String) -> Logger[S] {
{ ..self, target }
}
fn combine_targets(parent : String, child : String) -> String {
if parent == "" {
child
} else if child == "" {
parent
} else {
"\{parent}.\{child}"
}
}
pub fn[S] Logger::child(self : Logger[S], target : String) -> Logger[S] {
{ ..self, target: combine_targets(self.target, target) }
}
pub fn[S] Logger::with_context_fields(self : Logger[S], fields : Array[Field]) -> Logger[ContextSink[S]] {
{
min_level: self.min_level,
sink: ContextSink::{ sink: self.sink, context_fields: fields },
target: self.target,
timestamp: self.timestamp,
}
}
pub fn[S] Logger::bind(self : Logger[S], fields : Array[Field]) -> Logger[ContextSink[S]] {
self.with_context_fields(fields)
}
pub fn[S] Logger::with_filter(self : Logger[S], predicate : (Record) -> Bool) -> Logger[FilterSink[S]] {
{
min_level: self.min_level,
sink: filter_sink(self.sink, predicate),
target: self.target,
timestamp: self.timestamp,
}
}
pub fn[S] Logger::with_patch(self : Logger[S], patch : RecordPatch) -> Logger[PatchSink[S]] {
{
min_level: self.min_level,
sink: patch_sink(self.sink, patch),
target: self.target,
timestamp: self.timestamp,
}
}
pub fn[S] Logger::with_queue(
self : Logger[S],
max_pending~ : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> Logger[QueuedSink[S]] {
{
min_level: self.min_level,
sink: queued_sink(self.sink, max_pending=max_pending, overflow=overflow),
target: self.target,
timestamp: self.timestamp,
}
}
pub fn[S] Logger::with_min_level(self : Logger[S], min_level : Level) -> Logger[S] {
{ ..self, min_level }
}
pub fn[S] Logger::with_timestamp(self : Logger[S], enabled~ : Bool = true) -> Logger[S] {
{ ..self, timestamp: enabled }
}
pub fn[S] Logger::is_enabled(self : Logger[S], level : Level) -> Bool {
level.enabled(self.min_level)
}
pub fn[S : Sink] Logger::log(
self : Logger[S],
level : Level,
message : String,
fields~ : Array[Field] = [],
target? : String = "",
) -> Unit {
if !self.is_enabled(level) {
()
} else {
let actual_target = if target == "" { self.target } else { target }
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
self.sink.write(
record(level, message, timestamp_ms=timestamp_ms, target=actual_target, fields=fields),
)
}
}
pub fn[S : Sink] Logger::trace(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
self.log(Level::Trace, message, fields=fields)
}
pub fn[S : Sink] Logger::debug(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
self.log(Level::Debug, message, fields=fields)
}
pub fn[S : Sink] Logger::info(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
self.log(Level::Info, message, fields=fields)
}
pub fn[S : Sink] Logger::warn(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
self.log(Level::Warn, message, fields=fields)
}
pub fn[S : Sink] Logger::error(self : Logger[S], message : String, fields~ : Array[Field] = []) -> Unit {
self.log(Level::Error, message, fields=fields)
}
-42
View File
@@ -1,42 +0,0 @@
pub struct Field {
key : String
value : String
}
pub fn field(key : String, value : String) -> Field {
{ key, value }
}
pub fn fields(entries : Array[(String, String)]) -> Array[Field] {
entries.map(fn(entry) {
field(entry.0, entry.1)
})
}
pub struct Record {
level : Level
timestamp_ms : UInt64
target : String
message : String
fields : Array[Field]
}
pub fn Record::new(
level : Level,
message : String,
timestamp_ms~ : UInt64 = 0UL,
target~ : String = "",
fields~ : Array[Field] = [],
) -> Record {
{ level, timestamp_ms, target, message, fields }
}
fn record(
level : Level,
message : String,
timestamp_ms~ : UInt64 = 0UL,
target~ : String = "",
fields~ : Array[Field] = [],
) -> Record {
Record::new(level, message, timestamp_ms=timestamp_ms, target=target, fields=fields)
}
-650
View File
@@ -1,650 +0,0 @@
pub trait Sink {
write(Self, Record) -> Unit
}
pub struct ConsoleSink {
_dummy : Unit
}
pub fn console_sink() -> ConsoleSink {
{ _dummy: () }
}
pub impl Sink for ConsoleSink with write(self, rec) {
ignore(self)
println(format_text(rec))
}
pub struct ContextSink[S] {
sink : S
context_fields : Array[Field]
}
pub impl[S : Sink] Sink for ContextSink[S] with write(self, rec) {
let merged = if self.context_fields.length() == 0 {
rec.fields
} else if rec.fields.length() == 0 {
self.context_fields
} else {
self.context_fields + rec.fields
}
self.sink.write({ ..rec, fields: merged })
}
pub struct JsonConsoleSink {
_dummy : Unit
}
pub fn json_console_sink() -> JsonConsoleSink {
{ _dummy: () }
}
pub impl Sink for JsonConsoleSink with write(self, rec) {
ignore(self)
println(format_json(rec))
}
pub struct FileSink {
path : String
append : Ref[Bool]
default_append : Bool
handle : Ref[FileHandle?]
formatter : RecordFormatter
auto_flush : Ref[Bool]
default_auto_flush : Bool
rotation : Ref[FileRotation?]
default_rotation : FileRotation?
open_failures : Ref[Int]
write_failures : Ref[Int]
flush_failures : Ref[Int]
rotation_failures : Ref[Int]
}
pub struct FileRotation {
max_bytes : Int
max_backups : Int
}
pub struct FileSinkState {
path : String
available : Bool
append : Bool
auto_flush : Bool
rotation : FileRotation?
open_failures : Int
write_failures : Int
flush_failures : Int
rotation_failures : Int
}
pub struct FileSinkPolicy {
append : Bool
auto_flush : Bool
rotation : FileRotation?
}
pub fn FileSinkPolicy::new(
append~ : Bool = true,
auto_flush~ : Bool = true,
rotation~ : FileRotation? = None,
) -> FileSinkPolicy {
{ append, auto_flush, rotation }
}
pub fn FileSinkState::new(
path : String,
available~ : Bool = false,
append~ : Bool = true,
auto_flush~ : Bool = true,
rotation~ : FileRotation? = None,
open_failures~ : Int = 0,
write_failures~ : Int = 0,
flush_failures~ : Int = 0,
rotation_failures~ : Int = 0,
) -> FileSinkState {
{
path,
available,
append,
auto_flush,
rotation,
open_failures,
write_failures,
flush_failures,
rotation_failures,
}
}
pub fn file_rotation(max_bytes : Int, max_backups~ : Int = 1) -> FileRotation {
{
max_bytes: if max_bytes <= 0 { 1 } else { max_bytes },
max_backups: if max_backups <= 0 { 1 } else { max_backups },
}
}
pub fn native_files_supported() -> Bool {
native_files_supported_internal()
}
pub fn file_sink(
path : String,
append~ : Bool = true,
auto_flush~ : Bool = true,
rotation~ : FileRotation? = None,
formatter~ : RecordFormatter = fn(rec) {
format_text(rec)
},
) -> FileSink {
let handle = open_file_handle_internal(path, append)
{
path,
append: Ref::new(append),
default_append: append,
handle: Ref::new(handle),
formatter,
auto_flush: Ref::new(auto_flush),
default_auto_flush: auto_flush,
rotation: Ref::new(rotation),
default_rotation: rotation,
open_failures: Ref::new(if handle is Some(_) { 0 } else { 1 }),
write_failures: Ref::new(0),
flush_failures: Ref::new(0),
rotation_failures: Ref::new(0),
}
}
pub fn FileSink::is_available(self : FileSink) -> Bool {
self.handle.val is Some(_)
}
pub fn FileSink::flush(self : FileSink) -> Bool {
match self.handle.val {
None => false
Some(handle) => {
let ok = flush_file_handle_internal(handle)
if !ok {
self.flush_failures.val += 1
}
ok
}
}
}
pub fn FileSink::append_mode(self : FileSink) -> Bool {
self.append.val
}
pub fn FileSink::set_append_mode(self : FileSink, append : Bool) -> Unit {
self.append.val = append
}
pub fn FileSink::path(self : FileSink) -> String {
self.path
}
pub fn FileSink::auto_flush_enabled(self : FileSink) -> Bool {
self.auto_flush.val
}
pub fn FileSink::rotation_enabled(self : FileSink) -> Bool {
self.rotation.val is Some(_)
}
pub fn FileSink::rotation_config(self : FileSink) -> FileRotation? {
self.rotation.val
}
pub fn FileSink::set_auto_flush(self : FileSink, enabled : Bool) -> Unit {
self.auto_flush.val = enabled
}
pub fn FileSink::set_policy(self : FileSink, policy : FileSinkPolicy) -> Unit {
self.append.val = policy.append
self.auto_flush.val = policy.auto_flush
self.rotation.val = policy.rotation
}
pub fn FileSink::set_rotation(self : FileSink, rotation : FileRotation?) -> Unit {
self.rotation.val = rotation
}
pub fn FileSink::clear_rotation(self : FileSink) -> Unit {
self.rotation.val = None
}
pub fn FileSink::close(self : FileSink) -> Bool {
match self.handle.val {
None => false
Some(handle) => {
let ok = close_file_handle_internal(handle)
self.handle.val = None
ok
}
}
}
pub fn FileSink::rotation_failures(self : FileSink) -> Int {
self.rotation_failures.val
}
pub fn FileSink::open_failures(self : FileSink) -> Int {
self.open_failures.val
}
pub fn FileSink::write_failures(self : FileSink) -> Int {
self.write_failures.val
}
pub fn FileSink::flush_failures(self : FileSink) -> Int {
self.flush_failures.val
}
pub fn FileSink::reset_failure_counters(self : FileSink) -> Unit {
self.open_failures.val = 0
self.write_failures.val = 0
self.flush_failures.val = 0
self.rotation_failures.val = 0
}
pub fn FileSink::reset_policy(self : FileSink) -> Unit {
self.append.val = self.default_append
self.auto_flush.val = self.default_auto_flush
self.rotation.val = self.default_rotation
}
pub fn FileSink::policy(self : FileSink) -> FileSinkPolicy {
FileSinkPolicy::new(
append=self.append.val,
auto_flush=self.auto_flush.val,
rotation=self.rotation.val,
)
}
pub fn FileSink::default_policy(self : FileSink) -> FileSinkPolicy {
FileSinkPolicy::new(
append=self.default_append,
auto_flush=self.default_auto_flush,
rotation=self.default_rotation,
)
}
pub fn FileSink::policy_matches_default(self : FileSink) -> Bool {
let current = self.policy()
let default = self.default_policy()
current.append == default.append &&
current.auto_flush == default.auto_flush &&
policy_rotation_equals_internal(current.rotation, default.rotation)
}
fn policy_rotation_equals_internal(left : FileRotation?, right : FileRotation?) -> Bool {
match (left, right) {
(None, None) => true
(Some(a), Some(b)) => a.max_bytes == b.max_bytes && a.max_backups == b.max_backups
_ => false
}
}
pub fn FileSink::state(self : FileSink) -> FileSinkState {
{
path: self.path,
available: self.is_available(),
append: self.append.val,
auto_flush: self.auto_flush.val,
rotation: self.rotation.val,
open_failures: self.open_failures.val,
write_failures: self.write_failures.val,
flush_failures: self.flush_failures.val,
rotation_failures: self.rotation_failures.val,
}
}
pub fn FileSink::reopen(self : FileSink, append~ : Bool? = None) -> Bool {
let append_mode = append.unwrap_or(self.append.val)
self.append.val = append_mode
match self.handle.val {
None => ()
Some(handle) => {
ignore(close_file_handle_internal(handle))
self.handle.val = None
}
}
let reopened = open_file_handle_internal(self.path, append_mode)
self.handle.val = reopened
if reopened is Some(_) {
true
} else {
self.open_failures.val += 1
false
}
}
pub fn FileSink::reopen_with_current_policy(self : FileSink) -> Bool {
self.reopen()
}
pub fn FileSink::reopen_append(self : FileSink) -> Bool {
self.reopen(append=Some(true))
}
pub fn FileSink::reopen_truncate(self : FileSink) -> Bool {
self.reopen(append=Some(false))
}
fn rotated_file_path(path : String, index : Int) -> String {
"\{path}.\{index}"
}
fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
let closed = match sink.handle.val {
None => true
Some(handle) => {
let ok = close_file_handle_internal(handle)
sink.handle.val = None
ok
}
}
if !closed {
return false
}
if rotation.max_backups > 0 {
ignore(remove_file_internal(rotated_file_path(sink.path, rotation.max_backups)))
for index = rotation.max_backups - 1; index >= 1; {
let from_path = rotated_file_path(sink.path, index)
let to_path = rotated_file_path(sink.path, index + 1)
ignore(rename_file_internal(from_path, to_path))
continue index - 1
}
ignore(rename_file_internal(sink.path, rotated_file_path(sink.path, 1)))
} else {
ignore(remove_file_internal(sink.path))
}
sink.handle.val = open_file_handle_internal(sink.path, false)
sink.handle.val is Some(_)
}
fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool {
match sink.rotation.val {
None => true
Some(rotation) => match sink.handle.val {
None => false
Some(handle) => {
let size = file_size_internal(handle)
if size + next_line_bytes <= rotation.max_bytes {
true
} else {
let rotated = rotate_file_sink_internal(sink, rotation)
if !rotated {
sink.rotation_failures.val += 1
}
rotated
}
}
}
}
}
pub impl Sink for FileSink with write(self, rec) {
match self.handle.val {
None => {
self.write_failures.val += 1
}
Some(_) => {
let line = "\{(self.formatter)(rec)}\n"
let can_write = rotate_if_needed_internal(self, string_byte_length_internal(line))
if can_write {
match self.handle.val {
None => {
self.write_failures.val += 1
}
Some(active) => {
let wrote = write_file_handle_internal(active, line)
if wrote {
if self.auto_flush.val {
let flushed = flush_file_handle_internal(active)
if !flushed {
self.flush_failures.val += 1
}
}
} else {
self.write_failures.val += 1
}
}
}
} else {
self.write_failures.val += 1
}
}
}
}
pub struct FormattedConsoleSink {
formatter : RecordFormatter
}
pub fn formatted_console_sink(formatter : RecordFormatter) -> FormattedConsoleSink {
{ formatter, }
}
pub fn text_console_sink(formatter : TextFormatter) -> FormattedConsoleSink {
formatted_console_sink(fn(rec) {
format_text(rec, formatter=formatter)
})
}
pub impl Sink for FormattedConsoleSink with write(self, rec) {
println((self.formatter)(rec))
}
pub struct FormattedCallbackSink {
formatter : RecordFormatter
callback : (String) -> Unit
}
pub fn formatted_callback_sink(
formatter : RecordFormatter,
callback : (String) -> Unit,
) -> FormattedCallbackSink {
{ formatter, callback }
}
pub fn text_callback_sink(
formatter : TextFormatter,
callback : (String) -> Unit,
) -> FormattedCallbackSink {
formatted_callback_sink(fn(rec) {
format_text(rec, formatter=formatter)
}, callback)
}
pub impl Sink for FormattedCallbackSink with write(self, rec) {
(self.callback)((self.formatter)(rec))
}
pub struct FanoutSink[A, B] {
left : A
right : B
}
pub fn[A, B] fanout_sink(left : A, right : B) -> FanoutSink[A, B] {
{ left, right }
}
pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with write(self, rec) {
self.left.write(rec)
self.right.write({ ..rec })
}
pub struct SplitSink[A, B] {
left : A
right : B
predicate : (Record) -> Bool
}
pub fn[A, B] split_sink(left : A, right : B, predicate : (Record) -> Bool) -> SplitSink[A, B] {
{ left, right, predicate }
}
pub fn[A, B] split_by_level(
left : A,
right : B,
min_level~ : Level = Level::Warn,
) -> SplitSink[A, B] {
split_sink(left, right, fn(rec) {
rec.level.enabled(min_level)
})
}
pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with write(self, rec) {
if (self.predicate)(rec) {
self.left.write(rec)
} else {
self.right.write(rec)
}
}
pub struct CallbackSink {
callback : (Record) -> Unit
}
pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink {
{ callback, }
}
pub impl Sink for CallbackSink with write(self, rec) {
(self.callback)(rec)
}
pub struct BufferedSink[S] {
sink : S
buffer : Ref[Array[Record]]
flush_limit : Int
}
pub fn[S] buffered_sink(sink : S, flush_limit~ : Int = 1) -> BufferedSink[S] {
let actual_limit = if flush_limit <= 0 { 1 } else { flush_limit }
{ sink, buffer: Ref::new([]), flush_limit: actual_limit }
}
pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int {
self.buffer.val.length()
}
pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
if self.buffer.val.length() == 0 {
()
} else {
let pending = self.buffer.val
self.buffer.val = []
for rec in pending {
self.sink.write(rec)
}
}
}
pub impl[S : Sink] Sink for BufferedSink[S] with write(self, rec) {
self.buffer.val.push(rec)
if self.buffer.val.length() >= self.flush_limit {
self.flush()
}
}
pub(all) enum QueueOverflowPolicy {
DropNewest
DropOldest
}
pub struct QueuedSink[S] {
sink : S
queue : @queue.Queue[Record]
max_pending : Int
overflow : QueueOverflowPolicy
dropped_count : Ref[Int]
}
pub fn[S] queued_sink(
sink : S,
max_pending~ : Int = 0,
overflow~ : QueueOverflowPolicy = QueueOverflowPolicy::DropNewest,
) -> QueuedSink[S] {
{
sink,
queue: @queue.Queue::new(),
max_pending,
overflow,
dropped_count: Ref::new(0),
}
}
pub fn[S] QueuedSink::pending_count(self : QueuedSink[S]) -> Int {
self.queue.length()
}
pub fn[S] QueuedSink::dropped_count(self : QueuedSink[S]) -> Int {
self.dropped_count.val
}
pub fn[S : Sink] QueuedSink::drain(self : QueuedSink[S], max_items~ : Int = -1) -> Int {
if max_items == 0 {
return 0
}
let limit = if max_items < 0 { self.pending_count() } else { max_items }
for drained = 0; drained < limit; {
match self.queue.pop() {
None => break drained
Some(rec) => {
self.sink.write(rec)
continue drained + 1
}
}
} nobreak {
limit
}
}
pub fn[S : Sink] QueuedSink::flush(self : QueuedSink[S]) -> Int {
self.drain()
}
pub impl[S] Sink for QueuedSink[S] with write(self, rec) {
let full = self.max_pending > 0 && self.pending_count() >= self.max_pending
if !full {
self.queue.push(rec)
} else {
self.dropped_count.val += 1
match self.overflow {
QueueOverflowPolicy::DropNewest => ()
QueueOverflowPolicy::DropOldest => {
ignore(self.queue.pop())
self.queue.push(rec)
}
}
}
}
pub struct FilterSink[S] {
sink : S
predicate : (Record) -> Bool
}
pub fn[S] filter_sink(sink : S, predicate : (Record) -> Bool) -> FilterSink[S] {
{ sink, predicate }
}
pub impl[S : Sink] Sink for FilterSink[S] with write(self, rec) {
if (self.predicate)(rec) {
self.sink.write(rec)
}
}
pub struct PatchSink[S] {
sink : S
patch : RecordPatch
}
pub fn[S] patch_sink(sink : S, patch : RecordPatch) -> PatchSink[S] {
{ sink, patch }
}
pub impl[S : Sink] Sink for PatchSink[S] with write(self, rec) {
self.sink.write((self.patch)(rec))
}
-150
View File
@@ -1,150 +0,0 @@
async test "shutdown drains pending records" {
let written : Ref[Array[String]] = Ref::new([])
let flushes : Ref[Int] = Ref::new(0)
let logger = async_logger(
@bitlogger.callback_sink(fn(rec) {
written.val.push(rec.message)
}),
config=AsyncLoggerConfig::new(
max_pending=4,
overflow=AsyncOverflowPolicy::Blocking,
max_batch=4,
linger_ms=10,
flush=AsyncFlushPolicy::Batch,
),
min_level=@bitlogger.Level::Info,
target="async.test",
flush=fn(_) {
flushes.val += 1
1
},
)
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("one")
logger.info("two")
logger.shutdown()
})
inspect(logger.is_closed(), content="true")
inspect(logger.is_running(), content="false")
inspect(logger.has_failed(), content="false")
inspect(logger.pending_count(), content="0")
inspect(match logger.flush_policy() {
AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch"
AsyncFlushPolicy::Shutdown => "Shutdown"
}, content="Batch")
inspect(written.val.length(), content="2")
inspect(written.val[0], content="one")
inspect(written.val[1], content="two")
inspect(flushes.val, content="1")
}
async test "close clear counts abandoned records as dropped" {
let logger = async_logger(
@bitlogger.callback_sink(fn(_) {
}),
config=AsyncLoggerConfig::new(
max_pending=4,
overflow=AsyncOverflowPolicy::Blocking,
),
min_level=@bitlogger.Level::Info,
target="async.clear",
)
logger.info("one")
logger.info("two")
inspect(logger.pending_count(), content="2")
inspect(logger.dropped_count(), content="0")
logger.close(clear=true)
inspect(logger.is_closed(), content="true")
inspect(logger.pending_count(), content="0")
inspect(logger.dropped_count(), content="2")
}
async test "shutdown clear closes without worker startup" {
let logger = async_logger(
@bitlogger.callback_sink(fn(_) {
}),
config=AsyncLoggerConfig::new(
max_pending=2,
overflow=AsyncOverflowPolicy::Blocking,
),
min_level=@bitlogger.Level::Info,
target="async.noworker",
)
logger.info("one")
logger.shutdown(clear=true)
inspect(logger.is_closed(), content="true")
inspect(logger.is_running(), content="false")
inspect(logger.pending_count(), content="0")
inspect(logger.dropped_count(), content="1")
}
test "async logger config stringify roundtrips stable fields" {
let text = stringify_async_logger_config(
AsyncLoggerConfig::new(
max_pending=8,
overflow=AsyncOverflowPolicy::DropOldest,
max_batch=3,
linger_ms=25,
flush=AsyncFlushPolicy::Batch,
),
)
let config = parse_async_logger_config_text(text)
inspect(config.max_pending, content="8")
inspect(config.max_batch, content="3")
inspect(config.linger_ms, content="25")
inspect(match config.overflow {
AsyncOverflowPolicy::Blocking => "Blocking"
AsyncOverflowPolicy::DropOldest => "DropOldest"
AsyncOverflowPolicy::DropNewest => "DropNewest"
}, content="DropOldest")
inspect(match config.flush {
AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch"
AsyncFlushPolicy::Shutdown => "Shutdown"
}, content="Batch")
}
test "async build config stringify roundtrips nested logger and async fields" {
let text = stringify_async_logger_build_config(
AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(
min_level=@bitlogger.Level::Warn,
target="async.roundtrip",
timestamp=true,
sink=@bitlogger.SinkConfig::new(kind=@bitlogger.SinkKind::TextConsole),
),
async_config=AsyncLoggerConfig::new(
max_pending=2,
overflow=AsyncOverflowPolicy::DropNewest,
max_batch=5,
linger_ms=40,
flush=AsyncFlushPolicy::Shutdown,
),
),
)
let config = parse_async_logger_build_config_text(text)
inspect(config.logger.min_level.label(), content="WARN")
inspect(config.logger.target, content="async.roundtrip")
inspect(config.logger.timestamp, content="true")
inspect(config.async_config.max_pending, content="2")
inspect(config.async_config.max_batch, content="5")
inspect(config.async_config.linger_ms, content="40")
inspect(match config.async_config.overflow {
AsyncOverflowPolicy::Blocking => "Blocking"
AsyncOverflowPolicy::DropOldest => "DropOldest"
AsyncOverflowPolicy::DropNewest => "DropNewest"
}, content="DropNewest")
inspect(match config.async_config.flush {
AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch"
AsyncFlushPolicy::Shutdown => "Shutdown"
}, content="Shutdown")
}
-568
View File
@@ -1,568 +0,0 @@
pub(all) suberror AsyncLoggerClosed {
AsyncLoggerClosed
}
pub(all) enum AsyncOverflowPolicy {
Blocking
DropOldest
DropNewest
}
pub(all) enum AsyncFlushPolicy {
Never
Batch
Shutdown
}
pub struct AsyncLoggerConfig {
max_pending : Int
overflow : AsyncOverflowPolicy
max_batch : Int
linger_ms : Int
flush : AsyncFlushPolicy
}
pub fn AsyncLoggerConfig::new(
max_pending~ : Int = 0,
overflow~ : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
max_batch~ : Int = 1,
linger_ms~ : Int = 0,
flush~ : AsyncFlushPolicy = AsyncFlushPolicy::Never,
) -> AsyncLoggerConfig {
{
max_pending,
overflow,
max_batch: if max_batch <= 1 { 1 } else { max_batch },
linger_ms: if linger_ms < 0 { 0 } else { linger_ms },
flush,
}
}
fn parse_async_overflow(name : String) -> AsyncOverflowPolicy raise {
match name.to_upper() {
"BLOCKING" => AsyncOverflowPolicy::Blocking
"DROPOLDEST" => AsyncOverflowPolicy::DropOldest
"DROPLATEST" => AsyncOverflowPolicy::DropNewest
"DROPNEWEST" => AsyncOverflowPolicy::DropNewest
_ => raise Failure::Failure("Unsupported async overflow policy: " + name)
}
}
fn parse_async_flush(name : String) -> AsyncFlushPolicy raise {
match name.to_upper() {
"NEVER" => AsyncFlushPolicy::Never
"NONE" => AsyncFlushPolicy::Never
"BATCH" => AsyncFlushPolicy::Batch
"SHUTDOWN" => AsyncFlushPolicy::Shutdown
_ => raise Failure::Failure("Unsupported async flush policy: " + name)
}
}
pub fn parse_async_logger_config_text(input : String) -> AsyncLoggerConfig raise {
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(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(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(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(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(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(
max_pending=max_pending,
overflow=overflow,
max_batch=max_batch,
linger_ms=linger_ms,
flush=flush,
)
}
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_parser.JsonValue::String(match config.flush {
AsyncFlushPolicy::Never => "Never"
AsyncFlushPolicy::Batch => "Batch"
AsyncFlushPolicy::Shutdown => "Shutdown"
}),
})
}
pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool = false) -> String {
let value = async_logger_config_to_json(config)
if pretty {
@json_parser.stringify_pretty(value, 2)
} else {
@json_parser.stringify(value)
}
}
pub struct AsyncLoggerBuildConfig {
logger : @bitlogger.LoggerConfig
async_config : AsyncLoggerConfig
}
pub fn AsyncLoggerBuildConfig::new(
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
) -> AsyncLoggerBuildConfig {
{ logger, async_config }
}
pub fn parse_async_logger_build_config_text(input : String) -> AsyncLoggerBuildConfig raise {
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(@json_parser.stringify(value))
None => @bitlogger.default_logger_config()
}
let async_config = match obj.get("async_config") {
Some(value) => parse_async_logger_config_text(@json_parser.stringify(value))
None => AsyncLoggerConfig::new()
}
AsyncLoggerBuildConfig::new(logger=logger, async_config=async_config)
}
pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig,
) -> @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),
})
}
pub fn stringify_async_logger_build_config(
config : AsyncLoggerBuildConfig,
pretty~ : Bool = false,
) -> String {
let value = async_logger_build_config_to_json(config)
if pretty {
@json_parser.stringify_pretty(value, 2)
} else {
@json_parser.stringify(value)
}
}
pub struct AsyncLogger[S] {
min_level : @bitlogger.Level
target : String
timestamp : Bool
overflow : AsyncOverflowPolicy
max_batch : Int
linger_ms : Int
flush_policy : AsyncFlushPolicy
sink : S
flush_sink : (S) -> Int
context_fields : Array[@bitlogger.Field]
filter : (@bitlogger.Record) -> Bool
patch : @bitlogger.RecordPatch
queue : @async.Queue[@bitlogger.Record]
pending_count : Ref[Int]
dropped_count : Ref[Int]
is_closed : Ref[Bool]
is_running : Ref[Bool]
has_failed : Ref[Bool]
last_error : Ref[String]
}
pub fn[S] async_logger(
sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "",
flush~ : (S) -> Int = fn(_) { 0 },
) -> AsyncLogger[S] {
{
min_level,
target,
timestamp: false,
overflow: config.overflow,
max_batch: config.max_batch,
linger_ms: config.linger_ms,
flush_policy: config.flush,
sink,
flush_sink: flush,
context_fields: [],
filter: fn(_) { true },
patch: @bitlogger.identity_patch(),
queue: @async.Queue::new(kind=queue_kind_of(config)),
pending_count: Ref::new(0),
dropped_count: Ref::new(0),
is_closed: Ref::new(false),
is_running: Ref::new(false),
has_failed: Ref::new(false),
last_error: Ref::new(""),
}
}
fn queue_kind_of(config : AsyncLoggerConfig) -> @aqueue.Kind {
let limit = if config.max_pending < 0 { 0 } else { config.max_pending }
match config.overflow {
AsyncOverflowPolicy::Blocking => @aqueue.Kind::Blocking(limit)
AsyncOverflowPolicy::DropOldest => @aqueue.Kind::DiscardOldest(limit)
AsyncOverflowPolicy::DropNewest => @aqueue.Kind::DiscardLatest(limit)
}
}
pub fn[S] AsyncLogger::with_timestamp(self : AsyncLogger[S], enabled~ : Bool = true) -> AsyncLogger[S] {
{ ..self, timestamp: enabled }
}
pub fn[S] AsyncLogger::with_target(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {
{ ..self, target }
}
pub fn[S] AsyncLogger::with_context_fields(
self : AsyncLogger[S],
fields : Array[@bitlogger.Field],
) -> AsyncLogger[S] {
{ ..self, context_fields: fields }
}
pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S],
predicate : (@bitlogger.Record) -> Bool,
) -> AsyncLogger[S] {
let current = self.filter
{
..self,
filter: fn(rec) {
current(rec) && predicate(rec)
},
}
}
pub fn[S] AsyncLogger::with_patch(
self : AsyncLogger[S],
patch : @bitlogger.RecordPatch,
) -> AsyncLogger[S] {
let current = self.patch
{
..self,
patch: fn(rec) {
patch(current(rec))
},
}
}
pub fn[S] AsyncLogger::with_min_level(
self : AsyncLogger[S],
min_level : @bitlogger.Level,
) -> AsyncLogger[S] {
{ ..self, min_level }
}
fn combine_targets(parent : String, child : String) -> String {
if parent == "" {
child
} else if child == "" {
parent
} else {
"\{parent}.\{child}"
}
}
pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {
{ ..self, target: combine_targets(self.target, target) }
}
pub fn[S] AsyncLogger::is_enabled(self : AsyncLogger[S], level : @bitlogger.Level) -> Bool {
level.enabled(self.min_level)
}
pub async fn[S] AsyncLogger::log(
self : AsyncLogger[S],
level : @bitlogger.Level,
message : String,
fields~ : Array[@bitlogger.Field] = [],
target? : String = "",
) -> Unit {
guard self.is_enabled(level) else {
()
}
let actual_target = if target == "" { self.target } else { target }
let timestamp_ms = if self.timestamp { @env.now() } else { 0UL }
let rec = @bitlogger.Record::new(
level,
message,
timestamp_ms=timestamp_ms,
target=actual_target,
fields=merge_fields(self.context_fields, fields),
)
let rec = (self.patch)(rec)
guard (self.filter)(rec) else {
()
}
let accepted = self.queue.try_put(rec) catch {
err if err is AsyncLoggerClosed => false
err => raise err
}
if accepted {
self.pending_count.val += 1
} else {
match self.overflow {
AsyncOverflowPolicy::Blocking => {
self.queue.put(rec) catch {
err if err is AsyncLoggerClosed => ()
err => raise err
}
self.pending_count.val += 1
}
AsyncOverflowPolicy::DropOldest | AsyncOverflowPolicy::DropNewest => {
self.dropped_count.val += 1
}
}
}
}
fn merge_fields(
left : Array[@bitlogger.Field],
right : Array[@bitlogger.Field],
) -> Array[@bitlogger.Field] {
if left.length() == 0 {
right
} else if right.length() == 0 {
left
} else {
left + right
}
}
pub async fn[S] AsyncLogger::trace(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {
self.log(@bitlogger.Level::Trace, message, fields=fields)
}
pub async fn[S] AsyncLogger::debug(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {
self.log(@bitlogger.Level::Debug, message, fields=fields)
}
pub async fn[S] AsyncLogger::info(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {
self.log(@bitlogger.Level::Info, message, fields=fields)
}
pub async fn[S] AsyncLogger::warn(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {
self.log(@bitlogger.Level::Warn, message, fields=fields)
}
pub async fn[S] AsyncLogger::error(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {
self.log(@bitlogger.Level::Error, message, fields=fields)
}
pub fn[S] AsyncLogger::pending_count(self : AsyncLogger[S]) -> Int {
self.pending_count.val
}
pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int {
self.dropped_count.val
}
pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool {
self.is_closed.val
}
pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool {
self.is_running.val
}
pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {
self.has_failed.val
}
pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {
self.last_error.val
}
pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {
self.flush_policy
}
pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Unit {
self.is_closed.val = true
if clear {
let abandoned = self.pending_count.val
if abandoned > 0 {
self.dropped_count.val += abandoned
self.pending_count.val = 0
}
}
self.queue.close(error=AsyncLoggerClosed, clear=clear)
}
pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = false) -> Unit {
if clear {
self.close(clear=true)
} else {
self.wait_idle()
if self.pending_count() > 0 {
self.close(clear=true)
} else {
self.close()
}
}
while self.is_running() {
@async.pause()
}
}
pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {
while self.pending_count() > 0 {
if self.has_failed() {
break
}
@async.pause()
}
}
async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
while true {
let rec = logger.queue.get() catch {
err if err is AsyncLoggerClosed => break
err => raise err
}
logger.sink.write(rec)
if logger.pending_count.val > 0 {
logger.pending_count.val -= 1
}
for drained = 1; drained < logger.max_batch; {
let next = logger.queue.try_get() catch {
err if err is AsyncLoggerClosed => None
err => raise err
}
match next {
Some(next) => {
logger.sink.write(next)
if logger.pending_count.val > 0 {
logger.pending_count.val -= 1
}
continue drained + 1
}
None => {
if logger.linger_ms <= 0 {
break
}
let waited = @async.with_timeout_opt(logger.linger_ms, () => logger.queue.get()) catch {
err if err is AsyncLoggerClosed => None
err => raise err
}
match waited {
Some(next) => {
logger.sink.write(next)
if logger.pending_count.val > 0 {
logger.pending_count.val -= 1
}
continue drained + 1
}
None => break
}
}
}
}
match logger.flush_policy {
AsyncFlushPolicy::Batch => ignore((logger.flush_sink)(logger.sink))
_ => ()
}
}
match logger.flush_policy {
AsyncFlushPolicy::Shutdown => ignore((logger.flush_sink)(logger.sink))
_ => ()
}
}
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Unit {
self.is_running.val = true
self.has_failed.val = false
self.last_error.val = ""
run_worker(self) catch {
err => {
self.has_failed.val = true
self.last_error.val = err.to_string()
self.is_running.val = false
raise err
}
}
self.is_running.val = false
}
pub fn build_async_logger(
config : AsyncLoggerBuildConfig,
) -> AsyncLogger[@bitlogger.RuntimeSink] {
let logger = @bitlogger.build_logger(config.logger)
async_logger(
logger.sink,
config=config.async_config,
min_level=logger.min_level,
target=logger.target,
flush=fn(sink) { sink.flush() },
).with_timestamp(enabled=logger.timestamp)
}
pub fn build_async_text_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlogger.FormattedConsoleSink] {
async_logger(
@bitlogger.text_console_sink(config.logger.sink.text_formatter.to_formatter()),
config=config.async_config,
min_level=config.logger.min_level,
target=config.logger.target,
).with_timestamp(enabled=config.logger.timestamp)
}
-234
View File
@@ -1,234 +0,0 @@
pub(all) suberror AsyncLoggerClosed {
AsyncLoggerClosed
}
pub(all) enum AsyncOverflowPolicy {
Blocking
DropOldest
DropNewest
}
pub(all) enum AsyncFlushPolicy {
Never
Batch
Shutdown
}
pub struct AsyncLoggerConfig {
max_pending : Int
overflow : AsyncOverflowPolicy
max_batch : Int
linger_ms : Int
flush : AsyncFlushPolicy
}
pub fn AsyncLoggerConfig::new(
max_pending~ : Int = 0,
overflow~ : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
max_batch~ : Int = 1,
linger_ms~ : Int = 0,
flush~ : AsyncFlushPolicy = AsyncFlushPolicy::Never,
) -> AsyncLoggerConfig {
{
max_pending,
overflow,
max_batch: if max_batch <= 1 { 1 } else { max_batch },
linger_ms: if linger_ms < 0 { 0 } else { linger_ms },
flush,
}
}
pub struct AsyncLogger[S] {}
pub struct AsyncLoggerBuildConfig {
logger : @bitlogger.LoggerConfig
async_config : AsyncLoggerConfig
}
pub fn AsyncLoggerBuildConfig::new(
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
) -> AsyncLoggerBuildConfig {
{ logger, async_config }
}
pub fn parse_async_logger_build_config_text(input : String) -> AsyncLoggerBuildConfig raise {
ignore(input)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn parse_async_logger_config_text(input : String) -> AsyncLoggerConfig raise {
ignore(input)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn async_logger_config_to_json(config : AsyncLoggerConfig) -> @json_parser.JsonValue {
ignore(config)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn stringify_async_logger_config(config : AsyncLoggerConfig, pretty~ : Bool = false) -> String {
ignore(config)
ignore(pretty)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn async_logger_build_config_to_json(
config : AsyncLoggerBuildConfig,
) -> @json_parser.JsonValue {
ignore(config)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn stringify_async_logger_build_config(
config : AsyncLoggerBuildConfig,
pretty~ : Bool = false,
) -> String {
ignore(config)
ignore(pretty)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn async_logger[S : @bitlogger.Sink](
sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "",
flush~ : (S) -> Int = fn(_) { 0 },
) -> AsyncLogger[S] {
ignore(sink)
ignore(config)
ignore(min_level)
ignore(target)
ignore(flush)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_timestamp(self : AsyncLogger[S], enabled~ : Bool = true) -> AsyncLogger[S] {
ignore(self)
ignore(enabled)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_target(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {
ignore(self)
ignore(target)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_context_fields(
self : AsyncLogger[S],
fields : Array[@bitlogger.Field],
) -> AsyncLogger[S] {
ignore(self)
ignore(fields)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S],
predicate : (@bitlogger.Record) -> Bool,
) -> AsyncLogger[S] {
ignore(self)
ignore(predicate)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_patch(
self : AsyncLogger[S],
patch : @bitlogger.RecordPatch,
) -> AsyncLogger[S] {
ignore(self)
ignore(patch)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::with_min_level(
self : AsyncLogger[S],
min_level : @bitlogger.Level,
) -> AsyncLogger[S] {
ignore(self)
ignore(min_level)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {
ignore(self)
ignore(target)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::is_enabled(self : AsyncLogger[S], level : @bitlogger.Level) -> Bool {
ignore(self)
ignore(level)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::pending_count(self : AsyncLogger[S]) -> Int {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Unit {
ignore(self)
ignore(clear)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = false) -> Unit {
ignore(self)
ignore(clear)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Unit {
ignore(self)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn build_async_logger(
config : AsyncLoggerBuildConfig,
) -> AsyncLogger[@bitlogger.RuntimeSink] {
ignore(config)
abort("bitlogger_async currently only supports native/llvm backends")
}
pub fn build_async_text_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlogger.FormattedConsoleSink] {
ignore(config)
abort("bitlogger_async currently only supports native/llvm backends")
}
-17
View File
@@ -1,17 +0,0 @@
import {
"Nanaloveyuki/BitLogger/bitlogger" @bitlogger,
"maria/json_parser" @json_parser,
"moonbitlang/async" @async,
"moonbitlang/async/aqueue" @aqueue,
"moonbitlang/core/env" @env,
"moonbitlang/core/ref",
}
supported_targets = "+native"
options(
targets: {
"async_logger_native.mbt": [ "native", "llvm" ],
"async_logger_stub.mbt": [ "js", "wasm", "wasm-gc" ],
},
)
+142
View File
@@ -0,0 +1,142 @@
import { basename } from 'node:path'
import { createMoonbitLanguageRegistration } from 'moonbit-syntax-highlighter'
import { defineConfig, type DefaultTheme } from 'vitepress'
import { docsData } from './generated/docs-data.mjs'
type ApiDocEntry = {
file: string
text: string
group: string
category: string
}
const repoSlug = process.env.GITHUB_REPOSITORY?.split('/')[1] ?? 'BitLogger'
const docsBase = process.env.DOCS_BASE ?? (process.env.GITHUB_ACTIONS ? `/${repoSlug}/` : '/')
const repository = 'https://github.com/Nanaloveyuki/BitLogger'
function splitWords(input: string): string[] {
return input
.split(/[^A-Za-z0-9]+/)
.map(part => part.trim())
.filter(Boolean)
}
function formatWord(word: string): string {
const lower = word.toLowerCase()
if (lower === 'api') return 'API'
if (lower === 'json') return 'JSON'
if (lower === 'js') return 'JS'
if (lower === 'wasm') return 'WASM'
if (lower === 'llvm') return 'LLVM'
return lower.charAt(0).toUpperCase() + lower.slice(1)
}
function humanize(input: string): string {
const words = splitWords(input)
if (words.length === 0) return input
return words.map(formatWord).join(' ')
}
function readApiDocs(): ApiDocEntry[] {
return docsData.api.entries.map(entry => ({
file: entry.file,
text: entry.title,
group: entry.group,
category: entry.category,
}))
}
function buildApiSidebar(): DefaultTheme.SidebarItem[] {
const entries = readApiDocs()
const sidebar: DefaultTheme.SidebarItem[] = [{ text: 'Overview', link: '/api/' }]
const groups = new Map<string, Map<string, DefaultTheme.SidebarItem[]>>()
for (const entry of entries) {
if (entry.file === 'index.md') continue
const group = groups.get(entry.group) ?? new Map<string, DefaultTheme.SidebarItem[]>()
const categoryItems = group.get(entry.category) ?? []
categoryItems.push({
text: entry.text,
link: `/api/${basename(entry.file, '.md')}`,
})
group.set(entry.category, categoryItems)
groups.set(entry.group, group)
}
if (groups.size === 1 && groups.has('api')) {
const categories = groups.get('api')!
for (const category of [...categories.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
sidebar.push({
text: humanize(category),
collapsed: true,
items: categories.get(category)!,
})
}
return sidebar
}
for (const group of [...groups.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
const categories = groups.get(group)!
sidebar.push({
text: humanize(group),
collapsed: true,
items: [...categories.keys()]
.sort((a, b) => humanize(a).localeCompare(humanize(b)))
.map(category => ({
text: humanize(category),
collapsed: true,
items: categories.get(category)!,
})),
})
}
return sidebar
}
function buildChangesSidebar(): DefaultTheme.SidebarItem[] {
return [
{ text: 'Overview', link: '/changes/' },
{
text: 'Versions',
items: docsData.changes.map(item => ({ text: item.version, link: item.link })),
},
]
}
export default defineConfig({
title: 'BitLogger',
description: 'Structured logging library docs for MoonBit.',
base: docsBase,
cleanUrls: true,
srcExclude: ['dev/**'],
lastUpdated: true,
markdown: {
languages: [createMoonbitLanguageRegistration()],
},
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',
},
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',
},
},
})
@@ -0,0 +1,219 @@
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import { withBase } from 'vitepress'
import { docsData } from '../../generated/docs-data.mjs'
const selectedGroup = shallowRef('all')
const query = shallowRef('')
const groups = computed(() => docsData.api.groups)
const filteredGroups = computed(() => {
const keyword = query.value.trim().toLowerCase()
return groups.value
.filter(group => selectedGroup.value === 'all' || group.id === selectedGroup.value)
.map(group => ({
...group,
categories: group.categories
.map(category => ({
...category,
entries: category.entries.filter(entry => {
if (!keyword) return true
const haystack = [entry.title, entry.description, entry.name, ...entry.keywords]
.join(' ')
.toLowerCase()
return haystack.includes(keyword)
}),
}))
.filter(category => category.entries.length > 0),
}))
.filter(group => group.categories.length > 0)
})
const resultCount = computed(() =>
filteredGroups.value.reduce(
(total, group) => total + group.categories.reduce((sum, category) => sum + category.entries.length, 0),
0,
),
)
</script>
<template>
<section class="api-shell">
<div class="api-toolbar">
<label class="api-search">
<span>Find API</span>
<input v-model="query" type="search" placeholder="Search logger, sink, config, async...">
</label>
<label class="api-filter">
<span>Doc Group</span>
<select v-model="selectedGroup">
<option value="all">All groups</option>
<option v-for="group in groups" :key="group.id" :value="group.id">
{{ group.label }}
</option>
</select>
</label>
</div>
<div class="api-summary">
<strong>{{ resultCount }}</strong>
<span>matching API pages across {{ filteredGroups.length }} group views</span>
</div>
<div class="api-groups">
<article v-for="group in filteredGroups" :key="group.id" class="api-group-card">
<header class="api-group-header">
<div>
<p class="api-group-kicker">{{ group.label }}</p>
<h3>{{ group.entryCount }} APIs in this group</h3>
</div>
</header>
<div class="api-category-grid">
<section v-for="category in group.categories" :key="category.id" class="api-category-card">
<h4>{{ category.label }}</h4>
<p>{{ category.entries.length }} entries</p>
<ul>
<li v-for="entry in category.entries.slice(0, 8)" :key="entry.slug">
<a :href="withBase(entry.link)">{{ entry.title }}</a>
</li>
</ul>
<a v-if="category.entries.length > 8" class="api-more" :href="withBase('/api/')">
Browse {{ category.entries.length - 8 }} more in sidebar
</a>
</section>
</div>
</article>
</div>
</section>
</template>
<style scoped>
.api-shell {
margin: 1.5rem 0 2rem;
}
.api-toolbar {
display: grid;
gap: 0.9rem;
margin-bottom: 1rem;
}
.api-search,
.api-filter {
display: grid;
gap: 0.4rem;
}
.api-search span,
.api-filter span {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #9b4d28;
font-weight: 700;
}
.api-search input,
.api-filter select {
width: 100%;
border-radius: 14px;
border: 1px solid var(--vp-c-divider);
background: rgba(255, 255, 255, 0.9);
padding: 0.85rem 0.95rem;
font: inherit;
}
.api-summary {
display: flex;
align-items: baseline;
gap: 0.65rem;
margin-bottom: 1rem;
}
.api-summary strong {
font-size: 1.8rem;
}
.api-summary span {
color: var(--vp-c-text-2);
}
.api-groups {
display: grid;
gap: 1rem;
}
.api-group-card {
border: 1px solid var(--vp-c-divider);
border-radius: 28px;
padding: 1rem;
background: linear-gradient(180deg, rgba(255, 251, 247, 0.96), rgba(246, 242, 236, 0.96));
}
.api-group-header h3,
.api-category-card h4,
.api-category-card p {
margin: 0;
}
.api-group-kicker {
margin: 0 0 0.25rem;
color: #9b4d28;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.75rem;
font-weight: 700;
}
.api-category-grid {
display: grid;
gap: 0.85rem;
margin-top: 0.9rem;
}
.api-category-card {
border-radius: 20px;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(155, 77, 40, 0.12);
padding: 0.95rem;
}
.api-category-card p {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.api-category-card ul {
list-style: none;
padding: 0;
margin: 0.85rem 0 0;
display: grid;
gap: 0.45rem;
}
.api-category-card a {
text-decoration: none;
}
.api-more {
display: inline-block;
margin-top: 0.8rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
@media (min-width: 820px) {
.api-toolbar {
grid-template-columns: minmax(0, 2fr) minmax(220px, 0.8fr);
}
.api-category-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
@@ -0,0 +1,191 @@
<script setup lang="ts">
import { computed } from 'vue'
import { withBase } from 'vitepress'
import { docsData } from '../../generated/docs-data.mjs'
const topCategories = computed(() => docsData.api.categories.slice(0, 6))
const recentChanges = computed(() => docsData.changes.slice(0, 4))
const apiCount = computed(() => docsData.api.entries.filter(entry => entry.slug !== 'index').length)
const groupCount = computed(() => docsData.api.groups.length)
</script>
<template>
<section class="hub-grid">
<article class="hub-panel hub-panel-accent">
<p class="hub-eyebrow">Docs Hub</p>
<h2 class="hub-title">Start from use case, not from filenames.</h2>
<p class="hub-copy">
Browse builders, runtime helpers, presets, sync logging, and async logging from one place.
</p>
<div class="hub-stats">
<div>
<strong>{{ apiCount }}</strong>
<span>API pages</span>
</div>
<div>
<strong>{{ groupCount }}</strong>
<span>doc groups</span>
</div>
<div>
<strong>{{ docsData.changes.length }}</strong>
<span>release notes</span>
</div>
</div>
</article>
<article class="hub-panel">
<p class="hub-eyebrow">Popular Areas</p>
<ul class="hub-chip-list">
<li v-for="category in topCategories" :key="category.id">
<a class="hub-chip" :href="withBase('/api/')">
<span>{{ category.label }}</span>
<small>{{ category.entryCount }}</small>
</a>
</li>
</ul>
</article>
<article class="hub-panel">
<p class="hub-eyebrow">Release Trail</p>
<ul class="hub-link-list">
<li v-for="item in recentChanges" :key="item.version">
<a :href="withBase(item.link)">Version {{ item.version }}</a>
</li>
</ul>
</article>
</section>
</template>
<style scoped>
.hub-grid {
display: grid;
gap: 1rem;
margin: 1.5rem 0 2rem;
}
.hub-panel {
border: 1px solid var(--vp-c-divider);
border-radius: 24px;
padding: 1.25rem;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(247, 245, 239, 0.95));
box-shadow: 0 16px 40px rgba(62, 46, 31, 0.08);
}
.hub-panel-accent {
background:
radial-gradient(circle at top right, rgba(191, 68, 32, 0.16), transparent 34%),
linear-gradient(180deg, rgba(255, 250, 244, 0.98), rgba(247, 241, 232, 0.98));
}
.hub-eyebrow {
margin: 0 0 0.5rem;
color: #9b4d28;
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.75rem;
font-weight: 700;
}
.hub-title {
margin: 0;
font-size: 1.55rem;
line-height: 1.15;
}
.hub-copy {
margin: 0.85rem 0 0;
color: var(--vp-c-text-2);
}
.hub-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
margin-top: 1.15rem;
}
.hub-stats strong,
.hub-stats span {
display: block;
}
.hub-stats strong {
font-size: 1.6rem;
}
.hub-stats span {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.hub-chip-list,
.hub-link-list {
list-style: none;
padding: 0;
margin: 0;
}
.hub-chip-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.7rem;
}
.hub-chip {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 56px;
gap: 0.8rem;
padding: 0.7rem 0.9rem;
border-radius: 999px;
text-decoration: none;
color: inherit;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(155, 77, 40, 0.14);
box-sizing: border-box;
}
.hub-chip span {
min-width: 0;
}
.hub-chip small {
flex: 0 0 auto;
color: var(--vp-c-text-2);
}
@media (max-width: 640px) {
.hub-chip-list {
grid-template-columns: 1fr;
}
}
.hub-link-list {
display: grid;
gap: 0.5rem;
}
.hub-link-list a {
color: var(--vp-c-brand-1);
text-decoration: none;
}
@media (min-width: 860px) {
.hub-grid {
grid-template-columns: 1.35fr 1fr;
}
.hub-panel-accent {
grid-row: span 2;
}
}
@media (max-width: 640px) {
.hub-stats {
grid-template-columns: 1fr;
}
}
</style>
+37
View File
@@ -0,0 +1,37 @@
:root {
--vp-c-brand-1: #b34a24;
--vp-c-brand-2: #92381a;
--vp-c-brand-3: #d06d39;
--vp-c-brand-soft: rgba(176, 74, 36, 0.14);
--vp-home-hero-name-color: #6b2f17;
--vp-home-hero-text-color: #27170f;
--vp-home-hero-tagline-color: #5f5249;
--vp-font-family-base: "Segoe UI", "PingFang SC", "Hiragino Sans GB", sans-serif;
--vp-font-family-mono: "Cascadia Mono", "JetBrains Mono", monospace;
}
.VPContent {
background:
radial-gradient(circle at top right, rgba(208, 109, 57, 0.08), transparent 28%),
radial-gradient(circle at left 20%, rgba(179, 74, 36, 0.06), transparent 22%);
}
.VPHomeHero .name,
.VPDoc h1,
.VPDoc h2,
.VPDoc h3 {
letter-spacing: -0.02em;
}
.VPHomeHero .image-container {
display: none;
}
.vp-doc a {
text-underline-offset: 0.18em;
}
.vp-doc .custom-block,
.vp-doc div[class*='language-'] {
border-radius: 18px;
}
+17
View File
@@ -0,0 +1,17 @@
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import ApiOverview from './components/ApiOverview.vue'
import HomeDocHub from './components/HomeDocHub.vue'
import './custom.css'
const theme: Theme = {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('ApiOverview', ApiOverview)
app.component('HomeDocHub', HomeDocHub)
},
}
export default theme
+41 -318
View File
@@ -1,342 +1,65 @@
# BitLogger
BitLogger is a structured logging library written in MoonBit.
BitLogger is a structured logging library for MoonBit projects.
- [Mooncake package page](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md)
## Overview
BitLogger currently provides:
- log levels: `Trace`, `Debug`, `Info`, `Warn`, `Error`
- structured key-value fields
- plain console output
- JSON console output
- child target composition via `child(...)`
- context fields via `with_context_fields(...)`
- optional timestamps via `with_timestamp()`
- sink fanout via `fanout_sink(...)`
- sink routing via `split_sink(...)` and `split_by_level(...)`
- custom integration via `callback_sink(...)`
- in-memory buffering via `buffered_sink(...)`
- record filtering via `filter_sink(...)`
- reusable filter helpers such as `target_has_prefix(...)`, `message_contains(...)`, `level_at_least(...)`, and `field_equals(...)`
- record patching via `with_patch(...)` and `patch_sink(...)`
- patch helpers such as `prefix_message(...)`, `append_fields(...)`, and `redact_fields(...)`
- context binding via `bind(...)` and `fields(...)`
- explicit queued delivery via `queued_sink(...)` and `with_queue(...)`
- bounded backlog with `QueueOverflowPolicy::DropNewest` and `QueueOverflowPolicy::DropOldest`
- configurable text formatting via `text_formatter(...)`, `format_text(...)`, `text_console_sink(...)`, and template-driven `template` output
- lightweight style tags via `color_mode`, inline markup, `TextStyle`, `StyleTagRegistry`, custom tags, and builtin-tag overrides
- formatter-based callback integration via `formatted_callback_sink(...)`
- native-only file output via `file_sink(...)`, with basic size rotation, backup retention, explicit `reopen()` / `reopen_with_current_policy()` / `reopen_append()` / `reopen_truncate()`, and failure counters
- `native_files_supported()` for backend capability detection
- default global logger helpers
BitLogger gives you consistent levels, targets, structured fields, configurable text output, native file logging, and an async logging layer.
## Quick Start
```moonbit
let logger = Logger::new(console_sink(), min_level=Level::Info, target="demo")
.with_timestamp()
.with_context_fields([field("service", "bitlogger")])
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")])
```
Child target composition:
```moonbit
let worker = Logger::new(console_sink(), target="app").child("worker")
worker.info("job ready")
```
Custom callback sink:
```moonbit
let hook = Logger::new(
callback_sink(fn(rec) {
println("callback saw [\{rec.target}] \{rec.message}")
}),
target="hook",
)
hook.info("hello")
```
Basic buffered sink:
```moonbit
let sink = buffered_sink(console_sink(), flush_limit=2)
let logger = Logger::new(sink, target="buffered")
logger.info("one")
logger.info("two")
sink.flush()
```
Basic filter sink:
```moonbit
let sink = filter_sink(console_sink(), fn(rec) {
rec.target == "kept"
})
let kept = Logger::new(sink, target="kept")
let dropped = Logger::new(sink, target="dropped")
kept.info("visible")
dropped.info("hidden")
```
Chained logger filter:
```moonbit
let logger = Logger::new(console_sink(), target="service")
.with_filter(all_of([
target_has_prefix("service"),
message_contains("visible"),
]))
logger.info("hidden")
logger.child("api").info("visible")
```
Record patching:
```moonbit
let logger = Logger::new(console_sink(), target="auth")
.with_patch(compose_patches([
prefix_message("[safe] "),
redact_fields(["token"]),
append_fields([field("service", "bitlogger")]),
]))
logger.info("login", fields=[field("user", "alice"), field("token", "secret")])
```
Context binding:
```moonbit
let logger = Logger::new(console_sink(), target="audit")
.bind(fields([("service", "bitlogger"), ("scope", "login")]))
logger.info("accepted", fields=[field("user", "alice")])
```
Explicit queued sink:
```moonbit
let logger = Logger::new(console_sink(), target="queue")
.with_queue(max_pending=2, overflow=QueueOverflowPolicy::DropOldest)
logger.info("one")
logger.info("two")
logger.info("three")
ignore(logger.sink.flush())
```
Level-based split sink:
```moonbit
let logger = Logger::new(
split_by_level(
callback_sink(fn(rec) {
println("high priority: \{rec.level.label()} \{rec.message}")
}),
console_sink(),
min_level=Level::Warn,
),
min_level=Level::Trace,
target="split",
)
logger.info("normal output")
logger.warn("warning output")
```
Custom text formatter:
```moonbit
let formatter = text_formatter(
show_timestamp=false,
field_separator=",",
template="[{level}] {target} {message} :: {fields}",
color_mode=ColorMode::Always,
)
let logger = Logger::new(text_console_sink(formatter), target="pretty")
logger.info("hello", fields=[field("mode", "pretty")])
```
Inline style tags:
```moonbit
let formatter = text_formatter(
show_timestamp=false,
color_mode=ColorMode::Always,
).with_style_tags(
default_style_tag_registry()
.set_tag("accent", fg=Some("#4cc9f0"), bold=true)
.define_alias("danger", "red"),
)
let logger = Logger::new(text_console_sink(formatter), target="styled")
logger.info("<accent>styled</> output and <danger>alert</>")
```
Disable style markup parsing:
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
).without_style_markup()
let logger = Logger::new(text_console_sink(formatter), target="raw")
logger.info("<red>kept as raw text</>")
```
JSON config loading:
```moonbit
let config = parse_logger_config_text(
"{\"min_level\":\"debug\",\"target\":\"config.demo\",\"timestamp\":true,\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"field_separator\":\",\",\"template\":\"[{level}] {target} {message} :: {fields}\",\"color_mode\":\"always\"}},\"queue\":{\"max_pending\":2,\"overflow\":\"DropOldest\"}}",
)
let logger = build_logger(config)
logger.info("configured from json")
ignore(logger.flush())
```
JSON `style_tags`:
Start with `console(...)`, `json_console(...)`, `text_console(...)`, or `file(...)`, then add `with_queue(...)` or `with_file_rotation(...)` only when needed.
```moonbit
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"show_timestamp\":false,\"color_mode\":\"always\",\"style_tags\":{\"accent\":{\"fg\":\"#4cc9f0\",\"bold\":true}}}}}",
)
Use `Logger::new(...)` when you want to assemble custom sink graphs directly.
let logger = build_logger(config)
## Support Status
logger.info("<accent>styled from json</>")
```
- 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
- `src-async` is available, while `examples/async_basic` is still shipped as a native entry example
JSON `style_markup` mode:
## Main Features
```moonbit
let config = parse_logger_config_text(
"{\"sink\":{\"kind\":\"text_console\",\"text_formatter\":{\"color_mode\":\"always\",\"style_markup\":\"disabled\"}}}",
)
- Structured logs with levels, targets, messages, and fields
- Multiple outputs: console, JSON console, text console, and file
- Custom text formatting with templates, style tags, and color control
- Config-based builders: `build_logger(...)` and `build_async_logger(...)`
- Composition helpers: queue, filter, patch, fanout, split, callback
- Separate async package under `src-async`
let logger = build_logger(config)
## Examples
logger.info("<red>still raw</>")
```
- `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
Native file sink:
## Documentation
```moonbit
if native_files_supported() {
let logger = Logger::new(
file_sink("bitlogger.log", rotation=Some(file_rotation(128, max_backups=2))),
target="file",
)
logger.info("hello", fields=[field("kind", "file")])
ignore(logger.sink.flush())
ignore(logger.sink.close())
}
```
- [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
- `docs/dev/`: developer reference material kept in the repository, intentionally excluded from the public static docs site
File runtime state dump:
```moonbit
let logger = build_logger(
LoggerConfig::new(
sink=SinkConfig::new(kind=SinkKind::File, path="bitlogger-runtime.log"),
queue=Some(QueueConfig::new(16)),
),
)
logger.info("queued hello")
match logger.file_runtime_state() {
Some(snapshot) => println(stringify_runtime_file_state(snapshot, pretty=true))
None => ()
}
```
## Repository Layout
- `bitlogger/`: MoonBit library package, tests, and Mooncake package README
- `examples/basic/`: runnable example package
- `examples/async_basic/`: runnable async logger example built on `moonbitlang/async`
## Links
- [Mooncake package page](https://mooncakes.io/docs/Nanaloveyuki/BitLogger)
- [Chinese README](../README.md)
## Config Notes
- BitLogger now includes a JSON config layer via `parse_logger_config_text(...)`, `stringify_logger_config(...)`, and `build_logger(...)`.
- `QueueConfig`, `TextFormatterConfig`, and `SinkConfig` can also be exported independently through `queue_config_to_json(...)` / `stringify_queue_config(...)`, `text_formatter_config_to_json(...)` / `stringify_text_formatter_config(...)`, and `sink_config_to_json(...)` / `stringify_sink_config(...)`.
- Supported keys include `min_level`, `target`, `timestamp`, `sink.kind`, `sink.path`, `sink.append`, `sink.auto_flush`, `sink.rotation`, `sink.text_formatter`, and `queue`.
- `TextFormatter` and `TextFormatterConfig` now include `color_mode = Never | Auto | Always` for ANSI text coloring control.
- `TextFormatter` and `TextFormatterConfig` also include `color_support = basic | truecolor` so hex / RGB styling can be forced to downgrade to basic ANSI colors.
- `TextFormatter` and `TextFormatterConfig` also include `style_markup = disabled | builtin | full` so callers can choose whether style markup is parsed and whether custom tags are active.
- `target_style_markup` and `fields_style_markup` independently control whether `target` and `fields` are parsed for style markup.
- `message` also supports lightweight inline style tags such as `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, and `<bg:#202020>...</>`.
- Closing tags now support both the short form `</>` and named closing tags such as `</red>`, `</danger>`, and `</b>`.
- Builtin semantic tags now include `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, and `<muted>`.
- Runtime style-tag APIs now include `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, and `define_alias(...)`.
- Style-tag lookup priority is formatter-local `style_tags` > global style tag registry > builtin tags.
- `sink.text_formatter.style_tags` now supports a minimal object mapping with `fg`, `bg`, `bold`, `dim`, `italic`, and `underline`.
- `define_alias(...)` remains a runtime-only API and is not yet part of the JSON config schema.
- `sink.rotation` currently supports `max_bytes` and `max_backups` for basic size-based rotation and backup retention.
- `file_sink(...)` also exposes `reopen()`, `reopen_with_current_policy()`, `reopen_append()`, `reopen_truncate()`, `open_failures()`, `write_failures()`, `flush_failures()`, and `rotation_failures()` for basic observability.
- `file_sink(...)` also exposes `append_mode()`. Passing `append=...` to `reopen(...)` updates the current append policy used by later reopen calls, `reopen_with_current_policy()` makes that stored-policy reopen path explicit, and `reopen_append()` / `reopen_truncate()` cover the two common policy switches directly.
- `file_sink(...)` also supports `set_append_mode(...)` for explicitly changing the append policy that later reopen calls will use.
- `file_sink(...)` also exposes `path()` and `auto_flush_enabled()` for reading basic file-sink policy state.
- `file_sink(...)` also exposes `rotation_enabled()` and `rotation_config()` for reading whether rotation is active and which parameters are currently in effect.
- `file_sink(...)` also exposes `state()` for reading a single snapshot that includes path, availability, append policy, auto-flush flag, rotation config, and all current failure counters.
- `file_sink(...)` also exposes `policy()` and `default_policy()` for reading the current runtime policy and the sink's original default policy separately.
- `file_sink(...)` also exposes `policy_matches_default()` for explicitly checking whether the current runtime policy has drifted from the original defaults.
- `file_sink(...)` also exposes `set_policy(...)` for applying append, auto-flush, and rotation as a single bundled runtime policy update.
- `file_sink(...)` also exposes `reset_failure_counters()` so open/write/flush/rotation failure counters can be cleared after diagnostics or recovery handling.
- `file_sink(...)` also exposes `reset_policy()` so append, auto-flush, and rotation settings can be restored to the sink's original defaults.
- `file_sink(...)` also supports `set_auto_flush(...)`, `set_rotation(...)`, and `clear_rotation()` for runtime policy updates.
- `ConfiguredLogger` built through `build_logger(...)` also exposes `file_reopen()`, `file_reopen_with_current_policy()`, `file_reopen_append()`, `file_reopen_truncate()`, `file_flush()`, `file_close()`, `file_append_mode()`, `file_path()`, `file_auto_flush()`, `file_rotation_enabled()`, `file_rotation_config()`, `file_state()`, plus `file_set_append_mode(...)`, `file_set_auto_flush(...)`, `file_set_rotation(...)`, `file_clear_rotation()`, and the corresponding file failure counters, so config-driven file logging keeps a usable control surface.
- `ConfiguredLogger` also exposes `file_runtime_state()` so queued file loggers can report both the underlying file snapshot and the outer queue backlog/drop state in one read.
- `ConfiguredLogger` also exposes `file_policy()` and `file_default_policy()` for reading current runtime file policy and initial config policy separately.
- `ConfiguredLogger` also exposes `file_policy_matches_default()` for explicitly checking whether the current runtime file policy differs from its default config.
- `ConfiguredLogger` also exposes `file_set_policy(...)` for applying a bundled runtime file policy through the config-built control surface.
- `ConfiguredLogger` also exposes `file_reset_failure_counters()` for clearing file failure counters through the config-built control surface.
- `ConfiguredLogger` also exposes `file_reset_policy()` for restoring runtime file policy back to the initial config values.
- `file_sink_policy_to_json(...)` and `stringify_file_sink_policy(...)` can export standalone file-policy snapshots directly as JSON for policy diffing, diagnostics, or reporting.
- `file_sink_state_to_json(...)`, `stringify_file_sink_state(...)`, `runtime_file_state_to_json(...)`, and `stringify_runtime_file_state(...)` can export file and queued-file snapshots directly as JSON for diagnostics or reporting.
- `sink.text_formatter.template` currently supports fixed tokens: `{timestamp}`, `{timestamp_ms}`, `{level}`, `{target}`, `{message}`, and `{fields}`.
- `sink.text_formatter.color_mode` currently supports `never`, `auto`, and `always`.
- `sink.text_formatter.color_support` currently supports `basic` and `truecolor`.
- `sink.text_formatter.style_markup` currently supports `disabled`, `builtin`, and `full`.
- `sink.text_formatter.target_style_markup` and `sink.text_formatter.fields_style_markup` currently support `disabled`, `builtin`, and `full`.
- `sink.text_formatter.style_tags.<name>` currently supports `fg`, `bg`, `bold`, `dim`, `italic`, and `underline`.
- `fields_style_markup` currently applies to field values only, not field keys.
- Config-driven sink assembly currently supports `console`, `json_console`, `text_console`, and `file`.
- `queue` remains a synchronous bounded wrapper around the final sink, not an async runtime.
## Async Layer
- A separate `bitlogger_async/` package is now included.
- It uses `moonbitlang/async` and provides `AsyncLogger`, `async_logger(...)`, a background `run()` worker, and bounded async queue delivery.
- The current async API already supports `with_context_fields(...)`, `with_filter(...)`, `with_patch(...)`, `with_target(...)`, and `child(...)`.
- `shutdown()` is now the recommended way to stop the async worker. By default it waits for the queue to drain, closes the queue, and then waits for the worker to exit.
- Basic lifecycle observability is also available through `is_closed()`, `is_running()`, `has_failed()`, and `last_error()`.
- The async worker now supports batched queue draining via `max_batch` and basic flush policies through `flush=Never|Batch|Shutdown`.
- The recommended startup pattern is shown in [examples/async_basic/main.mbt](/E:/repo/MooLiteyukiBot/examples/async_basic/main.mbt:1).
- This layer currently targets `native/llvm` only and remains isolated from the synchronous logger core.
### Async Config
- `parse_async_logger_config_text(...)`, `stringify_async_logger_config(...)`, `parse_async_logger_build_config_text(...)`, and `build_async_logger(...)` are now available.
- The JSON root is split into `logger` and `async_config`.
- `logger` fully reuses the synchronous `LoggerConfig` schema, while `async_config` currently supports `max_pending`, `overflow`, `max_batch`, and `flush`.
- The recommended config-driven startup flow is shown in [examples/async_basic/main.mbt](/E:/repo/MooLiteyukiBot/examples/async_basic/main.mbt:1).
Common entry points: `text_console(...)`, `file(...)`, `with_queue(...)`, `build_logger(...)`, `build_async_logger(...)`
+146
View File
@@ -0,0 +1,146 @@
---
name: example-api
group: dev
category: example-group
update-time: 20260512
description: An example API file to show how to write API doc.
key-word:
- example
- async
- sync
- public
- doc
---
**ONE API ONE FILE**
## Example-api-name
long discription.
### Interface
```moonbit
pub fn function_name(input) -> output {}
```
#### input
- `args : type` - Explain
#### output
- `output : type` - Explain
---
> `---` Just when interface has double or more write. Used to separate two different APIs.
<!--
e.g.:
```moonbit
pub fn target_is(target : String) -> RecordPredicate {}
```
#### input
- `target : String` - the expected target value
#### output
- `RecordPredicate` - a predicate used for filtering records
---
-->
> Use<! --The content packaged with -->does not actually need to be written in the official document, it is only used as an example for reference.
It is not necessary to write the complete function implementation.
### Explanation
Detailed rules explaining key parameters and behaviors
- ...
### How to Use
Here are some specific examples provided.
e.g.:
#### <What-Time-To-Use>
> title like: `#### When Need Colorful Formatter`
When sometime ...:
```moonbit
impl
```
In this example, <something> will <do-what>.
And <extra-info>.
#### <Next-Use-Method>
...
### Error Case
e.g.:
- If `target` is empty, returns a predicate that always evaluates to false.
- ...
...
### Notes
1. ...
2. ...
...
---
## API MARKDOWN YAML HEADER
> This just is an example, `---` in fact has yaml grammer error.
```yaml
---
name: example-api
group: dev
category: example-group
update-time: 20260512
description: An example API file to show how to write API doc.
key-word:
- example
- async
- sync
- public
- doc
---
```
It has 6 key:
- `name` - short and clear api name
- `group` - in static doc template site will use this key to render how to fold and group
- `category` - fastly search category in repo and will be used in template site
- `update-time` - full number use YYYYMMdd(year, month, day)
- `discription` - short discription
- `key-word` - use 2~5 key-words to help user fastly search
## Title Capitalization Standards
NO `# ...`
- `## ...` use `Abcd`
- `### ...` use `Abcd`
- `#### ...` use `abcd`
NO `##### ...`
+82
View File
@@ -0,0 +1,82 @@
---
name: all-of
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that requires every nested predicate to pass.
key-word:
- combine
- filter
- predicate
- public
---
## All-of
Create a `RecordPredicate` that returns `true` only when every predicate in the array returns `true`. This helper is the standard way to build strict multi-condition filters.
### Interface
```moonbit
pub fn all_of(predicates : Array[RecordPredicate]) -> RecordPredicate {}
```
#### input
- `predicates : Array[RecordPredicate]` - Predicates that must all succeed for a record to match.
#### output
- `RecordPredicate` - Predicate that returns `true` only when every nested predicate returns `true`.
### Explanation
Detailed rules explaining key parameters and behaviors
- Predicates are evaluated in array order.
- Evaluation stops early on the first predicate that returns `false`.
- If the array is empty, the combined predicate returns `true` because no condition failed.
- This helper is ideal for combining namespace, level, and field requirements into one reusable rule.
### How to Use
Here are some specific examples provided.
#### When Require Several Conditions
When routing should be both target- and level-aware:
```moonbit
let predicate = all_of([
target_has_prefix("service.api"),
level_at_least(Level::Warn),
])
```
In this example, records must satisfy both conditions before they pass.
#### When Add Field Constraints
When only contextual failures should remain:
```moonbit
let predicate = all_of([
message_contains("failed"),
has_field("request_id"),
not_(field_equals("tenant", "internal")),
])
```
In this example, the filter stays readable even though the rule has several parts.
### Error Case
e.g.:
- If `predicates` is empty, the returned predicate always evaluates to `true`.
- If one nested predicate is too strict, the whole combination may reject more records than expected.
### Notes
1. Put the cheapest or most selective predicates earlier when evaluation cost matters.
2. `all_of(...)` is usually easier to maintain than a custom inline predicate closure.
+82
View File
@@ -0,0 +1,82 @@
---
name: any-of
group: api
category: filtering
update-time: 20260512
description: Create a reusable record predicate that passes when any nested predicate matches.
key-word:
- combine
- filter
- predicate
- public
---
## Any-of
Create a `RecordPredicate` that returns `true` when at least one predicate in the array returns `true`. This helper is useful for routing several independent cases through the same path.
### Interface
```moonbit
pub fn any_of(predicates : Array[RecordPredicate]) -> RecordPredicate {}
```
#### input
- `predicates : Array[RecordPredicate]` - Predicates where any successful match should admit the record.
#### output
- `RecordPredicate` - Predicate that returns `true` when at least one nested predicate returns `true`.
### Explanation
Detailed rules explaining key parameters and behaviors
- Predicates are evaluated in array order.
- Evaluation stops early on the first predicate that returns `true`.
- If the array is empty, the combined predicate returns `false` because no predicate matched.
- This helper is useful when several targets, levels, or field signatures should share one sink.
### How to Use
Here are some specific examples provided.
#### When Accept Several Target Paths
When multiple subsystems should share one route:
```moonbit
let predicate = any_of([
target_is("audit"),
target_has_prefix("security"),
])
```
In this example, either matching branch is enough for the record to pass.
#### When Combine Different Diagnostic Conditions
When several independent signals are interesting:
```moonbit
let predicate = any_of([
level_at_least(Level::Error),
message_contains("timeout"),
field_equals("retryable", "true"),
])
```
In this example, one satisfied condition is enough to keep the record visible.
### Error Case
e.g.:
- If `predicates` is empty, the returned predicate always evaluates to `false`.
- If one nested predicate is too broad, it may shadow the intent of the other branches.
### Notes
1. Put the most common or cheapest success path earlier when evaluation cost matters.
2. Use `any_of(...)` when a single sink should accept multiple independent match patterns.
+82
View File
@@ -0,0 +1,82 @@
---
name: append-fields
group: api
category: patching
update-time: 20260512
description: Create a reusable record patch that appends extra fields to the record.
key-word:
- patch
- fields
- transform
- public
---
## Append-fields
Create a `RecordPatch` that appends extra fields to `rec.fields`. Use it when records should be enriched with stable metadata before reaching sinks.
### Interface
```moonbit
pub fn append_fields(extra_fields : Array[Field]) -> RecordPatch {}
```
#### input
- `extra_fields : Array[Field]` - Fields appended after the record's existing field list.
#### output
- `RecordPatch` - Patch that returns a record with appended fields.
### Explanation
Detailed rules explaining key parameters and behaviors
- If `extra_fields` is empty, the patch returns the original record unchanged.
- If the original record has no fields, the appended fields become the new field list.
- Otherwise, the original fields stay first and `extra_fields` are appended afterward.
- This helper is useful for environment tags, service metadata, and bridge-layer context.
### How to Use
Here are some specific examples provided.
#### When Add Service Metadata
When every record should carry shared context:
```moonbit
let logger = Logger::new(console_sink())
.with_patch(append_fields([
field("service", "billing"),
field("region", "cn"),
]))
```
In this example, the extra fields are added to every emitted record.
#### When Compose With Message Rewriting
When both visible and structured context are needed:
```moonbit
let patch = compose_patches([
prefix_message("[api] "),
append_fields([field("component", "gateway")]),
])
```
In this example, the record gains both textual and structured enrichment.
### Error Case
e.g.:
- If `extra_fields` is empty, the patch behaves like a no-op.
- If appended field keys duplicate existing keys, both copies remain in the field list.
### Notes
1. This helper appends fields; it does not deduplicate or overwrite existing entries.
2. Field order can matter for downstream formatting or inspection, so keep appended context intentional.
+127
View File
@@ -0,0 +1,127 @@
---
name: application-async-logger
group: api
category: facade
update-time: 20260614
description: Application-facing alias for the runtime-sink async logger surface, preserving the same async calling semantics as AsyncLogger.
key-word:
- application
- async
- alias
- public
---
## Application-async-logger
`ApplicationAsyncLogger` is the application-facing async logger alias. It currently maps directly to `AsyncLogger[@bitlogger.RuntimeSink]` and keeps the same async lifecycle, state, and queue helper surface.
### Interface
```moonbit
pub type ApplicationAsyncLogger = AsyncLogger[@bitlogger.RuntimeSink]
```
#### output
- `ApplicationAsyncLogger` - Application-facing name for the runtime-sink async logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same async lifecycle helpers such as `run()`, `shutdown()`, `pending_count()`, and `state()`.
- Because it is `AsyncLogger[@bitlogger.RuntimeSink]`, the alias also keeps ordinary async logger composition and target behavior such as `with_target(...)`, `child(...)`, and per-call `log(..., target=...)` overrides.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Unlike the synchronous application alias, async `with_context_fields(...)` and `bind(...)` preserve the visible `ApplicationAsyncLogger` shape because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
- Because this is only an alias, methods that are async on `AsyncLogger[@bitlogger.RuntimeSink]` remain async here as well.
- The alias therefore keeps the same runtime-sink lifecycle, queue, failure-state, and runtime-dependent post-close semantics already documented on `AsyncLogger[@bitlogger.RuntimeSink]`.
- In the current direct alias coverage, values built through `build_application_async_logger(...)` keep the same serialized state snapshot shape, queue counters, lifecycle flags, failure fields, and runtime-sink helper surface that the underlying runtime-sink async logger exposes directly.
- When the value is built through `build_application_async_logger(...)`, the sync-first builder route also stays visible through the alias: any optional `LoggerConfig.queue` was already applied before async wrapping, and `logger.sink.kind` had already selected the concrete `RuntimeSink` variant before the application alias reused that value.
- That includes queued runtime-sink behavior and file-backed runtime helpers when the configured sink path supports them.
- Those helpers remain directly callable on `ApplicationAsyncLogger` itself; callers do not need an unwrap step to reach queue counters, lifecycle state, or `RuntimeSink` file helpers.
- The alias exists to give application boot code a clearer public type name for the standard runtime-sink async logger.
- Builders such as `build_application_async_logger(...)` and `parse_and_build_application_async_logger(...)` return this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For The Standard Async Runtime Logger
When application code wants a stable public type name for the runtime-sink async logger:
```moonbit
let logger : ApplicationAsyncLogger = build_application_async_logger(
AsyncLoggerBuildConfig::new(logger=LoggerConfig::new(target="app.async")),
)
```
In this example, the application alias keeps the same underlying async logger behavior while presenting an app-facing type name.
#### When Pass The Async Logger Through App-level APIs
When top-level boot code or services should expose an application-oriented async logger type:
```moonbit
async fn start_async(logger : ApplicationAsyncLogger) -> Unit {
logger.run()
}
```
In this example, callers see the app-facing alias instead of the more explicit generic async logger spelling, while `run()` keeps its async calling contract.
And the inherited async logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Direct Async Runtime Helpers On The Application Alias
When app code should inspect queue or file-backed runtime state without leaving the alias surface:
```moonbit
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, lifecycle and queue helpers are called directly on `ApplicationAsyncLogger`.
And unlike `LibraryAsyncLogger[@bitlogger.RuntimeSink]`, no `to_async_logger()` unwrap is required first.
#### When Need A One-call Target Override Without Rebuilding The Alias
When app-level async code should keep the same alias value but emit one record under a different target:
```moonbit
logger.log(@bitlogger.Level::Error, "boom", target="app.async.audit")
```
In this example, the emitted record uses `app.async.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Async Application Alias
When app-level async code should attach stable metadata to later queued records:
```moonbit
let contextual = logger.with_context_fields([@bitlogger.field("service", "billing")])
```
In this example, the returned value still has the visible type `ApplicationAsyncLogger`.
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
### Error Case
e.g.:
- Because this is only an alias, any runtime-sink limitations or target-specific async behavior still apply unchanged.
- If the value came from `build_application_async_logger(...)` or `parse_and_build_application_async_logger(...)`, the alias also does not undo the underlying sync-first sink selection; queued and file-backed runtime behavior still depend on the already-built `RuntimeSink` variant.
- If code needs a narrower public surface than the full async logger API, `LibraryAsyncLogger` is the better facade.
- If callers need queued runtime-sink helpers or file-backed runtime helpers, they remain directly available on this alias because no wrapper layer strips them away.
### Notes
1. This alias is about naming and public intent, not a different async implementation.
2. Inherited `AsyncLogger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_async_logger(...)` or `parse_and_build_application_async_logger(...)` for the usual construction paths.
4. Use `ApplicationTextAsyncLogger` or `build_application_text_async_logger(...)` when application code should keep the narrower text-console sink type instead of the broader runtime-sink alias.
+119
View File
@@ -0,0 +1,119 @@
---
name: application-logger
group: api
category: facade
update-time: 20260613
description: Application-facing alias for the configured sync runtime logger surface, preserving the full ConfiguredLogger helper set.
key-word:
- application
- facade
- alias
- public
---
## Application-logger
`ApplicationLogger` is the application-facing sync logger alias. It currently maps directly to `ConfiguredLogger` and keeps the same runtime helper surface for sync logging, queue inspection, and file controls.
### Interface
```moonbit
pub type ApplicationLogger = ConfiguredLogger
```
#### output
- `ApplicationLogger` - Application-facing name for the configured sync runtime logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same logging, queue, and file helper APIs exposed by `ConfiguredLogger`.
- Because `ConfiguredLogger` is itself `Logger[RuntimeSink]`, the alias also keeps ordinary logger composition and write behavior such as `with_target(...)`, `child(...)`, and `log(..., target=...)`.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Like the underlying synchronous logger line, `with_context_fields(...)` and `bind(...)` do not preserve the alias spelling. They return a `Logger[ContextSink[RuntimeSink]]` shape because sync shared-field binding extends the sink pipeline instead of storing extra alias-level context metadata.
- Because this is only an alias, the application-facing type does not hide any configured-runtime helpers or broader logger surface.
- That means queue, drain, flush, and file runtime helpers remain directly callable on `ApplicationLogger` itself; callers do not need an unwrap step to reach the underlying configured runtime logger behavior.
- The alias exists to give application boot code a clearer public entry name.
- Builders such as `build_application_logger(...)` and `parse_and_build_application_logger(...)` return this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For The Configured Runtime Logger
When application code wants a stable public type name for the configured sync logger:
```moonbit
let logger : ApplicationLogger = build_application_logger(LoggerConfig::new(target="app"))
```
In this example, the application alias keeps the same underlying runtime logger behavior while presenting an app-facing type name.
#### When Pass The Configured Logger Through App-level APIs
When top-level boot code or services should expose an application-oriented logger type:
```moonbit
fn start(logger : ApplicationLogger) -> Unit {
logger.info("started")
}
```
In this example, callers see the app-facing alias instead of the lower-level `ConfiguredLogger` name.
And the same queue/file/runtime helpers remain directly callable because no narrowing wrapper is added.
#### When Need Direct Runtime Helpers On The Application Alias
When app code should inspect queue state or file controls without leaving the alias surface:
```moonbit
ignore(logger.pending_count())
ignore(logger.flush())
```
In this example, the runtime helpers are called directly on `ApplicationLogger`.
And unlike `LibraryLogger[RuntimeSink]`, no `to_logger()` unwrap is required first.
And the inherited logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need A One-call Target Override Without Rebuilding The Alias
When app-level sync code should keep the same alias value but emit one record under a different target:
```moonbit
logger.log(Level::Error, "boom", target="app.audit")
```
In this example, the emitted record uses `app.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Sync Application Alias
When app-level sync code should attach stable metadata to later records:
```moonbit
let contextual = logger.with_context_fields([field("service", "billing")])
```
In this example, the returned value has the visible type `Logger[ContextSink[RuntimeSink]]` rather than the alias name `ApplicationLogger`.
And that type-shape change is expected because sync context binding extends the sink pipeline.
### Error Case
e.g.:
- Because this is only an alias, any backend limitations of `ConfiguredLogger` still apply unchanged.
- If code needs a narrower public surface than the full configured runtime logger, `LibraryLogger` is the better facade.
### Notes
1. This alias is about naming and public intent, not a different runtime implementation.
2. Inherited `Logger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_logger(...)` or `parse_and_build_application_logger(...)` for the usual construction paths.
4. Use `LibraryLogger` instead when a library boundary should intentionally hide configured-runtime helper methods behind a narrower facade.
+120
View File
@@ -0,0 +1,120 @@
---
name: application-text-async-logger
group: api
category: facade
update-time: 20260614
description: Application-facing alias for the text-console async logger surface, preserving the full AsyncLogger helper set for the concrete text sink shape.
key-word:
- application
- async
- text
- public
---
## Application-text-async-logger
`ApplicationTextAsyncLogger` is the application-facing async logger alias for text-console output. It currently maps directly to `AsyncLogger[@bitlogger.FormattedConsoleSink]` and keeps the same async lifecycle and queue helper surface while preserving the concrete text sink shape.
### Interface
```moonbit
pub type ApplicationTextAsyncLogger = AsyncLogger[@bitlogger.FormattedConsoleSink]
```
#### output
- `ApplicationTextAsyncLogger` - Application-facing name for the text-console async logger shape.
### Explanation
Detailed rules explaining key parameters and behaviors
- This alias does not introduce a new runtime type or wrapper layer.
- It preserves the same async lifecycle helpers as other async logger aliases.
- Because it is `AsyncLogger[@bitlogger.FormattedConsoleSink]`, the alias also keeps ordinary async logger composition and target behavior such as `with_target(...)`, `child(...)`, and per-call `log(..., target=...)` overrides.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Like the broader runtime-sink async alias, `with_context_fields(...)` and `bind(...)` preserve the visible `ApplicationTextAsyncLogger` shape because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
- Because this is only an alias, methods that are async on `AsyncLogger[@bitlogger.FormattedConsoleSink]` remain async here as well.
- The alias therefore keeps the same text-console-specific builder and lifecycle semantics already documented on `AsyncLogger[@bitlogger.FormattedConsoleSink]`, including the concrete sink shape plus the same close, queue, and failure-state behavior.
- The application-facing type does not hide any async state or lifecycle helpers; queue/backlog/failure inspection remains directly available on this alias just as it is on the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
- In the current direct alias coverage, values built through `build_application_text_async_logger(...)` keep the same serialized state snapshot shape, formatter behavior, queue counters, lifecycle flags, and failure fields that the underlying text-console async logger exposes directly.
- When the value is built through `build_application_text_async_logger(...)`, the direct async counters come from the outer async logger only; any optional sync queue configured on `LoggerConfig.queue` is not carried into this text-specific build path.
- When the value is built through `build_application_text_async_logger(...)`, `logger.sink.kind` also does not decide the runtime sink shape. The builder still constructs `FormattedConsoleSink` from `logger.sink.text_formatter`, even if the config said `Console`, `JsonConsole`, or `File`.
- When the value is built through `build_application_text_async_logger(...)`, `flush_policy()` still reports the configured async policy, but the text-specific build path keeps the default no-op async flush callback instead of wiring an explicit sink flush step.
- The alias exists to give application code a clearer public name when it wants the concrete text-console sink shape explicitly.
- `build_application_text_async_logger(...)` returns this alias.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Name For Text-console Async Output
When application code wants a stable type name for a text-console async logger:
```moonbit
let logger : ApplicationTextAsyncLogger = build_application_text_async_logger(
AsyncLoggerBuildConfig::new(logger=text_console(target="app.text.async")),
)
```
In this example, the application alias keeps the same underlying async logger behavior while preserving the text sink shape explicitly.
#### When Pass A Text-console Async Logger Through App-level APIs
When caller code should know it is working with the text-console variant:
```moonbit
async fn start_text_async(logger : ApplicationTextAsyncLogger) -> Unit {
logger.run()
}
```
In this example, the app-facing alias communicates the concrete text-console async shape directly, while `run()` keeps its async calling contract.
And the same pending-count, state, and failure helpers remain directly available because no narrowing wrapper is added.
And the inherited async logger target rules stay the same: `log(..., target=...)` can override the target per call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need A One-call Target Override On The Text-console Alias
When app-level text-console async code should keep the same alias value but emit one record under a different target:
```moonbit
logger.log(@bitlogger.Level::Error, "boom", target="app.text.async.audit")
```
In this example, the emitted record uses `app.text.async.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the alias value's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On The Text-console Async Alias
When app-level text-console async code should attach stable metadata to later queued records:
```moonbit
let contextual = logger.with_context_fields([@bitlogger.field("service", "billing")])
```
In this example, the returned value still has the visible type `ApplicationTextAsyncLogger`.
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
### Error Case
e.g.:
- Because this is only an alias, any async target limitations or runtime behavior still apply unchanged.
- If code does not need the concrete text-console sink shape, `ApplicationAsyncLogger` is the broader runtime-sink async alias.
- If the value came from `build_application_text_async_logger(...)`, carrying `logger.sink.kind=File` in the config still does not make this alias file-backed; that builder keeps the formatter-driven text-console path.
- If callers depend on the concrete formatter or direct text-console helper surface, they remain available on this alias because no wrapper layer narrows them away.
### Notes
1. This alias is about naming and public intent, not a different async implementation.
2. Inherited `AsyncLogger` behavior stays unchanged on this alias, including target overrides on `log(...)` and derived target composition through `with_target(...)` and `child(...)`.
3. Use `build_application_text_async_logger(...)` when callers want the `FormattedConsoleSink`-backed async type explicitly.
4. Use `ApplicationAsyncLogger` when application code should keep the broader runtime-sink shape instead of the text-console-specific one.
5. Use `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]` instead when a library boundary should intentionally narrow the exposed async surface while still preserving the concrete text sink type.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-flush-policy
group: api
category: async
update-time: 20260614
description: Public flush policy alias used by AsyncLoggerConfig, async parser labels, and async worker flushing.
key-word:
- async
- flush
- alias
- public
---
## Async-flush-policy
`AsyncFlushPolicy` is the public enum that defines when an async logger should call its flush function. It is a direct alias to the async model enum used by `AsyncLoggerConfig`, worker execution, and async logger state reporting.
### Interface
```moonbit
pub type AsyncFlushPolicy = @utils.AsyncFlushPolicy
```
#### output
- `AsyncFlushPolicy` - Public async flush enum with the variants `Never`, `Batch`, and `Shutdown`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a separate lifecycle wrapper.
- `AsyncFlushPolicy::Never` skips explicit flush calls from the async worker.
- `AsyncFlushPolicy::Batch` calls the configured flush function after each processed batch.
- `AsyncFlushPolicy::Shutdown` calls the configured flush function once after the worker loop exits.
- The current flush policy is also exposed through `AsyncLogger::flush_policy()` and included in `AsyncLoggerState`.
- The canonical labels `Never`, `Batch`, and `Shutdown` are also the labels emitted by async config and logger-state serializers, so parsing and diagnostics share one public vocabulary.
- Async config parsing accepts the canonical label `Never` and also the compatibility alias `None`, both mapping to the same public enum variant.
- `Batch` flushing happens after the worker finishes one drained batch, not after every individual record write.
### How to Use
Here are some specific examples provided.
#### When Need Explicit Flush After Every Processed Batch
When buffered sinks should flush incrementally during worker execution:
```moonbit
let config = AsyncLoggerConfig::new(flush=AsyncFlushPolicy::Batch)
```
In this example, each batch run triggers the provided flush callback.
#### When Need One Final Flush During Shutdown
When sink flushing should be deferred until the worker finishes:
```moonbit
let config = AsyncLoggerConfig::new(flush=AsyncFlushPolicy::Shutdown)
```
In this example, flushing happens when the worker loop exits instead of after each batch.
### Error Case
e.g.:
- If async config text uses unsupported flush policy text, async config parsing raises a failure.
- The parser error path for unsupported flush text is the same `Failure` surface used by the async config utilities.
- If a sink never needs explicit flushing, `Batch` or `Shutdown` can add unnecessary work without changing output.
- If the configured flush callback raises, the worker records failure state and stops instead of silently hiding the error.
### Notes
1. This policy only affects the async logger path and only matters when the configured sink has a meaningful flush function.
2. `AsyncFlushPolicy::Never` is the default in `AsyncLoggerConfig::new(...)`.
3. Serialized config uses the canonical `Never` label even though the parser also accepts `None`.
@@ -0,0 +1,104 @@
---
name: async-logger-build-config-to-json
group: api
category: async
update-time: 20260614
description: Convert AsyncLoggerBuildConfig into a JSON value for exporting the full shared async build shape that can later feed either async builder path.
key-word:
- async
- build
- config
- public
---
## Async-logger-build-config-to-json
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_parser.JsonValue {}
```
#### input
- `config : AsyncLoggerBuildConfig` - Complete build config used by async logger builders.
#### output
- `JsonValue` - Structured JSON representation of the full async build config.
### Explanation
Detailed rules explaining key parameters and behaviors
- The output always includes `logger` and `async_config`.
- Logger export is delegated to `@bitlogger.logger_config_to_json(...)`.
- Async export is delegated to `async_logger_config_to_json(...)`.
- Because both sections are always materialized, parsed defaults that were originally omitted in JSON input become explicit again in the exported build-config shape.
- This helper is useful when generated setup should preserve both sink/logger behavior and async runtime behavior together.
- The exported `logger` section keeps the full `LoggerConfig` shape, including fields that only matter on the full sync-first builder path such as the optional sync queue layer.
- That means the JSON shape is broader than the consumption pattern of `build_async_text_logger(...)`, which only uses selected text-oriented logger fields when building the sink.
- In particular, the exported `logger.sink.kind` remains whatever the config currently says, but a later `build_async_text_logger(...)` call still ignores that sink-kind branch and constructs `FormattedConsoleSink` from `logger.sink.text_formatter`.
- The same exported object is also the shared handoff shape for the application and library facade routes after parse. `parse_async_logger_build_config_text(...)` can read this JSON back into `AsyncLoggerBuildConfig`, and that parsed value can then flow unchanged into `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, or `build_library_async_text_logger(...)`.
- Because of that, the exported structure is descriptive config data rather than a commitment to one public async type. The later builder or facade API still decides whether the runtime-sink line or the text-console line is taken.
### How to Use
Here are some specific examples provided.
#### When Need Structured Export Of Full Async Setup
When a tool or test needs one object describing the whole async logger build:
```moonbit
let payload = async_logger_build_config_to_json(
AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(target="svc"),
async_config=AsyncLoggerConfig::new(max_pending=64),
),
)
```
In this example, both layers of configuration are exported together.
And later consumers can still choose whether to rebuild through `build_async_logger(...)` or the narrower `build_async_text_logger(...)` path.
And that later text-specific builder choice still matters more than the serialized `logger.sink.kind` value, because only the formatter-backed text path is consumed there.
And the same exported object can just as well be parsed and then routed into the application or library facade builders when the next consumer wants a narrower public async type.
#### When Need Roundtrip-friendly Build Config Data
When generated build config should later be parsed again:
```moonbit
let value = async_logger_build_config_to_json(AsyncLoggerBuildConfig::new())
```
In this example, the resulting JSON matches the supported async build config shape.
### Error Case
e.g.:
- If callers only need the async runtime section, this API is broader than necessary and `async_logger_config_to_json(...)` should be used instead.
- If callers want direct text output, they should use `stringify_async_logger_build_config(...)` instead.
- Exporting the full `logger` section does not imply that every async builder will later consume every logger field equally.
- Exporting `logger.sink.kind="file"` or `"console"` also does not force the later text-specific builder path to branch that way; only `build_async_logger(...)` follows sink kind when constructing the runtime sink.
- Choosing an application or library facade builder later does not change the meaning of the exported config by itself; those facade APIs inherit the same runtime-sink-versus-text-console split from the direct builder they delegate to.
### Notes
1. Use this helper when tools or tests need a structured JSON object instead of text.
2. Use `stringify_async_logger_build_config(...)` when the same build shape should be emitted as JSON text directly.
3. The resulting object round-trips through `parse_async_logger_build_config_text(...)`, even though different async builders later consume different parts of the embedded `LoggerConfig`.
4. After parsing, that same object can also feed the application or library facade builders; export preserves one shared build-config shape, not a direct-builder-only route.
@@ -0,0 +1,99 @@
---
name: async-logger-build-config-type
group: api
category: async
update-time: 20260614
description: Public async build config alias combining the base logger config and async runtime config for both the general async builder path and the specialized text-console builder path.
key-word:
- async
- build
- config
- public
---
## Async-logger-build-config-type
`AsyncLoggerBuildConfig` is the public config object that combines the base synchronous `LoggerConfig` with the async runtime `AsyncLoggerConfig`. It is a direct alias to the build-config model used by async builder APIs, parsers, and serializers, even though the available builders consume different parts of the embedded sync config.
### Interface
```moonbit
pub type AsyncLoggerBuildConfig = @utils.AsyncLoggerBuildConfig
```
#### output
- `AsyncLoggerBuildConfig` - Public async build config object containing `logger` and `async_config`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a built logger instance.
- The current fields are `logger : LoggerConfig` and `async_config : AsyncLoggerConfig`.
- The public `src-async` surface forwards this alias directly from `@utils.AsyncLoggerBuildConfig`, so constructor, parser, export, and stringify helpers all operate on one shared underlying build-config model.
- `AsyncLoggerBuildConfig::new(...)` constructs this type as the main handoff object for async build flows.
- `build_async_logger(...)`, `build_async_text_logger(...)`, `parse_async_logger_build_config_text(...)`, `async_logger_build_config_to_json(...)`, and `stringify_async_logger_build_config(...)` all consume or produce this same public shape.
- The same typed object also feeds the application and library facade builders. `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, and `build_library_async_text_logger(...)` all accept this exact config type and then delegate to one of the two direct builder lines.
- The parse-and-build facade helpers use the same model as well. `parse_and_build_application_async_logger(...)` and `parse_and_build_library_async_logger(...)` first parse JSON into this build-config shape and then forward into their respective facade builders.
- `build_async_logger(...)` consumes the full sync build path by calling `build_logger(config.logger)` first, so `LoggerConfig.sink`, `LoggerConfig.queue`, and the resulting runtime sink behavior all participate before the async layer is added.
- `build_async_text_logger(...)` is narrower: it builds a text console sink directly from `config.logger.sink.text_formatter` and the top-level `min_level`, `target`, and `timestamp` fields, without applying `LoggerConfig.queue`.
- On that text-specific path, `config.logger.sink.kind` also does not decide the runtime sink shape. `build_async_text_logger(...)` still constructs `FormattedConsoleSink` from `config.logger.sink.text_formatter` even if the config says `Console`, `JsonConsole`, or `File`.
- In practice, the builder function you choose is what decides how the embedded `LoggerConfig` is interpreted. The runtime-sink line (`build_async_logger(...)` and the application/library facades layered on it) preserves the full sync build path, while the text-console line (`build_async_text_logger(...)` and the facades layered on it) ignores the optional sync queue and does not branch on `logger.sink.kind`.
### How to Use
Here are some specific examples provided.
#### When Need One Typed Object For Full Async Logger Setup
When sync sink setup and async runtime policy should move together through application boot code:
```moonbit
let config : AsyncLoggerBuildConfig = AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(target="svc"),
async_config=AsyncLoggerConfig::new(max_pending=64),
)
```
In this example, both layers of logger setup are kept in one typed value.
And downstream code can still choose between the full sync-first builder path and the narrower text-console builder path.
And if downstream code chooses `build_async_text_logger(...)`, that builder choice still matters more than `logger.sink.kind` because only the formatter-backed text path is consumed there.
And the same typed object can be handed unchanged to `build_application_async_logger(...)`, `build_application_text_async_logger(...)`, `build_library_async_logger(...)`, or `build_library_async_text_logger(...)` when the caller wants a different public facade over the same underlying build decision.
#### When Need To Export Or Inspect The Full Build Shape
When application code should inspect the combined async build configuration before constructing the logger:
```moonbit
let config = AsyncLoggerBuildConfig::new(async_config=AsyncLoggerConfig::new(max_batch=4))
println(stringify_async_logger_build_config(config, pretty=true))
```
In this example, the same public config object supports both review and later build steps.
### Error Case
e.g.:
- `AsyncLoggerBuildConfig` itself does not have a runtime failure mode.
- If only async runtime policy is needed and the base sync logger config is irrelevant, this type may be broader than necessary and `AsyncLoggerConfig` is the smaller fit.
- If callers expect every `LoggerConfig` field to affect every async builder in the same way, that assumption is too broad: the text-specific builder intentionally ignores the optional sync queue layer.
- In particular, carrying `logger.sink.kind=File` inside this config type does not force the later text-specific builder path to create a file-backed async logger; only `build_async_logger(...)` branches on sink kind.
- Likewise, choosing an application or library facade builder does not change these config semantics by itself. Those facade APIs inherit the same runtime-sink-versus-text-console split from the direct builder they delegate to.
### Notes
1. Use `AsyncLoggerBuildConfig::new(...)` when one object should carry both sync and async logger setup.
2. Use `parse_async_logger_build_config_text(...)` when the same shape should come from JSON text instead of handwritten code.
3. Pick `build_async_logger(...)` when the full synchronous config path, including `LoggerConfig.queue`, should be preserved before async wrapping.
4. Pick `build_async_text_logger(...)` when the goal is specifically a concrete text console sink and only the selected text-oriented `LoggerConfig` fields should apply.
5. Pick the application or library facade builders when the same config should drive one of those narrower public surfaces; the chosen facade changes the exposed type, but the direct builder line underneath still determines whether queue and sink-kind settings are preserved or ignored.
+97
View File
@@ -0,0 +1,97 @@
---
name: async-logger-build-config
group: api
category: async
update-time: 20260614
description: Create the combined sync-and-async build config used by async logger builder APIs, whether callers later choose the full sync-first builder path or the specialized text-console builder path.
key-word:
- async
- build
- config
- public
---
## Async-logger-build-config
Create an `AsyncLoggerBuildConfig` value that combines the base synchronous `LoggerConfig` with the async runtime `AsyncLoggerConfig`. This is the constructor used when async builder APIs should receive one typed object carrying both layers of setup, even though different builders later consume different parts of the embedded sync config.
### Interface
```moonbit
pub fn AsyncLoggerBuildConfig::new(
logger~ : @bitlogger.LoggerConfig = @bitlogger.default_logger_config(),
async_config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
) -> AsyncLoggerBuildConfig {
```
#### input
- `logger : LoggerConfig` - Base synchronous logger config describing the sink, level, target, related sync logger settings, and any optional synchronous queue wrapper.
- `async_config : AsyncLoggerConfig` - Async runtime config describing queue, batching, linger, and flush behavior.
#### output
- `AsyncLoggerBuildConfig` - Combined build config used by async logger build and parse helpers.
### Explanation
Detailed rules explaining key parameters and behaviors
- Omitting `logger` uses `default_logger_config()`.
- Omitting `async_config` uses `AsyncLoggerConfig::new()`.
- The constructor simply packages both config objects into one public build shape.
- The constructor does not normalize or reinterpret either embedded config beyond those defaults; any normalization has already happened inside the `LoggerConfig` or `AsyncLoggerConfig` values passed in.
- When passed to `build_async_logger(...)`, the `logger` portion is built first through the normal synchronous config path before the outer async queue layer is applied.
- When passed to `build_async_text_logger(...)`, the same `logger` portion is consumed more narrowly: `text_formatter`, `min_level`, `target`, and `timestamp` are used directly to build a text console sink, while `LoggerConfig.queue` is not applied.
- On that text-specific path, `logger.sink.kind` also does not decide the runtime sink shape. `build_async_text_logger(...)` still constructs `FormattedConsoleSink` from `logger.sink.text_formatter` even if the config says `Console`, `JsonConsole`, or `File`.
- This helper is the main code-side counterpart to `parse_async_logger_build_config_text(...)`.
### How to Use
Here are some specific examples provided.
#### When Need One Typed Object For Async Builder Input
When sync sink setup and async runtime policy should travel together through build code:
```moonbit
let config = AsyncLoggerBuildConfig::new(
logger=@bitlogger.LoggerConfig::new(target="svc.async"),
async_config=AsyncLoggerConfig::new(max_pending=64, max_batch=8),
)
```
In this example, the builder input keeps both configuration layers in one typed value.
And later code can still decide whether that shared config should flow into the full sync-first builder or the narrower text-console builder.
And if later code chooses `build_async_text_logger(...)`, that builder choice still matters more than `logger.sink.kind` because only the formatter-backed text path is consumed there.
#### When Need Defaulted Async Build Settings
When code only wants the standard combined config shape with few overrides:
```moonbit
let config = AsyncLoggerBuildConfig::new(async_config=AsyncLoggerConfig::new(max_batch=4))
```
In this example, the base sync logger config falls back to its default value automatically.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages configuration values.
- If callers only need async runtime policy and not the full builder input shape, `AsyncLoggerConfig::new(...)` is the smaller API.
- If callers expect every field inside `LoggerConfig` to affect every async builder equally, that assumption is too broad: `build_async_text_logger(...)` intentionally skips the optional sync queue layer.
- In particular, carrying `logger.sink.kind=File` inside this config does not force the later text-specific builder path to create a file-backed async logger; only `build_async_logger(...)` branches on sink kind.
### Notes
1. Use this helper when async builder APIs should receive one combined config object.
2. Pair it with `build_async_logger(...)`, `build_async_text_logger(...)`, or `parse_async_logger_build_config_text(...)` depending on whether the source is code or JSON text.
3. Prefer `build_async_logger(...)` after constructing this value when configured sync sink behavior, including `LoggerConfig.queue`, should be preserved before async wrapping.
4. Prefer `build_async_text_logger(...)` after constructing this value when the goal is specifically config-driven text console output with a concrete `FormattedConsoleSink`.
+86
View File
@@ -0,0 +1,86 @@
---
name: async-logger-child
group: api
category: async
update-time: 20260512
description: Derive a child async logger by composing the current target with a child target segment.
key-word:
- async
- logger
- target
- public
---
## Async-logger-child
Create a child async logger by composing the current target with another target segment. This is the standard API for hierarchical async logger naming such as `app.worker` or `service.http.client`.
### Interface
```moonbit
pub fn[S] AsyncLogger::child(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Parent async logger whose target should be extended.
- `target : String` - Child target segment or suffix.
#### output
- `AsyncLogger[S]` - A new async logger value whose default target is the composed child path.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned logger is derived from `self`; the original async logger value is not mutated.
- If the parent target is empty, the child target becomes the full target.
- If the child target is empty, the parent target is preserved.
- If both are non-empty, they are joined with `.`.
- Only the stored target changes. Queue settings, sink wiring, flush policy, level gating, and lifecycle state remain shared with the same underlying async logger setup.
- In the current direct async coverage, `timestamp` is also preserved on the derived child logger while the source logger keeps its previous parent target.
### How to Use
Here are some specific examples provided.
#### When Need Hierarchical Async Targets
When subsystem logs should stay grouped under one async namespace:
```moonbit
let logger = async_logger(console_sink(), target="service")
let worker = logger.child("worker")
```
In this example, `worker` emits under `service.worker` while keeping the same async queue behavior.
And `logger` still keeps its original stored target, because `child(...)` returns a derived async logger value instead of mutating the parent.
#### When Build Deep Async Scopes Step By Step
When deeper target composition should remain readable:
```moonbit
let http = async_logger(console_sink(), target="app")
.child("http")
.child("client")
```
In this example, the final logger target becomes `app.http.client`.
### Error Case
e.g.:
- If `target` is empty, the returned logger keeps the original parent target.
- If callers need complete replacement rather than composition, `with_target(...)` should be used instead.
### Notes
1. This is the preferred API for hierarchical async logger naming.
2. Composition changes the target only and does not rebuild the queue or sink.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` still operate on the same async logger state after derivation.
4. Use `child("")` when code should keep the current target while still following a target-composition code path.
+85
View File
@@ -0,0 +1,85 @@
---
name: async-logger-close
group: api
category: async
update-time: 20260614
description: Close the async logger queue immediately and optionally convert current pending backlog into dropped records before queue closure.
key-word:
- async
- logger
- lifecycle
- public
---
## Async-logger-close
Close the async logger queue and stop treating the logger as an active enqueue target. This API is the low-level lifecycle close primitive behind shutdown flows.
### Interface
```moonbit
pub fn[S] AsyncLogger::close(self : AsyncLogger[S], clear? : Bool = false) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should be closed.
- `clear : Bool` - Whether pending records should be abandoned immediately instead of being left for drain behavior.
#### output
- `Unit` - No return value. The logger lifecycle state is updated in place.
### Explanation
Detailed rules explaining key parameters and behaviors
- `close(...)` marks the logger as closed immediately.
- `clear=false` closes the queue without explicitly abandoning pending records in the helper itself.
- `clear=true` counts pending records as dropped and resets `pending_count` to `0` before closing the queue.
- This helper does not itself wait for the worker to finish.
- `close(...)` also does not change `has_failed()` or `last_error()`; it is a closure primitive, not a failure reset API.
- Because this is a low-level close primitive, it does not first run `wait_idle()` or apply the runtime-dependent fallback logic used by `shutdown()`.
- After closure, later log attempts do not add new pending or dropped counts, but backend behavior can still differ before the closed queue rejects the record.
- In the current direct coverage, compatibility runtimes short-circuit before patch-path work on late log attempts, while native-worker runtimes may still build and patch the record before the closed queue rejects it.
### How to Use
Here are some specific examples provided.
#### When Need Immediate Queue Closure
When teardown should stop normal enqueue activity right away:
```moonbit
logger.close()
```
In this example, the logger enters closed state immediately.
#### When Need To Abandon Backlog Explicitly
When pending records should be discarded during fast shutdown:
```moonbit
logger.close(clear=true)
```
In this example, queued backlog is counted as dropped instead of waiting for further drain.
### Error Case
e.g.:
- If `clear=true`, pending records are intentionally discarded and contribute to `dropped_count()`.
- If `clear=false`, pending records may still exist after closure until worker drain or later cleanup resolves them.
- If callers perform late log attempts after closure, backlog counters still stay unchanged, but patch-path side effects are runtime-dependent and should not be treated as a portable post-close contract.
- If callers need graceful waiting for drain completion, `shutdown()` is usually the better API.
### Notes
1. This is a low-level lifecycle helper; prefer `shutdown()` for normal graceful teardown.
2. Use `clear=true` only when backlog loss is an acceptable shutdown tradeoff.
3. Pair it with `pending_count()`, `dropped_count()`, or `state()` when you need to observe what happened to existing backlog after closure.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-logger-config-to-json
group: api
category: async
update-time: 20260614
description: Convert AsyncLoggerConfig into a JSON value for export, persistence, or generated async config output using the stable serialized policy labels.
key-word:
- async
- config
- json
- public
---
## Async-logger-config-to-json
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_parser.JsonValue {}
```
#### input
- `config : AsyncLoggerConfig` - Async logger runtime config to export.
#### output
- `JsonValue` - Structured JSON representation of the async config.
### Explanation
Detailed rules explaining key parameters and behaviors
- The output includes `max_pending`, `max_batch`, `linger_ms`, `overflow`, and `flush`.
- Policy fields are serialized using the stable labels accepted by the config parser.
- This helper exports effective typed config after constructor normalization has already happened.
- If `max_pending` is negative in the config object, the exported JSON preserves that negative value because runtime queue clamping happens later, not during serialization.
- The JSON shape matches the `async_config` section used by async build config parsing.
### How to Use
Here are some specific examples provided.
#### When Need Structured Async Config Export
When async runtime policy should be embedded in a larger JSON payload:
```moonbit
let async_json = async_logger_config_to_json(
AsyncLoggerConfig::new(max_pending=128, max_batch=8),
)
```
In this example, callers receive a machine-readable config value.
#### When Need Roundtrip-friendly Async Settings
When code generates async policy and later persists it:
```moonbit
let value = async_logger_config_to_json(AsyncLoggerConfig::new(flush=AsyncFlushPolicy::Batch))
```
In this example, the exported JSON stays aligned with parser expectations.
### Error Case
e.g.:
- If `max_batch` or `linger_ms` were normalized during construction, the exported JSON reflects the normalized values rather than the original invalid inputs.
- If callers need to understand runtime queue behavior for negative `max_pending`, they should document that separately because serialization preserves the config value rather than the later queue-kind clamp.
- If callers want direct text output instead of a JSON value, they should use `stringify_async_logger_config(...)` instead.
### Notes
1. Serialized policy labels round-trip through `parse_async_logger_config_text(...)`.
2. The serializer emits canonical labels like `DropNewest` and `Never`, even though the parser also accepts compatibility aliases such as `DropLatest` and `None`.
+81
View File
@@ -0,0 +1,81 @@
---
name: async-logger-config-type
group: api
category: async
update-time: 20260614
description: Public async logger config alias used for async queue interpretation, batching, linger, and flush settings.
key-word:
- async
- config
- alias
- public
---
## Async-logger-config-type
`AsyncLoggerConfig` is the public config object used to describe async queue capacity, overflow behavior, batching, linger timing, and flush policy. It is a direct alias to the async config model used by `async_logger(...)`, config parsers, and async config serializers.
### Interface
```moonbit
pub type AsyncLoggerConfig = @utils.AsyncLoggerConfig
```
#### output
- `AsyncLoggerConfig` - Public async config object containing `max_pending`, `overflow`, `max_batch`, `linger_ms`, and `flush`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a runtime logger handle.
- The current fields are `max_pending : Int`, `overflow : AsyncOverflowPolicy`, `max_batch : Int`, `linger_ms : Int`, and `flush : AsyncFlushPolicy`.
- The public `src-async` surface forwards this alias directly from `@utils.AsyncLoggerConfig`, so constructor, parser, export, and stringify helpers all operate on one shared underlying config model.
- `AsyncLoggerConfig::new(...)` normalizes `max_batch` and `linger_ms`, but it preserves the provided `max_pending` value.
- Runtime queue creation later interprets negative `max_pending` as `0` when choosing the internal queue kind.
- `parse_async_logger_config_text(...)`, `async_logger_config_to_json(...)`, and `stringify_async_logger_config(...)` all operate on this same public config shape.
- The parser accepts the stable serialized labels such as `DropNewest` and `Never`, plus compatibility aliases such as `DropLatest` and `None`.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Async Runtime Policy Value
When async queue and flush behavior should be passed around as structured config:
```moonbit
let config : AsyncLoggerConfig = AsyncLoggerConfig::new(
max_pending=64,
overflow=AsyncOverflowPolicy::DropOldest,
)
```
In this example, async policy remains a typed object instead of immediately becoming JSON text or a logger instance.
#### When Need To Inspect The Config Before Building
When one layer should read or adjust async policy before logger construction:
```moonbit
let config = AsyncLoggerConfig::new(max_batch=4, linger_ms=20)
println(stringify_async_logger_config(config, pretty=true))
```
In this example, the same public config object supports both inspection and later build steps.
### Error Case
e.g.:
- `AsyncLoggerConfig` itself does not have a runtime failure mode.
- Constructor normalization still applies when the value is created through `AsyncLoggerConfig::new(...)`, so very small batch sizes and negative linger values may be adjusted before later serialization or use.
- Negative `max_pending` is not rewritten inside the config object itself; it is only clamped later when the async queue kind is derived at runtime.
### Notes
1. Use `AsyncLoggerConfig::new(...)` when you need a value of this type in code.
2. Use `AsyncLoggerBuildConfig` when the async config should travel together with the base synchronous `LoggerConfig`.
3. Use `parse_async_logger_config_text(...)` when the same shape should come from JSON text, including accepted aliases like `DropLatest` and `None`.
+94
View File
@@ -0,0 +1,94 @@
---
name: async-logger-config
group: api
category: async
update-time: 20260614
description: Create the async queue, batching, linger, and flush policy config used by async loggers.
key-word:
- async
- config
- queue
- public
---
## Async-logger-config
Create an `AsyncLoggerConfig` value describing queue capacity, overflow behavior, batching size, linger timing, and flush policy for async logger construction.
### Interface
```moonbit
pub fn AsyncLoggerConfig::new(
max_pending~ : Int = 0,
overflow~ : AsyncOverflowPolicy = AsyncOverflowPolicy::Blocking,
max_batch~ : Int = 1,
linger_ms~ : Int = 0,
flush~ : AsyncFlushPolicy = AsyncFlushPolicy::Never,
) -> AsyncLoggerConfig {}
```
#### input
- `max_pending : Int` - Requested maximum queued records before overflow policy matters; negative values are preserved in the config object and later treated as `0` by runtime queue creation.
- `overflow : AsyncOverflowPolicy` - Queue overflow strategy.
- `max_batch : Int` - Maximum records drained per batch.
- `linger_ms : Int` - Optional wait window for batch accumulation.
- `flush : AsyncFlushPolicy` - Flush behavior for batch/shutdown phases.
#### output
- `AsyncLoggerConfig` - Async runtime config object used by `async_logger(...)` or async build helpers.
### Explanation
Detailed rules explaining key parameters and behaviors
- `max_batch <= 1` is normalized to `1`.
- `linger_ms < 0` is normalized to `0`.
- `max_pending` is stored as provided by the constructor.
- When the async logger runtime later builds its internal queue, negative `max_pending` is interpreted as `0`.
- `overflow` and `flush` define the most important queue/runtime behavior tradeoffs.
- This type is used directly by `async_logger(...)` and embedded in `AsyncLoggerBuildConfig`.
### How to Use
Here are some specific examples provided.
#### When Tune Async Queue Backlog And Batch Size
When async behavior should be explicit in code:
```moonbit
let config = AsyncLoggerConfig::new(
max_pending=128,
overflow=AsyncOverflowPolicy::DropOldest,
max_batch=8,
linger_ms=10,
flush=AsyncFlushPolicy::Batch,
)
```
In this example, queue pressure, batch size, and flush timing are configured together.
#### When Export Async Config
When async policy should be serialized or logged:
```moonbit
println(stringify_async_logger_config(AsyncLoggerConfig::new(max_pending=8)))
```
In this example, the config becomes a stable JSON payload.
### Error Case
e.g.:
- If `max_batch` is set to `0` or below, constructor normalization changes it to `1`.
- If `linger_ms` is negative, constructor normalization changes it to `0`.
### Notes
1. This type controls async runtime behavior, not synchronous queue wrapping.
2. Prefer explicit values for production services so overflow and flush semantics are visible.
3. If queue limit semantics for negative values matter, document that behavior explicitly in calling code because the config object preserves the negative value while the runtime queue later clamps it to `0`.
+84
View File
@@ -0,0 +1,84 @@
---
name: async-logger-debug
group: api
category: async
update-time: 20260614
description: Enqueue a debug-level record through the async logger using the built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
- debug
- public
---
## Async-logger-debug
Enqueue a debug-level record through the async logger. This is the convenience wrapper for `log(Level::Debug, ...)`.
### Interface
```moonbit
pub async fn[S] AsyncLogger::debug(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the debug record.
- `message : String` - Debug message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
#### output
- `Unit` - No return value. The record is handled according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Debug, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Debug records are useful for development and targeted diagnostics.
- Use this helper when a named debug call is clearer than a raw `log(...)` call.
### How to Use
Here are some specific examples provided.
#### When Need Async Development Diagnostics
When intermediate async flow details should be visible during debugging:
```moonbit
logger.debug("loaded worker config")
```
In this example, the call site communicates its intended diagnostic level directly.
#### When Attach Structured Debug Context
When a debug event should include extra fields:
```moonbit
logger.debug(
"dispatch start",
fields=[@bitlogger.field("job_id", "42")],
)
```
In this example, the record carries structured context without needing the generic `log(...)` form.
### Error Case
e.g.:
- If the logger minimum level is above `Debug`, the record is skipped before enqueue.
- If the logger is closed or overflow policy prevents acceptance, the write may not become a normal queued record.
### Notes
1. Prefer this helper when the event is semantically debug-level.
2. Use `log(...)` when the level must be chosen dynamically or one call needs a target override.
+83
View File
@@ -0,0 +1,83 @@
---
name: async-logger-dropped-count
group: api
category: async
update-time: 20260512
description: Read the number of records dropped by the async logger because of overflow or queue clearing.
key-word:
- async
- logger
- queue
- public
---
## Async-logger-dropped-count
Read the cumulative number of records dropped by an async logger. This metric is useful when overflow policy or forced shutdown behavior may discard queued data.
### Interface
```moonbit
pub fn[S] AsyncLogger::dropped_count(self : AsyncLogger[S]) -> Int {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose dropped-record counter should be inspected.
#### output
- `Int` - Number of records dropped so far.
### Explanation
Detailed rules explaining key parameters and behaviors
- The counter increases when overflow policy discards records.
- The counter can also increase when `close(clear=true)` or `shutdown(clear=true)` abandons queued records.
- It can also increase when shutdown fallback logic converts remaining pending records into dropped records during a clear-close path.
- After a worker failure short-circuits `wait_idle()`, that shutdown-fallback increase is runtime-dependent in the current implementation: native-worker shutdown can convert leftover pending records into additional dropped records, while compatibility shutdown can leave that closed-queue remainder visible in `pending_count()` instead.
- That also means `dropped_count()` can still stay unchanged even after `is_closed=true` and retained failure state on compatibility-style shutdown paths, because closure there does not necessarily convert the leftover failed backlog into dropped records.
- Later log attempts against an already closed queue do not add to this counter by themselves.
- This is a cumulative counter for the lifetime of the logger value.
- Use this helper when you need a focused loss metric rather than a full `state()` snapshot.
### How to Use
Here are some specific examples provided.
#### When Need To Detect Loss Under Pressure
When queue overflow may discard records:
```moonbit
if logger.dropped_count() > 0 {
println("async log loss detected")
}
```
In this example, the code checks whether the async logger has already discarded records.
#### When Validate Shutdown Behavior In Tests
When a test intentionally clears the queue:
```moonbit
logger.close(clear=true)
ignore(logger.dropped_count())
```
In this example, the drop counter helps confirm that abandoned backlog was tracked.
### Error Case
e.g.:
- If no records have been dropped, the method simply returns `0`.
- If callers need to know why records were lost, they must interpret this metric together with overflow policy and shutdown behavior.
- If `wait_idle()` returned early because the worker failed, `dropped_count()` may still stay unchanged on compatibility shutdown paths even though one leftover closed-queue item remains visible in `pending_count()`.
### Notes
1. This helper reports record loss only; it does not explain the full logger state.
2. Pair it with `pending_count()` or `state()` when investigating backlog pressure.
+90
View File
@@ -0,0 +1,90 @@
---
name: async-logger-error
group: api
category: async
update-time: 20260614
description: Enqueue an error-level record through the async logger using the highest built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
- error
- public
---
## Async-logger-error
Enqueue an error-level record through the async logger. This is the convenience wrapper for `log(Level::Error, ...)`.
### Interface
```moonbit
pub async fn[S] AsyncLogger::error(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the error record.
- `message : String` - Error message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
#### output
- `Unit` - No return value. The record is handled according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Error, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Error records represent the highest built-in severity in this logger API.
- Use this helper when a named error call is clearer than a raw `log(...)` call.
### How to Use
Here are some specific examples provided.
#### When Need Async Failure Reporting
When an operation should emit a high-severity failure event:
```moonbit
logger.error("worker execution failed")
```
In this example, failure intent is explicit at the call site.
#### When Attach Structured Error Context
When an error event should include diagnostic fields:
```moonbit
logger.error(
"dispatch failed",
fields=[@bitlogger.field("job_id", "42")],
)
```
In this example, the error record carries structured context without falling back to the generic `log(...)` form.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
- If the logger is closed or overflow policy prevents acceptance, even an error-level record may not become a normal queued record.
- If callers need to inspect worker failure rather than emit an error record, `has_failed()` and `last_error()` are the relevant APIs.
### Notes
1. Use this helper for high-severity async application failures.
2. Use `log(...)` instead when an error call needs a one-off target override.
3. Emitting an error record is separate from the logger worker itself entering failure state.
+76
View File
@@ -0,0 +1,76 @@
---
name: async-logger-flush-policy
group: api
category: async
update-time: 20260614
description: Read the async logger flush policy currently governing batch-end and shutdown-end flushing behavior.
key-word:
- async
- logger
- flush
- public
---
## Async-logger-flush-policy
Read the async logger flush policy. This helper exposes which flush mode currently controls batch and shutdown behavior.
### Interface
```moonbit
pub fn[S] AsyncLogger::flush_policy(self : AsyncLogger[S]) -> AsyncFlushPolicy {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose flush policy should be inspected.
#### output
- `AsyncFlushPolicy` - Current flush policy used by the async worker logic.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned value reflects the policy captured when the async logger was created.
- `Batch` means the worker invokes the stored async flush callback after each completed drained batch, not after every individual record write.
- `Shutdown` means the worker invokes the stored async flush callback once after the worker loop exits.
- `Never` leaves that explicit callback path unused and relies entirely on sink behavior or external control.
- This helper reports callback timing policy, not a guarantee that a particular sink type has a meaningful built-in flush effect on every constructor path.
- In particular, text-specific async builder paths keep the default no-op flush callback even when the visible policy is `Batch` or `Shutdown`, so the reported policy can describe when the callback would run without implying extra sink work actually happens on that path.
### How to Use
Here are some specific examples provided.
#### When Need To Inspect Runtime Flush Semantics
When diagnostics should show how the worker flushes:
```moonbit
ignore(logger.flush_policy())
```
In this example, the configured flush mode is exposed directly from the logger.
#### When Export Async Runtime Metadata
When code should include flush behavior in custom state reporting:
```moonbit
let flush = logger.flush_policy()
```
In this example, flush behavior can be surfaced without reading the full snapshot.
### Error Case
e.g.:
- This helper does not expose a normal runtime error path; it returns the configured policy.
- If callers need the policy together with backlog and failure state, `state()` is usually the better API.
### Notes
1. This helper exposes configuration-driven runtime behavior, not dynamic worker health.
2. Use `state()` when you want flush policy packaged with the rest of async logger diagnostics.
+88
View File
@@ -0,0 +1,88 @@
---
name: async-logger-has-failed
group: api
category: async
update-time: 20260614
description: Read whether the async logger worker has recorded a runtime failure after run() startup reset and drain execution.
key-word:
- async
- logger
- failure
- public
---
## Async-logger-has-failed
Read whether the async logger worker has encountered a failure. This helper is a compact health signal for async delivery problems.
### Interface
```moonbit
pub fn[S] AsyncLogger::has_failed(self : AsyncLogger[S]) -> Bool {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose failure flag should be inspected.
#### output
- `Bool` - Whether the worker has failed.
### Explanation
Detailed rules explaining key parameters and behaviors
- `run()` clears previous failure state at startup.
- `run()` also clears the stored `last_error()` string at startup before drain work begins.
- If the worker loop raises an error, the logger records that failure and exposes it through this flag.
- Once set by a failed run, the flag stays `true` until a later `run()` invocation actually starts and resets it.
- Failure-driven shutdown does not clear this flag by itself, so `has_failed()` can remain `true` even after the logger is already closed.
- This helper is intentionally compact and should usually be paired with `last_error()` for details.
- Failure state is about runtime drain execution, not whether records were dropped due to overflow policy.
### How to Use
Here are some specific examples provided.
#### When Need A Fast Failure Signal
When runtime diagnostics should branch on worker health:
```moonbit
if logger.has_failed() {
println(logger.last_error())
}
```
In this example, the code checks failure state first, then reads the error detail.
#### When Inspect Async Runtime State In Tests
When a test needs to confirm that drain execution stayed healthy:
```moonbit
ignore(logger.has_failed())
```
In this example, the helper exposes a simple pass-fail runtime indicator.
### Error Case
e.g.:
- If `has_failed()` is `false`, queue pressure or dropped records may still exist for non-failure reasons.
- If `has_failed()` becomes `true`, `wait_idle()` may stop early while pending records still remain until a later close or clear path handles them.
- In the current regression coverage, that later handling can be an explicit `close(clear=true)` that resets pending backlog immediately or a runtime-dependent `shutdown(...)` path that either clears pending into dropped records or leaves the closed-queue remainder visible.
- If `has_failed()` is still `true` after shutdown, that does not by itself mean cleanup failed; the logger may already be `is_closed=true` while the remaining pending-versus-dropped outcome still reflects the active runtime's shutdown path.
- If `has_failed()` is `true`, callers should inspect `last_error()` or `state()` for more context.
- `close()` or `shutdown()` do not clear this flag by themselves; only a later `run()` that has already started resets it.
### Notes
1. This helper reports worker failure, not general queue stress.
2. Pair it with `last_error()` when you need actionable detail.
3. Pair it with `is_running()` or `state()` when you also need to know whether the worker has already exited and whether backlog remains.
+88
View File
@@ -0,0 +1,88 @@
---
name: async-logger-info
group: api
category: async
update-time: 20260614
description: Enqueue an info-level record through the async logger using the most common built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
- info
- public
---
## Async-logger-info
Enqueue an info-level record through the async logger. This is the convenience wrapper for `log(Level::Info, ...)`.
### Interface
```moonbit
pub async fn[S] AsyncLogger::info(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the info record.
- `message : String` - Info message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
#### output
- `Unit` - No return value. The record is handled according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Info, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Info is often the default operational logging level for async application events.
- Use this helper when explicit info intent is clearer than a raw `log(...)` call.
### How to Use
Here are some specific examples provided.
#### When Need Normal Operational Async Events
When async code should report routine progress or lifecycle events:
```moonbit
logger.info("worker started")
```
In this example, the event is expressed at the most common operational logging level.
#### When Add Structured Operational Metadata
When an info event should include stable structured detail:
```moonbit
logger.info(
"job queued",
fields=[@bitlogger.field("queue", "sync")],
)
```
In this example, the record remains concise while still carrying useful metadata.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
- If the logger minimum level is above `Info`, the record is skipped before enqueue.
- If the logger is closed or overflow policy prevents acceptance, the write may not become a normal queued record.
### Notes
1. This is often the most common convenience method for normal async application events.
2. Use `log(...)` when the call site needs a dynamic level or a one-off target override.
+89
View File
@@ -0,0 +1,89 @@
---
name: async-logger-is-closed
group: api
category: async
update-time: 20260614
description: Read whether the async logger has entered closed lifecycle state and should no longer be treated as a normal active enqueue target.
key-word:
- async
- logger
- lifecycle
- public
---
## Async-logger-is-closed
Read whether the async logger has been closed. This helper is useful for lifecycle checks around shutdown and queue finalization.
### Interface
```moonbit
pub fn[S] AsyncLogger::is_closed(self : AsyncLogger[S]) -> Bool {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose closure state should be inspected.
#### output
- `Bool` - Whether the logger has already been closed.
### Explanation
Detailed rules explaining key parameters and behaviors
- `close(...)` sets the closed state immediately.
- `shutdown(...)` also results in a closed logger by the end of its lifecycle flow.
- A closed logger should no longer be treated as a normal active enqueue target.
- This helper is only a direct read of the current `is_closed` ref; it does not wait for drain completion or clear any other state.
- This helper reflects lifecycle state only and does not indicate whether the worker is still draining.
- `is_closed()` becoming `true` does not imply the logger reached a clean success state. Failure flags, retained `last_error()`, and remaining backlog-related counters can still reflect the earlier worker outcome.
- After shutdown on a worker-failure path, `is_closed()` can therefore be `true` while backlog cleanup remains runtime-dependent: native-worker runtimes can convert leftover pending records into dropped ones, while compatibility runtimes can still report the remaining queue entries as pending.
- Exact post-close logging behavior is runtime-dependent: compatibility runtimes can short-circuit before patch and enqueue work, while native-worker runtimes may still build and patch a record before the closed queue rejects it, so `is_closed()` should be read as a lifecycle signal rather than a full enqueue-policy contract.
### How to Use
Here are some specific examples provided.
#### When Guard Late-stage Logging
When code should avoid treating a logger as fully active during teardown:
```moonbit
if logger.is_closed() {
println("logger already closed")
}
```
In this example, teardown logic can branch on the closure state.
#### When Verify Shutdown Progress
When tests or diagnostics want to inspect lifecycle state:
```moonbit
logger.close()
ignore(logger.is_closed())
```
In this example, the helper confirms that close state was entered.
### Error Case
e.g.:
- If `is_closed()` returns `true`, pending records may still exist until drain or clear behavior completes.
- If callers need to know whether the worker is still active, they should also inspect `is_running()`.
- If callers need to know whether closure also prevented later log attempts on the current backend, they must interpret this together with the active runtime behavior rather than this flag alone.
- Closing a logger does not by itself reset `has_failed()` or `last_error()`.
- A closed logger can still report leftover `pending_count()` or `dropped_count()` values from the shutdown path, so `is_closed()` alone is not enough to infer whether backlog was fully drained or explicitly abandoned.
### Notes
1. Closed state and running state are related but not identical.
2. Use this helper when lifecycle control matters more than queue counters.
3. Pair it with `pending_count()`, `is_running()`, or `state()` when you need to understand what closure means for remaining backlog on a live logger instance.
+88
View File
@@ -0,0 +1,88 @@
---
name: async-logger-is-running
group: api
category: async
update-time: 20260614
description: Read whether the async logger worker loop is currently running, regardless of queue backlog or recorded failure state.
key-word:
- async
- logger
- lifecycle
- public
---
## Async-logger-is-running
Read whether the async logger worker is currently active. This helper is useful for runtime diagnostics and shutdown coordination.
### Interface
```moonbit
pub fn[S] AsyncLogger::is_running(self : AsyncLogger[S]) -> Bool {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose worker state should be inspected.
#### output
- `Bool` - Whether the async worker loop is currently running.
### Explanation
Detailed rules explaining key parameters and behaviors
- `run()` sets the running state while the worker loop is active.
- The flag is cleared when the worker exits normally or after failure handling finishes.
- A logger may be closed while still running briefly during final drain or shutdown processing.
- After a worker failure, the logger can already be `is_running=false` while still retaining `has_failed=true`, the recorded `last_error()`, and runtime-dependent leftover backlog or dropped-count cleanup.
- This helper is only a direct read of the current `is_running` ref; it does not wait, synchronize, or infer why the value is what it is.
- This helper focuses on worker activity rather than queue size or failure details.
- `is_running()` can be `false` even when `pending_count()` is still nonzero, for example if the worker was never started or if it exited after a recorded failure.
- A later `run()` attempt can flip `is_running()` back to `true` before that retained failure/backlog state has been fully drained, because the restarted worker clears stale failure state only once the new run has actually started.
### How to Use
Here are some specific examples provided.
#### When Need Worker Activity Diagnostics
When runtime output should show whether the worker loop is active:
```moonbit
println(logger.is_running().to_string())
```
In this example, diagnostics can distinguish an idle configured logger from one with an active worker.
#### When Coordinate Shutdown Waiting
When code should poll worker completion explicitly:
```moonbit
while logger.is_running() {
@async.pause()
}
```
In this example, callers watch the worker lifecycle directly.
### Error Case
e.g.:
- If `is_running()` is `false`, pending records may still exist if the worker was never started or has already failed.
- If `is_running()` is `false`, the logger may also already be closed while still retaining the previous failure record and a runtime-dependent pending-versus-dropped cleanup result from shutdown.
- If callers need a one-shot lifecycle flow, `shutdown()` is usually better than manual polling.
- If `is_running()` is `true`, that still does not guarantee healthy drain progress; callers may need `has_failed()` or `state()` for failure context.
- Under concurrent activity, the returned value may change immediately after it is read.
### Notes
1. Use this helper for worker activity checks, not as a complete health signal.
2. Pair it with `has_failed()` or `state()` when diagnosing stalled pipelines.
3. Pair it with `pending_count()` when you need to distinguish an idle worker from a stopped logger that still has backlog.
+87
View File
@@ -0,0 +1,87 @@
---
name: async-logger-last-error
group: api
category: async
update-time: 20260614
description: Read the last error string recorded by the async logger worker after run() resets and runtime failure capture.
key-word:
- async
- logger
- failure
- public
---
## Async-logger-last-error
Read the last error string recorded by the async logger worker. This helper gives the textual detail behind `has_failed()`.
### Interface
```moonbit
pub fn[S] AsyncLogger::last_error(self : AsyncLogger[S]) -> String {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose last worker error should be inspected.
#### output
- `String` - Last recorded worker error message, or an empty string if no error was recorded.
### Explanation
Detailed rules explaining key parameters and behaviors
- A later `run()` clears the stored error string only after that run has actually started.
- If the worker loop fails, the error text is captured from the raised exception.
- An empty string normally means no failure has been recorded.
- Once a failure string is recorded, it stays in place until a later `run()` invocation actually starts and clears it.
- Failure-driven shutdown does not clear the stored error string by itself, so the same `last_error()` text can remain visible even after the logger is already closed.
- This helper reports worker execution errors, not ordinary overflow or backpressure conditions.
- That reset happens before the restarted worker resumes drain work.
### How to Use
Here are some specific examples provided.
#### When Need Failure Detail In Diagnostics
When a compact error message should be surfaced to operators:
```moonbit
if logger.has_failed() {
println(logger.last_error())
}
```
In this example, the error string is only read when failure is present.
#### When Export Full Async State
When custom diagnostics want to include the last error field directly:
```moonbit
let err = logger.last_error()
```
In this example, the helper provides the textual failure detail without building a full snapshot.
### Error Case
e.g.:
- If no runtime failure has occurred, the method returns an empty string.
- An empty string does not prove the queue is empty or the worker is idle; it only means no failure string is currently recorded.
- If callers need broader context than just the error text, they should use `state()`.
- `close()` or `shutdown()` do not clear a previously recorded error string by themselves; the reset happens only after a later `run()` has already started.
- If the same error string is still present after shutdown, that does not by itself mean cleanup was skipped; the logger may already be `is_closed=true` while the remaining pending-versus-dropped outcome still reflects the active runtime's shutdown path.
### Notes
1. Read this helper together with `has_failed()` when interpreting worker health.
2. The stored value is a diagnostic string, not a typed error object.
3. Pair it with `is_running()` or `pending_count()` when you need to know whether failure left the logger with unfinished backlog, because the previous error string can coexist with remaining pending records until later cleanup or restart.
+108
View File
@@ -0,0 +1,108 @@
---
name: async-logger-log
group: api
category: async
update-time: 20260614
description: Enqueue a record into the async logger with an explicit level, message, optional fields, and optional target override.
key-word:
- async
- logger
- log
- public
---
## Async-logger-log
Enqueue a record into the async logger with an explicit level and message. This is the core write API behind all async level-specific convenience methods and the only built-in async write API that accepts a per-call target override.
### Interface
```moonbit
pub async fn[S] AsyncLogger::log(
self : AsyncLogger[S],
level : @bitlogger.Level,
message : String,
fields~ : Array[@bitlogger.Field] = [],
target? : String = "",
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the record.
- `level : Level` - Severity level for the record.
- `message : String` - Log message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
- `target : String` - Optional per-call target override.
#### output
- `Unit` - No return value. The record is either skipped, enqueued, or dropped according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- Compatibility runtimes that guard closed writes first can return immediately when `is_closed()` is already `true`, before level checks, record construction, patch logic, filter logic, or queue work.
- Otherwise the logger checks `is_enabled(level)` before building a record.
- If `target` is empty, the logger uses its stored default target. If `target` is non-empty, that value overrides the stored target for this call only.
- Context fields, patch logic, and filter logic are applied before enqueue.
- If timestamping is enabled, `@env.now()` is captured before the record enters the queue.
- Overflow behavior depends on the configured `AsyncOverflowPolicy`.
- Closed-on-log behavior is runtime-dependent: compatibility runtimes short-circuit before record-building work, while native-worker runtimes may still build, patch, and filter the record before queue operations treat a closed queue as a non-accepted write.
- In the tested blocking-policy path, a late write against an already closed logger does not become a newly accepted pending record and does not add another dropped record; it simply fails to enter the queue after any runtime-specific pre-queue work finishes.
### How to Use
Here are some specific examples provided.
#### When Need A Fully Explicit Async Log Call
When code should choose level, fields, and target per event:
```moonbit
logger.log(
@bitlogger.Level::Info,
"worker started",
fields=[@bitlogger.field("job", "sync")],
target="service.worker",
)
```
In this example, the record overrides the logger's stored target only for this call.
#### When Reuse The Stored Async Target
When a call should keep the logger's existing target:
```moonbit
logger.log(@bitlogger.Level::Warn, "slow request")
```
In this example, the logger falls back to its stored target because no `target=` override is provided.
#### When Build Higher-level Async Logging Helpers
When application code wants a custom wrapper around the base API:
```moonbit
logger.log(@bitlogger.Level::Warn, "slow request")
```
In this example, `log(...)` acts as the common primitive under custom wrappers or convenience methods.
### Error Case
e.g.:
- If the level is below the current minimum threshold, the record is skipped before queue insertion.
- If the logger is closed or overflow policy rejects the record, enqueue may not proceed as a normal accepted write.
- A closed queue does not count as a newly accepted pending record.
- On compatibility runtimes, a late call after close can be skipped before patch or filter logic runs at all.
- On native-worker runtimes, a late call after close can still execute patch and filter logic before the queue rejects the write.
### Notes
1. Use this API when the call site needs full control instead of a fixed severity helper.
2. Prefer `info()`, `warn()`, and the other shortcuts when only the level differs and no per-call target override is needed.
+86
View File
@@ -0,0 +1,86 @@
---
name: async-logger-pending-count
group: api
category: async
update-time: 20260614
description: Read the current number of queued records that have not yet been drained or cleared from the async logger pipeline.
key-word:
- async
- logger
- queue
- public
---
## Async-logger-pending-count
Read the current number of queued records that are still waiting to be processed. This API is the most direct backlog metric for an async logger instance.
### Interface
```moonbit
pub fn[S] AsyncLogger::pending_count(self : AsyncLogger[S]) -> Int {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose current backlog should be inspected.
#### output
- `Int` - Current number of pending records still in the async pipeline.
### Explanation
Detailed rules explaining key parameters and behaviors
- The count increases when records are accepted into the queue.
- The count decreases as the worker drains records.
- The count is also reset to `0` when queued records are abandoned through clear-close paths such as `close(clear=true)`.
- Log attempts against a closed queue do not create new pending backlog.
- This is a point-in-time metric and may change immediately after it is read.
- Use this helper when a single backlog number is enough and a full `state()` snapshot is unnecessary.
### How to Use
Here are some specific examples provided.
#### When Need A Fast Backlog Check
When code should observe queue pressure directly:
```moonbit
let pending = logger.pending_count()
```
In this example, callers get the current queue backlog without building a full diagnostics snapshot.
#### When Wait For Near-idle Conditions
When operators or tests want to inspect drain progress:
```moonbit
if logger.pending_count() == 0 {
println("queue idle")
}
```
In this example, the queue backlog is checked directly.
### Error Case
e.g.:
- If the worker is not running, `pending_count()` may stay above `0` until records are drained or cleared.
- If `wait_idle()` returns early because `has_failed()` became `true`, `pending_count()` may still be above `0` until later cleanup or clear-close handling runs.
- In the current tested split, that later cleanup can either force the leftover pending item into `dropped_count()` on native-worker shutdown paths or leave the closed-queue remainder visible in `pending_count()` on compatibility shutdown paths.
- That means `pending_count()` can still stay above `0` even after `is_closed=true` on compatibility-style shutdown paths, because closure there does not necessarily convert the leftover failed backlog into dropped records.
- A later restarted `run()` can still drain that retained backlog after failure-reset startup, so a nonzero pending count after failure is not necessarily a permanent terminal state.
- If the queue is empty, the method simply returns `0`.
### Notes
1. Use `state()` when you also need dropped counts, failure state, or runtime mode.
2. This helper is useful for lightweight health checks and tests.
3. Pair it with `is_running()` or `has_failed()` when backlog alone is not enough to explain whether the worker is actively draining, stopped, or failed.
+93
View File
@@ -0,0 +1,93 @@
---
name: async-logger-run
group: api
category: async
update-time: 20260614
description: Start the async logger worker loop so queued records are drained to the underlying sink while lifecycle and failure state are reset and updated around execution.
key-word:
- async
- logger
- worker
- public
---
## Async-logger-run
Start the async logger worker loop. This is the core runtime API that drains queued records to the underlying sink and updates worker lifecycle state around that drain loop.
### Interface
```moonbit
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose queue should be drained by the worker loop.
#### output
- `Unit` - No return value. The method runs until the queue is closed or a worker failure occurs.
### Explanation
Detailed rules explaining key parameters and behaviors
- `run()` sets `is_running` to `true` before worker execution begins.
- Every invocation clears previous failure state first by setting `has_failed=false` and `last_error()` to an empty string once that `run()` call has actually started executing.
- The method then keeps draining records until `queue.get()` stops with `AsyncLoggerClosed` or a worker error escapes.
- On a normal queue-close exit, `run()` clears `is_running` and returns normally.
- On failure, the logger records `has_failed=true`, stores the error text in `last_error`, clears `is_running`, and then raises the error back out of `run()`.
- A worker failure does not guarantee the async backlog was fully drained first. If the failure happens after some records were already written, later queued records can remain pending when `run()` exits.
- A later `run()` invocation can resume draining that retained backlog, but the stale failure flag and stale `last_error()` value are only cleared after the new worker call actually begins running.
- This helper does not enforce a single-worker guard by itself, so the public contract should be treated as application-controlled worker startup rather than an API that deduplicates repeated `run()` calls.
### How to Use
Here are some specific examples provided.
#### When Need Background Queue Drain
When async logging should be processed by a worker task:
```moonbit
let logger = async_logger(console_sink())
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("started")
logger.shutdown()
})
```
In this example, `run()` is the worker loop that makes the async logger actually deliver queued records.
#### When Need Explicit Worker Lifetime Control
When worker execution should be started under application control:
```moonbit
group.spawn_bg(() => logger.run())
```
In this example, the application decides when the worker begins instead of hiding that lifecycle step.
### Error Case
e.g.:
- If the worker loop fails, `has_failed()` becomes `true`, `last_error()` stores the error text, and `run()` raises that failure to the caller.
- After a failed run, `pending_count()` can still be greater than zero until later shutdown or restart logic finishes handling the retained backlog.
- If `run()` is never started, accepted records may remain queued and not reach the sink.
- A later `run()` attempt starts from a fresh failure flag and empty `last_error()` string once that retrying worker has actually started, even if an earlier run failed.
- Starting more than one `run()` task for the same logger is not prevented by this method and can produce application-level worker coordination bugs.
### Notes
1. `async_logger(...)` only constructs the logger; `run()` is what activates queue draining.
2. Pair this API with `shutdown()` for a complete worker lifecycle.
3. Pair it with `has_failed()`, `last_error()`, or `state()` when tests need to inspect how a worker exit affected logger health.
4. Start one deliberate worker task per logger unless your own code is intentionally coordinating a different pattern.
+97
View File
@@ -0,0 +1,97 @@
---
name: async-logger-shutdown
group: api
category: async
update-time: 20260614
description: Gracefully stop an async logger by waiting for idle or clearing queued work, with worker-wait behavior depending on the active async runtime.
key-word:
- async
- logger
- lifecycle
- public
---
## Async-logger-shutdown
Gracefully stop an async logger. This is the main high-level shutdown API for async logging because it coordinates drain behavior, closure, and worker completion.
### Interface
```moonbit
pub async fn[S] AsyncLogger::shutdown(self : AsyncLogger[S], clear? : Bool = false) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should be shut down.
- `clear : Bool` - Whether pending records should be abandoned immediately instead of waiting for idle first.
#### output
- `Unit` - No return value. The method completes after shutdown coordination finishes.
### Explanation
Detailed rules explaining key parameters and behaviors
- `clear=false` first waits for idle, then closes the logger.
- In runtimes where shutdown clearing after idle is enabled, remaining backlog after `wait_idle()` triggers a fallback `close(clear=true)`.
- `clear=true` immediately closes and abandons pending records, even if no worker was ever started for that logger.
- In runtimes where shutdown waits for workers, the method then waits until `is_running()` becomes `false` before returning.
- In the current backend split, native-worker runtimes enable both the post-`wait_idle()` clear fallback and the final wait-for-worker phase, while compatibility runtimes skip both extra steps.
- That means a failure-short-circuited `wait_idle()` can still be followed by forced pending-to-dropped cleanup on native-worker runtimes, while compatibility runtimes close without that extra forced clear step.
- In the current tested failure path, native-worker shutdown turns the leftover pending item into one more dropped record, while compatibility shutdown leaves that leftover closed-queue count in `pending_count()` instead.
- Shutdown itself does not clear retained worker failure state. If a previous `run()` already recorded `has_failed=true` and a non-empty `last_error()`, those diagnostics can remain visible after shutdown completes.
- Because `clear=false` delegates to `wait_idle()` first, shutdown can also wait indefinitely when pending records exist but no worker is making progress and no failure flag is raised.
### How to Use
Here are some specific examples provided.
#### When Need Graceful Service Shutdown
When a service should stop logging only after queued records are drained:
```moonbit
logger.shutdown()
```
In this example, the logger waits for normal drain behavior before final closure.
#### When Need Fast Shutdown Under Pressure
When teardown should prefer speed over preserving backlog:
```moonbit
logger.shutdown(clear=true)
```
In this example, pending work is abandoned intentionally so shutdown can complete sooner.
### Error Case
e.g.:
- If `clear=true`, pending records are intentionally dropped rather than drained.
- If `wait_idle()` returns early because the worker failed, shutdown behavior after that point still depends on the active runtime's fallback and worker-wait rules.
- After a worker failure, native-worker shutdown may convert the remaining backlog into dropped records, while compatibility shutdown can leave the pending counter reflecting that leftover closed queue state.
- In the current direct regression coverage, that split appears as `pending_count() == 0` and `dropped_count()` increasing on native-worker runtimes, versus `pending_count() > 0` and no extra dropped cleanup on compatibility runtimes.
- Even after shutdown finishes with `is_closed=true`, callers can still observe retained `has_failed()` and `last_error()` from an earlier worker failure.
- In compatibility-style runtimes without background-worker waiting, shutdown still closes the logger but may not perform the extra wait-for-worker phase described for native-worker runtimes.
- If pending work exists but no worker was started, `shutdown(clear=false)` may never reach its later close step because it is still waiting inside `wait_idle()`.
- If callers skip `shutdown()` and only inspect flags manually, it is easier to leave the worker lifecycle in an unclear state.
### Notes
1. Prefer this API over raw `close()` in normal application shutdown paths.
2. Exact post-close waiting behavior depends on the active async runtime mode.
3. Choose `clear=true` only when loss of queued records is acceptable.
4. Pair it with `state()` or focused counters when tests need to assert whether shutdown drained backlog or converted it into dropped records.
5. Prefer `shutdown(clear=true)` when teardown must not depend on a still-running drain worker.
+121
View File
@@ -0,0 +1,121 @@
---
name: async-logger-state-new
group: api
category: async
update-time: 20260614
description: Construct an AsyncLoggerState snapshot from explicit runtime, queue, lifecycle, failure, and flush-policy values without probing a live logger.
key-word:
- async
- logger
- state
- public
---
## Async-logger-state-new
Construct an `AsyncLoggerState` snapshot from explicit runtime, queue, lifecycle, and failure values. This is the low-level constructor behind the public async logger state shape used in diagnostics.
### Interface
```moonbit
pub fn AsyncLoggerState::new(
runtime : AsyncRuntimeState,
pending_count : Int,
dropped_count : Int,
is_closed : Bool,
is_running : Bool,
has_failed : Bool,
last_error : String,
flush_policy : AsyncFlushPolicy,
) -> AsyncLoggerState {
```
#### input
- `runtime : AsyncRuntimeState` - Embedded backend-level runtime snapshot.
- `pending_count : Int` - Current async queue backlog.
- `dropped_count : Int` - Current dropped-record count.
- `is_closed : Bool` - Whether the logger has been closed.
- `is_running : Bool` - Whether the logger worker loop is currently running.
- `has_failed : Bool` - Whether the logger has recorded a runtime failure state.
- `last_error : String` - Latest error text, or an empty string when no failure has been recorded.
- `flush_policy : AsyncFlushPolicy` - Active async flush policy for the logger.
#### output
- `AsyncLoggerState` - Full async logger snapshot containing the supplied runtime, queue, lifecycle, and failure values.
### Explanation
Detailed rules explaining key parameters and behaviors
- This constructor simply packages the supplied fields into one public snapshot value.
- It does not inspect a live logger instance by itself.
- `AsyncLogger::state()` is the higher-level API that reads these values from a concrete logger.
- It also does not validate whether the supplied fields represent a combination that could come from one real logger instant.
- The supplied `runtime : AsyncRuntimeState` is stored exactly as provided; this constructor does not recompute `mode` or `background_worker` from the active backend.
- The constructed value matches the same public shape used by async logger serializers.
- Because `AsyncLoggerState` is only a data snapshot type, this constructor is mainly useful for tests, adapters, and synthetic diagnostics rather than ordinary logger inspection.
- Serialization helpers accept any `AsyncLoggerState` value, including hand-built ones from this constructor.
- That includes combinations such as `has_failed=true` together with non-zero `pending_count` or a retained `last_error`, and even a manually chosen runtime snapshot that does not match the current backend, all of which are valid for diagnostic snapshots and test fixtures.
### How to Use
Here are some specific examples provided.
#### When Need A Hand-built Async Logger Snapshot
When tests or adapters should construct a full async logger state explicitly:
```moonbit
let state = AsyncLoggerState::new(
runtime=AsyncRuntimeState::new(AsyncRuntimeMode::Compatibility, false),
pending_count=0,
dropped_count=0,
is_closed=false,
is_running=true,
has_failed=false,
last_error="",
flush_policy=AsyncFlushPolicy::Never,
)
```
In this example, a complete async logger snapshot is assembled directly without querying a live logger instance.
#### When Need Structured Diagnostics Input Before Serialization
When code should prepare a typed logger state value for later export:
```moonbit
let state = AsyncLoggerState::new(
runtime=async_runtime_state(),
pending_count=logger.pending_count(),
dropped_count=logger.dropped_count(),
is_closed=logger.is_closed(),
is_running=logger.is_running(),
has_failed=logger.has_failed(),
last_error=logger.last_error(),
flush_policy=logger.flush_policy(),
)
```
In this example, callers still use the direct constructor while making each diagnostic input explicit.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages the provided values.
- If callers want a snapshot directly from one live logger instance, `AsyncLogger::state()` is the simpler API.
- If callers manually combine a runtime snapshot, counters, or flush policy that do not actually belong together, the constructor still accepts that synthetic snapshot.
- If callers want the current backend-derived runtime pair instead of a synthetic one, they must pass `async_runtime_state()` explicitly or use `AsyncLogger::state()`.
- This constructor does not apply cleanup semantics such as clearing `last_error` on restart or draining pending records; callers must provide those fields exactly as they want them represented.
### Notes
1. Use this helper when code should construct an `AsyncLoggerState` value explicitly.
2. Pair it with `async_logger_state_to_json(...)` or `stringify_async_logger_state(...)` when the snapshot should be exported.
3. Prefer `AsyncLogger::state()` when the goal is to report the actual current state of one live logger instance.
+90
View File
@@ -0,0 +1,90 @@
---
name: async-logger-state-to-json
group: api
category: async
update-time: 20260614
description: Convert an AsyncLoggerState snapshot into a JSON value for diagnostics and transport using the canonical nested runtime shape and flush-policy labels.
key-word:
- async
- state
- json
- public
---
## Async-logger-state-to-json
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_parser.JsonValue {}
```
#### input
- `state : AsyncLoggerState` - Snapshot produced by `AsyncLogger::state()` or any manually constructed `AsyncLoggerState` value.
#### output
- `JsonValue` - Structured JSON representation of the async logger snapshot.
### Explanation
Detailed rules explaining key parameters and behaviors
- The JSON includes runtime mode, worker support, queue counters, lifecycle flags, last error, and flush policy.
- The top-level fields are `runtime`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `last_error`, and `flush_policy`.
- The nested `runtime` field reuses `async_runtime_state_to_json(...)`, and `flush_policy` is serialized with the canonical labels `Never`, `Batch`, or `Shutdown`.
- The public helper returns the same internal JSON snapshot shape used by `stringify_async_logger_state(...)`, so both export paths stay aligned without duplicate field assembly logic.
- This helper is suitable for health endpoints, diagnostics payloads, and custom serialization flows.
- It shares the same stable field names used by `stringify_async_logger_state(...)`.
- The state must already have been captured or constructed before serialization.
- Serialization preserves whatever snapshot combination it receives, including failure flags together with remaining backlog counts.
- This helper never rereads a logger instance by itself. If callers pass an older or manually constructed `AsyncLoggerState`, the JSON reflects that provided value exactly rather than refreshing fields from live runtime state.
- It also does not normalize mixed diagnostic combinations. If the provided snapshot says `is_closed=true` together with retained `has_failed=true`, `last_error`, or non-zero backlog counters, those exact combinations are emitted unchanged.
### How to Use
Here are some specific examples provided.
#### When Need Machine-readable Diagnostics
When the snapshot should be embedded into a JSON payload:
```moonbit
let state_json = async_logger_state_to_json(logger.state())
```
In this example, callers receive a structured value that can be composed into larger JSON objects.
#### When Need A Snapshot Before Custom Stringify
When another serializer or pipeline expects a JSON value:
```moonbit
let payload = async_logger_state_to_json(logger.state())
println(@json_parser.stringify(payload))
```
In this example, the helper stays useful even outside the built-in stringify wrapper.
### Error Case
e.g.:
- If the snapshot contains no error, `last_error` is serialized as an empty string.
- If the queue is empty, `pending_count` and `dropped_count` are still serialized normally as numeric values.
- If `has_failed` is `true`, serialization does not force `pending_count` to `0` or clear `last_error`; it reports the snapshot exactly as provided.
- If callers need newer logger data, they must capture a fresh `AsyncLogger::state()` first instead of expecting JSON conversion itself to refresh stale fields.
### Notes
1. This helper preserves the nested runtime snapshot instead of flattening `mode` and `background_worker` onto the top level.
2. The resulting object matches the compact string form produced by `stringify_async_logger_state(...)` after JSON stringification.
3. Use this API when downstream code wants a JSON value rather than a ready-made string.
4. Pair it with `AsyncLogger::state()` when you want current logger data, or with `AsyncLoggerState::new(...)` when tests or adapters are exporting a synthetic snapshot.
+86
View File
@@ -0,0 +1,86 @@
---
name: async-logger-state-type
group: api
category: async
update-time: 20260614
description: Public async logger state alias used for queue, lifecycle, runtime, and flush-policy diagnostics.
key-word:
- async
- logger
- state
- public
---
## Async-logger-state-type
`AsyncLoggerState` is the public snapshot type used to describe an async logger's runtime status. It is a direct alias to the async logger state model returned by `AsyncLogger::state()` and used by async diagnostics serializers.
### Interface
```moonbit
pub type AsyncLoggerState = @utils.AsyncLoggerState
```
#### output
- `AsyncLoggerState` - Public async logger snapshot containing `runtime`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `last_error`, and `flush_policy`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a live logger handle.
- The `runtime` field embeds an `AsyncRuntimeState` snapshot.
- The remaining fields capture queue counts, lifecycle status, failure state, last error text, and active flush policy.
- `AsyncLogger::state()` returns this type directly, while `async_logger_state_to_json(...)` and `stringify_async_logger_state(...)` export the same data shape for diagnostics.
- `AsyncLogger::state()` currently builds this snapshot from `async_runtime_state()` plus the logger's current counters, lifecycle flags, last error, and flush policy.
- When the value comes from `AsyncLogger::state()`, the fields are read one by one rather than through a transactional snapshot primitive.
- `AsyncLoggerState::new(...)` can also construct this type manually, but manual construction is synthetic snapshot data and does not read one live logger instant by itself.
- The type itself does not distinguish live logger snapshots from hand-built ones; callers must track whether a given `AsyncLoggerState` value came from `AsyncLogger::state()` or from manual construction.
- When a live snapshot is taken after worker failure, `has_failed=true`, a retained `last_error`, and non-zero `pending_count` may legitimately coexist until later cleanup or restart.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Snapshot For Async Diagnostics
When application code should inspect async logger state before deciding how to report it:
```moonbit
let state : AsyncLoggerState = logger.state()
```
In this example, queue, lifecycle, and runtime information stay available as structured data.
#### When Need To Branch On Failure Or Backlog
When code should react to the current async logger condition:
```moonbit
let state = logger.state()
if state.has_failed || state.pending_count > 0 {
println(stringify_async_logger_state(state, pretty=true))
}
```
In this example, the typed snapshot supports both logic and later export.
### Error Case
e.g.:
- `AsyncLoggerState` itself does not have a runtime failure mode.
- `last_error` may be an empty string when no failure has occurred, which is normal and not a special error condition by itself.
- Because this is just a data shape, manual construction can represent combinations that do not come from a live logger at one exact instant.
- Receiving an `AsyncLoggerState` value alone does not prove it came from one current logger read rather than from a synthetic constructor path.
- This type does not imply cleanup semantics by itself; values only report the supplied or captured fields and do not drain backlog or clear recorded failure state.
### Notes
1. Use `AsyncLogger::state()` when you need a value of this type from one logger instance.
2. Use `AsyncRuntimeState` when only backend-level capability information is needed and logger-instance state is unnecessary.
3. Use `async_logger_state_to_json(...)` or `stringify_async_logger_state(...)` when this snapshot should leave typed space and become stable diagnostic output.
+103
View File
@@ -0,0 +1,103 @@
---
name: async-logger-state
group: api
category: async
update-time: 20260614
description: Read a full async logger runtime snapshot including the embedded runtime snapshot, queue counters, lifecycle flags, last error, and flush policy.
key-word:
- async
- state
- diagnostics
- public
---
## Async-logger-state
Read a complete async logger runtime snapshot for diagnostics. This API is the preferred way to inspect queue backlog, dropped counts, lifecycle status, and runtime mode before exporting them through JSON helpers when needed.
### Interface
```moonbit
pub fn[S] AsyncLogger::state(self : AsyncLogger[S]) -> AsyncLoggerState {}
```
#### input
- `self : AsyncLogger[S]` - The async logger whose runtime snapshot should be read.
#### output
- `AsyncLoggerState` - A snapshot containing runtime mode, worker support, queue counts, lifecycle flags, last error, and flush policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- `AsyncLoggerState` includes `runtime`, `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, `last_error`, and `flush_policy`.
- `state()` returns a value snapshot rather than a live handle.
- This helper is equivalent to `AsyncLoggerState::new(async_runtime_state(), self.pending_count(), self.dropped_count(), self.is_closed(), self.is_running(), self.has_failed(), self.last_error(), self.flush_policy())`.
- `async_logger_state_to_json(...)` and `stringify_async_logger_state(...)` convert the snapshot to stable diagnostic output.
- `runtime` embeds the result of `async_runtime_state()` from the moment `state()` is called, so callers do not need to join separate helpers manually.
- The runtime portion is therefore recomputed from the active backend helper on each call, while the remaining fields come from the logger's current counters, flags, and flush policy at that same general moment.
- Because the snapshot is assembled field by field when `state()` is called, later logger changes require calling `state()` again rather than reusing an older `AsyncLoggerState` value as if it refreshed itself.
- That field-by-field assembly also means this helper is not an atomic freeze across all refs; under concurrent logger activity, neighboring fields can reflect slightly different instants.
- After a worker failure, `has_failed=true`, a non-empty `last_error`, and `pending_count>0` can legitimately appear together in one snapshot until later cleanup or a later started `run()` changes them.
- After shutdown cleanup on an already failed logger, snapshots can also legitimately show `is_closed=true` together with retained `has_failed=true` and the same `last_error()`, while `pending_count` versus `dropped_count` still reflects the active runtime's cleanup path.
- `state()` only reports the current field values; it does not clear failure state, drain backlog, or synchronize pending work by itself.
### How to Use
Here are some specific examples provided.
#### When Need Startup Diagnostics
When you want to expose current async logger mode and queue state at startup:
```moonbit
let logger = build_async_logger(config)
println(stringify_async_logger_state(logger.state(), pretty=true))
```
In this example, the snapshot can be printed directly without extra manual formatting.
And downstream operators can see both runtime mode and queue-related status together.
#### When Need Failure Investigation Data
When diagnosing async delivery issues:
```moonbit
let state = logger.state()
if state.has_failed {
println(stringify_async_logger_state(state, pretty=true))
}
```
In this example, the same snapshot object works for conditional diagnostics and serialization.
And the reported failure fields can still appear together with non-zero backlog when a worker stopped early.
### Error Case
e.g.:
- If no error has occurred, `last_error` is just an empty string.
- If the queue is empty, `pending_count` is `0`; this is normal and not a special error condition.
- `flush_policy` reports the logger's configured async flush mode, not whether a flush has already happened.
- The embedded `runtime` object is also just a snapshot of the current backend helper result at read time; `state()` does not cache it onto the logger for later reuse.
- If concurrent logger activity is still changing counters or flags while `state()` runs, the returned value is still useful for diagnostics but should not be treated as a transactional snapshot.
- A snapshot showing `has_failed=true` does not imply `pending_count` is already `0`; remaining queued records may still be visible until later cleanup or restart.
- A snapshot showing `is_closed=true` also does not imply failure state was cleared; after failure-driven shutdown, `has_failed=true` and the recorded `last_error` can still remain visible until a later `run()` actually restarts the logger.
### Notes
1. Prefer this API over manually combining `pending_count()`, `dropped_count()`, and runtime-mode helpers.
2. Use `pretty=true` when emitting logs for humans and the compact form for machine-oriented payloads.
3. Use `AsyncLoggerState::new(...)` only when tests or adapters need to construct a manual snapshot instead of reading one directly from a logger instance.
4. If consumers need stronger cross-field consistency than a diagnostic snapshot, they should not assume `state()` provides an atomic read barrier.
@@ -0,0 +1,98 @@
---
name: async-logger-to-library-async-logger
group: api
category: facade
update-time: 20260614
description: Convert a full async logger into the narrower library-facing async facade without rebuilding or detaching the underlying async state.
key-word:
- async
- library
- facade
- public
---
## Async-logger-to-library-async-logger
Convert `AsyncLogger[S]` into `LibraryAsyncLogger[S]`. This keeps the same async queue and sink behavior while projecting the value onto the smaller library-facing async surface.
### Interface
```moonbit
pub fn[S] AsyncLogger::to_library_async_logger(self : AsyncLogger[S]) -> LibraryAsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Full async logger to project into the library facade.
#### output
- `LibraryAsyncLogger[S]` - Narrower library-facing wrapper over the same async logger state.
### Explanation
Detailed rules explaining key parameters and behaviors
- This conversion does not rebuild the queue, sink, or runtime state.
- Target, min level, async config, flush behavior, pending counts, and failure state are preserved because the same underlying async logger value is wrapped.
- The original `AsyncLogger[S]` handle remains the same live logger. If caller code keeps that original value, later facade calls and later unwraps still observe the same shared queue, counters, sink helpers, and lifecycle mutations.
- The returned facade keeps library-facing async operations including `log(...)`, `run()`, and `shutdown(...)`.
- Async inspection helpers and broader composition APIs remain on the underlying `AsyncLogger[S]` and are intentionally hidden until `to_async_logger()` is used again.
- If later facade-level `run()` or `shutdown()` calls record worker failure, leave backlog behind, or follow runtime-dependent shutdown cleanup rules, unwrapping later still exposes that same post-call state instead of a translated facade copy.
- That includes states where delegated shutdown already finished with `is_closed=true` while retained `has_failed()` and `last_error()` remain on the wrapped logger, together with the same runtime-dependent pending-versus-dropped cleanup outcome.
- When `S` itself exposes richer runtime helpers, projecting to the library facade does not strip those capabilities from the wrapped logger; they are still reachable after `to_async_logger()`.
- When `S` is `RuntimeSink`, projection also preserves queued runtime state and file-backed runtime helper behavior behind the facade instead of replacing them with a library-specific copy.
- Unwrapping later with `to_async_logger()` therefore exposes the same queue counters, failure snapshots, file state, and runtime file controls that the original async logger already carried.
- That also means runtime sink mutations still alias in both directions: changing file append mode, auto-flush, rotation, or other sink helper state through a later unwrapped logger changes the same live runtime sink that the original `AsyncLogger[S]` already held.
### How to Use
Here are some specific examples provided.
#### When Need To Expose A Narrower Async Type
When internal setup uses the full async logger API but public library code should return a smaller facade:
```moonbit
let logger = async_logger(console_sink(), target="lib.async")
let public_logger = logger.to_library_async_logger()
```
In this example, `public_logger` keeps the same async behavior but exposes the library-facing facade.
#### When Need To Narrow Surface Without Resetting Runtime State
When an already-used async logger should be projected to a library boundary without changing its current state:
```moonbit
let full = build_async_logger(config)
let public_logger = full.to_library_async_logger()
ignore(public_logger.is_enabled(@bitlogger.Level::Info))
```
In this example, the projection changes the exposed type only; it does not rebuild queue or lifecycle state.
If `full` is still kept elsewhere, it continues sharing that same live runtime state with `public_logger`.
### Error Case
e.g.:
- If callers later need APIs outside the library facade, they must unwrap with `to_async_logger()`.
- The conversion does not clear pending items or reset runtime state.
- If callers later need state helpers such as `pending_count()` or `state()`, they must unwrap again with `to_async_logger()`.
- Projection does not normalize failure snapshots; a later unwrap can still show combinations such as retained `last_error()` with remaining `pending_count()` when the wrapped async logger really ended up in that state.
- Projection also does not normalize delegated shutdown results; a later unwrap can still show `is_closed=true` together with retained `has_failed()`, the same `last_error()`, and the same runtime-dependent leftover backlog or dropped-count cleanup that accumulated behind the facade.
- Projection also does not normalize richer runtime sink state; if the original async logger already carried queued runtime data or file-backed helper state, a later unwrap still exposes that same live state.
- Projection does not create an isolated wrapper copy. If callers keep the original `AsyncLogger[S]`, then later facade-level writes, shutdown, or sink-helper mutations still affect that original handle too.
### Notes
1. Use this when package boundaries should avoid exposing the full async logger type.
2. This is a projection API, not a reconfiguration step.
3. Use `build_library_async_logger(...)` or `build_library_async_text_logger(...)` when construction and narrowing should happen together from config.
+86
View File
@@ -0,0 +1,86 @@
---
name: async-logger-trace
group: api
category: async
update-time: 20260614
description: Enqueue a trace-level record through the async logger using the lowest built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
- trace
- public
---
## Async-logger-trace
Enqueue a trace-level record through the async logger. This is the convenience wrapper for `log(Level::Trace, ...)`.
### Interface
```moonbit
pub async fn[S] AsyncLogger::trace(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the trace record.
- `message : String` - Trace message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
#### output
- `Unit` - No return value. The record is handled according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Trace, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Trace records are often skipped in production because they are the lowest built-in severity.
- Use this helper when explicit trace intent is clearer than a raw `log(...)` call.
### How to Use
Here are some specific examples provided.
#### When Need Fine-grained Async Diagnostics
When low-level execution flow should be observable during debugging:
```moonbit
logger.trace("entered reconciliation step")
```
In this example, the call site makes trace intent explicit.
#### When Attach Structured Trace Data
When a trace event should carry extra fields:
```moonbit
logger.trace(
"cache probe",
fields=[@bitlogger.field("key", "user:42")],
)
```
In this example, the record stays lightweight while still carrying structured detail.
### Error Case
e.g.:
- If the logger minimum level is above `Trace`, the record is skipped before enqueue.
- If the logger is closed or overflow policy prevents acceptance, the write may not become a normal queued record.
### Notes
1. Prefer this helper when trace intent is more readable than `log(Level::Trace, ...)`.
2. Use `log(...)` instead when one trace call needs a one-off target override.
3. Trace-level async logging can increase queue pressure quickly under verbose workloads.
+101
View File
@@ -0,0 +1,101 @@
---
name: async-logger-type
group: api
category: async
update-time: 20260614
description: Public asynchronous logger root type used for queue-backed sink-preserving logging pipelines with explicit lifecycle, failure, and flush-callback state.
key-word:
- async
- logger
- type
- public
---
## Async-logger-type
`AsyncLogger[S]` is the public asynchronous root logger type. It stores a concrete sink type `S` together with queue policy, runtime state counters, and lifecycle flags, then serves as the base value for async composition, worker control, and async write APIs.
### Interface
```moonbit
pub struct AsyncLogger[S] {
min_level : @bitlogger.Level
target : String
timestamp : Bool
overflow : AsyncOverflowPolicy
max_batch : Int
linger_ms : Int
flush_policy : AsyncFlushPolicy
sink : S
flush_sink : (S) -> Int raise
context_fields : Array[@bitlogger.Field]
filter : (@bitlogger.Record) -> Bool
patch : @bitlogger.RecordPatch
queue : @async.Queue[@bitlogger.Record]
pending_count : Ref[Int]
dropped_count : Ref[Int]
is_closed : Ref[Bool]
is_running : Ref[Bool]
has_failed : Ref[Bool]
last_error : Ref[String]
}
```
#### output
- `AsyncLogger[S]` - Public queue-backed asynchronous logger value parameterized by the concrete sink type `S`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current fields cover targeting, overflow policy, batching, linger timing, flush policy, the wrapped sink, context/filter/patch behavior, queue state, and worker lifecycle flags.
- `flush_sink : (S) -> Int raise` stores the raising flush callback used by batch and shutdown flush policies.
- `pending_count`, `dropped_count`, `is_closed`, `is_running`, `has_failed`, and `last_error` are mutable runtime refs that power the higher-level lifecycle and diagnostics helpers.
- The sink type parameter is preserved across async composition, which is why helpers such as `with_target(...)`, `with_context_fields(...)`, `with_filter(...)`, and `with_patch(...)` keep returning `AsyncLogger[S]`.
- `async_logger(...)` constructs this type as the main asynchronous entry point.
- This root type is also what sits underneath both async facade families: `ApplicationAsyncLogger` is a direct alias over the runtime-sink line, while `LibraryAsyncLogger[S]` is a narrowing wrapper around an `AsyncLogger[S]` value.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Root Async Logger Value
When queue-backed logging should begin from a concrete sink-preserving root object:
```moonbit
let logger : AsyncLogger[ConsoleSink] = async_logger(console_sink(), target="app.async")
```
In this example, the root async logger keeps the concrete sink type visible for later typed composition.
#### When Need Worker Control Plus Runtime State
When code should both enqueue logs and inspect async runtime behavior:
```moonbit
let logger = async_logger(console_sink(), config=AsyncLoggerConfig::new(max_pending=32))
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the root type combines queue-backed logging with explicit runtime observability.
### Error Case
e.g.:
- `AsyncLogger[S]` itself does not have a runtime failure mode.
- Actual enqueue, worker, and flush behavior still depends on the wrapped sink `S`, async runtime support, and the configured queue policy.
- Post-close logging behavior is not a property of the struct shape alone; it also depends on the active runtime implementation.
### Notes
1. Use `async_logger(...)`, `build_async_logger(...)`, or `build_async_text_logger(...)` when you need a value of this type.
2. Use `ApplicationAsyncLogger` when application code wants the same full async lifecycle and state helper surface under an app-facing alias.
3. Use `LibraryAsyncLogger[S]` when a library boundary should intentionally narrow the public async surface and expose broader lifecycle/state helpers only through `to_async_logger()`.
4. Prefer the method helpers such as `state()`, `has_failed()`, `last_error()`, `is_running()`, and `shutdown()` instead of reading these public fields conceptually as if they were a stable manual mutation surface.
+86
View File
@@ -0,0 +1,86 @@
---
name: async-logger-wait-idle
group: api
category: async
update-time: 20260614
description: Wait until the async logger backlog drains to zero or a worker failure interrupts normal progress, using the repo's direct async call style.
key-word:
- async
- logger
- queue
- public
---
## Async-logger-wait-idle
Wait until the async logger backlog drains to zero. This helper is useful when callers want to observe idle state without fully shutting down the logger.
### Interface
```moonbit
pub async fn[S] AsyncLogger::wait_idle(self : AsyncLogger[S]) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger whose backlog should be waited on.
#### output
- `Unit` - No return value. The method completes when the logger becomes idle or failure prevents normal progress.
### Explanation
Detailed rules explaining key parameters and behaviors
- The helper keeps yielding while `pending_count() > 0`.
- If `has_failed()` becomes `true`, waiting stops early instead of continuing to spin.
- This API does not close the logger or stop the worker.
- A return from `wait_idle()` therefore means either backlog reached `0` or failure interrupted normal drain progress.
- `wait_idle()` does not clear retained failure state by itself, so if pending records remain after a worker failure, later `wait_idle()` calls also short-circuit until another path changes that state.
- A later `run()` can make `wait_idle()` meaningful again for the retained backlog, but only after that new worker invocation has actually started and reset the stale failure flag.
- If no worker is draining the queue and no failure flag is raised, `wait_idle()` can wait indefinitely.
- It is narrower than `shutdown()` and is useful when the logger should continue to be used later.
### How to Use
Here are some specific examples provided.
#### When Need A Drain Barrier Without Shutdown
When code should wait for queued work to flush before continuing:
```moonbit
logger.wait_idle()
```
In this example, the caller waits for backlog drain but leaves the logger usable afterward.
#### When Need Phase Boundaries In Tests
When a test wants to ensure earlier async logs were processed:
```moonbit
logger.wait_idle()
println("phase complete")
```
In this example, the wait acts as a barrier between test phases.
### Error Case
e.g.:
- If the worker has failed, `wait_idle()` stops waiting even if pending records remain.
- If the worker was never started, or if nothing is making pending records decrease, `wait_idle()` can block indefinitely.
- If callers need backlog cleanup after a failure-short-circuit, they still need a later `close(clear=true)` or `shutdown(...)` path.
- In the current direct coverage, `wait_idle()` can return with `pending_count() > 0` after a worker failure, and the later cleanup path may either clear that backlog explicitly or follow the runtime-dependent shutdown split documented on `shutdown(...)`.
- If callers retry `wait_idle()` immediately after such a failure without restarting the worker or forcing cleanup first, the call can return again with the same retained pending backlog.
### Notes
1. Use this helper when you want a drain barrier without closing the logger.
2. Prefer `shutdown()` when lifecycle completion matters more than continued reuse.
3. Pair it with `pending_count()` or `state()` when you need to distinguish true idleness from an early stop caused by worker failure.
+88
View File
@@ -0,0 +1,88 @@
---
name: async-logger-warn
group: api
category: async
update-time: 20260614
description: Enqueue a warning-level record through the async logger using the built-in severity shortcut and the repo's direct async call style.
key-word:
- async
- logger
- warn
- public
---
## Async-logger-warn
Enqueue a warning-level record through the async logger. This is the convenience wrapper for `log(Level::Warn, ...)`.
### Interface
```moonbit
pub async fn[S] AsyncLogger::warn(
self : AsyncLogger[S],
message : String,
fields~ : Array[@bitlogger.Field] = [],
) -> Unit {}
```
#### input
- `self : AsyncLogger[S]` - Async logger that should receive the warning record.
- `message : String` - Warning message text.
- `fields : Array[Field]` - Optional structured fields added to the record.
#### output
- `Unit` - No return value. The record is handled according to logger state and policy.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates to `log(Level::Warn, ..., fields=fields)`.
- The record is still subject to min-level gating, patching, filtering, and overflow policy.
- This helper does not accept a per-call target override. It uses the logger's stored target unless the logger was derived earlier with `with_target(...)` or `child(...)`.
- Warning records are useful for degraded but non-fatal runtime conditions.
- Use this helper when a named warning call is clearer than a raw `log(...)` call.
### How to Use
Here are some specific examples provided.
#### When Need Async Degradation Signals
When the system should report a non-fatal problem:
```moonbit
logger.warn("retry budget running low")
```
In this example, the event is surfaced at warning severity without using the generic `log(...)` form.
#### When Attach Structured Warning Detail
When a warning event should include context:
```moonbit
logger.warn(
"queue near capacity",
fields=[@bitlogger.field("pending", logger.pending_count().to_string())],
)
```
In this example, the warning carries structured operational detail.
And any shared context already carried by the logger still participates ahead of these per-call fields when the record is built.
The write still uses the logger's stored target because this shortcut does not take a one-off `target=` override.
### Error Case
e.g.:
- If the logger minimum level is above `Warn`, the record is skipped before enqueue.
- If the logger is closed or overflow policy prevents acceptance, the write may not become a normal queued record.
### Notes
1. Use this helper for notable but non-fatal async runtime conditions.
2. Use `log(...)` instead when one warning call must override the target without deriving a new logger value.
@@ -0,0 +1,97 @@
---
name: async-logger-with-context-fields
group: api
category: async
update-time: 20260512
description: Attach reusable structured fields to an async logger so every queued record inherits them.
key-word:
- async
- logger
- fields
- public
---
## Async-logger-with-context-fields
Bind shared structured fields to an async logger. This is the standard way to attach stable metadata such as service name, component, region, or subsystem identity without repeating them for every async log call.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_context_fields(
self : AsyncLogger[S],
fields : Array[@bitlogger.Field],
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger that should gain shared fields.
- `fields : Array[Field]` - Structured fields that will be prepended to each emitted record.
#### output
- `AsyncLogger[S]` - A new async logger value carrying the shared field set.
### Explanation
Detailed rules explaining key parameters and behaviors
- Context fields are merged during `log(...)` before enqueue.
- When a log call also passes per-record fields, the context fields are placed before those per-call fields.
- This API returns a new logger value; it does not mutate the original async logger.
- The provided `fields` array replaces the previously stored shared field set on the returned async logger; it does not append onto whatever `context_fields` the source logger already had.
- Unlike synchronous `Logger::with_context_fields(...)`, this async variant stores fields directly on `AsyncLogger` instead of changing the visible sink type.
- Only the stored `context_fields` value changes. Target, minimum level, timestamp flag, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, the original logger keeps its previous `context_fields`, while the derived logger prepends the stored shared fields ahead of per-call fields exactly once when records are built.
### How to Use
Here are some specific examples provided.
#### When Need Stable Async Service Metadata
When every queued record should carry service-level metadata:
```moonbit
let logger = async_logger(console_sink(), target="billing")
.with_context_fields([
@bitlogger.field("service", "billing"),
@bitlogger.field("region", "cn"),
])
```
In this example, both fields are attached before each record enters the queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Build Child Async Loggers For Subsystems
When a subsystem has both a target and fixed fields:
```moonbit
let worker = async_logger(console_sink(), target="app")
.child("worker")
.with_context_fields([@bitlogger.field("component", "worker")])
```
In this example, target composition and field binding stay separate but work together cleanly.
### Error Case
e.g.:
- If `fields` is empty, the logger remains valid and just adds no extra metadata.
- If a derived async logger already had shared context fields, calling `with_context_fields(...)` again replaces that stored shared field set on the new derived logger rather than stacking both sets together.
- If duplicate field keys are provided, all fields are still emitted; conflict handling is left to the consumer side.
### Notes
1. Use this for stable metadata, not highly dynamic event-specific values.
2. This async variant preserves the visible `AsyncLogger[S]` type while still injecting shared fields.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a fresh derived logger when one code path needs shared metadata and another should stay unchanged.
5. If you need to combine two shared field sets, combine them in the `fields` argument yourself instead of expecting repeated `with_context_fields(...)` calls to accumulate them.
+90
View File
@@ -0,0 +1,90 @@
---
name: async-logger-with-filter
group: api
category: async
update-time: 20260512
description: Attach predicate-based filtering to an async logger so only matching records reach the queue.
key-word:
- async
- logger
- filter
- public
---
## Async-logger-with-filter
Attach a record predicate to an async logger so only matching records are enqueued. This is the main API for async routing-by-predicate without rewriting sink implementations.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_filter(
self : AsyncLogger[S],
predicate : (@bitlogger.Record) -> Bool,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger to constrain.
- `predicate : (Record) -> Bool` - Record predicate that decides whether a record should be enqueued.
#### output
- `AsyncLogger[S]` - A new async logger value that only enqueues matching records.
### Explanation
Detailed rules explaining key parameters and behaviors
- Filtering happens after record construction and patch application but before enqueue.
- Existing filter logic is preserved and combined with the new predicate using logical `and`.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored filter pipeline changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- Filtering avoids unnecessary queue pressure for records that should never be delivered.
- In the current direct async coverage, derived filters can compose target, level, and message predicates together while the original logger still accepts writes according to its previous filter state.
### How to Use
Here are some specific examples provided.
#### When Keep Only One Async Target Namespace
When an async logger should only enqueue records from a specific subsystem:
```moonbit
let logger = async_logger(console_sink(), target="service")
.with_filter(@bitlogger.target_has_prefix("service.api"))
```
In this example, non-matching records are dropped before they reach the async queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Combine Several Async Filter Rules
When filtering depends on multiple conditions:
```moonbit
let logger = async_logger(console_sink(), target="api")
.with_filter(fn(rec) {
rec.level.enabled(@bitlogger.Level::Warn) && rec.message.contains("timeout")
})
```
In this example, only warning-or-higher timeout records are enqueued.
### Error Case
e.g.:
- If the predicate always returns `false`, the logger silently drops every record before enqueue.
- If the predicate always returns `true`, the wrapper behaves like a pass-through filter.
### Notes
1. Use this API for selection logic, not record mutation.
2. Async filtering is especially useful when queue capacity should be reserved for high-value records.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when one branch should enforce extra filter rules and the base async logger should stay unchanged.
+88
View File
@@ -0,0 +1,88 @@
---
name: async-logger-with-min-level
group: api
category: async
update-time: 20260512
description: Replace the async logger minimum enabled level so lower-severity records are skipped before enqueue.
key-word:
- async
- logger
- level
- public
---
## Async-logger-with-min-level
Replace the async logger's minimum enabled level. This API controls the first gate checked before record creation and queue insertion.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_min_level(
self : AsyncLogger[S],
min_level : @bitlogger.Level,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose level threshold should change.
- `min_level : Level` - New minimum enabled level.
#### output
- `AsyncLogger[S]` - A new async logger value carrying the updated threshold.
### Explanation
Detailed rules explaining key parameters and behaviors
- `log(...)` checks `is_enabled(level)` before creating a record or touching the queue.
- Lower-severity records below `min_level` are skipped before enqueue.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- This API replaces the stored threshold and does not alter queue configuration.
- Only `min_level` changes. Sink, target, timestamp flag, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, the derived logger reports the new threshold through `is_enabled(...)`, while the original logger keeps its previous minimum level and still accepts records that remain enabled there.
### How to Use
Here are some specific examples provided.
#### When Raise Async Noise Floor In Production
When only warning and error records should reach the async queue:
```moonbit
let logger = async_logger(console_sink())
.with_min_level(@bitlogger.Level::Warn)
```
In this example, lower-severity records are skipped before queue pressure increases.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Derive A More Verbose Async Branch
When one branch of code should keep a different threshold:
```moonbit
let base = async_logger(console_sink(), min_level=@bitlogger.Level::Info)
let debug_logger = base.with_min_level(@bitlogger.Level::Debug)
```
In this example, the async sink and queue settings are reused while the threshold changes per logger value.
### Error Case
e.g.:
- If `min_level` is set too high, expected lower-severity diagnostics may disappear before they ever enter the queue.
- If callers need richer predicate logic than a simple threshold, `with_filter(...)` should be used instead.
### Notes
1. This API reduces async queue pressure by dropping disabled levels before enqueue.
2. Use it before adding more complex async filtering rules.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when one branch should tighten the threshold and the base async logger should keep its broader level gate.
+90
View File
@@ -0,0 +1,90 @@
---
name: async-logger-with-patch
group: api
category: async
update-time: 20260512
description: Attach record transformation logic to an async logger before records reach the queue.
key-word:
- async
- logger
- patch
- public
---
## Async-logger-with-patch
Attach a record patch to an async logger so each record is transformed before filtering and enqueue. This is the main API for async record rewriting without changing the sink implementation.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_patch(
self : AsyncLogger[S],
patch : @bitlogger.RecordPatch,
) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger to wrap.
- `patch : RecordPatch` - Transformation applied to each record before enqueue.
#### output
- `AsyncLogger[S]` - A new async logger value that rewrites each record before filtering and queue insertion.
### Explanation
Detailed rules explaining key parameters and behaviors
- Patch logic runs after record creation and field merging but before filtering and enqueue.
- Existing patch logic is preserved and composed so the new patch wraps the current one.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored patch pipeline changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- Patching can normalize, redact, or enrich records before they consume queue capacity.
- In the current direct async coverage, patched target/message/field changes are visible to later filter logic, and the original async logger still emits unpatched records when used separately.
### How to Use
Here are some specific examples provided.
#### When Need Async Record Enrichment
When records should gain stable extra data before enqueue:
```moonbit
let logger = async_logger(console_sink())
.with_patch(@bitlogger.append_fields([
@bitlogger.field("channel", "async"),
]))
```
In this example, the added fields are part of the record before filtering and queue insertion.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Need Redaction Before Queueing
When sensitive fields should be removed early:
```moonbit
let logger = async_logger(console_sink())
.with_patch(@bitlogger.redact_fields(["token", "password"]))
```
In this example, sensitive values are rewritten before they enter the async pipeline.
### Error Case
e.g.:
- If the patch removes or rewrites important data incorrectly, later filters and sinks will only see the patched version.
- If callers need selection logic rather than transformation, `with_filter(...)` should be used instead.
### Notes
1. Use patches for transformation, not filtering decisions.
2. Redaction before enqueue helps keep sensitive data out of the queued pipeline.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Derive a patched logger when one path needs rewritten records and another should keep the original async record shape.
+81
View File
@@ -0,0 +1,81 @@
---
name: async-logger-with-target
group: api
category: async
update-time: 20260512
description: Replace the default target carried by an async logger so later records inherit a new target namespace.
key-word:
- async
- logger
- target
- public
---
## Async-logger-with-target
Replace the async logger's default target. This API retargets later enqueue operations without changing the queue, overflow policy, or sink wiring.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_target(self : AsyncLogger[S], target : String) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose default target should be replaced.
- `target : String` - New default target namespace.
#### output
- `AsyncLogger[S]` - A new async logger value carrying the updated target.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned logger keeps the same sink, queue state, overflow policy, flush policy, and lifecycle flags.
- This API replaces the default target instead of composing it.
- Per-call `target?` arguments on `log(...)` can still override the default target.
- The original logger value is not mutated.
- In the current direct async coverage, derived loggers keep existing flags such as `timestamp`, while the original logger still retains its previous target.
### How to Use
Here are some specific examples provided.
#### When Need A Stable Async Target Namespace
When one async logger should always emit under a fixed target:
```moonbit
let logger = async_logger(console_sink())
.with_target("service.worker")
```
In this example, later async log calls inherit `service.worker` unless a call overrides the target explicitly.
#### When Reuse One Async Setup Across Namespaces
When multiple subsystem loggers should share the same async queue behavior:
```moonbit
let base = async_logger(console_sink(), config=AsyncLoggerConfig::new(max_pending=64))
let api = base.with_target("api")
let jobs = base.with_target("jobs")
```
In this example, target routing changes without rebuilding the async runtime configuration.
### Error Case
e.g.:
- If `target` is empty, the logger remains valid and later records default to an empty target.
- If callers need hierarchical target composition rather than replacement, `child(...)` is the better API.
### Notes
1. Use this API for replacement, not parent-child target composition.
2. It is useful when several subsystems should share one async queue policy.
3. Use it when you want a derived logger value; the original async logger keeps its earlier default target.
+85
View File
@@ -0,0 +1,85 @@
---
name: async-logger-with-timestamp
group: api
category: async
update-time: 20260512
description: Enable or disable automatic timestamp capture for records emitted by an async logger.
key-word:
- async
- logger
- timestamp
- public
---
## Async-logger-with-timestamp
Enable or disable automatic timestamp capture on async log emission. This API controls whether `AsyncLogger::log(...)` records the current time before enqueue or leaves the timestamp at `0UL`.
### Interface
```moonbit
pub fn[S] AsyncLogger::with_timestamp(self : AsyncLogger[S], enabled~ : Bool = true) -> AsyncLogger[S] {}
```
#### input
- `self : AsyncLogger[S]` - Base async logger whose timestamp behavior should change.
- `enabled : Bool` - Whether emitted records should capture current time automatically.
#### output
- `AsyncLogger[S]` - A new async logger value with updated timestamp behavior.
### Explanation
Detailed rules explaining key parameters and behaviors
- When enabled, `log(...)` captures `@env.now()` before placing the record into the queue.
- When disabled, emitted records use `0UL` as the timestamp value.
- This setting affects later emitted records only.
- The returned logger is derived from `self`; the original async logger value is not mutated.
- Only the stored `timestamp` flag changes. Target, minimum level, queue configuration, and lifecycle/failure state stay on the same `AsyncLogger[S]` surface.
- In the current direct async coverage, a derived timestamp-enabled logger records non-zero timestamps while the original logger continues emitting `0UL` timestamps when it was left disabled.
### How to Use
Here are some specific examples provided.
#### When Need Real Event Time Before Queueing
When downstream formatting or JSON output should include event time:
```moonbit
let logger = async_logger(console_sink())
.with_timestamp()
```
In this example, each record captures its timestamp before entering the async queue.
And the returned async logger still keeps the same queue-facing API surface as the source logger.
#### When Need Deterministic Async Records
When timestamps should be disabled for tests or reduced output:
```moonbit
let logger = async_logger(console_sink())
.with_timestamp(enabled=false)
```
In this example, queued records are emitted without runtime time capture.
### Error Case
e.g.:
- If timestamps are disabled, formatters that expect meaningful time values may show empty or zero-like timestamp output.
- If callers need timestamps only for one record, a separate logger value is usually clearer than toggling behavior repeatedly.
### Notes
1. This API controls record creation before enqueue, not formatter display policy.
2. It is useful for tests, deterministic snapshots, and production timing.
3. State helpers such as `pending_count()`, `dropped_count()`, `is_closed()`, and `has_failed()` remain available on the returned logger because the visible async logger surface is preserved.
4. Use a derived logger value when only one branch should capture timestamps and the base logger should remain deterministic.
+141
View File
@@ -0,0 +1,141 @@
---
name: async-logger
group: api
category: async
update-time: 20260614
description: Create an async logger with bounded queueing, overflow policy, lifecycle helpers, background run control, and a raising flush callback.
key-word:
- async
- logger
- queue
- public
---
## Async-logger
Create an `AsyncLogger[S]` on top of a sink and async queue configuration. This API is the main entry for queue-backed async logging, including overflow policy, batching, lifecycle control, and runtime observability.
### Interface
```moonbit
pub fn[S] async_logger(
sink : S,
config~ : AsyncLoggerConfig = AsyncLoggerConfig::new(),
min_level~ : @bitlogger.Level = @bitlogger.Level::Info,
target~ : String = "",
flush~ : (S) -> Int raise = fn(_) { 0 },
) -> AsyncLogger[S] {}
```
#### input
- `sink : S` - Underlying sink used after queue drain.
- `config : AsyncLoggerConfig` - Queue size, overflow behavior, batching, linger, and flush policy.
- `min_level : Level` - Level gate applied before enqueue.
- `target : String` - Default target for emitted records.
- `flush : (S) -> Int raise` - Flush callback used by batch/shutdown flush policies and allowed to raise if sink flushing fails.
#### output
- `AsyncLogger[S]` - A queue-backed async logger with lifecycle and state helpers.
### Explanation
Detailed rules explaining key parameters and behaviors
- `async_logger(...)` only builds the logger. Actual background draining is started by `run()`.
- `async_logger(...)` returns the full `AsyncLogger[S]` surface directly. It is therefore the underlying constructor used by both application-facing async aliases and the narrower `LibraryAsyncLogger[S]` wrapper line.
- The constructed logger starts with `is_closed=false`, `is_running=false`, `has_failed=false`, `last_error=""`, and zeroed pending/dropped counters.
- The constructed logger also keeps the core async target contract unchanged: `log(..., target=...)` can override the target for one call, while fixed-level helpers such as `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- Unlike synchronous `Logger`, async `with_context_fields(...)` and `bind(...)` preserve the visible `AsyncLogger[S]` type because shared fields are stored directly on the async logger value instead of being modeled as a separate sink wrapper.
- `ApplicationAsyncLogger` and `ApplicationTextAsyncLogger` are only alias names over concrete `AsyncLogger[...]` shapes, so they keep the same lifecycle, queue, failure, and state helpers without adding a wrapper layer.
- `LibraryAsyncLogger[S]` wraps an `AsyncLogger[S]` value instead of aliasing it. That library facade preserves queue-backed logging behavior, but it narrows the directly exposed helper surface until callers recover the full logger with `to_async_logger()`.
- In non-native targets, the implementation uses compatibility behavior while keeping the same public surface.
- `src-async` is designed for `native / llvm / js / wasm / wasm-gc`, but current release-facing local verification is stronger for `native / js / wasm / wasm-gc` than for `llvm`.
- `llvm` should currently be read as experimental and locally unverified in this environment rather than as a stable checked target.
- `flush` is used only when batch or shutdown policy wants explicit flushing.
- If the supplied flush callback raises, worker failure state is recorded through `has_failed()` and `last_error()`.
- `wait_idle()` is failure-aware rather than a pure backlog-to-zero guarantee. If a worker failure sets `has_failed=true`, waiting stops early and the logger can still report `pending_count() > 0` until later cleanup or restart work happens.
- A later `run()` attempt starts by clearing stale failure state back to `has_failed=false` and `last_error=""` before it resumes draining any backlog still left in the queue.
- The exact behavior of late log attempts after closure is runtime-dependent, so callers should use lifecycle helpers like `is_closed()` and `shutdown()` instead of assuming identical post-close enqueue semantics everywhere.
- Queue overflow behavior depends on `AsyncOverflowPolicy`.
### How to Use
Here are some specific examples provided.
#### When Need Background Queue Drain
When your sink should not be written directly on the caller path:
```moonbit
let logger = async_logger(callback_sink(fn(rec) { println(rec.message) }))
@async.with_task_group(group => {
group.spawn_bg(() => logger.run())
logger.info("hello")
logger.shutdown()
})
```
In this example, the worker drains queued records in the background and `shutdown()` waits for completion.
And the logging call path stays queue-oriented rather than direct-sink oriented.
#### When Need A One-call Target Override On The Root Async Logger
When async code should keep one logger value but emit a single record under a different target:
```moonbit
logger.log(@bitlogger.Level::Error, "boom", target="app.async.audit")
```
In this example, the emitted record uses `app.async.audit` only for that one call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Shared Context On A Root Async Logger
When async code should attach stable metadata to later queued records:
```moonbit
let contextual = logger.with_context_fields([@bitlogger.field("service", "billing")])
```
In this example, the returned value still has the visible type `AsyncLogger[S]`.
And that shape preservation is intentional because async context binding updates stored logger metadata instead of changing the exposed sink type.
#### When Need Configurable Overflow And Flush Behavior
When queue semantics matter for service durability and load:
```moonbit
let logger = async_logger(
console_sink(),
config=AsyncLoggerConfig::new(
max_pending=128,
overflow=AsyncOverflowPolicy::DropOldest,
max_batch=8,
flush=AsyncFlushPolicy::Batch,
),
)
```
In this example, queue pressure and flush timing are both explicit.
### Error Case
e.g.:
- If the logger is closed, further enqueue attempts stop being normal active logging operations.
- If queue drain fails internally, runtime state can reflect that through `has_failed()` and `last_error()`.
### Notes
1. `async_logger(...)` is the async counterpart to `Logger::new(...)`.
2. Use `state()`, `pending_count()`, and `dropped_count()` for runtime diagnostics.
3. Example entrypoint limitations such as `async fn main` support are separate from the library-level portability of this API.
4. See [target-verification.md](./target-verification.md) for the current local verification matrix.
5. Pair this constructor with `run()` and `shutdown()` when you need the full worker lifecycle rather than just a configured async logger value.
6. Choose the facade name based on boundary intent: use `AsyncLogger[S]` for the full surface, `ApplicationAsyncLogger` or `ApplicationTextAsyncLogger` for application-facing alias names, and `LibraryAsyncLogger[S]` when a package boundary should intentionally narrow what downstream code can call directly.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-overflow-policy
group: api
category: async
update-time: 20260614
description: Public overflow policy alias used by AsyncLoggerConfig, async parser labels, and runtime queue behavior.
key-word:
- async
- overflow
- alias
- public
---
## Async-overflow-policy
`AsyncOverflowPolicy` is the public enum that controls what an `AsyncLogger` should do when its queue cannot accept a record immediately. It is a direct alias to the async model enum used by `AsyncLoggerConfig` and the runtime queue selection logic.
### Interface
```moonbit
pub type AsyncOverflowPolicy = @utils.AsyncOverflowPolicy
```
#### output
- `AsyncOverflowPolicy` - Public async queue overflow enum with the variants `Blocking`, `DropOldest`, and `DropNewest`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a separate runtime adapter.
- `AsyncOverflowPolicy::Blocking` waits for queue space when a non-blocking enqueue does not succeed.
- `AsyncOverflowPolicy::DropOldest` lets the underlying async queue discard older pending records when capacity is limited.
- `AsyncOverflowPolicy::DropNewest` discards the incoming record instead of blocking.
- The same enum is used by `AsyncLoggerConfig::new(...)`, async config parsing, and the queue-kind mapping inside `AsyncLogger`.
- The canonical labels `Blocking`, `DropOldest`, and `DropNewest` are also the labels emitted again by async config serializers, so export and parse stay aligned around one public vocabulary.
- Async config parsing accepts the canonical label `DropNewest` and also the compatibility alias `DropLatest`, both mapping to the same public enum variant.
- When `try_put(...)` reports that a record was not accepted under a drop policy, `dropped_count()` increases for that rejected enqueue.
### How to Use
Here are some specific examples provided.
#### When Need Backpressure Instead Of Silent Dropping
When producers should wait for queue space:
```moonbit
let config = AsyncLoggerConfig::new(max_pending=64, overflow=AsyncOverflowPolicy::Blocking)
```
In this example, logging pressure can slow producers instead of dropping data immediately.
#### When Need Bounded Async Logging With Drop Behavior
When async logging should keep moving under load without blocking callers:
```moonbit
let config = AsyncLoggerConfig::new(max_pending=64, overflow=AsyncOverflowPolicy::DropNewest)
```
In this example, the incoming record is dropped if the queue cannot accept it.
### Error Case
e.g.:
- If async config text uses unsupported overflow text, async config parsing raises a failure.
- The parser error path for unsupported overflow text is the same `Failure` surface used by the async config utilities.
- Under drop policies, sustained overload increases `dropped_count()` instead of guaranteeing delivery.
- The precise record discarded by the underlying queue behavior depends on the selected policy and queue implementation, so callers should not assume `dropped_count()` alone identifies which message was lost.
### Notes
1. This policy applies to `bitlogger_async`, not synchronous `QueuedSink`.
2. Choose `Blocking` only when producer-side waiting is acceptable for the caller.
3. Serialized config uses the canonical `DropNewest` label even though the parser also accepts `DropLatest`.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-runtime-mode-label
group: api
category: async
update-time: 20260614
description: Convert AsyncRuntimeMode into its canonical stable string label for logs, JSON, and diagnostics.
key-word:
- async
- runtime
- label
- public
---
## Async-runtime-mode-label
Convert `AsyncRuntimeMode` into a stable string label. This helper is useful when runtime mode should be logged, serialized, or exposed through human-readable diagnostics.
### Interface
```moonbit
pub fn async_runtime_mode_label(mode : AsyncRuntimeMode) -> String {}
```
#### input
- `mode : AsyncRuntimeMode` - Runtime mode enum value.
#### output
- `String` - Stable mode label such as `native_worker` or `compatibility`.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned value is intended for diagnostics and stable output, not just debugging prints.
- It keeps mode serialization logic in one place.
- This helper is used by async runtime JSON helpers.
- `async_runtime_state_to_json(...)` serializes the `mode` field through this helper, so runtime snapshots and direct label rendering share the same canonical text.
- Labels are more stable for telemetry and docs than ad hoc manual matching at call sites.
- The current canonical labels are exactly `native_worker` for `NativeWorker` and `compatibility` for `Compatibility`.
- This helper is only a pure enum-to-string mapping. It does not inspect the active backend or verify that the supplied enum still matches the current runtime environment.
### How to Use
Here are some specific examples provided.
#### When Need A Stable Log Label
When the mode should be included in structured or plain logs:
```moonbit
println(async_runtime_mode_label(async_runtime_mode()))
```
In this example, the label is directly usable in diagnostics.
#### When Build Custom Serialization
When callers want to embed the mode into their own payloads:
```moonbit
let label = async_runtime_mode_label(async_runtime_mode())
```
In this example, code gets a stable string without duplicating enum matching logic.
### Error Case
e.g.:
- This API assumes a valid `AsyncRuntimeMode` input and does not expose a normal runtime error path.
- If callers need the current backend-derived mode rather than a previously stored enum value, they must call `async_runtime_mode()` first and then label that result.
- If callers need the whole runtime object rather than a string label, use `async_runtime_state()`.
### Notes
1. Use this helper when mode values should be rendered as stable text instead of manual enum matching.
2. `async_runtime_state_to_json(...)` uses these exact labels when serializing runtime snapshots.
+91
View File
@@ -0,0 +1,91 @@
---
name: async-runtime-mode
group: api
category: async
update-time: 20260614
description: Read the current async runtime mode and distinguish native-worker behavior from compatibility behavior using the same mode contract exposed through async runtime snapshots.
key-word:
- async
- runtime
- mode
- public
---
## Async-runtime-mode
Read the current backend-specific async runtime mode. This API is the narrowest capability probe when you only care whether the current build is running native worker semantics or compatibility behavior.
### Interface
```moonbit
pub fn async_runtime_mode() -> AsyncRuntimeMode {}
```
#### input
- `none` - No arguments are required.
#### output
- `AsyncRuntimeMode` - Either `NativeWorker` or `Compatibility`.
### Explanation
Detailed rules explaining key parameters and behaviors
- The return value is determined by the active backend implementation.
- `NativeWorker` is the expected mode on native-style backends, while `Compatibility` is the expected mode on targets without native background-worker semantics.
- `async_runtime_mode_label(...)` converts the enum into a stable string value.
- `async_runtime_supports_background_worker()` is a narrower boolean probe built on the same idea.
- `async_runtime_state()` packages this mode together with the current background-worker capability into one `AsyncRuntimeState` snapshot.
- In the current backend implementations, `NativeWorker` pairs with `background_worker=true` and `Compatibility` pairs with `background_worker=false`.
- Concretely, the native runtime entrypoint returns `native_worker_async_runtime_mode()`, while the compatibility stub returns `compatibility_async_runtime_mode()`.
- The enum is therefore selected directly by the active backend helper on each call rather than read back out of a cached runtime snapshot object.
- This API is intentionally small and useful for lightweight branching.
- The mode result describes runtime behavior only; it should not be read as proof that every backend has been equally re-verified in the current release cycle.
### How to Use
Here are some specific examples provided.
#### When Need A Small Capability Branch
When behavior should differ between native worker mode and compatibility mode:
```moonbit
match async_runtime_mode() {
AsyncRuntimeMode::NativeWorker => println("native worker")
AsyncRuntimeMode::Compatibility => println("compat")
}
```
In this example, the branch is explicit and readable.
#### When Need Stable String Output
When the mode should be included in logs or telemetry labels:
```moonbit
println(async_runtime_mode_label(async_runtime_mode()))
```
In this example, the output becomes a stable string instead of an enum pattern-match requirement.
### Error Case
e.g.:
- This API does not have a normal runtime failure mode; it reflects the compiled backend behavior.
- If callers need the current mode paired with the matching worker-support flag, prefer a fresh `async_runtime_state()` call instead of combining `async_runtime_mode()` with an older saved runtime snapshot.
- If you need worker support as a direct boolean, use `async_runtime_supports_background_worker()` instead.
### Notes
1. Use this API for minimal mode branching.
2. Use `async_runtime_state()` when you also want worker support packaged into one object.
3. Use `async_runtime_mode_label(...)` when the result should leave enum space and become stable text such as `native_worker` or `compatibility`.
4. This mode distinction is about runtime behavior, not whether `src-async` itself is expected to compile for the target.
5. See [target-verification.md](./target-verification.md) for the current verification status of individual targets.
+89
View File
@@ -0,0 +1,89 @@
---
name: async-runtime-state-new
group: api
category: async
update-time: 20260614
description: Construct an AsyncRuntimeState snapshot from explicit runtime mode and worker-support values without probing or validating the live backend.
key-word:
- async
- runtime
- state
- public
---
## Async-runtime-state-new
Construct an `AsyncRuntimeState` snapshot from explicit runtime mode and worker-support values. This is the low-level constructor behind the public async runtime state shape used in diagnostics.
### Interface
```moonbit
pub fn AsyncRuntimeState::new(
mode : AsyncRuntimeMode,
background_worker : Bool,
) -> AsyncRuntimeState {
```
#### input
- `mode : AsyncRuntimeMode` - Backend-specific async runtime mode such as `NativeWorker` or `Compatibility`.
- `background_worker : Bool` - Whether native-style background-worker support is available.
#### output
- `AsyncRuntimeState` - Runtime state snapshot containing the supplied mode and worker-support flag.
### Explanation
Detailed rules explaining key parameters and behaviors
- This constructor simply packages `mode` and `background_worker` into one public snapshot value.
- It does not query the current backend automatically.
- `async_runtime_state()` is the higher-level API that reads these values from the live runtime environment.
- It also does not validate whether the supplied pair matches the current backend contract.
- The supplied `mode` and `background_worker` values are stored exactly as provided; this constructor does not recompute, normalize, or cross-check either field.
- The constructed value matches the same public shape used by async runtime serializers.
- Because `AsyncRuntimeState` is only a data snapshot type, this constructor is mainly useful for tests, adapters, and synthetic diagnostics rather than ordinary runtime probing.
### How to Use
Here are some specific examples provided.
#### When Need A Hand-built Runtime Snapshot
When tests or adapters should construct a runtime state explicitly:
```moonbit
let runtime = AsyncRuntimeState::new(
AsyncRuntimeMode::Compatibility,
false,
)
```
In this example, the runtime snapshot is built directly without probing the active backend.
#### When Need Structured Runtime Diagnostics Input
When code should prepare a runtime state value before serialization:
```moonbit
let runtime = AsyncRuntimeState::new(async_runtime_mode(), async_runtime_supports_background_worker())
```
In this example, callers still use the direct constructor while keeping the data source explicit.
### Error Case
e.g.:
- This constructor itself does not have a normal failure mode; it only packages the provided values.
- If callers want the current backend snapshot directly, `async_runtime_state()` is the simpler API.
- If callers manually pair `NativeWorker` with `false` or `Compatibility` with `true`, the constructor still accepts that snapshot because it does not enforce backend consistency.
- If callers want the currently probed runtime pair instead of a synthetic one, they must pass `async_runtime_mode()` plus `async_runtime_supports_background_worker()` explicitly or use `async_runtime_state()`.
### Notes
1. Use this helper when code should construct an `AsyncRuntimeState` value explicitly.
2. Pair it with `AsyncLoggerState::new(...)` when assembling a full async logger snapshot by hand.
3. Prefer `async_runtime_state()` when the goal is to report the actual current backend pair rather than an arbitrary constructed snapshot.
+80
View File
@@ -0,0 +1,80 @@
---
name: async-runtime-state-to-json
group: api
category: async
update-time: 20260614
description: Convert AsyncRuntimeState into a JSON value for runtime capability and mode diagnostics using the canonical mode labels and snapshot field names.
key-word:
- async
- state
- json
- public
---
## Async-runtime-state-to-json
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_parser.JsonValue {}
```
#### input
- `state : AsyncRuntimeState` - Runtime capability snapshot, usually produced by `async_runtime_state()`.
#### output
- `JsonValue` - Structured JSON representation of the runtime state.
### Explanation
Detailed rules explaining key parameters and behaviors
- The output includes `mode` and `background_worker`.
- `mode` is serialized through `async_runtime_mode_label(...)`.
- The compact serialized shape is `{"mode":"native_worker|compatibility","background_worker":true|false}`.
- This helper focuses on runtime capabilities rather than queue counters or logger lifecycle flags.
- The exported JSON is suitable for diagnostics endpoints and startup environment checks.
- This helper serializes the provided `AsyncRuntimeState` exactly as given; it does not call `async_runtime_state()` or recheck backend capability by itself.
- That means manually constructed runtime snapshots are exported unchanged, with `mode` relabeled through `async_runtime_mode_label(...)` and `background_worker` kept exactly as supplied.
### How to Use
Here are some specific examples provided.
#### When Need Structured Runtime Capability Checks
When startup diagnostics should include async runtime capability data:
```moonbit
let runtime_json = async_runtime_state_to_json(async_runtime_state())
```
In this example, the runtime snapshot becomes a reusable JSON value.
#### When Need To Embed Runtime State In A Larger Payload
When async support should be one field in a bigger diagnostics object:
```moonbit
let payload = async_runtime_state_to_json(state)
```
In this example, callers can reuse the exported object directly.
### Error Case
e.g.:
- If callers expect logger queue counters or failure status, this API is too narrow and `async_logger_state_to_json(...)` should be used instead.
- If the runtime is in compatibility mode, the helper still serializes normally using the matching mode label.
- If callers need the current backend-derived runtime rather than an older or synthetic snapshot, they must capture a fresh `async_runtime_state()` first.
### Notes
1. The output field names are fixed as `mode` and `background_worker`.
2. The `mode` field always uses the canonical labels from `async_runtime_mode_label(...)`, not enum names like `NativeWorker` or `Compatibility`.
+82
View File
@@ -0,0 +1,82 @@
---
name: async-runtime-state-type
group: api
category: async
update-time: 20260614
description: Public async runtime state alias used for backend capability snapshots and diagnostics, pairing runtime mode with background-worker support.
key-word:
- async
- runtime
- state
- public
---
## Async-runtime-state-type
`AsyncRuntimeState` is the public snapshot type used to describe backend-level async runtime capability. It is a direct alias to the async runtime state model returned by `async_runtime_state()` and used by async diagnostics serializers.
### Interface
```moonbit
pub type AsyncRuntimeState = @utils.AsyncRuntimeState
```
#### output
- `AsyncRuntimeState` - Public runtime snapshot containing `mode` and `background_worker`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a live runtime controller.
- The current fields are `mode : AsyncRuntimeMode` and `background_worker : Bool`.
- `async_runtime_state()` returns this type directly as an environment-level snapshot.
- `async_runtime_state()` currently builds that snapshot from `async_runtime_mode()` and `async_runtime_supports_background_worker()`.
- `async_runtime_state_to_json(...)` and `stringify_async_runtime_state(...)` serialize the same snapshot shape for diagnostics.
- `AsyncRuntimeState::new(...)` can also construct this type manually, but manual construction is synthetic data and does not probe the current backend by itself.
- The type itself does not distinguish live backend snapshots from hand-built ones; callers must track whether a given `AsyncRuntimeState` value came from `async_runtime_state()` or from manual construction.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Backend Capability Snapshot
When startup or diagnostics code should keep the runtime state as structured data:
```moonbit
let runtime : AsyncRuntimeState = async_runtime_state()
```
In this example, the backend capability snapshot stays available as a typed value instead of only as printed text.
#### When Need To Read Runtime Mode And Worker Support Together
When code should inspect both async mode and worker availability from one object:
```moonbit
let runtime = async_runtime_state()
if runtime.background_worker {
println(async_runtime_mode_label(runtime.mode))
}
```
In this example, both fields are consumed from the same public snapshot type.
### Error Case
e.g.:
- `AsyncRuntimeState` itself does not have a runtime failure mode.
- The snapshot is point-in-time diagnostic data; it should not be treated as proof that every target was re-verified in the current release cycle.
- Because this is just a data shape, manual construction can represent combinations that do not come from the current backend probe.
- Receiving an `AsyncRuntimeState` value alone does not prove it came from the current backend rather than from a synthetic constructor path.
### Notes
1. Use `async_runtime_state()` when you need a value of this type from the current backend.
2. Use `AsyncLoggerState` when logger-instance queue and lifecycle information is also required.
3. Use `async_runtime_mode_label(...)` when the `mode` field should be rendered as stable text such as `native_worker` or `compatibility`.
+85
View File
@@ -0,0 +1,85 @@
---
name: async-runtime-state
group: api
category: async
update-time: 20260614
description: Read the current backend-specific async runtime snapshot as the paired result of runtime mode and background-worker capability.
key-word:
- async
- runtime
- diagnostics
- public
---
## Async-runtime-state
Read the current backend-specific async runtime state for diagnostics. This API focuses on the environment-level async mode rather than any one logger instance.
### Interface
```moonbit
pub fn async_runtime_state() -> AsyncRuntimeState {}
```
#### input
- `none` - This API reads the current backend mode and does not require logger input.
#### output
- `AsyncRuntimeState` - Runtime snapshot containing `mode` and `background_worker` capability.
### Explanation
Detailed rules explaining key parameters and behaviors
- `mode` is derived from the active backend implementation.
- `background_worker` tells callers whether native worker semantics are available.
- This helper is equivalent to `AsyncRuntimeState::new(async_runtime_mode(), async_runtime_supports_background_worker())`.
- The returned pair is rebuilt from those two lower-level helpers on each call rather than read from a cached runtime object.
- In the current backend implementations, the resulting pair is `NativeWorker + true` or `Compatibility + false`.
- `async_runtime_state_to_json(...)` and `stringify_async_runtime_state(...)` serialize this state.
- This API is environment-scoped and does not depend on a particular `AsyncLogger` instance.
- The returned value is a snapshot data object, not a live runtime handle, so later backend checks require calling `async_runtime_state()` again rather than reusing an older value as if it refreshed itself.
### How to Use
Here are some specific examples provided.
#### When Need Startup Diagnostics
When startup logs should reveal async backend behavior:
```moonbit
println(stringify_async_runtime_state(async_runtime_state(), pretty=true))
```
In this example, backend mode is exposed before any logger is started.
#### When Branch Behavior By Runtime Capability
When code should react differently depending on worker support:
```moonbit
let runtime = async_runtime_state()
if runtime.background_worker {
println("native worker path")
}
```
In this example, branch decisions are based on actual runtime capability instead of assumptions.
### Error Case
e.g.:
- This API does not normally expose a dynamic error path; it reports the currently compiled backend behavior.
- The returned `AsyncRuntimeState` value is not cached onto the helper. If callers need a newer backend read, they must call `async_runtime_state()` again instead of expecting an older value to refresh itself.
- If callers need richer runtime state, they should use `AsyncLogger::state()` on a logger instance instead.
### Notes
1. Use this API for environment-level diagnostics.
2. Use `AsyncLogger::state()` for logger-instance diagnostics.
3. Use `AsyncRuntimeState::new(...)` only when code or tests need to construct a manual snapshot instead of probing the current backend.
@@ -0,0 +1,85 @@
---
name: async-runtime-supports-background-worker
group: api
category: async
update-time: 20260512
description: Return whether the current backend provides native async background worker support.
key-word:
- async
- runtime
- worker
- public
---
## Async-runtime-supports-background-worker
Return a boolean telling callers whether the current backend provides native background worker semantics for `bitlogger_async`. This is the narrowest capability probe when only worker support matters.
### Interface
```moonbit
pub fn async_runtime_supports_background_worker() -> Bool {}
```
#### input
- `none` - No explicit arguments are required.
#### output
- `Bool` - `true` when native background worker semantics are available, otherwise `false`.
### Explanation
Detailed rules explaining key parameters and behaviors
- `true` indicates native worker capability.
- `false` indicates compatibility-mode behavior.
- This helper is derived from backend-specific async runtime implementation choice.
- The boolean is read from the active backend helper on each call rather than from a cached runtime snapshot object.
- In the current backend split, the native implementation returns `true` while the compatibility stub returns `false`, matching the same mode pair exposed through `async_runtime_mode()` and `async_runtime_state()`.
- The async library still targets multiple backends even when this helper returns `false`.
- Use it when an enum branch is unnecessary and a boolean capability check is enough.
### How to Use
Here are some specific examples provided.
#### When Need A Simple Capability Branch
When logic only depends on worker availability:
```moonbit
if async_runtime_supports_background_worker() {
println("native worker available")
}
```
In this example, callers avoid a more verbose mode match.
#### When Add Diagnostics Labels
When capability should be surfaced in diagnostics:
```moonbit
println(if async_runtime_supports_background_worker() { "worker" } else { "compat" })
```
In this example, a simple boolean can drive compact status output.
### Error Case
e.g.:
- This API does not normally fail at runtime; it reflects compiled backend behavior.
- If callers need the current paired mode and worker flag together, prefer a fresh `async_runtime_state()` call instead of mixing this boolean with an older saved mode value.
- If you need the exact mode name rather than a boolean, use `async_runtime_mode()` or `async_runtime_state()`.
### Notes
1. Use this helper for minimal capability checks.
2. Prefer `async_runtime_state()` when you want the same information in a richer object.
3. A `false` result should be read as "compatibility-mode runtime behavior" rather than "async library unsupported on this target".
4. This helper does not by itself imply that every non-worker backend has been equally re-verified for the current release; see [target-verification.md](./target-verification.md).
+71
View File
@@ -0,0 +1,71 @@
---
name: buffered-sink-flush
group: api
category: sink
update-time: 20260613
description: Flush buffered records from a BufferedSink into its wrapped sink.
key-word:
- sink
- buffer
- flush
- public
---
## Buffered-sink-flush
Flush buffered records from a `BufferedSink[S]` into its wrapped sink. This is the direct sink-level batching control API for simple synchronous buffering.
### Interface
```moonbit
pub fn[S : Sink] BufferedSink::flush(self : BufferedSink[S]) -> Unit {
```
#### input
- `self : BufferedSink[S]` - Buffered sink whose pending records should be forwarded to the wrapped sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- If the buffer is empty, the method does nothing.
- When buffered records exist, they are copied into a temporary `pending` array, the live buffer is cleared, and each record is then written to the wrapped sink in order.
- This helper drains only the local buffer; any later behavior still depends on the wrapped sink `S`.
- The method does not return a count or success flag.
### How to Use
Here are some specific examples provided.
#### When Need Explicit Batch Delivery
When a buffered sink should forward pending records before the threshold is reached:
```moonbit
sink.flush()
```
In this example, callers force buffered records to be written to the wrapped sink immediately.
#### When Need A Manual Flush Barrier
When tests or synchronous code want buffering but still need a deliberate release point:
```moonbit
let sink = buffered_sink(console_sink(), flush_limit=4)
sink.flush()
```
In this example, the buffered sink exposes a direct batching barrier without using queue semantics.
### Error Case
e.g.:
- If the wrapped sink's write behavior fails or has side effects, this helper does not add an extra reporting layer on top of `S`.
- If callers need a count-returning drain API with drop tracking, `QueuedSink::flush()` or `QueuedSink::drain()` may fit better.
### Notes
1. Use this helper when code owns a `BufferedSink` directly and wants explicit control over when buffered records are forwarded.
2. Automatic flush still happens when buffered record count reaches `flush_limit` during writes.
+75
View File
@@ -0,0 +1,75 @@
---
name: buffered-sink-pending-count
group: api
category: sink
update-time: 20260613
description: Read the current buffered-record count from a BufferedSink.
key-word:
- sink
- buffer
- queue
- public
---
## Buffered-sink-pending-count
Read the current buffered-record count from a `BufferedSink[S]`. This is the direct sink-level metric for how many records are still waiting in the in-memory buffer.
### Interface
```moonbit
pub fn[S] BufferedSink::pending_count(self : BufferedSink[S]) -> Int {
```
#### input
- `self : BufferedSink[S]` - Buffered sink whose current in-memory backlog should be inspected.
#### output
- `Int` - Current number of buffered records.
### Explanation
Detailed rules explaining key parameters and behaviors
- The return value is `self.buffer.val.length()` at the time of the call.
- This is a point-in-time metric and may change immediately after it is read.
- It reflects buffered records that have not yet been flushed to the wrapped sink.
- This helper does not mutate the sink.
### How to Use
Here are some specific examples provided.
#### When Need Direct Buffer Backlog Visibility
When code is working with a `BufferedSink` value directly and wants to observe pending buffered records:
```moonbit
let pending = sink.pending_count()
```
In this example, callers can inspect the current in-memory backlog without touching the wrapped sink.
#### When Verify Manual Flush Progress
When explicit flush steps should be checked operationally:
```moonbit
ignore(sink.flush())
ignore(sink.pending_count())
```
In this example, the metric helps verify whether buffered records were forwarded out of the local buffer.
### Error Case
e.g.:
- This helper does not have a normal failure mode; it only reads current buffer length.
- If callers need overflow-aware backlog semantics instead of simple buffering, `QueuedSink::pending_count()` is the better API.
### Notes
1. Use this helper for direct visibility into simple synchronous buffering.
2. Pair it with `flush()` when inspecting whether batched writes have been forwarded.
+77
View File
@@ -0,0 +1,77 @@
---
name: buffered-sink-type
group: api
category: sink
update-time: 20260613
description: Public buffered sink type used for threshold-based synchronous batching over another sink.
key-word:
- sink
- buffer
- type
- public
---
## Buffered-sink-type
`BufferedSink[S]` is the public buffering sink type used for threshold-based synchronous batching over another sink. It is the concrete sink type returned by `buffered_sink(...)` and preserves the wrapped sink type in its type parameter.
### Interface
```moonbit
pub struct BufferedSink[S] {
sink : S
buffer : Ref[Array[Record]]
flush_limit : Int
}
```
#### output
- `BufferedSink[S]` - Public synchronous sink type that buffers records before forwarding them to the wrapped sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current fields are `sink : S`, `buffer : Ref[Array[Record]]`, and `flush_limit : Int`.
- `buffered_sink(...)` constructs this type directly from a wrapped sink and flush threshold.
- The sink preserves the wrapped sink type `S`, which is useful when typed composition still matters after buffering is introduced.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Buffered Sink Value
When code should keep the concrete buffering sink type visible:
```moonbit
let sink : BufferedSink[ConsoleSink] = buffered_sink(console_sink(), flush_limit=2)
```
In this example, the sink value stays explicit and preserves the wrapped console sink type.
#### When Need A Typed Logger With Synchronous Batching
When logging should preserve the buffering sink type in the logger:
```moonbit
let logger : Logger[BufferedSink[ConsoleSink]] = Logger::new(
buffered_sink(console_sink(), flush_limit=2),
target="buffered",
)
```
In this example, the concrete buffered sink remains part of the logger type.
### Error Case
e.g.:
- `BufferedSink[S]` itself does not have a runtime failure mode.
- Runtime behavior still depends on the wrapped sink `S`, and buffered records can remain pending if flushing never happens.
### Notes
1. Use `buffered_sink(...)` when you need a value of this type.
2. Use `QueuedSink[S]` when overflow-aware queueing behavior is needed instead of simple buffering.
+66
View File
@@ -0,0 +1,66 @@
---
name: buffered-sink
group: api
category: sink
update-time: 20260520
description: Create a sink that buffers records and flushes them manually or at a threshold.
key-word:
- sink
- buffer
- flush
- public
---
## Buffered-sink
Create a sink that buffers records before forwarding them to another sink. This helper is useful when callers want explicit or threshold-based sync batching without using the queue wrapper API.
### Interface
```moonbit
pub fn[S] buffered_sink(sink : S, flush_limit~ : Int = 1) -> BufferedSink[S] {
```
#### input
- `sink : S` - Wrapped sink that receives flushed records.
- `flush_limit : Int` - Buffer length threshold that triggers automatic flush.
#### output
- `BufferedSink[S]` - Buffering sink with `pending_count()` and `flush()` helpers.
### Explanation
Detailed rules explaining key parameters and behaviors
- Records are stored in an in-memory buffer until flushed.
- `flush_limit <= 0` is normalized to `1`.
- Flushing forwards the buffered records in order to the wrapped sink.
### How to Use
Here are some specific examples provided.
#### When Need Manual Or Threshold-based Batch Delivery
When writes should accumulate before they reach the destination:
```moonbit
let sink = buffered_sink(console_sink(), flush_limit=2)
let logger = Logger::new(sink, target="buffered")
```
In this example, records stay buffered until the threshold is reached or `flush()` is called.
### Error Case
e.g.:
- If callers never flush a buffer whose threshold is not reached, records remain pending.
- If bounded dropping behavior is required instead of simple buffering, use `queued_sink(...)` or `Logger::with_queue(...)`.
### Notes
1. This helper is simpler than explicit queue overflow management.
2. It is useful for synchronous batching scenarios and tests.
+106
View File
@@ -0,0 +1,106 @@
---
name: build-application-async-logger
group: api
category: facade
update-time: 20260614
description: Build the application-facing runtime-sink async logger alias from an AsyncLoggerBuildConfig through the sync-first async builder path.
key-word:
- application
- async
- facade
- public
---
## Build-application-async-logger
Build an `ApplicationAsyncLogger` from `AsyncLoggerBuildConfig`. This is the application-facing runtime-sink async alias returned through `build_async_logger(...)`.
### Interface
```moonbit
pub fn build_application_async_logger(
config : AsyncLoggerBuildConfig,
) -> ApplicationAsyncLogger {
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined sync logger config and async queue/runtime config.
#### output
- `ApplicationAsyncLogger` - Application-facing async runtime logger.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_logger(...)` directly.
- That means the embedded `LoggerConfig` is built first through the normal synchronous config path before the outer async layer is applied.
- Any optional synchronous queue layer and runtime-sink controls chosen by `build_logger(config.logger)` remain active under the returned logger.
- In particular, a sync queue configured on `LoggerConfig.queue` is preserved inside the wrapped `RuntimeSink` variant instead of being stripped away by the application alias.
- Because the result is only the `ApplicationAsyncLogger` alias over `AsyncLogger[@bitlogger.RuntimeSink]`, this builder does not hide any async helpers or introduce a wrapper layer.
- The broader async helper surface is therefore preserved and directly exposed on the returned alias rather than being rebuilt or hidden behind an unwrap step.
- The returned alias also keeps inherited async logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- The returned logger keeps the full async lifecycle and state helper surface directly, including helpers such as `run()`, `shutdown()`, `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, `has_failed()`, and `last_error()`.
- It also keeps the same queue counters, failure state, sink shape, and runtime-dependent post-close behavior as the underlying runtime-sink async logger.
- Because this facade delegates to `build_async_logger(...)`, `Batch` and `Shutdown` also keep the underlying runtime-sink flush wiring: the returned alias uses the built sink's real `flush()` path instead of the default no-op callback used by the text-specific builder line.
- In the current direct builder coverage, this alias matches `build_async_logger(config)` all the way through serialized state snapshots, runtime-sink variant choice, queue counters, lifecycle flags, and later failure fields after `run()` or `shutdown()`.
- File-backed runtime helpers on the returned `RuntimeSink` also stay aligned with the direct builder result instead of being hidden behind a separate application-layer wrapper.
- Use this alias-oriented builder when application code wants the standard runtime-sink async shape without narrowing the public surface.
### How to Use
Here are some specific examples provided.
#### When Need Config-driven App Async Boot
When both sync sink shape and async queue policy are assembled as typed config:
```moonbit
let logger = build_application_async_logger(
AsyncLoggerBuildConfig::new(
logger=LoggerConfig::new(target="app.async"),
async_config=AsyncLoggerConfig::new(max_pending=8),
),
)
```
In this example, the app-facing async facade is built directly from typed config.
And any configured synchronous runtime sink controls remain available through the returned `RuntimeSink`-backed async logger.
And unlike `build_library_async_logger(...)`, no `to_async_logger()` unwrap is required to reach queue, lifecycle, or file-backed runtime helpers.
The returned value also keeps the ordinary async logger target semantics because the facade does not wrap or narrow the underlying `AsyncLogger[@bitlogger.RuntimeSink]`.
#### When Need Async State Helpers Immediately After App Construction
When application code should keep the ordinary async helper surface directly:
```moonbit
let logger = build_application_async_logger(config)
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the application alias exposes async state helpers directly because no narrowing wrapper is added.
### Error Case
e.g.:
- If file output is selected on a backend without native file support, backend behavior still applies when the worker drains records.
- If the logger is never `run()`, enqueue behavior and lifecycle state still follow the normal async logger rules.
- If callers rely on file-backed runtime helpers, they should treat this builder as the same runtime-sink result as `build_async_logger(config)`, not as a reduced alias with different helper behavior.
- If callers specifically want the text-console async path where `Batch` and `Shutdown` keep the default no-op flush callback, `build_application_text_async_logger(...)` is the different contract; this runtime-sink alias preserves the real sink `flush()` behavior from `build_async_logger(...)`.
### Notes
1. This is a facade over the existing async runtime logger builder.
2. Use `parse_and_build_application_async_logger(...)` when starting from JSON text.
3. Use `build_application_text_async_logger(...)` instead when callers should keep the concrete text-console sink type and the direct text-builder path.
4. Use `build_library_async_logger(...)` instead when a library boundary should narrow the exposed async surface.
+90
View File
@@ -0,0 +1,90 @@
---
name: build-application-logger
group: api
category: facade
update-time: 20260520
description: Build the application-facing configured logger alias from a LoggerConfig by delegating directly to the normal runtime logger build path.
key-word:
- application
- facade
- logger
- public
---
## Build-application-logger
Build an `ApplicationLogger` from `LoggerConfig`. This facade is the application-oriented sync entry point and currently aliases the configured runtime logger shape returned by `build_logger(...)`.
### Interface
```moonbit
pub fn build_application_logger(config : LoggerConfig) -> ApplicationLogger {
```
#### input
- `config : LoggerConfig` - Fully assembled sync logger config.
#### output
- `ApplicationLogger` - Application-facing configured runtime logger.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_logger(...)` directly.
- The embedded config still goes through the normal runtime logger build path, including runtime sink selection, optional queue wrapping, and timestamp application.
- Because the result is only the `ApplicationLogger` alias over `ConfiguredLogger`, this builder does not hide any queue, drain, flush, or file runtime helper methods.
- The configured runtime helper surface is therefore preserved and directly exposed on the returned alias rather than being rebuilt or hidden behind an unwrap step.
- The returned alias also keeps inherited `Logger` behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- That means `log(..., target=...)` can override the target for one write, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Use this alias-oriented entrypoint when application boot code wants an app-specific name without changing the underlying configured runtime logger surface.
### How to Use
Here are some specific examples provided.
#### When Need An App-level Sync Builder Entry
When boot code assembles config values before runtime construction:
```moonbit
let logger = build_application_logger(
LoggerConfig::new(target="app", sink=SinkConfig::new(kind=SinkKind::Console)),
)
```
In this example, the application facade builds the same configured runtime logger shape as `build_logger(...)`.
And any queue/file/runtime helpers selected by the config remain directly available on the returned alias value.
And unlike `build_library_logger(...)`, no `to_logger()` unwrap is required to reach that helper surface.
The returned value also keeps the ordinary logger target semantics because the facade does not wrap or narrow the underlying `ConfiguredLogger`.
#### When Need A Per-call Target Override After App-oriented Build
When typed app config should still build a logger that supports a one-write target override:
```moonbit
let logger = build_application_logger(config)
logger.log(Level::Error, "boom", target="app.audit")
```
In this example, the emitted record uses `app.audit` for that write.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If the config uses file output on a backend without native file support, backend runtime limitations still apply after construction.
- If queueing is not configured, queue helper values simply reflect the non-queued runtime shape.
### Notes
1. This is a facade API, not a separate runtime implementation.
2. Use `parse_and_build_application_logger(...)` when starting from JSON text.
3. Use `build_library_logger(...)` instead when the public surface should intentionally hide configured-runtime helper methods.
@@ -0,0 +1,119 @@
---
name: build-application-text-async-logger
group: api
category: facade
update-time: 20260614
description: Build the application-facing text-console async logger alias from an AsyncLoggerBuildConfig using the direct concrete text-sink builder path.
key-word:
- application
- async
- text
- public
---
## Build-application-text-async-logger
Build an `ApplicationTextAsyncLogger` from `AsyncLoggerBuildConfig`. This is the application-oriented async alias builder for the concrete text-console sink shape returned by `build_async_text_logger(...)`.
### Interface
```moonbit
pub fn build_application_text_async_logger(
config : AsyncLoggerBuildConfig,
) -> ApplicationTextAsyncLogger {
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined sync logger config and async queue/runtime config.
#### output
- `ApplicationTextAsyncLogger` - Application-facing async logger backed by `FormattedConsoleSink`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_text_logger(...)` directly.
- It is intended for config-driven async text console output where callers want the concrete text sink shape rather than the broader runtime sink enum wrapper.
- The builder always creates a `FormattedConsoleSink` from `config.logger.sink.text_formatter` instead of selecting among sink kinds.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this facade still follows the text-console path and uses only the configured `text_formatter` details.
- Unlike `build_application_async_logger(...)`, this alias-oriented builder does not go through the full synchronous configured-logger build path first.
- It uses the selected text-oriented `LoggerConfig` fields directly and therefore does not apply `LoggerConfig.queue` or preserve sync runtime sink controls.
- A sync queue configured on `LoggerConfig.queue` is therefore ignored by this builder instead of being preserved under the returned async logger.
- Because the result is only the `ApplicationTextAsyncLogger` alias over `AsyncLogger[@bitlogger.FormattedConsoleSink]`, this builder returns the same underlying async logger value as `build_async_text_logger(...)` and does not hide any async helpers or introduce a wrapper layer.
- The returned alias also keeps inherited async logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- In particular, `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless code derives another logger first with `with_target(...)` or `child(...)`.
- The returned logger keeps the full async lifecycle and state helper surface directly, including helpers such as `run()`, `shutdown()`, `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, `has_failed()`, and `last_error()`.
- It also keeps the same close, queue, and failure-state semantics as the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
- In the current direct text-builder coverage, this alias matches `build_async_text_logger(config)` through serialized state snapshots, formatter behavior, queue counters, lifecycle flags, and later failure fields after worker execution.
- Its configured `flush_policy` is still visible on the returned alias, but this text-specific build path does not wire the explicit sink flush callback that `build_application_async_logger(...)` inherits through `build_async_logger(...)`.
- That means `Batch` and `Shutdown` only drive the default no-op async flush callback here; they do not add an extra explicit sink flush step beyond ordinary `FormattedConsoleSink` writes.
- Use `build_library_async_text_logger(...)` instead when the next boundary should keep the same concrete text sink type but intentionally narrow the directly exposed async helper surface.
### How to Use
Here are some specific examples provided.
#### When Need An App Async Builder With Text Sink Shape
When async output should stay on text console formatting:
```moonbit
let logger = build_application_text_async_logger(
AsyncLoggerBuildConfig::new(
logger=text_console(target="app.text.async"),
async_config=AsyncLoggerConfig::new(max_pending=4),
),
)
```
In this example, the async logger is built for text-console output specifically.
And the chosen builder path matters more than `config.logger.sink.kind`: this facade still uses the text formatter directly because it always builds a `FormattedConsoleSink`.
And the returned value keeps the ordinary async logger target semantics because this facade does not wrap or narrow the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`.
#### When Need A Per-call Target Override After App Text Construction
When typed app async text config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_application_text_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="app.text.audit")
```
In this example, the emitted record uses `app.text.audit` for that call.
And later `debug(...)`, `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Async State Helpers Immediately After Text App Construction
When application code should keep the ordinary async helper surface directly on the text-console variant:
```moonbit
let logger = build_application_text_async_logger(config)
ignore(logger.pending_count())
ignore(logger.state())
```
In this example, the application text alias exposes async state helpers directly because no narrowing wrapper is added.
### Error Case
e.g.:
- If callers need sink-kind-driven branching such as JSON console or file-backed async output, they should use `build_application_async_logger(...)` instead.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this facade still ignores that sink-kind choice and does not create a file-backed async logger.
- If runtime draining is never started, records still follow the normal async queue lifecycle rules.
- If callers rely on the formatter or state shape after text-builder construction, they should treat this as the same direct `build_async_text_logger(config)` result rather than a reduced alias with different helper behavior.
### Notes
1. This is a narrower text-console async facade than `build_application_async_logger(...)`.
2. It is most useful when callers want the `FormattedConsoleSink`-backed async type explicitly.
3. Use `build_application_async_logger(...)` instead when callers need the broader runtime-sink build path, including sync queue application through `build_logger(config.logger)`.
4. Use `build_library_async_text_logger(...)` instead when the public boundary should narrow the exposed async surface while preserving the same concrete text sink type.
+121
View File
@@ -0,0 +1,121 @@
---
name: build-async-logger
group: api
category: async
update-time: 20260614
description: Build an async logger from combined logger and async config by first building the sync runtime logger and then wrapping its sink in the async layer.
key-word:
- async
- config
- builder
- public
---
## Build-async-logger
Build an async logger directly from `AsyncLoggerBuildConfig`. This is the config-driven async entry point that bridges synchronous logger config, sink creation, and async queue setup in one call.
### Interface
```moonbit
pub fn build_async_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlogger.RuntimeSink] {}
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined synchronous logger config plus async queue/flush config.
#### output
- `AsyncLogger[RuntimeSink]` - A config-built async logger with runtime sink control preserved.
### Explanation
Detailed rules explaining key parameters and behaviors
- The `logger` section is built through the same config machinery used by synchronous configured loggers.
- That means `LoggerConfig.sink` and the optional synchronous `LoggerConfig.queue` are applied before the async layer is added.
- The resulting async logger inherits `min_level`, `target`, and timestamp behavior from the built synchronous logger.
- File, formatter, and any configured synchronous queue choices all come from config rather than direct code-side sink wiring.
- The returned sink type is `RuntimeSink`, which keeps configured control helpers available where relevant.
- This builder returns the underlying `AsyncLogger[@bitlogger.RuntimeSink]` value directly. `build_application_async_logger(...)` only re-exports the same result under the `ApplicationAsyncLogger` alias, while `build_library_async_logger(...)` wraps the same result in `LibraryAsyncLogger[@bitlogger.RuntimeSink]`.
- The returned async logger also keeps ordinary target rules unchanged: `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Because this path starts from `build_logger(config.logger)`, it preserves the broader runtime-sink build path, including sync-side queue decoration when `LoggerConfig.queue` is present.
- Because this path starts from `build_logger(config.logger)`, `config.logger.sink.kind` has already selected the concrete `RuntimeSink` variant before async wrapping, and optional sync queue decoration can further turn that built sink into queued runtime variants such as `QueuedConsole` or `QueuedFile`.
- This runtime-sink path also wires the explicit async flush callback as `flush=fn(sink) { sink.flush() }`, so `Batch` and `Shutdown` policies invoke the built runtime sink's real flush behavior instead of the default no-op callback.
- In the current direct builder coverage, the returned logger exposes the expected serialized async state snapshot, runtime-sink variant choice, queue counters, lifecycle flags, and later failure fields after `run()` or `shutdown()`.
- File-backed runtime helpers also stay directly available on the returned `RuntimeSink` with the same behavior later observed through the application and library facade equivalence tests.
- Use `build_async_text_logger(...)` instead when you want the direct text-console async builder path with `FormattedConsoleSink` and without the sync runtime-sink construction layer.
- The `src-async` library is designed to compile on `native / llvm / js / wasm / wasm-gc`, but runtime mode differs by backend.
- Current local release-facing verification is explicit for `native / js / wasm / wasm-gc`.
- `llvm` remains experimental and did not complete local verification in this environment.
- On non-native targets, the async library still compiles and exposes the same public surface through compatibility-mode behavior rather than native background-worker semantics.
### How to Use
Here are some specific examples provided.
#### When Need Fully Config-driven Async Bootstrapping
When your application should build async logging entirely from configuration:
```moonbit
let config = parse_async_logger_build_config_text(raw) catch {
err => return
}
let logger = build_async_logger(config)
```
In this example, parsing and async runtime wiring are separated cleanly.
And the returned logger can immediately be started with `run()`.
#### When Need A Per-call Target Override After Typed Async Build
When typed async config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="svc.audit")
```
In this example, the emitted record uses `svc.audit` for that call.
And later `debug(...)`, `info(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Runtime Sink Features After Async Build
When the sink shape is configured but runtime features still matter:
```moonbit
let logger = build_async_logger(config)
println(stringify_async_logger_state(logger.state(), pretty=true))
```
In this example, the built async logger remains introspectable even though construction was config-driven.
And any configured synchronous runtime sink controls are preserved inside the returned `RuntimeSink`.
And if `AsyncFlushPolicy::Batch` or `Shutdown` is configured, this builder uses the runtime sink's real `flush()` path rather than the default no-op callback used by the text-specific builder line.
### Error Case
e.g.:
- If the config text was invalid, error handling should happen earlier in `parse_async_logger_build_config_text(...)`.
- If the configured sink shape is unsupported for a specific capability, the resulting runtime behavior follows the existing sink/runtime rules rather than inventing a separate builder-only failure model.
- If callers depend on queued runtime-sink helpers or file-backed runtime helpers, this builder is the direct API that preserves them rather than a reduced facade path.
- If callers depend on the exact runtime-sink variant, they should read this builder as preserving the sync-first sink selection result from `build_logger(config.logger)`, not as deferring sink-kind branching until after the async layer is added.
- If callers need the text-console path where `Batch` and `Shutdown` keep the default no-op flush callback, `build_async_text_logger(...)` is the different builder contract; this runtime-sink path intentionally wires the sink's real `flush()` behavior.
### Notes
1. Prefer this API when applications externalize both sync sink choice and async queue behavior.
2. Use `async_logger(...)` directly when you want explicit code-defined sink wiring.
3. Library portability is broader than example portability: a runnable `async fn main` example may still be target-limited even when the async library itself compiles for that backend.
4. See [target-verification.md](./target-verification.md) for the current verification boundary.
5. If the next boundary is library-facing rather than application-facing, build here and then narrow with `build_library_async_logger(...)` instead of documenting the broader runtime helper surface directly.
+101
View File
@@ -0,0 +1,101 @@
---
name: build-async-text-logger
group: api
category: async
update-time: 20260614
description: Build an async logger with a concrete text-console sink from combined logger and async config, using only the selected text-oriented LoggerConfig fields instead of the full sync build path.
key-word:
- async
- text
- builder
- public
---
## Build-async-text-logger
Build an async logger directly from `AsyncLoggerBuildConfig`, but keep the concrete sink type as `FormattedConsoleSink` instead of the broader runtime sink wrapper. This helper is the text-console specific counterpart to `build_async_logger(...)`.
### Interface
```moonbit
pub fn build_async_text_logger(config : AsyncLoggerBuildConfig) -> AsyncLogger[@bitlogger.FormattedConsoleSink] {
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined sync logger config plus async queue and flush config.
#### output
- `AsyncLogger[FormattedConsoleSink]` - Config-built async logger backed by a concrete text console sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- This builder converts `config.logger.sink.text_formatter` into a runtime `TextFormatter` and wires it into `text_console_sink(...)`.
- It always constructs a `FormattedConsoleSink` directly instead of branching on `config.logger.sink.kind`.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this builder still follows the text-console path and uses only the configured `text_formatter` details.
- The returned logger inherits `min_level`, `target`, and timestamp behavior from `config.logger`.
- Unlike `build_async_logger(...)`, this helper does not run the full synchronous `build_logger(config.logger)` path first.
- That means it uses `config.logger.sink.text_formatter`, `min_level`, `target`, and `timestamp` directly, but it does not apply `LoggerConfig.queue` or preserve other sync runtime sink controls.
- This builder returns the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]` value directly. `build_application_text_async_logger(...)` only re-exports that same result under the `ApplicationTextAsyncLogger` alias, while `build_library_async_text_logger(...)` wraps the same result in `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
- The returned async logger also keeps ordinary target rules unchanged: `log(..., target=...)` can override the target for one call, while severity helpers such as `debug(...)`, `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- In the current direct text-builder coverage, the returned logger exposes the expected serialized async state snapshot, formatter behavior, queue counters, lifecycle flags, and later failure fields after worker execution.
- The async `flush_policy` still comes from `config.async_config`, but this text-specific builder does not supply the explicit `flush=fn(sink) { sink.flush() }` callback used by `build_async_logger(...)`.
- In practice, `Batch` and `Shutdown` therefore only trigger the default no-op async flush callback on this path, while each record write still follows whatever immediate behavior `FormattedConsoleSink` already has on its own.
- This helper is best suited to text-console output paths where callers want the concrete formatted sink type instead of `RuntimeSink`.
- This async text path follows the same target story as the broader async library: `native / js / wasm / wasm-gc` have stronger local verification, while `llvm` remains experimental and locally unverified in this environment.
### How to Use
Here are some specific examples provided.
#### When Need Config-built Async Text Console Output
When async queue behavior is config-driven and output should stay on text console formatting:
```moonbit
let logger = build_async_text_logger(
AsyncLoggerBuildConfig::new(
logger=text_console(target="async.text"),
async_config=AsyncLoggerConfig::new(max_pending=4),
),
)
```
In this example, the async logger is built around a text console sink rather than the generic runtime sink enum.
And the chosen builder path matters more than `config.logger.sink.kind`: this helper still uses the text formatter directly because it always builds a `FormattedConsoleSink`.
#### When Need A Per-call Target Override After Built Async Text Construction
When typed async text config should still build a logger that supports a one-call target override:
```moonbit
let logger = build_async_text_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="async.text.audit")
```
In this example, the emitted record uses `async.text.audit` for that call.
And later `debug(...)`, `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If callers need sink-kind-driven branching across console, JSON, text, or file output, `build_async_logger(...)` is the better fit.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this builder still ignores that sink-kind choice and does not create a file-backed async logger.
- If the logger is never `run()`, pending records still follow the normal async queue lifecycle rules.
- If callers rely on the concrete formatter or post-run state shape, this builder is the direct API that preserves those text-console details rather than a reduced alias or wrapper.
### Notes
1. This API is narrower than `build_async_logger(...)` because it preserves a concrete text sink type.
2. It is the base builder used by the application and library async text facades.
3. See [target-verification.md](./target-verification.md) for the current local verification matrix.
4. Use this direct builder when callers should keep the full async helper surface on the concrete text sink type rather than a naming alias or a narrowed library wrapper.
+112
View File
@@ -0,0 +1,112 @@
---
name: build-library-async-logger
group: api
category: facade
update-time: 20260614
description: Build the library-facing async logger facade from an AsyncLoggerBuildConfig while intentionally hiding direct async state helpers.
key-word:
- library
- async
- facade
- public
---
## Build-library-async-logger
Build a `LibraryAsyncLogger[RuntimeSink]` from `AsyncLoggerBuildConfig`. This is the library-facing async facade over the general config-driven async builder.
### Interface
```moonbit
pub fn build_library_async_logger(
config : AsyncLoggerBuildConfig,
) -> LibraryAsyncLogger[RuntimeSink] {
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined sync logger config and async queue/runtime config.
#### output
- `LibraryAsyncLogger[RuntimeSink]` - Library-facing async logger wrapper.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API builds the general async runtime logger and then wraps it in the narrower `LibraryAsyncLogger[@bitlogger.RuntimeSink]` facade.
- The embedded `LoggerConfig` still goes through the normal synchronous config path first, so sink shape and any optional synchronous queue layer are already applied before the outer async layer is wrapped and then narrowed.
- The returned facade wraps the same underlying `AsyncLogger[@bitlogger.RuntimeSink]` value that `build_async_logger(...)` would produce directly.
- The result keeps async lifecycle operations such as `run()` and `shutdown()` while narrowing the public shape.
- The broader async helper surface is preserved rather than rebuilt, but it is intentionally not directly exposed on the returned facade. Queue counters, lifecycle state, idle-wait helpers, and file-backed runtime helpers stay behind `to_async_logger()` instead of disappearing.
- The narrower facade does not change the underlying runtime-sink queue counters, failure state, sink shape, or runtime-dependent post-close behavior; it only hides the broader helper surface until `to_async_logger()` is used.
- Because this facade starts from `build_async_logger(...)`, the wrapped async logger also keeps the runtime-sink flush wiring: `Batch` and `Shutdown` use the built sink's real `flush()` path instead of the default no-op callback used by the text-specific builder line.
- The facade still preserves the underlying async target rules on its exposed write methods: `log(..., target=...)` can override the target for one call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- In the current direct builder coverage, unwrapping this facade produces the same async state snapshot, runtime-sink variant, queue counters, lifecycle flags, and failure fields as calling `build_async_logger(config)` directly.
- That same unwrap also preserves file-backed runtime helpers on `RuntimeSink` values rather than replacing them with facade-specific behavior.
- Async state helpers such as `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, and failure-status inspection remain on the underlying `AsyncLogger`, not on the returned facade itself.
- `to_async_logger()` can be used to recover the underlying full async logger.
- Use this builder when the boundary should preserve the runtime-sink async build path but still hide broader async inspection helpers from downstream callers.
### How to Use
Here are some specific examples provided.
#### When Need A Narrower Async Type For Libraries
When a reusable package should expose async logging but not the full runtime type directly:
```moonbit
let logger = build_library_async_logger(
AsyncLoggerBuildConfig::new(
logger=LoggerConfig::new(target="lib.async"),
async_config=AsyncLoggerConfig::new(max_pending=4),
),
)
```
In this example, async runtime construction is hidden behind the library facade.
#### When Need Async State Helpers After Library-oriented Construction
When config-built async state inspection is still needed internally:
```moonbit
let logger = build_library_async_logger(config)
let full = logger.to_async_logger()
ignore(full.pending_count())
```
In this example, the library async facade is unwrapped before using async state helpers.
And unlike `ApplicationAsyncLogger`, the narrower builder result does not expose those broader async helpers directly on the facade surface.
#### When Need A Per-call Target Override Through The Library Async Builder Facade
When typed library async config should still build a facade that allows a one-call target override without unwrapping first:
```moonbit
let logger = build_library_async_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="lib.audit")
```
In this example, the emitted record uses `lib.audit` for that call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If backend-specific sink limitations exist, they still apply under the facade.
- If callers need async state, failure-status, or idle-wait helpers outside the library facade, they must unwrap with `to_async_logger()`.
- If callers need file-backed runtime helpers such as file-state or queued runtime inspection, they must unwrap first, but the helper behavior itself stays aligned with the direct `build_async_logger(config)` result.
- If callers instead want the text-console builder path where `Batch` and `Shutdown` keep the default no-op flush callback, they should use `build_library_async_text_logger(...)`; this runtime-sink facade preserves the real sink `flush()` behavior from `build_async_logger(...)`.
### Notes
1. Prefer this API when library boundaries should stay narrow.
2. Use `parse_and_build_library_async_logger(...)` when starting from JSON text.
3. Use `build_library_async_text_logger(...)` instead when the library-facing async type should preserve the narrower `FormattedConsoleSink` shape rather than `RuntimeSink`.
+115
View File
@@ -0,0 +1,115 @@
---
name: build-library-async-text-logger
group: api
category: facade
update-time: 20260614
description: Build the library-facing text-console async logger facade from an AsyncLoggerBuildConfig using the concrete text-console builder path.
key-word:
- library
- async
- text
- public
---
## Build-library-async-text-logger
Build a `LibraryAsyncLogger[FormattedConsoleSink]` from `AsyncLoggerBuildConfig`. This facade is the library-oriented async builder for the concrete text-console sink shape returned by `build_async_text_logger(...)`.
### Interface
```moonbit
pub fn build_library_async_text_logger(
config : AsyncLoggerBuildConfig,
) -> LibraryAsyncLogger[FormattedConsoleSink] {
```
#### input
- `config : AsyncLoggerBuildConfig` - Combined sync logger config and async queue/runtime config.
#### output
- `LibraryAsyncLogger[FormattedConsoleSink]` - Library-facing async logger backed by formatted console output.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API delegates to `build_async_text_logger(...)` and then narrows the result to `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
- It always produces a concrete `FormattedConsoleSink` from `config.logger.sink.text_formatter` instead of branching on sink kinds.
- That means even if `config.logger.sink.kind` says `Console`, `JsonConsole`, or `File`, this facade still follows the text-console path and uses only the configured `text_formatter` details.
- Unlike `build_library_async_logger(...)`, this facade does not go through the full synchronous configured-logger build path first.
- It uses the selected text-oriented `LoggerConfig` fields directly and therefore does not apply `LoggerConfig.queue` or preserve sync runtime sink controls.
- A sync queue configured on `LoggerConfig.queue` is therefore ignored by this builder instead of being preserved behind the wrapped text-console async logger.
- The returned facade wraps the same underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]` value that `build_async_text_logger(...)` would return directly, so `run()`, `shutdown()`, and queue or failure-state behavior are unchanged under the narrower public type.
- In the current direct text-builder coverage, unwrapping this facade yields the same async state snapshot, formatter behavior, queue counters, lifecycle flags, and later failure fields as calling `build_async_text_logger(config)` directly.
- The configured `flush_policy` is still carried by that underlying async logger, but this text-specific builder path does not provide the explicit sink flush callback used by `build_library_async_logger(...)` through `build_async_logger(...)`.
- As a result, `Batch` and `Shutdown` only invoke the default no-op async flush callback on this text-console path unless downstream code unwraps and adds different behavior elsewhere.
- The facade still preserves the underlying async target rules on its exposed write methods: `log(..., target=...)` can override the target for one call, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- Async state helpers such as `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, and failure-status inspection remain on the underlying `AsyncLogger[@bitlogger.FormattedConsoleSink]`, not on the returned facade itself.
- `to_async_logger()` can recover the underlying full async logger if needed.
- Use this builder when the boundary should preserve the concrete text-console sink type while still hiding broader async inspection and helper APIs from downstream callers.
### How to Use
Here are some specific examples provided.
#### When Need A Narrow Async Text Logger For Libraries
When a library wants text-console async output and a narrower public type:
```moonbit
let logger = build_library_async_text_logger(
AsyncLoggerBuildConfig::new(
logger=text_console(target="lib.text.async"),
async_config=AsyncLoggerConfig::new(max_pending=4),
),
)
```
In this example, the async text sink shape is preserved under the library facade.
And the chosen builder path matters more than `config.logger.sink.kind`: this facade still uses the text formatter directly because it always builds a `FormattedConsoleSink` before narrowing to `LibraryAsyncLogger[@bitlogger.FormattedConsoleSink]`.
#### When Need Async State Helpers After Library Text Construction
When library-facing text-console construction should still allow internal async inspection later:
```moonbit
let logger = build_library_async_text_logger(config)
let full = logger.to_async_logger()
ignore(full.pending_count())
```
In this example, the facade is unwrapped before using async state helpers.
#### When Need A Per-call Target Override Through The Library Async Text Builder Facade
When typed library async text config should still build a facade that allows a one-call target override without unwrapping first:
```moonbit
let logger = build_library_async_text_logger(config)
logger.log(@bitlogger.Level::Error, "boom", target="lib.text.audit")
```
In this example, the emitted record uses `lib.text.audit` for that call.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If callers need sink-kind-driven branching such as JSON console or file-backed async output, they should use `build_library_async_logger(...)` instead.
- If the config carries file-oriented fields such as `path` only because `sink.kind` was set to `File`, this facade still ignores that sink-kind choice and does not create a file-backed async logger.
- If callers expect async state or idle-wait helpers directly on the returned facade, they must unwrap first with `to_async_logger()`.
- If callers need to inspect the actual formatter-backed async state after library-level `run()` or `shutdown()` calls, unwrapping exposes the same post-call counters and failure fields that the direct text builder would have produced.
- Normal async lifecycle expectations still apply if the logger is never run.
### Notes
1. This is the library-side counterpart to `build_application_text_async_logger(...)`.
2. It is most useful when a concrete text-console async sink type matters to the caller boundary.
3. Use `build_library_async_logger(...)` instead when the library-facing async type should keep the broader `RuntimeSink` build path, including sync queue application through `build_logger(config.logger)`.
+101
View File
@@ -0,0 +1,101 @@
---
name: build-library-logger
group: api
category: facade
update-time: 20260613
description: Build the library-facing sync logger facade from a LoggerConfig by delegating to the configured runtime logger build path and then wrapping the result.
key-word:
- library
- facade
- logger
- public
---
## Build-library-logger
Build a `LibraryLogger[RuntimeSink]` from `LoggerConfig`. This facade keeps a smaller library-oriented sync surface while still using config-driven runtime assembly underneath.
### Interface
```moonbit
pub fn build_library_logger(config : LoggerConfig) -> LibraryLogger[RuntimeSink] {
```
#### input
- `config : LoggerConfig` - Fully assembled sync logger config.
#### output
- `LibraryLogger[RuntimeSink]` - Library-facing logger wrapper over the configured runtime sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- This API builds a configured runtime logger first and then wraps that same value as `LibraryLogger[RuntimeSink]`.
- The embedded config still goes through the normal runtime logger build path, including runtime sink selection, optional queue wrapping, and timestamp application.
- The returned facade wraps the same underlying `ConfiguredLogger` value that `build_logger(...)` would produce directly.
- The facade intentionally exposes a smaller logging surface than the full configured runtime logger.
- In particular, the configured runtime helper surface is preserved rather than rebuilt, but it is intentionally not directly exposed on the returned facade. Queue metrics, flush or drain helpers, and file controls stay behind `to_logger()` instead of disappearing.
- The facade still preserves the underlying logger target rules on its exposed write methods: `log(..., target=...)` can override the target for one write, while `info(...)`, `warn(...)`, and `error(...)` continue using the stored logger target unless the facade first derived another logger with `with_target(...)` or `child(...)`.
- Queue metrics, flush and drain helpers, and file runtime controls remain on the underlying `ConfiguredLogger`, not on the returned facade itself.
- Call `to_logger()` if a caller must recover the underlying full logger object.
- Use this builder when the boundary should preserve the configured runtime logger path but still hide broader runtime helper methods from downstream callers.
### How to Use
Here are some specific examples provided.
#### When Need A Smaller Library-facing Logging Type
When package code should accept or produce a narrower logger facade:
```moonbit
let logger = build_library_logger(
LoggerConfig::new(target="lib", sink=SinkConfig::new(kind=SinkKind::Console)),
)
```
In this example, the logger is built from config and then narrowed to the library facade.
#### When Need Runtime Helpers After Library-oriented Construction
When config-built runtime queue or file controls are still needed internally:
```moonbit
let logger = build_library_logger(config)
let full = logger.to_logger()
ignore(full.sink.pending_count())
```
In this example, the library facade is unwrapped before using runtime-specific helpers through the preserved `RuntimeSink` value.
And the unwrapped value still carries the same `RuntimeSink` pipeline built from the original config.
And unlike `ApplicationLogger`, the narrower builder result does not expose those runtime helpers directly on the facade surface.
#### When Need A Per-call Target Override Through The Library Builder Facade
When typed library config should still build a facade that allows a one-write target override without unwrapping first:
```moonbit
let logger = build_library_logger(config)
logger.log(Level::Error, "boom", target="lib.audit")
```
In this example, the emitted record uses `lib.audit` for that write.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the facade's stored target unless code derives another facade first with `with_target(...)` or `child(...)`.
### Error Case
e.g.:
- If backend-specific sink limitations exist, they still apply after the facade is built.
- If code later needs queue metrics, flush or drain helpers, or file runtime controls, it must unwrap with `to_logger()`.
### Notes
1. Prefer this facade when library APIs should not expose the full configured runtime logger type.
2. Use `parse_and_build_library_logger(...)` when starting from JSON text.
3. Use `to_logger()` when internal code later needs broader logger composition or direct access to the preserved `RuntimeSink` value without changing the public facade type.
+102
View File
@@ -0,0 +1,102 @@
---
name: build-logger
group: api
category: config
update-time: 20260512
description: Build a configured runtime logger from a LoggerConfig while preserving queue and file control helpers.
key-word:
- logger
- config
- runtime
- public
---
## Build-logger
Build a `ConfiguredLogger` from `LoggerConfig`. This is the main config-to-runtime bridge for synchronous logging and is the builder used before async wrapping in config-driven async flows.
### Interface
```moonbit
pub fn build_logger(config : LoggerConfig) -> ConfiguredLogger {}
```
#### input
- `config : LoggerConfig` - Fully assembled logger config including level, target, timestamp, sink, and optional queue wrapper.
#### output
- `ConfiguredLogger` - A runtime logger backed by `RuntimeSink`, with queue and file control helpers preserved.
### Explanation
Detailed rules explaining key parameters and behaviors
- `build_logger(...)` first constructs a base `RuntimeSink` from `config.sink`, then applies `config.queue` when present, and finally builds `Logger::new(...)` with `config.min_level`, `config.target`, and `config.timestamp`.
- The returned logger still supports normal logging methods because `ConfiguredLogger` is `Logger[RuntimeSink]`.
- The returned logger also keeps inherited logger target behavior such as `with_target(...)`, `child(...)`, and per-call `target=` overrides on `log(...)`.
- That means `log(..., target=...)` can override the target for one write, while severity helpers such as `info(...)`, `warn(...)`, and `error(...)` continue to use the stored logger target unless a derived logger was created first with `with_target(...)` or `child(...)`.
- Queue metrics and file controls remain available through forwarding helpers on the configured logger.
- `build_application_logger(...)` only re-exports this same configured runtime logger result under the `ApplicationLogger` alias, while `build_library_logger(...)` wraps the same result in `LibraryLogger[RuntimeSink]`.
- This API is deterministic and data-driven, making it suitable for bootstrapping from parsed config.
### How to Use
Here are some specific examples provided.
#### When Need Structured Config-first Bootstrapping
When config is already assembled as typed values:
```moonbit
let logger = build_logger(
LoggerConfig::new(
min_level=Level::Info,
target="svc",
sink=SinkConfig::new(kind=SinkKind::TextConsole),
),
)
```
In this example, no JSON parsing is required because config objects were built directly.
And the runtime logger is ready immediately, with the same ordinary logger target semantics as any other `Logger` value.
#### When Need A Per-call Target Override After Typed Config Build
When typed config should still build a logger that supports a one-write target override:
```moonbit
let logger = build_logger(config)
logger.log(Level::Error, "boom", target="svc.audit")
```
In this example, the emitted record uses `svc.audit` for that write.
And later `info(...)`, `warn(...)`, or `error(...)` calls still use the logger's stored target unless code derives another logger first with `with_target(...)` or `child(...)`.
#### When Need Config-built Queue Or File Runtime Helpers
When the sink shape comes from config but runtime controls still matter:
```moonbit
let logger = build_logger(config)
ignore(logger.pending_count())
ignore(logger.file_runtime_state())
```
In this example, config-driven construction does not remove observability or control helpers.
### Error Case
e.g.:
- If config contains a file sink on a non-native backend, callers must still respect backend capability behavior.
- If queue is not configured, queue-related counters simply reflect the non-queued runtime shape.
### Notes
1. Use this API when config is already typed as `LoggerConfig`.
2. Use `parse_and_build_logger(...)` when the starting point is raw JSON text.
3. Use the application or library facade builders only when the boundary name or exposed surface should differ; they do not change the underlying configured runtime logger pipeline built here.
+72
View File
@@ -0,0 +1,72 @@
---
name: callback-sink-type
group: api
category: sink
update-time: 20260613
description: Public callback sink type used for forwarding structured records to user code.
key-word:
- sink
- callback
- type
- public
---
## Callback-sink-type
`CallbackSink` is the public callback sink type used for forwarding structured `Record` values to user code. It is the concrete sink type returned by `callback_sink(...)` and is intended for tests, adapters, and custom integrations that want raw record access.
### Interface
```moonbit
pub struct CallbackSink {
callback : (Record) -> Unit
}
```
#### output
- `CallbackSink` - Public synchronous sink type that forwards full `Record` values to a stored callback.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a public root struct, not a type alias.
- The current field is `callback : (Record) -> Unit`.
- `callback_sink(...)` constructs this type directly from a record callback.
- Unlike `FormattedCallbackSink`, this sink preserves full structured record access instead of converting records into text first.
### How to Use
Here are some specific examples provided.
#### When Need A Typed Structured Callback Sink Value
When code should keep the concrete record-callback sink type visible:
```moonbit
let sink : CallbackSink = callback_sink(fn(rec) { println(rec.message) })
```
In this example, the sink value stays explicit and preserves direct access to structured record data.
#### When Need A Typed Callback-backed Logger
When logging should preserve the raw-record callback sink type in the logger:
```moonbit
let logger : Logger[CallbackSink] = Logger::new(callback_sink(fn(rec) { println(rec.target) }))
```
In this example, the concrete callback sink remains part of the logger type.
### Error Case
e.g.:
- `CallbackSink` itself does not have a runtime failure mode.
- Behavior inside the stored callback is fully user-defined, so failures there are outside the sink type's own API contract.
### Notes
1. Use `callback_sink(...)` when you need a value of this type.
2. Use `FormattedCallbackSink` when the destination expects final rendered text rather than full `Record` values.
+69
View File
@@ -0,0 +1,69 @@
---
name: callback-sink
group: api
category: sink
update-time: 20260520
description: Create a sink that forwards records to a user callback.
key-word:
- sink
- callback
- record
- public
---
## Callback-sink
Create a sink that forwards each `Record` to a callback. This is the most direct built-in integration hook for tests, adapters, and custom side effects.
### Interface
```moonbit
pub fn callback_sink(callback : (Record) -> Unit) -> CallbackSink {
```
#### input
- `callback : (Record) -> Unit` - Function called for each emitted record.
#### output
- `CallbackSink` - Sink that forwards records to the callback.
### Explanation
Detailed rules explaining key parameters and behaviors
- The callback receives the full structured record.
- This sink is useful for tests, custom bridges, or integration code that wants raw record access.
- Formatting is not applied automatically because the callback works on `Record` values directly.
### How to Use
Here are some specific examples provided.
#### When Need To Capture Structured Records
When tests or adapters want direct access to target, message, and fields:
```moonbit
let logger = Logger::new(
callback_sink(fn(rec) {
println(rec.target)
}),
target="hook",
)
```
In this example, the callback sees the structured record rather than pre-rendered text.
### Error Case
e.g.:
- If text output is needed instead of raw records, use `text_callback_sink(...)`.
- Callback behavior is fully user-defined, so failures inside the callback are outside the sink's own API contract.
### Notes
1. This sink is commonly useful in tests and adapters.
2. It composes naturally with filter, patch, fanout, and queue wrappers.
+64
View File
@@ -0,0 +1,64 @@
---
name: color-mode-label
group: api
category: formatter
update-time: 20260520
description: Convert a ColorMode value into its stable string label.
key-word:
- color
- formatter
- label
- public
---
## Color-mode-label
Convert `ColorMode` into its stable string label. This helper is useful for diagnostics, tests, and config inspection output that should mirror the built-in color mode names.
### Interface
```moonbit
pub fn color_mode_label(mode : ColorMode) -> String {
```
#### input
- `mode : ColorMode` - Color mode enum value to label.
#### output
- `String` - Stable label such as `never`, `auto`, or `always`.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned strings match the built-in color mode vocabulary.
- This helper is presentation-oriented and does not by itself enable or disable color rendering.
- It is useful when tests or diagnostics should expose the configured color policy clearly.
### How to Use
Here are some specific examples provided.
#### When Need A Readable Color Mode Name
When config or test output should include the current color policy:
```moonbit
let label = color_mode_label(ColorMode::Always)
```
In this example, `label` becomes `"always"`.
### Error Case
e.g.:
- There is no failure path for valid `ColorMode` values.
- If code needs rendering behavior rather than display text, the enum value itself is usually more useful than the label string.
### Notes
1. This helper is mostly useful for readable inspection and assertions.
2. It is a natural companion to `color_support_label(...)`.
+71
View File
@@ -0,0 +1,71 @@
---
name: color-mode
group: api
category: formatter
update-time: 20260613
description: Public color-mode enum alias used by text formatters and formatter config.
key-word:
- color
- formatter
- alias
- public
---
## Color-mode
`ColorMode` is the public enum that controls when ANSI color rendering is enabled for text formatting. It is a direct alias to the formatter enum used by both `text_formatter(...)` and `TextFormatterConfig::new(...)`.
### Interface
```moonbit
pub type ColorMode = @utils.ColorMode
```
#### output
- `ColorMode` - Public formatter color-policy enum with the variants `Never`, `Auto`, and `Always`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a wrapper or a separate formatter mode.
- `ColorMode::Never` disables ANSI color output.
- `ColorMode::Always` always enables ANSI color rendering.
- `ColorMode::Auto` currently follows the built-in `NO_COLOR` environment check.
- The same enum is used by runtime formatters and serializable formatter config objects.
### How to Use
Here are some specific examples provided.
#### When Need Plain Text Without ANSI Codes
When logs should stay uncolored even if the terminal supports color:
```moonbit
let formatter = text_formatter(color_mode=ColorMode::Never)
```
In this example, rendered text keeps the formatter output readable without ANSI escape sequences.
#### When Need Forced Terminal Colors
When tests or demos should always emit colored output:
```moonbit
let formatter = text_formatter(color_mode=ColorMode::Always)
```
In this example, ANSI color rendering is enabled regardless of `NO_COLOR`.
### Error Case
e.g.:
- `ColorMode` itself does not have a runtime failure mode.
- `ColorMode::Auto` is policy-based, so the final visible result still depends on environment state such as `NO_COLOR`.
### Notes
1. Use `color_mode_label(...)` when you need a stable string form for diagnostics or tests.
2. This enum controls whether color is used, while `ColorSupport` controls how rich the color encoding can be.
+64
View File
@@ -0,0 +1,64 @@
---
name: color-support-label
group: api
category: formatter
update-time: 20260520
description: Convert a ColorSupport value into its stable string label.
key-word:
- color
- formatter
- label
- public
---
## Color-support-label
Convert `ColorSupport` into its stable string label. This helper is useful for diagnostics, tests, and config-oriented output that should mirror the built-in color support names.
### Interface
```moonbit
pub fn color_support_label(support : ColorSupport) -> String {
```
#### input
- `support : ColorSupport` - Color support enum value to label.
#### output
- `String` - Stable label such as `basic` or `truecolor`.
### Explanation
Detailed rules explaining key parameters and behaviors
- The returned strings are stable enum labels used by config and tests.
- This helper is presentation-oriented and does not change formatter behavior by itself.
- It is useful when code should display or assert a readable color support mode.
### How to Use
Here are some specific examples provided.
#### When Need A Readable Color Support Name
When diagnostics or tests should show the selected color capability:
```moonbit
let label = color_support_label(ColorSupport::Basic)
```
In this example, `label` becomes `"basic"`.
### Error Case
e.g.:
- There is no failure path for valid `ColorSupport` values.
- If code needs to choose rendering behavior, the enum value itself is usually more useful than its label string.
### Notes
1. This helper is mostly useful for readable output and assertions.
2. It pairs naturally with config parsing and formatter inspection tests.
+77
View File
@@ -0,0 +1,77 @@
---
name: color-support
group: api
category: formatter
update-time: 20260613
description: Public color-support enum alias used by text formatters and formatter config.
key-word:
- color
- formatter
- alias
- public
---
## Color-support
`ColorSupport` is the public enum that controls how much color precision a text formatter should use when ANSI color output is enabled. It is a direct alias to the formatter enum used by both runtime formatters and serializable formatter config.
### Interface
```moonbit
pub type ColorSupport = @utils.ColorSupport
```
#### output
- `ColorSupport` - Public formatter color-capability enum with the variants `Basic` and `TrueColor`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a wrapper or a separate rendering engine.
- `ColorSupport::Basic` prefers named/basic ANSI color codes.
- `ColorSupport::TrueColor` allows 24-bit color codes when the style input uses values such as hex colors.
- This setting only matters when color output is enabled through `ColorMode`.
- The same enum is used by `text_formatter(...)` and `TextFormatterConfig::new(...)`.
### How to Use
Here are some specific examples provided.
#### When Need Conservative Basic ANSI Colors
When output should stay within the smaller named ANSI color set:
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
color_support=ColorSupport::Basic,
)
```
In this example, style rendering prefers the basic color vocabulary instead of 24-bit codes.
#### When Need Hex-style Richer Color Rendering
When custom style tags should preserve truecolor output where possible:
```moonbit
let formatter = text_formatter(
color_mode=ColorMode::Always,
color_support=ColorSupport::TrueColor,
)
```
In this example, formatter styles can emit richer ANSI color codes for hex-based styles.
### Error Case
e.g.:
- `ColorSupport` itself does not have a runtime failure mode.
- If `ColorMode::Never` disables ANSI color output, changing `ColorSupport` does not produce a visible effect.
### Notes
1. Use `color_support_label(...)` when you need a stable string form.
2. This enum controls color precision, not whether color rendering is enabled at all.
+82
View File
@@ -0,0 +1,82 @@
---
name: compose-patches
group: api
category: patching
update-time: 20260512
description: Create a reusable record patch pipeline from several ordered patches.
key-word:
- patch
- compose
- pipeline
- public
---
## Compose-patches
Create a `RecordPatch` that applies several patches in order. This helper is the standard way to build deterministic record transformation pipelines.
### Interface
```moonbit
pub fn compose_patches(patches : Array[RecordPatch]) -> RecordPatch {}
```
#### input
- `patches : Array[RecordPatch]` - Patches applied sequentially from first to last.
#### output
- `RecordPatch` - Patch that returns the final record after every nested patch has run.
### Explanation
Detailed rules explaining key parameters and behaviors
- Composition is ordered: each patch receives the result of the previous patch.
- Later patches can overwrite changes made by earlier patches.
- If the patch array is empty, the returned patch behaves like `identity_patch()`.
- This helper is useful for combining normalization, enrichment, and redaction into one reusable unit.
### How to Use
Here are some specific examples provided.
#### When Build A Stable Rewrite Pipeline
When a logger needs several transformations in one place:
```moonbit
let patch = compose_patches([
set_target("api.gateway"),
prefix_message("[safe] "),
redact_fields(["token", "password"]),
])
```
In this example, target rewrite, message prefixing, and field redaction happen in fixed order.
#### When Reuse The Same Policy Across Loggers
When several sinks should share one transformation bundle:
```moonbit
let shared_patch = compose_patches([
append_fields([field("service", "billing")]),
redact_field("secret"),
])
```
In this example, the composed patch can be attached to multiple loggers without repeating inline closures.
### Error Case
e.g.:
- If `patches` is empty, the returned patch leaves records unchanged.
- If several patches rewrite the same property, the last patch in the array determines the final value.
### Notes
1. Keep patch order explicit because composition semantics are intentionally sequential.
2. Prefer one composed patch over many ad hoc inline wrappers when the transformation policy is shared.
+77
View File
@@ -0,0 +1,77 @@
---
name: config-error
group: api
category: config
update-time: 20260613
description: Public config parsing error alias used by synchronous config-loading helpers.
key-word:
- config
- error
- alias
- public
---
## Config-error
`ConfigError` is the public error type raised by synchronous config parsing helpers. It is a direct alias to the internal config error definition and currently exposes a single structured error case for invalid input.
### Interface
```moonbit
pub type ConfigError = @utils.ConfigError
```
#### output
- `ConfigError` - Public config parsing error type with the case `InvalidConfig(String)`.
### Explanation
Detailed rules explaining key parameters and behaviors
- This is a type alias, not a separate public wrapper.
- The current public error case is `ConfigError::InvalidConfig(message)`.
- The alias is used by parsing helpers such as `parse_logger_config_text(...)` and by lower-level config parsing routines beneath it.
- Error messages describe concrete schema problems such as invalid JSON, wrong value types, unsupported enum text, or missing required values for a chosen sink kind.
### How to Use
Here are some specific examples provided.
#### When Need To Catch Config Parse Failures
When raw config text should be validated before boot:
```moonbit
let config = parse_logger_config_text(raw) catch {
err if err is ConfigError => {
println(err.to_string())
return
}
}
```
In this example, the caller keeps config validation failures separate from normal runtime work.
#### When Need To Surface A Clear Parse Message
When tooling should report why config input was rejected:
```moonbit
ignore(parse_logger_config_text("{bad json}")) catch {
err => println(err.to_string())
}
```
In this example, the error carries a concrete message instead of failing silently.
### Error Case
e.g.:
- Invalid JSON input raises `ConfigError::InvalidConfig(...)`.
- Wrong field types, unsupported level text, unsupported sink kinds, or an empty `path` for a file sink also raise `ConfigError::InvalidConfig(...)`.
### Notes
1. This alias currently belongs to the synchronous config-loading path in `bitlogger`.
2. Async config parsers in `bitlogger_async` currently raise the generic failure surface used by that package instead of `ConfigError`.
+80
View File
@@ -0,0 +1,80 @@
---
name: configured-logger-close
group: api
category: runtime
update-time: 20260613
description: Close the configured runtime logger sink and return whether the wrapped RuntimeSink reported a successful close action.
key-word:
- logger
- runtime
- lifecycle
- public
---
## Configured-logger-close
Close the sink behind a `ConfiguredLogger`. This helper is the configured logger wrapper over `RuntimeSink::close(...)` for config-driven runtime teardown.
### Interface
```moonbit
pub fn ConfiguredLogger::close(self : ConfiguredLogger) -> Bool {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose sink should be closed.
#### output
- `Bool` - Whether the wrapped `RuntimeSink::close(...)` call reported a successful close action.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates directly to `self.sink.close()`.
- Plain file sinks forward to `FileSink::close()` through `RuntimeSink`.
- Queue-backed file sinks close the wrapped file sink instead of only the queue wrapper.
- Generic `close()` on queued file sinks does not drain pending queue entries first; it closes the wrapped file sink directly.
- Console-style sinks and queue-wrapped console-style sinks return `true` as a no-op success because they do not expose a meaningful close step here.
- For file-backed configured loggers, later `close()` calls return `false` after the wrapped file handle has already been cleared.
### How to Use
Here are some specific examples provided.
#### When Need Config-driven Runtime Teardown
When a config-built logger should release its sink resources:
```moonbit
ignore(logger.close())
```
In this example, sink teardown happens through the configured logger facade.
#### When Need To Observe Close Outcome
When application code wants a success flag from runtime close behavior:
```moonbit
let closed = logger.close()
```
In this example, callers can branch on the reported close result.
### Error Case
e.g.:
- If the configured runtime sink shape has no real close action, the helper may still return `true` as a no-op success.
- If the configured logger shares the same wrapped runtime sink state with another facade and one path already closed that file-backed sink, a later close attempt returns `false`.
- If callers need a file-specific close path with queue flush nuances, `file_close()` may be the better API.
### Notes
1. This is the generic configured runtime close helper.
2. Prefer `file_close()` when the configured sink shape is file-backed and queued records should be flushed before teardown.
3. Converting the same configured logger through wrapper paths such as library projection still shares the wrapped runtime sink state, so close effects are visible across those facades.
+75
View File
@@ -0,0 +1,75 @@
---
name: configured-logger-drain
group: api
category: runtime
update-time: 20260613
description: Drain queued work from a configured runtime logger with optional item limits through RuntimeSink.
key-word:
- logger
- runtime
- queue
- public
---
## Configured-logger-drain
Drain queued work from a `ConfiguredLogger`. This helper is the configured logger wrapper over `RuntimeSink::drain(...)` when config-driven queue wrapping should be advanced in a controlled, bounded way.
### Interface
```moonbit
pub fn ConfiguredLogger::drain(self : ConfiguredLogger, max_items~ : Int = -1) -> Int {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose queued work should be drained.
- `max_items : Int` - Optional upper bound on how many queued items to drain. Negative values mean no explicit bound.
#### output
- `Int` - Count returned by the wrapped `RuntimeSink::drain(...)` call.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates directly to `self.sink.drain(max_items=max_items)`.
- Queue-wrapped sinks forward to the concrete queue sink's `drain(...)` behavior and may drain up to `max_items` records.
- Plain file sinks fall back to `FileSink::flush()` behavior through `RuntimeSink` and return `1` or `0`.
- Plain console-style sinks return `0` because they do not own a drainable queue here.
### How to Use
Here are some specific examples provided.
#### When Need Bounded Queue Progress
When queued output should be advanced in chunks:
```moonbit
let drained = logger.drain(max_items=16)
```
In this example, callers limit how much queued work is processed in one step.
#### When Need Full Manual Drain
When the configured queue should be emptied explicitly:
```moonbit
ignore(logger.drain())
```
In this example, the configured runtime logger drains without imposing an item cap.
### Error Case
e.g.:
- If the configured runtime sink is not queue-backed, draining may return `0` or follow the plain-file flush fallback.
- If callers only need generic flush semantics, `flush()` may be the simpler API.
### Notes
1. Prefer this helper when queue progress should be bounded or observable.
2. Use `pending_count()` to inspect remaining backlog after the drain call when the configured sink is queue-backed.
@@ -0,0 +1,76 @@
---
name: configured-logger-dropped-count
group: api
category: runtime
update-time: 20260613
description: Read the cumulative dropped-record count from a configured runtime logger through RuntimeSink.
key-word:
- logger
- runtime
- queue
- public
---
## Configured-logger-dropped-count
Read the cumulative dropped-record count from a `ConfiguredLogger`. This helper is the configured logger wrapper over `RuntimeSink::dropped_count(...)` when config-driven queue wrapping may discard records under pressure.
### Interface
```moonbit
pub fn ConfiguredLogger::dropped_count(self : ConfiguredLogger) -> Int {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose dropped-record metric should be inspected.
#### output
- `Int` - Number returned by the wrapped `RuntimeSink::dropped_count()` call.
### Explanation
Detailed rules explaining key parameters and behaviors
- This helper delegates directly to `self.sink.dropped_count()`.
- Queue-backed runtime sink variants return their live dropped-count metric.
- Plain console and plain file runtime sink variants return `0` because they do not track queued record drops.
- The counter is cumulative for the lifetime of the concrete runtime sink value owned by the configured logger.
### How to Use
Here are some specific examples provided.
#### When Need Loss Visibility On Config-built Queues
When a config-driven queue may discard records:
```moonbit
if logger.dropped_count() > 0 {
println("configured logger dropped records")
}
```
In this example, the runtime logger exposes queue loss without manual sink inspection.
#### When Compare Queue Tuning Changes
When queue overflow policy should be validated operationally:
```moonbit
ignore(logger.dropped_count())
```
In this example, the helper exposes the metric needed to compare runtime queue tuning.
### Error Case
e.g.:
- If the configured logger is not queue-backed, the method simply returns `0`.
- If callers need queue shape and file status together, `file_runtime_state()` may carry more useful context for file sinks.
### Notes
1. This helper reports cumulative loss, not the reason for that loss.
2. Pair it with `pending_count()` and queue configuration when investigating pressure.
@@ -0,0 +1,75 @@
---
name: configured-logger-file-append-mode
group: api
category: runtime
update-time: 20260512
description: Read the current append-mode policy used by the configured runtime file sink.
key-word:
- logger
- runtime
- file
- public
---
## Configured-logger-file-append-mode
Read the current append-mode policy used by a `ConfiguredLogger` file sink. This helper exposes whether future reopen behavior is currently append-oriented.
### Interface
```moonbit
pub fn ConfiguredLogger::file_append_mode(self : ConfiguredLogger) -> Bool {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose append-mode policy should be inspected.
#### output
- `Bool` - Current append-mode policy for the file sink.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their current append policy through the wrapped `RuntimeSink`.
- Queued file sinks forward the policy from the wrapped inner file sink.
- Non-file sinks return `false`.
- This helper reports runtime file policy, not whether a file is currently writable.
### How to Use
Here are some specific examples provided.
#### When Need Runtime Append-policy Visibility
When diagnostics should show how future reopen behavior is configured:
```moonbit
let append = logger.file_append_mode()
```
In this example, the configured logger exposes current reopen policy directly.
#### When Validate Runtime Policy Changes
When policy mutation should be observable after a setter call:
```moonbit
ignore(logger.file_set_append_mode(true))
ignore(logger.file_append_mode())
```
In this example, callers verify the updated append-mode policy.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If callers need the full current file policy rather than just append mode, `file_policy()` is the better API.
### Notes
1. Use this helper when append policy is the only file setting you need to inspect.
2. Pair it with reopen helpers when debugging runtime file behavior.
@@ -0,0 +1,75 @@
---
name: configured-logger-file-auto-flush
group: api
category: runtime
update-time: 20260512
description: Read whether the configured runtime file sink currently has auto-flush enabled.
key-word:
- logger
- runtime
- file
- public
---
## Configured-logger-file-auto-flush
Read whether auto-flush is currently enabled on a `ConfiguredLogger` file sink. This helper exposes one important runtime durability policy flag.
### Interface
```moonbit
pub fn ConfiguredLogger::file_auto_flush(self : ConfiguredLogger) -> Bool {}
```
#### input
- `self : ConfiguredLogger` - Config-driven runtime logger whose auto-flush policy should be inspected.
#### output
- `Bool` - Whether auto-flush is currently enabled.
### Explanation
Detailed rules explaining key parameters and behaviors
- File-backed sinks report their current auto-flush policy through the wrapped `RuntimeSink`.
- Queued file sinks forward the policy from the wrapped inner file sink.
- Non-file sinks return `false`.
- This helper exposes policy state only and does not force any flush action.
### How to Use
Here are some specific examples provided.
#### When Need Runtime Durability Visibility
When diagnostics should expose whether each write auto-flushes:
```moonbit
let enabled = logger.file_auto_flush()
```
In this example, runtime file durability policy is surfaced directly.
#### When Validate Policy Updates
When code should observe auto-flush after a setter call:
```moonbit
ignore(logger.file_set_auto_flush(true))
ignore(logger.file_auto_flush())
```
In this example, callers verify the updated policy.
### Error Case
e.g.:
- If the configured sink is not file-backed, the method returns `false`.
- If callers need to actually flush the file, `file_flush()` is the operational API.
### Notes
1. Use this helper to inspect runtime durability policy.
2. It complements `file_set_auto_flush(...)` rather than replacing real flush actions.

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