Skip to content

Repository files navigation

GardeLogStore

GardeLogStore is a Java 21/Kotlin append-only log library. The JVM backend is the reference implementation of binary format v1; the repository also contains Java, import/export, payload, JDBC, viewer, CLI, and Minecraft-focused modules, plus experimental read-only C, C++, Rust, and .NET parsers.

Current development version: 0.2.0-dev. The project is pre-1.0: source APIs can still evolve, while existing format-v1 fixtures remain compatibility tests.

Component status

Status Components
Beta/reference garde-log-api, garde-log-format, garde-log-jvm
Beta Java facade, JSONL import/export, TXT export, viewer, CLI, Minecraft schema codec
Experimental payload compression/encryption helpers, JDBC transfer, C, C++, Rust, .NET
Planned Android storage adapter, browser/WebAssembly viewer

“Experimental” is deliberate: native and .NET readers pass shared fixtures, but have less production history and no write path. Android and Web are not runtime artifacts and are not presented as implemented backends.

Modules

  • garde-log-api — public records, options, exceptions, and store API.
  • garde-log-format — format-v1 encoding and validation.
  • garde-log-jvm — file channels, locking, durability, verification, recovery.
  • garde-log-java-api — Java-oriented builder and facade.
  • garde-log-payload — bounded envelopes, gzip, and experimental AES-GCM.
  • garde-log-export / garde-log-import — atomic export and bounded import.
  • garde-log-sql — transactional JDBC transfer without a bundled driver.
  • garde-log-viewer — UI-independent paging/filtering.
  • garde-log-minecraft — schema 1/2 payloads without a Minecraft dependency.
  • garde-log-cli — standalone command-line application.
  • garde-log-benchmark — JMH only; excluded from normal build execution.

Source ownership

modules/** is the only canonical, maintained JVM source tree. The root-level src/** tree is a restored archive kept for recovery traceability; it is not compiled, packaged, linted, or used by tests. New code and fixes belong in the matching module. The verifySourceTopology gate keeps the archive isolated and checks that non-exempt archived sources have canonical module successors.

Backend discovery and capabilities

The public GardeLogStore.open facade discovers its filesystem implementation through the GardeLogStoreProvider SPI. The parallel backend registry is a separate SPI: installed bridge modules publish GardeLogBackendProvider with ServiceLoader, a descriptor, and an explicit capability set. The capability vocabulary is READ, APPEND, VERIFY, and REPAIR; availability and capabilities are checked before a provider is opened. Read-only options require READ plus VERIFY; writable options require all four capabilities.

GardeLogBackendSelector.open is fail-closed: fallbackToJvm defaults to false. Callers must opt in explicitly or use select, whose result makes the decision auditable. GardeLogBackendSelection exposes requestedMode, selectedMode (the concrete result of resolving AUTO before fallback), resolvedMode (the backend actually opened), requiredCapabilities, usedFallback, and fallbackReason.

Fallback is safe only for known backend unavailability, including a provider throwing GardeLogBackendUnavailableException. I/O, corruption, lock, permission, provider-configuration, and other operational failures propagate unchanged and never trigger fallback. Multiple providers for one concrete mode throw GardeLogBackendProviderConflictException, even when fallback was enabled. Individual providers never fall back on their own.

Minecraft is a schema and dependency-free adapter over an already opened JVM store, not a runtime backend. Likewise, the standalone C, C++, Rust, and .NET readers do not become JVM backends unless a real provider bridge is installed. The Rust reader supports concurrent callers, but its shared seek cursor is protected by a mutex, so file-position operations are serialized rather than performed as parallel positional reads.

Kotlin quick start

dependencies {
    implementation("ru.garde:garde-log-jvm:0.2.0-dev")
}
import java.nio.file.Path
import ru.garde.logstore.GardeLogStore
import ru.garde.logstore.core.DurabilityMode
import ru.garde.logstore.core.LogStoreOptions

GardeLogStore.open(
    Path.of("logs"),
    "server",
    LogStoreOptions(durabilityMode = DurabilityMode.DATA_AND_INDEX),
).use { store ->
    val id = store.appendText(type = 1, text = "server started")
    println(store.readById(id)?.payloadAsString())
}

Java quick start

import java.nio.file.Path;
import ru.garde.logstore.core.DurabilityMode;
import ru.garde.logstore.javaapi.JavaGardeLogStore;

try (var store = JavaGardeLogStore.builder(Path.of("logs"), "server")
        .durability(DurabilityMode.DATA_AND_INDEX)
        .open()) {
    long id = store.appendText("server started");
    store.findById(id).ifPresent(record ->
        System.out.println(record.payloadAsString()));
}

Use ru.garde:garde-log-java-api:0.2.0-dev for the Java facade. Artifacts can be tested locally with ./gradlew publishToMavenLocal; no signing credentials or publication secrets are stored in this repository.

Durability, locking, and threading

One writable opener is allowed per store through <name>.lock and FileLock. Any number of read-only openers may coexist with it. Read-only mode never creates or repairs files. Inside one JVM store instance, reads share a read lock; append, repair, and close are exclusive.

  • NONE: no per-append force call.
  • DATA_ONLY: force the .glog record before publishing its index entry.
  • DATA_AND_INDEX: force both data and index.

The legacy flushOnAppend=true remains compatible and selects full forcing. Append remembers both original file sizes and attempts to roll both files back on failure. A valid data record missing from the index is recovered on open; an incomplete or corrupt tail is reported and requires explicit tail repair. Every accepted index must describe contiguous records starting immediately after the GLOG header: gaps, overlaps, and an end offset inconsistent with the data file are corruption, not sparse-index semantics.

Recovery and corruption

verify() is read-only. Index repair writes a complete sibling temporary file, forces and validates it, then uses atomic replacement where supported. To truncate an incomplete final record, call repairPartialLastRecord(truncate = true) or run repair-tail ... --yes. Keep a backup before modifying a damaged store; CRC32 detects accidental damage but is not authentication.

CLI

info <directory> <name>
view|tail <directory> <name> [--last <count>]
export-jsonl|export-txt <directory> <name> <target>
import-jsonl <directory> <name> <source>
verify <directory> <name>
repair-index|repair-tail <directory> <name> --yes
dump-meta <directory> <name> [--last <count>]
generate-vectors <directory> --yes

Run it with ./gradlew :garde-log-cli:run --args="verify logs server". Exit codes are 0 for success, 1 for an operational error, 2 for usage errors, and 3 for a completed verification that found corruption.

Minecraft integration

The Minecraft module has no Fabric, Forge, NeoForge, Paper, Bukkit, NMS, or Minecraft dependency and is not a separately selectable storage backend. Convert platform components in the mod/plugin boundary, open the normal JVM store, then encode a schema payload:

val payload = MinecraftLogPayloads.incomingChat(
    timestampMillis = System.currentTimeMillis(),
    plainText = "[Player] hello",
    senderUuid = playerUuid.toString(),
)
MinecraftLogRecords.appendTo(store, payload)

Schema 1 and 2 decode remain supported; unknown JSON fields are ignored for forward compatibility. See Minecraft schema.

Limits and compatibility

  • Format fields are signed big-endian integers; record IDs must be positive and strictly increasing.
  • The format parser rejects payload declarations above 256 MiB. JVM writes use the lower configurable maxPayloadBytes limit (1 MiB by default).
  • List-returning JVM APIs default to at most 100,000 records; sequence APIs are available for lazy traversal.
  • Store names must be a single safe filename component.
  • Payload bytes, unknown record types, and unknown flag bits remain opaque.

The normative layout is format-v1, with operational notes in FORMAT.md.

Build and verification

Java 21 is required. The release/CI-equivalent Windows command is:

.\scripts\check-all.ps1 -Strict

Unix:

./scripts/check-all.sh --strict

Strict mode treats missing Cargo, CMake, or .NET as a failure. Without -Strict/--strict, unavailable optional toolchains are reported and skipped. Both scripts run the Gradle verifyIntegration gate, native format/lint/tests, and .NET build/tests. Temporary native builds use unique directories and are removed afterwards.

test-vectors/manifest.tsv is the canonical inventory of real format-v1 fixtures and their expected validity/error. JVM, C, C++, Rust, and .NET tests consume it and reject missing or unregistered fixture directories. The Gradle vector gate also regenerates the complete registered tree outside the committed directory and compares every generated file byte-for-byte with the committed fixtures.

Useful focused commands:

./gradlew verifyIntegration
./gradlew publishToMavenLocal
./gradlew verifyTestVectors
./gradlew :garde-log-benchmark:jmh

Documentation

MIT licensed; see LICENSE.

About

Kotlin/JVM append-only binary log storage library with indexed reads, recovery, repair, and JSONL/TXT export.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages