A powerful, modular logging infrastructure for Internet Computer (IC) projects. Designed to be instantiated once and shared across canisters or classes, providing namespace-based filtering, log levels, buffered storage, and upgrade-safe state management.
- Shared, Central Logging: Single logger instance across modules or canisters.
- Buffered Ring Buffer: Capped FIFO storage with O(1) replacement of the oldest entry at capacity.
- Namespace & Level Filtering: Log by custom namespaces; filter by
Debug,Info,Warn,Error,Fatal. - Flexible Querying: Pagination via
takeandprev, plus namespace and level filters. - Export & Clear: Retrieve or clear all or filtered logs for audit, analytics, or privacy.
- Upgrade-Safe: Built with versioned migrations to preserve logs across canister upgrades.
- Extensibility Hooks:
onEvictcallbacks for storing old data elsewhere.
Add to your mops.toml:
mops add stable-local-log
Import in your Motoko module:
import StableLocalLog "mo:stable-local-log";Use the Init helper from ClassPlus to create your logger instance. You must supply:
org_icdevs_class_plus_manager: YourClassPlusInitializationManager.initialState:Local_log.initialState()for default state.args: OptionalInitArgswithmin_levelandbufferSize.pullEnvironment: Function loader forEnvironment(required for cycle sharing).onInitialize: Optional async setup hook.onStorageChange: Hook invoked on state mutation.
Example:
let mkLogger = Local_log.Init({
org_icdevs_class_plus_manager = myManager;
initialState = Local_log.initialState();
args = ?{
min_level = ?#Info; // filter out Debug
bufferSize = ?2000; // ring buffer max entries
};
pullEnvironment = ?(() -> {
var org_icdevs_timer_tool = ?myTimerTool;
advanced = null;
onEvict = ?(func(entries) { Debug.print("Evicted entries: " # debug_show(entries.size())); });
});
onInitialize = ?(func _(logger) async { /* warm-up or migration */ });
onStorageChange = func(state) { /* persist state or metrics */ };
});
let logger = mkLogger();mkLogger is a callable function; logger is the initialized Local_log instance.
- LogLevel:
#Debug,#Info,#Warn,#Error,#Fatal - LogEntry:
{ timestamp: Nat; message: Text; level: Nat; namespace: Text } - LogQuery:
{ namespaces: ?[Text]; level: ?LogLevel; take: ?Nat; prev: ?Nat } - LogExportResult:
{ exportedCount: Nat; exported: [LogEntry] } - InitArgs:
{ min_level: ?LogLevel; bufferSize: ?Nat } - Environment:
{ tt: TimerToolLib.TimerTool; advanced: ?{ icrc85: ICRC85Options }; onEvict: ?(LogEntry -> ()) }
| Method | Params | Returns | Description |
|---|---|---|---|
log_add |
(message: Text, level: LogLevel, namespace: Text) |
() |
Add a log entry to one namespace |
log_debug/info/warn/error/fatal |
(message: Text, namespace: Text) |
() |
Shortcut for log_add at respective level |
log_query |
(q: LogQuery) |
[LogEntry] |
Retrieve filtered/paginated logs |
log_export |
(q: LogQuery) |
LogExportResult |
Export logs matching query |
log_clear |
() |
Nat |
Clear all entries and return count removed |
log_size |
(namespaces: ?[Text], level: ?LogLevel) |
Nat |
Count entries matching filters |
log_set_buffer_size |
(v: Nat) |
Nat |
Set max buffer size, truncate if needed |
log_get_buffer_size |
() |
Nat |
Get current buffer size |
log_set_min_level |
(level: LogLevel) |
Nat |
Set and return new minimum level |
getState |
() |
State |
Inspect internal state (for debug/migration) |
// Add logs
logger.log_info("Service started", "app");
logger.log_error("Database error", "db");
// Query last 50 warnings or above
let recent = logger.log_query({ namespaces=null; level=?#Warn; take=?50; prev=null });
// Export and clear
let exportRes = logger.log_export({ namespaces=null; level=null; take=null; prev=null });
ignore logger.log_clear();- onEvict Callback: Handle entries dropped by ring buffer rollover.
- Upgrade Migrations: Uses
Migration.migrateto maintain state compatibility. - Cycle Sharing (ICRC85): Controlled via
initialize_icrc85andEnvironment.advancedsettings.
Contributions, issues, and pull requests are welcome! Please fork the repository and open a PR with your changes.
This project is open-sourced under the MIT License. See the LICENSE file for details.
This motoko class has a default OVS behavior that sends cycles to the developer to provide funding for maintenance and continued development. In accordance with the OVS specification and ICRC85, this behavior may be overridden by another OVS sharing heuristic or turned off. We encourage all users to implement some form of OVS sharing as it helps us provide quality software and support to the community.
Default behavior: 0.2 XDR per 10000 processed events processed per month up to 1 XDR;
Default Beneficiary: PanIndustrial.com
Dependent Libraries: