-
Notifications
You must be signed in to change notification settings - Fork 0
Rust API
Every example on this page is compiled and executed by repository CI against synthetic fixtures, except blocks marked illustrative-only.
The normal Rust dependency is the public SDK package:
[dependencies]
journal = { package = "systemd-journal-sdk", version = "0.7.7" }Use the lower-level packages only when the public package does not expose the
type you need. For example, structured directory writes currently use
StructuredField from systemd-journal-sdk-log-writer:
[dependencies]
journal = { package = "systemd-journal-sdk", version = "0.7.7" }
journal_log_writer = { package = "systemd-journal-sdk-log-writer", version = "0.7.7" }Callers that intentionally want local-host identity can also depend on
systemd-journal-sdk-host and import its journal_host lib. Core writers never
import or call that helper automatically; callers pass helper-returned values to
the writer explicitly.
[dependencies]
journal_host = { package = "systemd-journal-sdk-host", version = "0.7.7" }Use FileReader when the caller owns ordering and reads one journal file.
use journal::FileReader;
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
reader.add_match(b"PRIORITY=6");
reader.seek_head();
while reader.next()? {
let entry = reader.get_entry()?;
if let Some(message) = entry.get_str("MESSAGE") {
println!("{message}");
}
}
# Ok::<(), Box<dyn std::error::Error>>(())get_entry() materializes maps and owned payloads. It is convenient, but it is
not the lowest-cost scan path.
Use visit_entry_payloads() when the consumer can work with FIELD=value
bytes directly.
use journal::FileReader;
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
reader.seek_head();
while reader.next()? {
reader.visit_entry_payloads(|payload| {
if payload.starts_with(b"MESSAGE=") {
let value = &payload[b"MESSAGE=".len()..];
println!("{}", String::from_utf8_lossy(value));
}
Ok(())
})?;
}
# Ok::<(), Box<dyn std::error::Error>>(())Uncompressed payloads are borrowed from mmap-backed journal data. Compressed payloads are decompressed into row-owned storage. The payload is valid only inside the callback for this visitor shape.
Use entry_data_restart() and enumerate_entry_payload() when a facade-like
caller needs current-row payloads that stay valid until the row changes.
use journal::FileReader;
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
reader.seek_head();
if reader.next()? {
reader.entry_data_restart()?;
while let Some(payload) = reader.enumerate_entry_payload()? {
println!("{}", String::from_utf8_lossy(payload));
}
}
# Ok::<(), Box<dyn std::error::Error>>(())Do not keep the returned slice after advancing, seeking, restarting DATA enumeration, remapping, or closing the reader. Copy if longer ownership is required.
Use DirectoryReader for stock-like ordering across active and archived files.
use journal::DirectoryReader;
let mut reader = DirectoryReader::open("/var/log/journal")?;
reader.seek_tail();
while reader.previous()? {
let realtime = reader.get_realtime_usec()?;
let entry = reader.get_entry()?;
println!("{realtime} {:?}", entry.get_str("MESSAGE"));
}
# Ok::<(), Box<dyn std::error::Error>>(())Directory reading merges journal files in journal order. It is the right API
for journalctl --directory style behavior.
The default reader is live. Use snapshot bounds when a query may ignore entries appended after it starts.
use journal::{FileReader, ReaderOptions};
let options = ReaderOptions::snapshot();
let mut reader = FileReader::open_with_options("/var/log/journal/example/system.journal", options)?;
reader.seek_head();
# Ok::<(), Box<dyn std::error::Error>>(())Snapshot bounds avoid live-file refresh work during long scans.
Unique values for one field should use the FIELD object's DATA chain, not a row scan.
use journal::FileReader;
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
reader.visit_unique_values("SYSLOG_IDENTIFIER", |value| {
println!("{}", String::from_utf8_lossy(value));
Ok(())
})?;
# Ok::<(), Box<dyn std::error::Error>>(())Use query_unique() only when the caller needs an owned vector of all values.
For facade-style stateful enumeration, FileReader exposes
query_unique_state(), restart_unique_state(), clear_unique_state(), and
enumerate_unique_payload(field_name). DirectoryReader exposes the same
state controls and enumerate_unique_payload() for multi-file de-duplicated
payloads. These methods keep the same indexed FIELD/DATA-chain contract, so
query setup and each enumeration step can fail independently if the journal
contains damaged or undecompressible DATA. Directory-level unique enumeration
builds an exact 8-entry per-open-reader LRU cache from per-file FIELD/DATA
chains and reuses it for repeated queries or restarts while the already-open
file set's header signatures stay unchanged. The entry count is bounded, but
each cache entry keeps the full exact unique set for one field.
Explorer is the API for filters, facets, histogram, FTS, and selected returned rows.
use journal::{ExplorerQuery, FileReader};
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
let query = ExplorerQuery::default()
.with_filter("PRIORITY", ["3", "4"])
.with_facet("SYSLOG_IDENTIFIER")
.with_histogram("PRIORITY");
let result = reader.explore(&query)?;
println!("matched rows: {}", result.stats.rows_matched);
# Ok::<(), Box<dyn std::error::Error>>(())Default Explorer behavior:
-
ExplorerStrategy::Traversal; -
ExplorerFieldMode::FirstValue; - source realtime enabled;
- indexed filters;
- all-field expansion only for returned rows.
Do not enable debug_collect_column_fields_by_row_traversal in production.
Use ExplorerStrategy::Compare to validate a query shape before using the
index strategy.
use journal::{ExplorerFieldMode, ExplorerQuery, ExplorerStrategy, FileReader};
let mut reader = FileReader::open("/var/log/journal/example/system.journal")?;
let query = ExplorerQuery {
facets: vec![b"PRIORITY".to_vec()],
field_mode: ExplorerFieldMode::AllValues,
use_source_realtime: false,
limit: 0,
..ExplorerQuery::default()
};
let result = reader.explore_with_strategy(&query, ExplorerStrategy::Compare)?;
if let Some(comparison) = result.comparison {
println!("traversal: {:?}", comparison.traversal_duration);
println!("index: {:?}", comparison.index_duration);
}
# Ok::<(), Box<dyn std::error::Error>>(())The index strategy is exact only for its supported subset. It is not a universal faster mode.
Use Log for production ingestion directories.
use journal::{Config, EntryTimestamps, Log, Origin, RetentionPolicy, RotationPolicy, Source};
use std::path::Path;
use std::time::Duration;
let origin = Origin {
machine_id: Some("00112233445566778899aabbccddeeff".parse()?),
namespace: None,
source: Source::System,
};
let config = Config::new(
origin,
RotationPolicy::default()
.with_number_of_entries(100_000)
.with_duration_of_journal_file(Duration::from_secs(3600)),
RetentionPolicy::default()
.with_number_of_journal_files(8)
.with_duration_of_journal_files(Duration::from_secs(7 * 24 * 3600)),
)
.with_boot_id("ffeeddccbbaa99887766554433221100".parse()?)
.with_compact(true)
.with_live_publish_every_entries(64);
let mut log = Log::new(Path::new("/var/log/journal-sdk"), config)?;
let timestamps = EntryTimestamps::default()
.with_entry_realtime_usec(1_700_000_000_000_000)
.with_entry_monotonic_usec(1);
log.write_entry_with_timestamps(
&[
b"MESSAGE=plugin started".as_slice(),
b"PRIORITY=6".as_slice(),
b"SYSLOG_IDENTIFIER=example-plugin".as_slice(),
],
timestamps,
)?;
log.sync()?;
log.close()?;
# Ok::<(), Box<dyn std::error::Error>>(())Log stores files below <directory>/<machine-id>/. By default it uses
Netdata-compatible chain active names. Use
Config::with_strict_systemd_naming(true) only when the consumer needs
<source>.journal active naming.
By default, Log syncs each archived journal file on the caller path during
rotation, explicit close, and stale-active startup archive. Latency-sensitive
callers may set Config::with_sync_on_archive(false) to skip that archive-file
sync. With the opt-out, the caller owns archived-file durability before relying
on side indexes or allowing retention to delete archived files.
The optional journal_host helper can load local-host identity for callers that
then pass those values into Origin and entry timestamps explicitly. On Linux,
containerized callers can set LoadOptions::with_host_filesystem_prefix("/host")
when they intentionally want host machine identity instead of container-local
identity. Missing host files fall back to container-local files; present invalid
host files return an error so collectors do not silently switch identity.
Use structured fields when the producer already has field names and values split.
use journal::{Config, EntryTimestamps, Log, Origin, RetentionPolicy, RotationPolicy, Source};
use journal_log_writer::StructuredField;
use std::path::Path;
let origin = Origin {
machine_id: Some("00112233445566778899aabbccddeeff".parse()?),
namespace: None,
source: Source::System,
};
let config = Config::new(
origin,
RotationPolicy::default(),
RetentionPolicy::default(),
)
.with_boot_id("ffeeddccbbaa99887766554433221100".parse()?);
let mut log = Log::new(Path::new("/var/log/journal-sdk"), config)?;
let fields = [
StructuredField::new(b"MESSAGE", b"binary-safe structured entry"),
StructuredField::new(b"PRIORITY", b"6"),
StructuredField::new(b"BINARY_PAYLOAD", b"\x00\x01\x02\xff"),
];
let timestamps = EntryTimestamps::default()
.with_entry_realtime_usec(1_700_000_000_000_001)
.with_entry_monotonic_usec(2);
log.write_fields_with_timestamps(&fields, timestamps)?;
# Ok::<(), Box<dyn std::error::Error>>(())This avoids constructing KEY=value bytes only to split them again.
use journal::{Config, FieldNamePolicy, Origin, RetentionPolicy, RotationPolicy, Source};
let origin = Origin {
machine_id: Some("00112233445566778899aabbccddeeff".parse()?),
namespace: None,
source: Source::System,
};
let config = Config::new(
origin,
RotationPolicy::default(),
RetentionPolicy::default(),
)
.with_boot_id("ffeeddccbbaa99887766554433221100".parse()?)
.with_field_name_policy(FieldNamePolicy::Journald);
# Ok::<(), Box<dyn std::error::Error>>(())Use:
-
FieldNamePolicy::Journaldfor trusted journald-like producers; -
FieldNamePolicy::Rawonly for file-format-level tools and tests. -
FieldNamePolicy::JournalAppfor untrusted application-facing rules.
Raw files are journal files, but stock systemd tooling is not guaranteed to
accept invalid systemd field names.
Use journal::netdata when the consumer needs Netdata-shaped function output.
use journal::netdata::{
NetdataFunctionConfig, NetdataFunctionRunOptions, NetdataJournalFunction,
SystemdJournalProfile,
};
use serde_json::json;
use std::path::Path;
let function = NetdataJournalFunction::systemd_journal();
let request = json!({
"after": 0,
"before": 0,
"last": 200,
"facets": ["PRIORITY", "SYSLOG_IDENTIFIER"],
"histogram": "PRIORITY"
});
let response = function.run_directory_request_json_with_options(
Path::new("/var/log/journal"),
&request,
NetdataFunctionRunOptions::from_timeout_seconds(30),
)?;
println!("{}", serde_json::to_string(&response)?);
# Ok::<(), Box<dyn std::error::Error>>(())Customize NetdataFunctionConfig::source_selector_name and
source_selector_help when the same function shape serves a domain-specific
journal backend. The wire id remains __logs_sources; only the label and help
shown by Netdata change.
let mut config = NetdataFunctionConfig::systemd_journal();
config.source_selector_name = "Trap Jobs".to_string();
config.source_selector_help = "Select the trap job to query".to_string();
let function = NetdataJournalFunction::new(config, SystemdJournalProfile);This layer is Netdata-specific. Generic log explorers should use Explorer directly unless they need the Netdata request and response shape.
use journal::verify_file;
verify_file("/var/log/journal/example/system.journal")?;
# Ok::<(), Box<dyn std::error::Error>>(())Use verify_file_with_key() for sealed files when a verification key is
available. Verification is for integrity checks, not normal query serving.
File-path verification uses the same bounded reader access architecture as
normal file reads, so it avoids whole-file resident buffers while still walking
the object graph and sealed HMAC ranges.