mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-09-07 09:54:07 +00:00
⬆️ upgrade async and replace native file FFI
升级 async 依赖,迁移到 MoonBit 原生 async/fs 文件接口,移除项目自有 C FFI,并固定文本文件为 LF。CI 全部通过。
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
# Normalize repository text files and check them out with LF on every platform.
|
||||||
|
* text=auto eol=lf
|
||||||
@@ -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.
|
||||||
@@ -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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ BitLogger API navigation.
|
|||||||
- [patch-sink.md](./patch-sink.md)
|
- [patch-sink.md](./patch-sink.md)
|
||||||
- [patch-sink-type.md](./patch-sink-type.md)
|
- [patch-sink-type.md](./patch-sink-type.md)
|
||||||
- [file-sink.md](./file-sink.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-available.md](./file-sink-available.md)
|
||||||
- [file-sink-flush.md](./file-sink-flush.md)
|
- [file-sink-flush.md](./file-sink-flush.md)
|
||||||
- [file-sink-close.md](./file-sink-close.md)
|
- [file-sink-close.md](./file-sink-close.md)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = "Nanaloveyuki/BitLogger"
|
|||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
"moonbitlang/async@0.20.2",
|
"moonbitlang/async@0.21.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
readme = "src/README.mbt.md"
|
readme = "src/README.mbt.md"
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ pub struct AsyncLogger[S] {
|
|||||||
flush_policy : AsyncFlushPolicy
|
flush_policy : AsyncFlushPolicy
|
||||||
sink : S
|
sink : S
|
||||||
flush_callback : (S) -> Unit raise
|
flush_callback : (S) -> Unit raise
|
||||||
|
flush_async_callback : (async (S) -> Unit)?
|
||||||
context_fields : Array[@bitlogger.Field]
|
context_fields : Array[@bitlogger.Field]
|
||||||
filter : (@bitlogger.Record) -> Bool
|
filter : (@bitlogger.Record) -> Bool
|
||||||
patch : @bitlogger.RecordPatch
|
patch : @bitlogger.RecordPatch
|
||||||
@@ -160,6 +161,7 @@ pub fn[S] async_logger(
|
|||||||
flush_policy: config.flush,
|
flush_policy: config.flush,
|
||||||
sink,
|
sink,
|
||||||
flush_callback: flush,
|
flush_callback: flush,
|
||||||
|
flush_async_callback: None,
|
||||||
context_fields: [],
|
context_fields: [],
|
||||||
filter: fn(_) { true },
|
filter: fn(_) { true },
|
||||||
patch: @bitlogger.identity_patch(),
|
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 {
|
fn async_logger_phase_is_closed(phase : AsyncLifecyclePhase) -> Bool {
|
||||||
match phase {
|
match phase {
|
||||||
@@ -535,8 +545,11 @@ pub async fn[S] AsyncLogger::shutdown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
fn[S] run_flush_callback(logger : AsyncLogger[S]) -> Unit raise {
|
async fn[S] run_flush_callback(logger : AsyncLogger[S]) -> Unit {
|
||||||
(logger.flush_callback)(logger.sink)
|
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 if err is AsyncLoggerClosed => break
|
||||||
err => raise err
|
err => raise err
|
||||||
}
|
}
|
||||||
logger.sink.write(rec)
|
logger.sink.write_async(rec)
|
||||||
if logger.pending_count.val > 0 {
|
if logger.pending_count.val > 0 {
|
||||||
logger.pending_count.val -= 1
|
logger.pending_count.val -= 1
|
||||||
}
|
}
|
||||||
@@ -557,7 +570,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
|
|||||||
}
|
}
|
||||||
match next {
|
match next {
|
||||||
Some(next) => {
|
Some(next) => {
|
||||||
logger.sink.write(next)
|
logger.sink.write_async(next)
|
||||||
if logger.pending_count.val > 0 {
|
if logger.pending_count.val > 0 {
|
||||||
logger.pending_count.val -= 1
|
logger.pending_count.val -= 1
|
||||||
}
|
}
|
||||||
@@ -575,7 +588,7 @@ async fn[S : @bitlogger.Sink] run_worker(logger : AsyncLogger[S]) -> Unit {
|
|||||||
}
|
}
|
||||||
match waited {
|
match waited {
|
||||||
Some(next) => {
|
Some(next) => {
|
||||||
logger.sink.write(next)
|
logger.sink.write_async(next)
|
||||||
if logger.pending_count.val > 0 {
|
if logger.pending_count.val > 0 {
|
||||||
logger.pending_count.val -= 1
|
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(
|
pub async fn[S : @bitlogger.Sink] AsyncLogger::run(
|
||||||
self : AsyncLogger[S],
|
self : AsyncLogger[S],
|
||||||
) -> Unit {
|
) -> Unit {
|
||||||
@@ -619,14 +633,20 @@ pub async fn[S : @bitlogger.Sink] AsyncLogger::run(
|
|||||||
pub fn build_async_logger(
|
pub fn build_async_logger(
|
||||||
config : AsyncLoggerBuildConfig,
|
config : AsyncLoggerBuildConfig,
|
||||||
) -> AsyncLogger[@bitlogger.RuntimeSink] {
|
) -> AsyncLogger[@bitlogger.RuntimeSink] {
|
||||||
let logger = @bitlogger.build_logger(config.logger)
|
let logger = @bitlogger.build_logger_for_async(config.logger)
|
||||||
async_logger(
|
let logger = async_logger(
|
||||||
logger.sink,
|
logger.sink,
|
||||||
config=config.async_config,
|
config=config.async_config,
|
||||||
min_level=logger.min_level,
|
min_level=logger.min_level,
|
||||||
target=logger.target,
|
target=logger.target,
|
||||||
flush=fn(sink) { ignore(sink.flush_progress()) },
|
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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ async test "native async file lifecycle flushes rotates and restarts" {
|
|||||||
} else {
|
} else {
|
||||||
let path = "logs/bitlogger-async-native-e2e.log"
|
let path = "logs/bitlogger-async-native-e2e.log"
|
||||||
let first_flushes : Ref[Int] = Ref(0)
|
let first_flushes : Ref[Int] = Ref(0)
|
||||||
let first_sink = @bitlogger.file_sink(
|
let first_sink = @bitlogger.file_sink_async(
|
||||||
path,
|
path,
|
||||||
append=false,
|
append=false,
|
||||||
auto_flush=false,
|
auto_flush=false,
|
||||||
@@ -47,7 +47,7 @@ async test "native async file lifecycle flushes rotates and restarts" {
|
|||||||
inspect(first_sink.close(), content="true")
|
inspect(first_sink.close(), content="true")
|
||||||
|
|
||||||
let restart_flushes : Ref[Int] = Ref(0)
|
let restart_flushes : Ref[Int] = Ref(0)
|
||||||
let restarted_sink = @bitlogger.file_sink(
|
let restarted_sink = @bitlogger.file_sink_async(
|
||||||
path,
|
path,
|
||||||
append=true,
|
append=true,
|
||||||
auto_flush=false,
|
auto_flush=false,
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ pub struct AsyncLogger[S] {
|
|||||||
flush_policy : @utils.AsyncFlushPolicy
|
flush_policy : @utils.AsyncFlushPolicy
|
||||||
sink : S
|
sink : S
|
||||||
flush_callback : (S) -> Unit raise
|
flush_callback : (S) -> Unit raise
|
||||||
|
flush_async_callback : (async (S) -> Unit)?
|
||||||
context_fields : Array[@core.Field]
|
context_fields : Array[@core.Field]
|
||||||
filter : (@core.Record) -> Bool
|
filter : (@core.Record) -> Bool
|
||||||
patch : (@core.Record) -> @core.Record
|
patch : (@core.Record) -> @core.Record
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub struct FileSink {
|
|||||||
priv default_auto_flush : Bool
|
priv default_auto_flush : Bool
|
||||||
priv rotation : Ref[FileRotation?]
|
priv rotation : Ref[FileRotation?]
|
||||||
priv default_rotation : FileRotation?
|
priv default_rotation : FileRotation?
|
||||||
|
priv deferred : Bool
|
||||||
priv open_failures : Ref[Int]
|
priv open_failures : Ref[Int]
|
||||||
priv write_failures : Ref[Int]
|
priv write_failures : Ref[Int]
|
||||||
priv flush_failures : Ref[Int]
|
priv flush_failures : Ref[Int]
|
||||||
@@ -65,7 +66,64 @@ pub fn file_sink(
|
|||||||
rotation? : FileRotation? = None,
|
rotation? : FileRotation? = None,
|
||||||
formatter? : RecordFormatter = fn(rec) { @formatting.format_text(rec) },
|
formatter? : RecordFormatter = fn(rec) { @formatting.format_text(rec) },
|
||||||
) -> FileSink {
|
) -> 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,
|
path,
|
||||||
append: Ref(append),
|
append: Ref(append),
|
||||||
@@ -76,6 +134,7 @@ pub fn file_sink(
|
|||||||
default_auto_flush: auto_flush,
|
default_auto_flush: auto_flush,
|
||||||
rotation: Ref(rotation),
|
rotation: Ref(rotation),
|
||||||
default_rotation: rotation,
|
default_rotation: rotation,
|
||||||
|
deferred,
|
||||||
open_failures: Ref(if handle is Some(_) { 0 } else { 1 }),
|
open_failures: Ref(if handle is Some(_) { 0 } else { 1 }),
|
||||||
write_failures: Ref(0),
|
write_failures: Ref(0),
|
||||||
flush_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 {
|
pub fn FileSink::append_mode(self : FileSink) -> Bool {
|
||||||
self.append.val
|
self.append.val
|
||||||
@@ -274,6 +347,23 @@ pub fn FileSink::state(self : FileSink) -> FileSinkState {
|
|||||||
pub fn FileSink::reopen(self : FileSink, append? : Bool? = None) -> Bool {
|
pub fn FileSink::reopen(self : FileSink, append? : Bool? = None) -> Bool {
|
||||||
let append_mode = append.unwrap_or(self.append.val)
|
let append_mode = append.unwrap_or(self.append.val)
|
||||||
self.append.val = append_mode
|
self.append.val = append_mode
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} else {
|
||||||
match self.handle.val {
|
match self.handle.val {
|
||||||
None => ()
|
None => ()
|
||||||
Some(handle) => {
|
Some(handle) => {
|
||||||
@@ -290,6 +380,7 @@ pub fn FileSink::reopen(self : FileSink, append? : Bool? = None) -> Bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
pub fn FileSink::reopen_with_current_policy(self : FileSink) -> Bool {
|
pub fn FileSink::reopen_with_current_policy(self : FileSink) -> Bool {
|
||||||
@@ -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 {
|
fn rotate_file_sink_internal(sink : FileSink, rotation : FileRotation) -> Bool {
|
||||||
let closed = match sink.handle.val {
|
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) {
|
pub impl @sink_graph.Sink for FileSink with fn write(self, rec : Record) {
|
||||||
match self.handle.val {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,14 @@ test "file sink counts an injected partial backup-chain failure" {
|
|||||||
rec.message
|
rec.message
|
||||||
})
|
})
|
||||||
if seed.is_available() {
|
if seed.is_available() {
|
||||||
seed.write(@core.Record::new(@core.Level::Info, "1234567890"))
|
@sink_graph.Sink::write(
|
||||||
seed.write(@core.Record::new(@core.Level::Info, "abcdefghij"))
|
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.rotation_failures(), content="0")
|
||||||
inspect(seed.close(), content="true")
|
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.rotation_failures(), content="1")
|
||||||
inspect(sink.state().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
|
rec.message
|
||||||
})
|
})
|
||||||
if seed.is_available() {
|
if seed.is_available() {
|
||||||
seed.write(@core.Record::new(@core.Level::Info, "1234567890"))
|
@sink_graph.Sink::write(
|
||||||
seed.write(@core.Record::new(@core.Level::Info, "abcdefghij"))
|
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")
|
inspect(seed.close(), content="true")
|
||||||
|
|
||||||
let oldest = @utils.open_file_handle_internal(test_path + ".2", 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)
|
@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.rotation_failures(), content="1")
|
||||||
inspect(sink.write_failures(), content="1")
|
inspect(sink.write_failures(), content="1")
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
// Values
|
// Values
|
||||||
pub fn file_sink(String, append? : Bool, auto_flush? : Bool, rotation? : @file_model.FileRotation?, formatter? : (@core.Record) -> String) -> FileSink
|
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
|
pub fn native_files_supported() -> Bool
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
@@ -24,6 +26,7 @@ pub fn FileSink::clear_rotation(Self) -> Unit
|
|||||||
pub fn FileSink::close(Self) -> Bool
|
pub fn FileSink::close(Self) -> Bool
|
||||||
pub fn FileSink::default_policy(Self) -> @file_model.FileSinkPolicy
|
pub fn FileSink::default_policy(Self) -> @file_model.FileSinkPolicy
|
||||||
pub fn FileSink::flush(Self) -> Bool
|
pub fn FileSink::flush(Self) -> Bool
|
||||||
|
pub async fn FileSink::flush_async(Self) -> Bool
|
||||||
pub fn FileSink::flush_failures(Self) -> Int
|
pub fn FileSink::flush_failures(Self) -> Int
|
||||||
pub fn FileSink::is_available(Self) -> Bool
|
pub fn FileSink::is_available(Self) -> Bool
|
||||||
pub fn FileSink::open_failures(Self) -> Int
|
pub fn FileSink::open_failures(Self) -> Int
|
||||||
|
|||||||
@@ -916,7 +916,7 @@ fn parse_inline_markup(
|
|||||||
formatter : TextFormatter,
|
formatter : TextFormatter,
|
||||||
) -> Array[StyledSegment] {
|
) -> Array[StyledSegment] {
|
||||||
let segments : Array[StyledSegment] = []
|
let segments : Array[StyledSegment] = []
|
||||||
let buffer = StringBuilder::new()
|
let buffer = StringBuilder()
|
||||||
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
|
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
|
||||||
let chars = input.to_array()
|
let chars = input.to_array()
|
||||||
let current_style = fn() { stack[stack.length() - 1].style }
|
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 enabled = use_ansi_color(formatter.color_mode)
|
||||||
let scoped = formatter.with_style_markup(mode)
|
let scoped = formatter.with_style_markup(mode)
|
||||||
let segments = parse_inline_markup(text, scoped)
|
let segments = parse_inline_markup(text, scoped)
|
||||||
let out = StringBuilder::new()
|
let out = StringBuilder()
|
||||||
for segment in segments {
|
for segment in segments {
|
||||||
out.write_string(ansi_wrap_with_style(segment.text, segment.style, enabled))
|
out.write_string(ansi_wrap_with_style(segment.text, segment.style, enabled))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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_policy_to_json(@file_model.FileSinkPolicy) -> Json
|
||||||
|
|
||||||
pub fn file_sink_state_to_json(@file_model.FileSinkState) -> Json
|
pub fn file_sink_state_to_json(@file_model.FileSinkState) -> Json
|
||||||
|
|||||||
@@ -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_state_or_none(Self) -> @file_model.FileSinkState?
|
||||||
pub fn RuntimeSink::file_write_failures(Self) -> Int
|
pub fn RuntimeSink::file_write_failures(Self) -> Int
|
||||||
pub fn RuntimeSink::flush(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::flush_progress(Self) -> RuntimeSinkProgress
|
||||||
pub fn RuntimeSink::pending_count(Self) -> Int
|
pub fn RuntimeSink::pending_count(Self) -> Int
|
||||||
pub impl @sink_graph.Sink for RuntimeSink
|
pub impl @sink_graph.Sink for RuntimeSink
|
||||||
|
|||||||
@@ -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(
|
fn[S : @sink_graph.Sink] queued_sink_close_internal(
|
||||||
sink : QueuedSink[S],
|
sink : QueuedSink[S],
|
||||||
@@ -202,6 +216,23 @@ pub fn RuntimeSink::flush(self : RuntimeSink) -> Int {
|
|||||||
runtime_sink_progress_compat_count(self.flush_progress())
|
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(
|
pub fn RuntimeSink::drain_progress(
|
||||||
self : RuntimeSink,
|
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 {
|
match config.kind {
|
||||||
SinkKind::Console => RuntimeSink::Console(@sink_graph.console_sink())
|
SinkKind::Console => RuntimeSink::Console(@sink_graph.console_sink())
|
||||||
SinkKind::JsonConsole =>
|
SinkKind::JsonConsole =>
|
||||||
@@ -682,6 +716,20 @@ pub fn build_runtime_sink(config : SinkConfig) -> RuntimeSink {
|
|||||||
)
|
)
|
||||||
SinkKind::File =>
|
SinkKind::File =>
|
||||||
RuntimeSink::File(
|
RuntimeSink::File(
|
||||||
|
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(
|
@file_runtime.file_sink(
|
||||||
config.path,
|
config.path,
|
||||||
append=config.append,
|
append=config.append,
|
||||||
@@ -693,11 +741,23 @@ pub fn build_runtime_sink(config : SinkConfig) -> RuntimeSink {
|
|||||||
formatter=config.text_formatter.to_formatter(),
|
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(
|
pub fn apply_queue_config(
|
||||||
sink : RuntimeSink,
|
sink : RuntimeSink,
|
||||||
|
|||||||
+20
-2
@@ -64,8 +64,15 @@ pub fn Logger::dropped_count(self : Logger[RuntimeSink]) -> Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
pub fn build_logger(config : LoggerConfig) -> Logger[RuntimeSink] {
|
fn build_logger_internal(
|
||||||
let sink = @runtime.build_runtime_sink(config.sink)
|
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 {
|
let actual_sink = match config.queue {
|
||||||
None => sink
|
None => sink
|
||||||
Some(queue) => @runtime.apply_queue_config(sink, queue)
|
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(
|
pub fn parse_and_build_logger(
|
||||||
input : String,
|
input : String,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ pub struct BufferedSink[S] {
|
|||||||
flush_limit : Int
|
flush_limit : Int
|
||||||
}
|
}
|
||||||
pub fn[S : Sink] BufferedSink::flush(Self[S]) -> Unit
|
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 fn[S] BufferedSink::pending_count(Self[S]) -> Int
|
||||||
pub impl[S : Sink] Sink for BufferedSink[S]
|
pub impl[S : Sink] Sink for BufferedSink[S]
|
||||||
|
|
||||||
@@ -124,8 +125,10 @@ pub struct QueuedSink[S] {
|
|||||||
dropped_count : @ref.Ref[Int]
|
dropped_count : @ref.Ref[Int]
|
||||||
}
|
}
|
||||||
pub fn[S : Sink] QueuedSink::drain(Self[S], max_items? : Int) -> 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] QueuedSink::dropped_count(Self[S]) -> Int
|
||||||
pub fn[S : Sink] QueuedSink::flush(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 fn[S] QueuedSink::pending_count(Self[S]) -> Int
|
||||||
pub impl[S] Sink for QueuedSink[S]
|
pub impl[S] Sink for QueuedSink[S]
|
||||||
|
|
||||||
@@ -142,4 +145,5 @@ pub using @queue_model {type QueueOverflowPolicy}
|
|||||||
// Traits
|
// Traits
|
||||||
pub(open) trait Sink {
|
pub(open) trait Sink {
|
||||||
fn write(Self, @core.Record) -> Unit
|
fn write(Self, @core.Record) -> Unit
|
||||||
|
async fn write_async(Self, @core.Record) -> Unit = _
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ type RecordPatch = @record_ops.RecordPatch
|
|||||||
///|
|
///|
|
||||||
pub(open) trait Sink {
|
pub(open) trait Sink {
|
||||||
fn write(Self, Record) -> Unit
|
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))
|
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 {
|
pub struct JsonConsoleSink {
|
||||||
_dummy : Unit
|
_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())
|
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] {
|
pub struct SplitSink[A, B] {
|
||||||
left : A
|
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 {
|
pub struct CallbackSink {
|
||||||
callback : (Record) -> Unit
|
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
|
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 {
|
pub fn[S : Sink] QueuedSink::flush(self : QueuedSink[S]) -> Int {
|
||||||
self.drain()
|
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) {
|
pub impl[S] Sink for QueuedSink[S] with fn write(self, rec) {
|
||||||
let full = self.max_pending > 0 && self.pending_count() >= self.max_pending
|
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] {
|
pub struct PatchSink[S] {
|
||||||
sink : 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) {
|
pub impl[S : Sink] Sink for PatchSink[S] with fn write(self, rec) {
|
||||||
self.sink.write((self.patch)(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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,3 +64,20 @@ pub fn file_sink(
|
|||||||
) -> FileSink {
|
) -> FileSink {
|
||||||
@file_runtime.file_sink(path, append~, auto_flush~, rotation~, formatter~)
|
@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~,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+193
-105
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
///|
|
|
||||||
#external
|
|
||||||
type NativeFileHandle
|
|
||||||
|
|
||||||
///|
|
|
||||||
#borrow(path, mode)
|
|
||||||
extern "C" fn file_open_ffi(path : Bytes, mode : Bytes) -> NativeFileHandle = "bitlogger_file_open"
|
|
||||||
|
|
||||||
///|
|
|
||||||
extern "C" fn file_is_null_ffi(handle : NativeFileHandle) -> Bool = "bitlogger_pointer_is_null"
|
|
||||||
|
|
||||||
///|
|
|
||||||
#borrow(buffer)
|
|
||||||
extern "C" fn file_write_ffi(
|
|
||||||
buffer : Bytes,
|
|
||||||
size : Int,
|
|
||||||
count : Int,
|
|
||||||
handle : NativeFileHandle,
|
|
||||||
) -> Int = "bitlogger_file_write"
|
|
||||||
|
|
||||||
///|
|
|
||||||
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 {
|
pub struct FileHandle {
|
||||||
path : String
|
priv path : String
|
||||||
raw : NativeFileHandle
|
priv append : Bool
|
||||||
|
priv initialized : Ref[Bool]
|
||||||
|
priv position : Ref[Int64]
|
||||||
|
priv closed : Ref[Bool]
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
fn run_async_bool_internal(operation : async () -> Bool) -> Bool {
|
||||||
|
let result = Ref(false)
|
||||||
|
@async.run_async_main(() => result.val = operation())
|
||||||
|
result.val
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
fn run_async_i64_internal(operation : async () -> Int64) -> Int64 {
|
||||||
|
let result = Ref(0L)
|
||||||
|
@async.run_async_main(() => result.val = operation())
|
||||||
|
result.val
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
fn new_file_handle_internal(
|
||||||
|
path : String,
|
||||||
|
append : Bool,
|
||||||
|
initialized : Bool,
|
||||||
|
) -> FileHandle {
|
||||||
|
{
|
||||||
|
path,
|
||||||
|
append,
|
||||||
|
initialized: Ref(initialized),
|
||||||
|
position: Ref(0L),
|
||||||
|
closed: Ref(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
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? {
|
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||||
let mode = if append { "ab" } else { "wb" }
|
let opened = run_async_bool_internal(() => {
|
||||||
let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes(mode))
|
probe_open_file_handle_async_internal(path, append)
|
||||||
if file_is_null_ffi(raw) {
|
})
|
||||||
None
|
if opened {
|
||||||
|
Some(new_file_handle_internal(path, append, true))
|
||||||
} else {
|
} 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 {
|
pub fn file_exists_internal(path : String) -> Bool {
|
||||||
let raw = file_open_ffi(string_to_c_bytes(path), string_to_c_bytes("rb"))
|
run_async_bool_internal(() => @fs.exists(path))
|
||||||
if file_is_null_ffi(raw) {
|
}
|
||||||
false
|
|
||||||
} else {
|
///|
|
||||||
ignore(file_close_ffi(raw))
|
pub async fn file_exists_async_internal(path : String) -> Bool {
|
||||||
true
|
@fs.exists(path) catch {
|
||||||
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,45 +140,102 @@ pub fn write_file_handle_internal(
|
|||||||
handle : FileHandle,
|
handle : FileHandle,
|
||||||
content : String,
|
content : String,
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
let bytes = string_to_c_bytes(content)
|
run_async_bool_internal(() => {
|
||||||
let written = file_write_ffi(bytes, 1, bytes.length() - 1, handle.raw)
|
write_file_handle_async_internal(handle, content)
|
||||||
written == bytes.length() - 1
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
///|
|
||||||
|
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 {
|
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 {
|
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 {
|
pub fn file_size_i64_internal(handle : FileHandle) -> Int64 {
|
||||||
ignore(file_seek_ffi(handle.raw, 0, 2))
|
run_async_i64_internal(() => file_size_i64_async_internal(handle))
|
||||||
let size = file_tell_i64_ffi(handle.raw)
|
}
|
||||||
if size < 0L {
|
|
||||||
0L
|
///|
|
||||||
} else {
|
pub async fn rename_file_async_internal(
|
||||||
size
|
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 {
|
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 {
|
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 {
|
pub fn string_byte_length_internal(content : String) -> Int {
|
||||||
string_to_c_bytes(content).length() - 1
|
@utf8.encode(content).length()
|
||||||
}
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
|
|||||||
@@ -1,28 +1,106 @@
|
|||||||
///|
|
///|
|
||||||
pub struct FileHandle {
|
pub struct FileHandle {
|
||||||
path : String
|
priv dummy : Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
///|
|
///|
|
||||||
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
pub fn open_file_handle_internal(path : String, append : Bool) -> FileHandle? {
|
||||||
ignore(append)
|
ignore(append)
|
||||||
ignore(path)
|
ignore(path)
|
||||||
let _unused : FileHandle = { path: "" }
|
let _unused : FileHandle = { dummy: () }
|
||||||
ignore(_unused)
|
ignore(_unused)
|
||||||
None
|
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 {
|
pub fn file_exists_internal(path : String) -> Bool {
|
||||||
ignore(path)
|
ignore(path)
|
||||||
false
|
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(
|
pub fn write_file_handle_internal(
|
||||||
handle : FileHandle,
|
handle : FileHandle,
|
||||||
content : String,
|
content : String,
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
ignore(handle.dummy)
|
||||||
ignore(handle)
|
ignore(handle)
|
||||||
ignore(content)
|
ignore(content)
|
||||||
false
|
false
|
||||||
|
|||||||
+3
-1
@@ -2,14 +2,16 @@ import {
|
|||||||
"Nanaloveyuki/BitLogger/src/core",
|
"Nanaloveyuki/BitLogger/src/core",
|
||||||
"Nanaloveyuki/BitLogger/src/formatting",
|
"Nanaloveyuki/BitLogger/src/formatting",
|
||||||
"Nanaloveyuki/BitLogger/src/record_ops",
|
"Nanaloveyuki/BitLogger/src/record_ops",
|
||||||
|
"moonbitlang/async",
|
||||||
|
"moonbitlang/async/fs",
|
||||||
"moonbitlang/core/env",
|
"moonbitlang/core/env",
|
||||||
|
"moonbitlang/core/encoding/utf8",
|
||||||
"moonbitlang/core/json",
|
"moonbitlang/core/json",
|
||||||
"moonbitlang/core/ref",
|
"moonbitlang/core/ref",
|
||||||
"moonbitlang/core/string",
|
"moonbitlang/core/string",
|
||||||
}
|
}
|
||||||
|
|
||||||
options(
|
options(
|
||||||
"native-stub": [ "stub.c" ],
|
|
||||||
targets: {
|
targets: {
|
||||||
"file_backend_native.mbt": [ "native", "llvm" ],
|
"file_backend_native.mbt": [ "native", "llvm" ],
|
||||||
"file_backend_stub.mbt": [ "js", "wasm", "wasm-gc" ],
|
"file_backend_stub.mbt": [ "js", "wasm", "wasm-gc" ],
|
||||||
|
|||||||
@@ -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 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 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 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 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 flush_file_handle_internal(FileHandle) -> Bool
|
||||||
|
|
||||||
pub fn format_json(@core.Record) -> String
|
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 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 open_file_handle_internal(String, Bool) -> FileHandle?
|
||||||
|
|
||||||
pub fn prefix_message(String) -> (@core.Record) -> @core.Record
|
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 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 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 rename_file_internal(String, String) -> Bool
|
||||||
|
|
||||||
pub fn reset_global_style_tag_registry() -> Unit
|
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 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
|
pub fn write_file_handle_internal(FileHandle, String) -> Bool
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|
||||||
// Types and methods
|
// Types and methods
|
||||||
pub struct FileHandle {
|
pub struct FileHandle {
|
||||||
path : String
|
// private fields
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type aliases
|
// Type aliases
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
|
||||||
#define _CRT_SECURE_NO_WARNINGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user