From b4d69675fd408d07f80a46945585d45970459590 Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Tue, 25 Aug 2026 16:06:37 +0800 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20async=20and=20re?= =?UTF-8?q?place=20native=20file=20FFI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 升级 async 依赖,迁移到 MoonBit 原生 async/fs 文件接口,移除项目自有 C FFI,并固定文本文件为 LF。CI 全部通过。 --- .gitattributes | 2 + docs/api/file-sink-async.md | 65 ++++ docs/api/file-sink.md | 2 + docs/api/index.md | 1 + moon.mod | 2 +- src-async/async_logger_shared.mbt | 36 ++- .../async_native_file_integration_test.mbt | 4 +- src-async/pkg.generated.mbti | 1 + src/file_runtime/file_sink.mbt | 241 ++++++++++++++- .../file_sink_rotation_wbtest.mbt | 30 +- src/file_runtime/pkg.generated.mbti | 3 + src/formatting/formatter.mbt | 4 +- src/pkg.generated.mbti | 2 + src/runtime/pkg.generated.mbti | 1 + src/runtime/runtime_logger.mbt | 86 +++++- src/runtime_logger.mbt | 22 +- src/sink_graph/pkg.generated.mbti | 4 + src/sink_graph/sinks.mbt | 101 +++++++ src/sinks_file.mbt | 17 ++ src/utils/file_backend_native.mbt | 278 ++++++++++++------ src/utils/file_backend_stub.mbt | 82 +++++- src/utils/moon.pkg | 4 +- src/utils/pkg.generated.mbti | 18 +- src/utils/stub.c | 55 ---- 24 files changed, 860 insertions(+), 201 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/api/file-sink-async.md delete mode 100644 src/utils/stub.c diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..79cabbd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize repository text files and check them out with LF on every platform. +* text=auto eol=lf diff --git a/docs/api/file-sink-async.md b/docs/api/file-sink-async.md new file mode 100644 index 0000000..637535c --- /dev/null +++ b/docs/api/file-sink-async.md @@ -0,0 +1,65 @@ +--- +name: file-sink-async +group: api +category: sink +update-time: 20260825 +description: Create a file sink from an async context using the native async filesystem interface. +key-word: + - file + - sink + - async + - public +--- + +## File-sink-async + +Create a `FileSink` from an async context. The constructor opens the file with the native `moonbitlang/async/fs` interface and keeps the same rotation, policy, and failure-counter surface as `file_sink(...)`. + +### Interface + +```moonbit +pub async fn file_sink_async( + path : String, + append~ : Bool = true, + auto_flush~ : Bool = true, + rotation~ : FileRotation? = None, + formatter~ : RecordFormatter = fn(rec) { format_text(rec) }, +) -> FileSink {} +``` + +#### input + +- `path : String` - Destination file path. +- `append : Bool` - Whether writes append rather than truncate on the first open. +- `auto_flush : Bool` - Whether each async write requests an async file sync. +- `rotation : FileRotation?` - Optional size-based rotation policy. +- `formatter : RecordFormatter` - Formatter used to render each record. + +#### output + +- `FileSink` - A file sink ready for use by an async logger or direct async sink writes. + +### Explanation + +- Use this constructor when the caller is already running inside an async event loop. +- File writes, flushes, file-size checks, renames, removals, and rotation reopening use the async filesystem path. +- The returned sink implements `Sink::write_async(...)`; the synchronous `write(...)` surface remains available for synchronous callers. +- On non-native targets, the sink follows the existing unavailable-backend behavior. + +### How to Use + +```moonbit +let sink = file_sink_async("app.log", auto_flush=false) +let logger = async_logger(sink) +``` + +### Error Case + +- If the initial async open fails, the returned sink is unavailable and increments `open_failures()`. +- Later async writes record write and rotation failures through the existing sink counters. + +### Notes + +1. Use `file_sink(...)` for synchronous construction outside an async event loop. + +2. Pair this API with `native_files_supported()` when code must also compile for non-native targets. diff --git a/docs/api/file-sink.md b/docs/api/file-sink.md index 7e8e566..c397526 100644 --- a/docs/api/file-sink.md +++ b/docs/api/file-sink.md @@ -98,3 +98,5 @@ e.g.: 3. Non-native targets can still compile code referencing this API, but callers should treat actual file availability and successful writes as target-sensitive runtime behavior. 4. See [target-verification.md](./target-verification.md) for the current verification boundary between design intent and locally re-checked targets. + +5. Use [`file_sink_async(...)`](./file-sink-async.md) when constructing a file sink from an async event loop. diff --git a/docs/api/index.md b/docs/api/index.md index 885a351..4786c40 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -153,6 +153,7 @@ BitLogger API navigation. - [patch-sink.md](./patch-sink.md) - [patch-sink-type.md](./patch-sink-type.md) - [file-sink.md](./file-sink.md) +- [file-sink-async.md](./file-sink-async.md) - [file-sink-available.md](./file-sink-available.md) - [file-sink-flush.md](./file-sink-flush.md) - [file-sink-close.md](./file-sink-close.md) diff --git a/moon.mod b/moon.mod index 8f77aed..01d73b3 100644 --- a/moon.mod +++ b/moon.mod @@ -3,7 +3,7 @@ name = "Nanaloveyuki/BitLogger" version = "0.8.0" import { - "moonbitlang/async@0.20.2", + "moonbitlang/async@0.21.0", } readme = "src/README.mbt.md" diff --git a/src-async/async_logger_shared.mbt b/src-async/async_logger_shared.mbt index cabdc87..1757cca 100644 --- a/src-async/async_logger_shared.mbt +++ b/src-async/async_logger_shared.mbt @@ -132,6 +132,7 @@ pub struct AsyncLogger[S] { flush_policy : AsyncFlushPolicy sink : S flush_callback : (S) -> Unit raise + flush_async_callback : (async (S) -> Unit)? context_fields : Array[@bitlogger.Field] filter : (@bitlogger.Record) -> Bool patch : @bitlogger.RecordPatch @@ -160,6 +161,7 @@ pub fn[S] async_logger( flush_policy: config.flush, sink, flush_callback: flush, + flush_async_callback: None, context_fields: [], filter: fn(_) { true }, patch: @bitlogger.identity_patch(), @@ -171,6 +173,14 @@ pub fn[S] async_logger( } } +///| +fn[S] with_async_flush_callback_internal( + logger : AsyncLogger[S], + callback : async (S) -> Unit, +) -> AsyncLogger[S] { + { ..logger, flush_async_callback: Some(callback) } +} + ///| fn async_logger_phase_is_closed(phase : AsyncLifecyclePhase) -> Bool { match phase { @@ -535,8 +545,11 @@ pub async fn[S] AsyncLogger::shutdown( } ///| -fn[S] run_flush_callback(logger : AsyncLogger[S]) -> Unit raise { - (logger.flush_callback)(logger.sink) +async fn[S] run_flush_callback(logger : AsyncLogger[S]) -> Unit { + match logger.flush_async_callback { + Some(callback) => callback(logger.sink) + None => (logger.flush_callback)(logger.sink) + } } ///| @@ -546,7 +559,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit { err if err is AsyncLoggerClosed => break err => raise err } - logger.sink.write(rec) + logger.sink.write_async(rec) if logger.pending_count.val > 0 { logger.pending_count.val -= 1 } @@ -557,7 +570,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit { } match next { Some(next) => { - logger.sink.write(next) + logger.sink.write_async(next) if logger.pending_count.val > 0 { logger.pending_count.val -= 1 } @@ -575,7 +588,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit { } match waited { Some(next) => { - logger.sink.write(next) + logger.sink.write_async(next) if logger.pending_count.val > 0 { logger.pending_count.val -= 1 } @@ -598,6 +611,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit { } ///| +#warnings("-fragile_catch_all") pub async fn[S : @bitlogger.Sink] AsyncLogger::run( self : AsyncLogger[S], ) -> Unit { @@ -619,14 +633,20 @@ pub async fn[S : @bitlogger.Sink] AsyncLogger::run( pub fn build_async_logger( config : AsyncLoggerBuildConfig, ) -> AsyncLogger[@bitlogger.RuntimeSink] { - let logger = @bitlogger.build_logger(config.logger) - async_logger( + let logger = @bitlogger.build_logger_for_async(config.logger) + let logger = async_logger( logger.sink, config=config.async_config, min_level=logger.min_level, target=logger.target, flush=fn(sink) { ignore(sink.flush_progress()) }, - ).with_timestamp(enabled=logger.timestamp) + ) + let flush_async : async (@bitlogger.RuntimeSink) -> Unit = sink => { + sink.flush_async() + } + with_async_flush_callback_internal(logger, flush_async).with_timestamp( + enabled=logger.timestamp, + ) } ///| diff --git a/src-async/async_native_file_integration_test.mbt b/src-async/async_native_file_integration_test.mbt index 2422d33..ae0e801 100644 --- a/src-async/async_native_file_integration_test.mbt +++ b/src-async/async_native_file_integration_test.mbt @@ -5,7 +5,7 @@ async test "native async file lifecycle flushes rotates and restarts" { } else { let path = "logs/bitlogger-async-native-e2e.log" let first_flushes : Ref[Int] = Ref(0) - let first_sink = @bitlogger.file_sink( + let first_sink = @bitlogger.file_sink_async( path, append=false, auto_flush=false, @@ -47,7 +47,7 @@ async test "native async file lifecycle flushes rotates and restarts" { inspect(first_sink.close(), content="true") let restart_flushes : Ref[Int] = Ref(0) - let restarted_sink = @bitlogger.file_sink( + let restarted_sink = @bitlogger.file_sink_async( path, append=true, auto_flush=false, diff --git a/src-async/pkg.generated.mbti b/src-async/pkg.generated.mbti index 7d645fa..b0e8a7a 100644 --- a/src-async/pkg.generated.mbti +++ b/src-async/pkg.generated.mbti @@ -77,6 +77,7 @@ pub struct AsyncLogger[S] { flush_policy : @utils.AsyncFlushPolicy sink : S flush_callback : (S) -> Unit raise + flush_async_callback : (async (S) -> Unit)? context_fields : Array[@core.Field] filter : (@core.Record) -> Bool patch : (@core.Record) -> @core.Record diff --git a/src/file_runtime/file_sink.mbt b/src/file_runtime/file_sink.mbt index e9aea64..10d32cb 100644 --- a/src/file_runtime/file_sink.mbt +++ b/src/file_runtime/file_sink.mbt @@ -45,6 +45,7 @@ pub struct FileSink { priv default_auto_flush : Bool priv rotation : Ref[FileRotation?] priv default_rotation : FileRotation? + priv deferred : Bool priv open_failures : Ref[Int] priv write_failures : Ref[Int] priv flush_failures : Ref[Int] @@ -65,7 +66,64 @@ pub fn file_sink( rotation? : FileRotation? = None, formatter? : RecordFormatter = fn(rec) { @formatting.format_text(rec) }, ) -> FileSink { - let handle = @utils.open_file_handle_internal(path, append) + new_file_sink_internal( + path, + append, + auto_flush, + rotation, + formatter, + @utils.open_file_handle_internal(path, append), + ) +} + +///| +pub async fn file_sink_async( + path : String, + append? : Bool = true, + auto_flush? : Bool = true, + rotation? : FileRotation? = None, + formatter? : RecordFormatter = fn(rec) { @formatting.format_text(rec) }, +) -> FileSink { + new_file_sink_internal( + path, + append, + auto_flush, + rotation, + formatter, + @utils.open_file_handle_async_internal(path, append), + ) +} + +///| +#doc(hidden) +pub fn file_sink_deferred( + path : String, + append? : Bool = true, + auto_flush? : Bool = true, + rotation? : FileRotation? = None, + formatter? : RecordFormatter = fn(rec) { @formatting.format_text(rec) }, +) -> FileSink { + new_file_sink_internal( + path, + append, + auto_flush, + rotation, + formatter, + @utils.deferred_file_handle_internal(path, append), + deferred=true, + ) +} + +///| +fn new_file_sink_internal( + path : String, + append : Bool, + auto_flush : Bool, + rotation : FileRotation?, + formatter : RecordFormatter, + handle : FileHandle?, + deferred? : Bool = false, +) -> FileSink { { path, append: Ref(append), @@ -76,6 +134,7 @@ pub fn file_sink( default_auto_flush: auto_flush, rotation: Ref(rotation), default_rotation: rotation, + deferred, open_failures: Ref(if handle is Some(_) { 0 } else { 1 }), write_failures: Ref(0), flush_failures: Ref(0), @@ -103,6 +162,20 @@ pub fn FileSink::flush(self : FileSink) -> Bool { } } +///| +pub async fn FileSink::flush_async(self : FileSink) -> Bool { + match self.handle.val { + None => false + Some(handle) => { + let ok = @utils.flush_file_handle_async_internal(handle) + if !ok { + self.flush_failures.val += 1 + } + ok + } + } +} + ///| pub fn FileSink::append_mode(self : FileSink) -> Bool { self.append.val @@ -274,20 +347,38 @@ pub fn FileSink::state(self : FileSink) -> FileSinkState { 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(@utils.close_file_handle_internal(handle)) - self.handle.val = None + if self.deferred { + match self.handle.val { + None => () + Some(handle) => { + ignore(@utils.close_file_handle_internal(handle)) + self.handle.val = None + } + } + let reopened = @utils.deferred_file_handle_internal(self.path, append_mode) + self.handle.val = reopened + if reopened is Some(_) { + true + } else { + self.open_failures.val += 1 + false } - } - let reopened = @utils.open_file_handle_internal(self.path, append_mode) - self.handle.val = reopened - if reopened is Some(_) { - true } else { - self.open_failures.val += 1 - false + match self.handle.val { + None => () + Some(handle) => { + ignore(@utils.close_file_handle_internal(handle)) + self.handle.val = None + } + } + let reopened = @utils.open_file_handle_internal(self.path, append_mode) + self.handle.val = reopened + if reopened is Some(_) { + true + } else { + self.open_failures.val += 1 + false + } } } @@ -333,6 +424,29 @@ fn rename_file_if_present( } } +///| +async fn remove_file_if_present_async(path : String) -> Bool { + if !@utils.file_exists_async_internal(path) { + true + } else { + @utils.remove_file_async_internal(path) || + !@utils.file_exists_async_internal(path) + } +} + +///| +async fn rename_file_if_present_async( + from_path : String, + to_path : String, +) -> Bool { + if !@utils.file_exists_async_internal(from_path) { + true + } else { + @utils.rename_file_async_internal(from_path, to_path) || + !@utils.file_exists_async_internal(from_path) + } +} + ///| fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool { let closed = match sink.handle.val { @@ -396,6 +510,73 @@ fn rotate_if_needed_internal(sink : FileSink, next_line_bytes : Int) -> Bool { } } +///| +async fn rotate_file_sink_async_internal( + sink : FileSink, + rotation : FileRotation, +) -> Bool { + let closed = match sink.handle.val { + None => true + Some(handle) => { + let ok = @utils.close_file_handle_internal(handle) + sink.handle.val = None + ok + } + } + if !closed { + return false + } + if rotation.max_backups > 0 { + if !remove_file_if_present_async( + rotated_file_path(sink.path, rotation.max_backups), + ) { + return false + } + 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) + if !rename_file_if_present_async(from_path, to_path) { + return false + } + continue index - 1 + } + if !rename_file_if_present_async(sink.path, rotated_file_path(sink.path, 1)) { + return false + } + } else if !remove_file_if_present_async(sink.path) { + return false + } + sink.handle.val = @utils.open_file_handle_async_internal(sink.path, false) + sink.handle.val is Some(_) +} + +///| +async fn rotate_if_needed_async_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 = @utils.file_size_i64_async_internal(handle) + let next_line = next_line_bytes.to_int64() + if size + next_line <= rotation_max_bytes_internal(rotation) { + true + } else { + let rotated = rotate_file_sink_async_internal(sink, rotation) + if !rotated { + sink.rotation_failures.val += 1 + } + rotated + } + } + } + } +} + ///| pub impl @sink_graph.Sink for FileSink with fn write(self, rec : Record) { match self.handle.val { @@ -429,3 +610,37 @@ pub impl @sink_graph.Sink for FileSink with fn write(self, rec : Record) { } } } + +///| +pub impl @sink_graph.Sink for FileSink with fn write_async(self, rec : Record) { + match self.handle.val { + None => self.write_failures.val += 1 + Some(_) => { + let line = "\{(self.formatter)(rec)}\n" + let can_write = rotate_if_needed_async_internal( + self, + @utils.string_byte_length_internal(line), + ) + if can_write { + match self.handle.val { + None => self.write_failures.val += 1 + Some(active) => { + let wrote = @utils.write_file_handle_async_internal(active, line) + if wrote { + if self.auto_flush.val { + let flushed = @utils.flush_file_handle_async_internal(active) + if !flushed { + self.flush_failures.val += 1 + } + } + } else { + self.write_failures.val += 1 + } + } + } + } else { + self.write_failures.val += 1 + } + } + } +} diff --git a/src/file_runtime/file_sink_rotation_wbtest.mbt b/src/file_runtime/file_sink_rotation_wbtest.mbt index 46971bb..c0bf1c8 100644 --- a/src/file_runtime/file_sink_rotation_wbtest.mbt +++ b/src/file_runtime/file_sink_rotation_wbtest.mbt @@ -12,8 +12,14 @@ test "file sink counts an injected partial backup-chain failure" { rec.message }) if seed.is_available() { - seed.write(@core.Record::new(@core.Level::Info, "1234567890")) - seed.write(@core.Record::new(@core.Level::Info, "abcdefghij")) + @sink_graph.Sink::write( + seed, + @core.Record::new(@core.Level::Info, "1234567890"), + ) + @sink_graph.Sink::write( + seed, + @core.Record::new(@core.Level::Info, "abcdefghij"), + ) inspect(seed.rotation_failures(), content="0") inspect(seed.close(), content="true") @@ -33,7 +39,10 @@ test "file sink counts an injected partial backup-chain failure" { } }, } - sink.write(@core.Record::new(@core.Level::Info, "klmnopqrst")) + @sink_graph.Sink::write( + sink, + @core.Record::new(@core.Level::Info, "klmnopqrst"), + ) inspect(sink.rotation_failures(), content="1") inspect(sink.state().rotation_failures, content="1") @@ -65,8 +74,14 @@ test "file sink counts an injected oldest-backup removal failure" { rec.message }) if seed.is_available() { - seed.write(@core.Record::new(@core.Level::Info, "1234567890")) - seed.write(@core.Record::new(@core.Level::Info, "abcdefghij")) + @sink_graph.Sink::write( + seed, + @core.Record::new(@core.Level::Info, "1234567890"), + ) + @sink_graph.Sink::write( + seed, + @core.Record::new(@core.Level::Info, "abcdefghij"), + ) inspect(seed.close(), content="true") let oldest = @utils.open_file_handle_internal(test_path + ".2", true) @@ -95,7 +110,10 @@ test "file sink counts an injected oldest-backup removal failure" { @utils.rename_file_internal(from_path, to_path) }, } - sink.write(@core.Record::new(@core.Level::Info, "klmnopqrst")) + @sink_graph.Sink::write( + sink, + @core.Record::new(@core.Level::Info, "klmnopqrst"), + ) inspect(sink.rotation_failures(), content="1") inspect(sink.write_failures(), content="1") diff --git a/src/file_runtime/pkg.generated.mbti b/src/file_runtime/pkg.generated.mbti index 18654d5..8f055fd 100644 --- a/src/file_runtime/pkg.generated.mbti +++ b/src/file_runtime/pkg.generated.mbti @@ -10,6 +10,8 @@ import { // Values pub fn file_sink(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> FileSink +pub async fn file_sink_async(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> FileSink + pub fn native_files_supported() -> Bool // Errors @@ -24,6 +26,7 @@ pub fn FileSink::clear_rotation(Self) -> Unit pub fn FileSink::close(Self) -> Bool pub fn FileSink::default_policy(Self) -> @file_model.FileSinkPolicy pub fn FileSink::flush(Self) -> Bool +pub async fn FileSink::flush_async(Self) -> Bool pub fn FileSink::flush_failures(Self) -> Int pub fn FileSink::is_available(Self) -> Bool pub fn FileSink::open_failures(Self) -> Int diff --git a/src/formatting/formatter.mbt b/src/formatting/formatter.mbt index cb9377c..90decc0 100644 --- a/src/formatting/formatter.mbt +++ b/src/formatting/formatter.mbt @@ -916,7 +916,7 @@ fn parse_inline_markup( formatter : TextFormatter, ) -> Array[StyledSegment] { let segments : Array[StyledSegment] = [] - let buffer = StringBuilder::new() + let buffer = StringBuilder() let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }] let chars = input.to_array() let current_style = fn() { stack[stack.length() - 1].style } @@ -990,7 +990,7 @@ fn render_styled_text( let enabled = use_ansi_color(formatter.color_mode) let scoped = formatter.with_style_markup(mode) let segments = parse_inline_markup(text, scoped) - let out = StringBuilder::new() + let out = StringBuilder() for segment in segments { out.write_string(ansi_wrap_with_style(segment.text, segment.style, enabled)) } diff --git a/src/pkg.generated.mbti b/src/pkg.generated.mbti index 90bb5a9..792d9d4 100644 --- a/src/pkg.generated.mbti +++ b/src/pkg.generated.mbti @@ -75,6 +75,8 @@ pub fn file_rotation_i64(Int64, max_backups? : Int) -> @file_model.FileRotation pub fn file_sink(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> @file_runtime.FileSink +pub async fn file_sink_async(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> @file_runtime.FileSink + pub fn file_sink_policy_to_json(@file_model.FileSinkPolicy) -> Json pub fn file_sink_state_to_json(@file_model.FileSinkState) -> Json diff --git a/src/runtime/pkg.generated.mbti b/src/runtime/pkg.generated.mbti index 03c63e5..b98a17b 100644 --- a/src/runtime/pkg.generated.mbti +++ b/src/runtime/pkg.generated.mbti @@ -68,6 +68,7 @@ pub fn RuntimeSink::file_state(Self) -> @file_model.FileSinkState pub fn RuntimeSink::file_state_or_none(Self) -> @file_model.FileSinkState? pub fn RuntimeSink::file_write_failures(Self) -> Int pub fn RuntimeSink::flush(Self) -> Int +pub async fn RuntimeSink::flush_async(Self) -> Unit pub fn RuntimeSink::flush_progress(Self) -> RuntimeSinkProgress pub fn RuntimeSink::pending_count(Self) -> Int pub impl @sink_graph.Sink for RuntimeSink diff --git a/src/runtime/runtime_logger.mbt b/src/runtime/runtime_logger.mbt index b759d86..3a7bf5d 100644 --- a/src/runtime/runtime_logger.mbt +++ b/src/runtime/runtime_logger.mbt @@ -81,6 +81,20 @@ pub impl @sink_graph.Sink for RuntimeSink with fn write(self, rec) { } } +///| +pub impl @sink_graph.Sink for RuntimeSink with fn write_async(self, rec) { + match self { + Console(sink) => sink.write(rec) + JsonConsole(sink) => sink.write(rec) + TextConsole(sink) => sink.write(rec) + File(sink) => sink.write_async(rec) + QueuedConsole(sink) => sink.write(rec) + QueuedJsonConsole(sink) => sink.write(rec) + QueuedTextConsole(sink) => sink.write(rec) + QueuedFile(sink) => sink.write(rec) + } +} + ///| fn[S : @sink_graph.Sink] queued_sink_close_internal( sink : QueuedSink[S], @@ -202,6 +216,23 @@ pub fn RuntimeSink::flush(self : RuntimeSink) -> Int { runtime_sink_progress_compat_count(self.flush_progress()) } +///| +pub async fn RuntimeSink::flush_async(self : RuntimeSink) -> Unit { + match self { + Console(_) => () + JsonConsole(_) => () + TextConsole(_) => () + File(sink) => ignore(sink.flush_async()) + QueuedConsole(sink) => ignore(sink.flush_async()) + QueuedJsonConsole(sink) => ignore(sink.flush_async()) + QueuedTextConsole(sink) => ignore(sink.flush_async()) + QueuedFile(sink) => { + ignore(sink.flush_async()) + ignore(sink.sink.flush_async()) + } + } +} + ///| pub fn RuntimeSink::drain_progress( self : RuntimeSink, @@ -671,7 +702,10 @@ pub fn RuntimeSink::file_runtime_state(self : RuntimeSink) -> RuntimeFileState? } ///| -pub fn build_runtime_sink(config : SinkConfig) -> RuntimeSink { +fn build_runtime_sink_internal( + config : SinkConfig, + defer_file_open : Bool, +) -> RuntimeSink { match config.kind { SinkKind::Console => RuntimeSink::Console(@sink_graph.console_sink()) SinkKind::JsonConsole => @@ -682,22 +716,48 @@ pub fn build_runtime_sink(config : SinkConfig) -> RuntimeSink { ) SinkKind::File => RuntimeSink::File( - @file_runtime.file_sink( - config.path, - append=config.append, - auto_flush=config.auto_flush, - rotation=config.rotation, - formatter=fn(rec) { - @formatting.format_text( - rec, - formatter=config.text_formatter.to_formatter(), - ) - }, - ), + if defer_file_open { + @file_runtime.file_sink_deferred( + config.path, + append=config.append, + auto_flush=config.auto_flush, + rotation=config.rotation, + formatter=fn(rec) { + @formatting.format_text( + rec, + formatter=config.text_formatter.to_formatter(), + ) + }, + ) + } else { + @file_runtime.file_sink( + config.path, + append=config.append, + auto_flush=config.auto_flush, + rotation=config.rotation, + formatter=fn(rec) { + @formatting.format_text( + rec, + formatter=config.text_formatter.to_formatter(), + ) + }, + ) + }, ) } } +///| +pub fn build_runtime_sink(config : SinkConfig) -> RuntimeSink { + build_runtime_sink_internal(config, false) +} + +///| +#doc(hidden) +pub fn build_runtime_sink_for_async(config : SinkConfig) -> RuntimeSink { + build_runtime_sink_internal(config, true) +} + ///| pub fn apply_queue_config( sink : RuntimeSink, diff --git a/src/runtime_logger.mbt b/src/runtime_logger.mbt index ebd3ee0..a8b0a0f 100644 --- a/src/runtime_logger.mbt +++ b/src/runtime_logger.mbt @@ -64,8 +64,15 @@ pub fn Logger::dropped_count(self : Logger[RuntimeSink]) -> Int { } ///| -pub fn build_logger(config : LoggerConfig) -> Logger[RuntimeSink] { - let sink = @runtime.build_runtime_sink(config.sink) +fn build_logger_internal( + config : LoggerConfig, + defer_file_open : Bool, +) -> Logger[RuntimeSink] { + let sink = if defer_file_open { + @runtime.build_runtime_sink_for_async(config.sink) + } else { + @runtime.build_runtime_sink(config.sink) + } let actual_sink = match config.queue { None => sink Some(queue) => @runtime.apply_queue_config(sink, queue) @@ -75,6 +82,17 @@ pub fn build_logger(config : LoggerConfig) -> Logger[RuntimeSink] { ) } +///| +pub fn build_logger(config : LoggerConfig) -> Logger[RuntimeSink] { + build_logger_internal(config, false) +} + +///| +#doc(hidden) +pub fn build_logger_for_async(config : LoggerConfig) -> Logger[RuntimeSink] { + build_logger_internal(config, true) +} + ///| pub fn parse_and_build_logger( input : String, diff --git a/src/sink_graph/pkg.generated.mbti b/src/sink_graph/pkg.generated.mbti index 88ce399..6410457 100644 --- a/src/sink_graph/pkg.generated.mbti +++ b/src/sink_graph/pkg.generated.mbti @@ -51,6 +51,7 @@ pub struct BufferedSink[S] { flush_limit : Int } pub fn[S : Sink] BufferedSink::flush(Self[S]) -> Unit +pub async fn[S : Sink] BufferedSink::flush_async(Self[S]) -> Unit pub fn[S] BufferedSink::pending_count(Self[S]) -> Int pub impl[S : Sink] Sink for BufferedSink[S] @@ -124,8 +125,10 @@ pub struct QueuedSink[S] { dropped_count : @ref.Ref[Int] } pub fn[S : Sink] QueuedSink::drain(Self[S], max_items? : Int) -> Int +pub async fn[S : Sink] QueuedSink::drain_async(Self[S], max_items? : Int) -> Int pub fn[S] QueuedSink::dropped_count(Self[S]) -> Int pub fn[S : Sink] QueuedSink::flush(Self[S]) -> Int +pub async fn[S : Sink] QueuedSink::flush_async(Self[S]) -> Int pub fn[S] QueuedSink::pending_count(Self[S]) -> Int pub impl[S] Sink for QueuedSink[S] @@ -142,4 +145,5 @@ pub using @queue_model {type QueueOverflowPolicy} // Traits pub(open) trait Sink { fn write(Self, @core.Record) -> Unit + async fn write_async(Self, @core.Record) -> Unit = _ } diff --git a/src/sink_graph/sinks.mbt b/src/sink_graph/sinks.mbt index de38f17..3bb69b8 100644 --- a/src/sink_graph/sinks.mbt +++ b/src/sink_graph/sinks.mbt @@ -19,6 +19,12 @@ type RecordPatch = @record_ops.RecordPatch ///| pub(open) trait Sink { fn write(Self, Record) -> Unit + async fn write_async(Self, Record) -> Unit = _ +} + +///| +impl Sink with fn write_async(self : Self, rec : Record) { + self.write(rec) } ///| @@ -63,6 +69,18 @@ pub impl[S : Sink] Sink for ContextSink[S] with fn write(self, rec) { self.sink.write(rec.with_fields(merged)) } +///| +pub impl[S : Sink] Sink for ContextSink[S] with fn write_async(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_async(rec.with_fields(merged)) +} + ///| pub struct JsonConsoleSink { _dummy : Unit @@ -148,6 +166,15 @@ pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with fn write(self, rec) self.right.write(rec.copy()) } +///| +pub impl[A : Sink, B : Sink] Sink for FanoutSink[A, B] with fn write_async( + self, + rec, +) { + self.left.write_async(rec) + self.right.write_async(rec.copy()) +} + ///| pub struct SplitSink[A, B] { left : A @@ -182,6 +209,18 @@ pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with fn write(self, rec) { } } +///| +pub impl[A : Sink, B : Sink] Sink for SplitSink[A, B] with fn write_async( + self, + rec, +) { + if (self.predicate)(rec) { + self.left.write_async(rec) + } else { + self.right.write_async(rec) + } +} + ///| pub struct CallbackSink { callback : (Record) -> Unit @@ -299,6 +338,29 @@ pub impl[S : Sink] Sink for BufferedSink[S] with fn write(self, rec) { } } +///| +pub async fn[S : Sink] BufferedSink::flush_async( + 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_async(rec) + } + } +} + +///| +pub impl[S : Sink] Sink for BufferedSink[S] with fn write_async(self, rec) { + self.buffer.val.push(rec) + if self.buffer.val.length() >= self.flush_limit { + self.flush_async() + } +} + ///| pub type QueueOverflowPolicy = @queue_model.QueueOverflowPolicy @@ -358,11 +420,38 @@ pub fn[S : Sink] QueuedSink::drain( } } +///| +pub async fn[S : Sink] QueuedSink::drain_async( + 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_async(rec) + continue drained + 1 + } + } + } nobreak { + limit + } +} + ///| pub fn[S : Sink] QueuedSink::flush(self : QueuedSink[S]) -> Int { self.drain() } +///| +pub async fn[S : Sink] QueuedSink::flush_async(self : QueuedSink[S]) -> Int { + self.drain_async() +} + ///| pub impl[S] Sink for QueuedSink[S] with fn write(self, rec) { let full = self.max_pending > 0 && self.pending_count() >= self.max_pending @@ -398,6 +487,13 @@ pub impl[S : Sink] Sink for FilterSink[S] with fn write(self, rec) { } } +///| +pub impl[S : Sink] Sink for FilterSink[S] with fn write_async(self, rec) { + if (self.predicate)(rec) { + self.sink.write_async(rec) + } +} + ///| pub struct PatchSink[S] { sink : S @@ -413,3 +509,8 @@ pub fn[S] patch_sink(sink : S, patch : RecordPatch) -> PatchSink[S] { pub impl[S : Sink] Sink for PatchSink[S] with fn write(self, rec) { self.sink.write((self.patch)(rec)) } + +///| +pub impl[S : Sink] Sink for PatchSink[S] with fn write_async(self, rec) { + self.sink.write_async((self.patch)(rec)) +} diff --git a/src/sinks_file.mbt b/src/sinks_file.mbt index d467724..eedbe95 100644 --- a/src/sinks_file.mbt +++ b/src/sinks_file.mbt @@ -64,3 +64,20 @@ pub fn file_sink( ) -> FileSink { @file_runtime.file_sink(path, append~, auto_flush~, rotation~, formatter~) } + +///| +pub async fn file_sink_async( + path : String, + append? : Bool = true, + auto_flush? : Bool = true, + rotation? : FileRotation? = None, + formatter? : RecordFormatter = fn(rec) { format_text(rec) }, +) -> FileSink { + @file_runtime.file_sink_async( + path, + append~, + auto_flush~, + rotation~, + formatter~, + ) +} diff --git a/src/utils/file_backend_native.mbt b/src/utils/file_backend_native.mbt index 3ec720c..1dcf323 100644 --- a/src/utils/file_backend_native.mbt +++ b/src/utils/file_backend_native.mbt @@ -1,106 +1,137 @@ ///| -fn string_to_c_bytes(str : String) -> Bytes { - let res : Array[Byte] = [] - let len = str.length() - let mut i = 0 - while i < len { - let mut c = str.code_unit_at(i).to_int() - if 0xD800 <= c && c <= 0xDBFF { - c -= 0xD800 - i = i + 1 - let l = str.code_unit_at(i).to_int() - 0xDC00 - c = (c << 10) + l + 0x10000 - } - if c < 0x80 { - res.push(c.to_byte()) - } else if c < 0x800 { - res.push((0xc0 + (c >> 6)).to_byte()) - res.push((0x80 + (c & 0x3f)).to_byte()) - } else if c < 0x10000 { - res.push((0xe0 + (c >> 12)).to_byte()) - res.push((0x80 + ((c >> 6) & 0x3f)).to_byte()) - res.push((0x80 + (c & 0x3f)).to_byte()) - } else { - res.push((0xf0 + (c >> 18)).to_byte()) - res.push((0x80 + ((c >> 12) & 0x3f)).to_byte()) - res.push((0x80 + ((c >> 6) & 0x3f)).to_byte()) - res.push((0x80 + (c & 0x3f)).to_byte()) - } - i = i + 1 - } - res.push((0).to_byte()) - Bytes::from_array(res) +pub struct FileHandle { + priv path : String + priv append : Bool + priv initialized : Ref[Bool] + priv position : Ref[Int64] + priv closed : Ref[Bool] } ///| -#external -type NativeFileHandle +fn run_async_bool_internal(operation : async () -> Bool) -> Bool { + let result = Ref(false) + @async.run_async_main(() => result.val = operation()) + result.val +} ///| -#borrow(path, mode) -extern "C" fn file_open_ffi(path : Bytes, mode : Bytes) -> NativeFileHandle = "bitlogger_file_open" +fn run_async_i64_internal(operation : async () -> Int64) -> Int64 { + let result = Ref(0L) + @async.run_async_main(() => result.val = operation()) + result.val +} ///| -extern "C" fn file_is_null_ffi(handle : NativeFileHandle) -> Bool = "bitlogger_pointer_is_null" +fn new_file_handle_internal( + path : String, + append : Bool, + initialized : Bool, +) -> FileHandle { + { + path, + append, + initialized: Ref(initialized), + position: Ref(0L), + closed: Ref(false), + } +} ///| -#borrow(buffer) -extern "C" fn file_write_ffi( - buffer : Bytes, - size : Int, - count : Int, - handle : NativeFileHandle, -) -> Int = "bitlogger_file_write" +async fn probe_open_file_handle_async_internal( + path : String, + append : Bool, +) -> Bool { + try { + let create_mode = if append { + @fs.OpenOrCreate + } else { + @fs.CreateOrTruncate + } + let file = @fs.open(path, mode=@fs.WriteOnly, append~, create_mode~) + file.close() + true + } catch { + _ => false + } +} ///| -extern "C" fn file_flush_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_flush" - -///| -extern "C" fn file_close_ffi(handle : NativeFileHandle) -> Int = "bitlogger_file_close" - -///| -extern "C" fn file_seek_ffi( - handle : NativeFileHandle, - offset : Int, - origin : Int, -) -> Int = "bitlogger_file_seek" - -///| -extern "C" fn file_tell_i64_ffi(handle : NativeFileHandle) -> Int64 = "bitlogger_file_tell_i64" - -///| -#borrow(from_path, to_path) -extern "C" fn file_rename_ffi(from_path : Bytes, to_path : Bytes) -> Int = "bitlogger_file_rename" - -///| -#borrow(path) -extern "C" fn file_remove_ffi(path : Bytes) -> Int = "bitlogger_file_remove" - -///| -pub struct FileHandle { - path : String - raw : NativeFileHandle +pub async fn open_file_handle_async_internal( + path : String, + append : Bool, +) -> FileHandle? { + if probe_open_file_handle_async_internal(path, append) { + Some(new_file_handle_internal(path, append, true)) + } else { + None + } } ///| pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { - let mode = if append { "ab" } else { "wb" } - let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes(mode)) - if file_is_null_ffi(raw) { - None + let opened = run_async_bool_internal(() => { + probe_open_file_handle_async_internal(path, append) + }) + if opened { + Some(new_file_handle_internal(path, append, true)) } else { - Some({ raw, path }) + None + } +} + +///| +pub fn deferred_file_handle_internal( + path : String, + append : Bool, +) -> FileHandle? { + Some(new_file_handle_internal(path, append, false)) +} + +///| +pub async fn write_file_handle_async_internal( + handle : FileHandle, + content : String, +) -> Bool { + if handle.closed.val { + return false + } + let bytes = @utf8.encode(content) + try { + let file = @fs.open( + handle.path, + mode=@fs.WriteOnly, + append=handle.append, + create_mode=if handle.initialized.val { + @fs.OpenOrCreate + } else if handle.append { + @fs.OpenOrCreate + } else { + @fs.CreateOrTruncate + }, + ) + defer file.close() + if handle.append { + file.write(bytes) + } else { + file.write_at(bytes[:], position=handle.position.val) + handle.position.val += bytes.length().to_int64() + } + handle.initialized.val = true + true + } catch { + _ => false } } ///| pub fn file_exists_internal(path : String) -> Bool { - let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes("rb")) - if file_is_null_ffi(raw) { - false - } else { - ignore(file_close_ffi(raw)) - true + run_async_bool_internal(() => @fs.exists(path)) +} + +///| +pub async fn file_exists_async_internal(path : String) -> Bool { + @fs.exists(path) catch { + _ => false } } @@ -109,45 +140,102 @@ pub fn write_file_handle_internal( handle : FileHandle, content : String, ) -> Bool { - let bytes = string_to_c_bytes(content) - let written = file_write_ffi(bytes, 1, bytes.length() - 1, handle.raw) - written == bytes.length() - 1 + run_async_bool_internal(() => { + write_file_handle_async_internal(handle, content) + }) +} + +///| +pub async fn flush_file_handle_async_internal(handle : FileHandle) -> Bool { + if handle.closed.val { + return false + } + try { + let file = @fs.open(handle.path, mode=@fs.WriteOnly) + file.sync() + file.close() + true + } catch { + _ => false + } } ///| pub fn flush_file_handle_internal(handle : FileHandle) -> Bool { - file_flush_ffi(handle.raw) == 0 + if handle.closed.val { + false + } else { + // Each write owns and closes its File, so there is no buffered handle to flush here. + true + } } ///| pub fn close_file_handle_internal(handle : FileHandle) -> Bool { - file_close_ffi(handle.raw) == 0 + if handle.closed.val { + false + } else { + handle.closed.val = true + true + } +} + +///| +pub async fn file_size_i64_async_internal(handle : FileHandle) -> Int64 { + if handle.closed.val { + return 0L + } + try { + let file = @fs.open(handle.path, mode=@fs.ReadOnly) + let size = file.size() + file.close() + size + } catch { + _ => 0L + } } ///| pub fn file_size_i64_internal(handle : FileHandle) -> Int64 { - ignore(file_seek_ffi(handle.raw, 0, 2)) - let size = file_tell_i64_ffi(handle.raw) - if size < 0L { - 0L - } else { - size + run_async_i64_internal(() => file_size_i64_async_internal(handle)) +} + +///| +pub async fn rename_file_async_internal( + from_path : String, + to_path : String, +) -> Bool { + try { + @fs.rename(from_path, to_path) + true + } catch { + _ => false } } ///| pub fn rename_file_internal(from_path : String, to_path : String) -> Bool { - file_rename_ffi(string_to_c_bytes(from_path), string_to_c_bytes(to_path)) == 0 + run_async_bool_internal(() => rename_file_async_internal(from_path, to_path)) +} + +///| +pub async fn remove_file_async_internal(path : String) -> Bool { + try { + @fs.remove(path) + true + } catch { + _ => false + } } ///| pub fn remove_file_internal(path : String) -> Bool { - file_remove_ffi(string_to_c_bytes(path)) == 0 + run_async_bool_internal(() => remove_file_async_internal(path)) } ///| pub fn string_byte_length_internal(content : String) -> Int { - string_to_c_bytes(content).length() - 1 + @utf8.encode(content).length() } ///| diff --git a/src/utils/file_backend_stub.mbt b/src/utils/file_backend_stub.mbt index fb6ce67..0460843 100644 --- a/src/utils/file_backend_stub.mbt +++ b/src/utils/file_backend_stub.mbt @@ -1,28 +1,106 @@ ///| pub struct FileHandle { - path : String + priv dummy : Unit } ///| pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? { ignore(append) ignore(path) - let _unused : FileHandle = { path: "" } + let _unused : FileHandle = { dummy: () } ignore(_unused) None } +///| +pub fn deferred_file_handle_internal( + path : String, + append : Bool, +) -> FileHandle? { + ignore(append) + ignore(path) + None +} + ///| pub fn file_exists_internal(path : String) -> Bool { ignore(path) false } +///| +async fn stub_async_noop_internal() -> Unit { + ignore(@fs.unimplemented) + @async.pause() +} + +///| +pub async fn open_file_handle_async_internal( + path : String, + append : Bool, +) -> FileHandle? { + stub_async_noop_internal() + ignore(append) + ignore(path) + None +} + +///| +pub async fn file_exists_async_internal(path : String) -> Bool { + stub_async_noop_internal() + ignore(path) + false +} + +///| +pub async fn write_file_handle_async_internal( + handle : FileHandle, + content : String, +) -> Bool { + stub_async_noop_internal() + ignore(handle) + ignore(content) + false +} + +///| +pub async fn flush_file_handle_async_internal(handle : FileHandle) -> Bool { + stub_async_noop_internal() + ignore(handle) + false +} + +///| +pub async fn file_size_i64_async_internal(handle : FileHandle) -> Int64 { + stub_async_noop_internal() + ignore(handle) + 0L +} + +///| +pub async fn rename_file_async_internal( + from_path : String, + to_path : String, +) -> Bool { + stub_async_noop_internal() + ignore(from_path) + ignore(to_path) + false +} + +///| +pub async fn remove_file_async_internal(path : String) -> Bool { + stub_async_noop_internal() + ignore(path) + false +} + ///| pub fn write_file_handle_internal( handle : FileHandle, content : String, ) -> Bool { + ignore(handle.dummy) ignore(handle) ignore(content) false diff --git a/src/utils/moon.pkg b/src/utils/moon.pkg index fbe655b..b602a4f 100644 --- a/src/utils/moon.pkg +++ b/src/utils/moon.pkg @@ -2,14 +2,16 @@ import { "Nanaloveyuki/BitLogger/src/core", "Nanaloveyuki/BitLogger/src/formatting", "Nanaloveyuki/BitLogger/src/record_ops", + "moonbitlang/async", + "moonbitlang/async/fs", "moonbitlang/core/env", + "moonbitlang/core/encoding/utf8", "moonbitlang/core/json", "moonbitlang/core/ref", "moonbitlang/core/string", } options( - "native-stub": [ "stub.c" ], targets: { "file_backend_native.mbt": [ "native", "llvm" ], "file_backend_stub.mbt": [ "js", "wasm", "wasm-gc" ], diff --git a/src/utils/pkg.generated.mbti b/src/utils/pkg.generated.mbti index 8f117ec..fe3f446 100644 --- a/src/utils/pkg.generated.mbti +++ b/src/utils/pkg.generated.mbti @@ -23,12 +23,20 @@ pub fn compose_patches(Array[(@core.Record) -> @core.Record]) -> (@core.Record) pub fn default_style_tag_registry() -> @formatting.StyleTagRegistry +pub fn deferred_file_handle_internal(String, Bool) -> FileHandle? + pub fn field_equals(String, String) -> (@core.Record) -> Bool +pub async fn file_exists_async_internal(String) -> Bool + pub fn file_exists_internal(String) -> Bool +pub async fn file_size_i64_async_internal(FileHandle) -> Int64 + pub fn file_size_i64_internal(FileHandle) -> Int64 +pub async fn flush_file_handle_async_internal(FileHandle) -> Bool + pub fn flush_file_handle_internal(FileHandle) -> Bool pub fn format_json(@core.Record) -> String @@ -49,6 +57,8 @@ pub fn native_files_supported_internal() -> Bool pub fn not_((@core.Record) -> Bool) -> (@core.Record) -> Bool +pub async fn open_file_handle_async_internal(String, Bool) -> FileHandle? + pub fn open_file_handle_internal(String, Bool) -> FileHandle? pub fn prefix_message(String) -> (@core.Record) -> @core.Record @@ -57,8 +67,12 @@ pub fn redact_field(String, placeholder? : String) -> (@core.Record) -> @core.Re pub fn redact_fields(Array[String], placeholder? : String) -> (@core.Record) -> @core.Record +pub async fn remove_file_async_internal(String) -> Bool + pub fn remove_file_internal(String) -> Bool +pub async fn rename_file_async_internal(String, String) -> Bool + pub fn rename_file_internal(String, String) -> Bool pub fn reset_global_style_tag_registry() -> Unit @@ -81,13 +95,15 @@ pub fn text_formatter(show_timestamp? : Bool, show_level? : Bool, show_target? : pub fn text_style(fg? : String?, bg? : String?, bold? : Bool, dim? : Bool, italic? : Bool, underline? : Bool) -> @formatting.TextStyle +pub async fn write_file_handle_async_internal(FileHandle, String) -> Bool + pub fn write_file_handle_internal(FileHandle, String) -> Bool // Errors // Types and methods pub struct FileHandle { - path : String + // private fields } // Type aliases diff --git a/src/utils/stub.c b/src/utils/stub.c deleted file mode 100644 index df33982..0000000 --- a/src/utils/stub.c +++ /dev/null @@ -1,55 +0,0 @@ -#include - -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include - -int32_t bitlogger_pointer_is_null(void *ptr) { - return ptr == 0; -} - -void *bitlogger_file_open(const char *path, const char *mode) { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - return fopen(path, mode); -#if defined(__clang__) -#pragma clang diagnostic pop -#endif -} - -int32_t bitlogger_file_write(const char *buffer, int32_t size, int32_t count, void *handle) { - return (int32_t)fwrite(buffer, (size_t)size, (size_t)count, (FILE *)handle); -} - -int32_t bitlogger_file_flush(void *handle) { - return fflush((FILE *)handle); -} - -int32_t bitlogger_file_close(void *handle) { - return fclose((FILE *)handle); -} - -int32_t bitlogger_file_seek(void *handle, int32_t offset, int32_t origin) { - return fseek((FILE *)handle, offset, origin); -} - -int64_t bitlogger_file_tell_i64(void *handle) { -#if defined(_WIN32) - __int64 position = _ftelli64((FILE *)handle); -#else - long long position = ftello((FILE *)handle); -#endif - return position < 0 ? -1 : (int64_t)position; -} - -int32_t bitlogger_file_rename(const char *from_path, const char *to_path) { - return rename(from_path, to_path); -} - -int32_t bitlogger_file_remove(const char *path) { - return remove(path); -}