Logging — LogIt & SystemLog
Oidis provides two distinct logging systems that serve different purposes. Understanding when to use each is essential for building maintainable applications.
LogIt — Development Logging
LogIt is the primary logging utility for day-to-day development. It wraps console.* methods with structured output and is available on both frontend and backend.
Import:
import { LogIt } from "@io-oidis-commons/.../Utils/LogIt.js";
API
// Informational messages
LogIt.Info("User logged in", userId);
// Warnings (non-critical issues)
LogIt.Warning("Cache miss for key", cacheKey);
// Errors (with optional exception object)
LogIt.Error("Failed to save record", ex);
// Debug messages (stripped or suppressed in production)
LogIt.Debug("Processing batch", { size: items.length, offset: offset });
When to Use LogIt
- Catch blocks — Always log the error:
LogIt.Error("Context message", ex) - Flow debugging — Trace execution paths during development
- Startup messages — Log initialization steps in Loaders
- Data inspection — Temporarily log values during debugging
Rules
Never use console.log, console.warn, or console.error directly. The Oidis linter flags all console.* calls as errors. Always use LogIt instead.
LogIt calls are not persisted — they appear in the terminal (backend) or browser DevTools (frontend) and then they're gone. For anything that needs a persistent record, use SystemLog.
Placeholder substitution — pass strings
LogIt messages support indexed placeholders ({0}, {1}, …) filled from the trailing arguments, but the substituted values must be strings:
LogIt.Info("Imported {0} rows from {1}", String(rows.length), filePath);
// -> Imported 12 rows from C:/data/export.xlsx
A non-string value (number, boolean, object) passed into a placeholder is rendered through Convert's debug form — Object type: <type>. Return value: <value> — which is almost never what you want inside a sentence. This is intentional (that raw form doubles as an object-inspection dump), not a bug:
LogIt.Debug("interval={0}s", 60); // interval=Object type: number. Return value: 60s
LogIt.Debug("interval={0}s", String(60)); // interval=60s
So wrap counts, indexes and other non-strings in String() whenever they feed a {N} placeholder.
Leading severity in LogIt.Info
LogIt.Info takes an optional severity ahead of the format values — LogIt.Info(message, LogSeverity.LOW, ...values) — and reads the first extra argument as that severity whenever it names a LogSeverity member. LogSeverity is a numeric enum (LOW = 0, MEDIUM = 1, HIGH = 2), so a first value of 0, 1 or 2 — as a number or as String(count) — is taken as the severity too. It then disappears from the message, the remaining values shift one placeholder to the left, and because the default minimum severity is HIGH, a message whose first value was 0 or 1 is not printed at all. This is the intended API; when the first value can be such a small number, pass the severity explicitly or write the values into the message:
import { LogSeverity } from "@io-oidis-commons/.../Enums/LogSeverity.js";
LogIt.Info("Pruned {0} entries from {1}", String(removed), dir); // removed === 1 -> not printed
LogIt.Info("Pruned {0} entries from {1}", LogSeverity.HIGH, String(removed), dir); // explicit severity
LogIt.Info(`Pruned ${removed} entries from ${dir}`); // no placeholders
Only Info has the leading severity; Warning, Error, Debug and Verbose treat their first extra argument as an ordinary value.
SystemLog — Audit Trail
SystemLog is a persistent, MongoDB-backed audit trail for tracking critical operations. Unlike LogIt, entries are stored in a database collection with user attribution, rotation, and queryable metadata.
Import (backend only):
import { SystemLog } from "@io-oidis-hub/.../LogProcessor/SystemLog.js";
Static API
// Simple messages at predefined levels; the optional 2nd argument ($autoLogIt : boolean) also prints via LogIt
// (default: false for Info, true for Warning and Error)
await SystemLog.Info("User registered");
await SystemLog.Warning("Rate limit approaching");
await SystemLog.Error("External API timeout");
// Full-featured trace logging
SystemLog.Trace({
message: "Order submitted to ERP",
traceBack: `GroupId: ${group.Id}, Items: ${items.length}, Period: ${period}`,
level: LogLevel.INFO,
tags: ["erp", "order"],
onBehalf: userId,
autoLogIt: true
});
// Log rotation — removes entries older than sysLog.maxAge days (project config); no-op when sysLog.enabled is false
await SystemLog.Clean();
Trace Options
The Trace() method accepts an options object for maximum flexibility:
| Option | Type | Description |
|---|---|---|
message | string | Primary log message |
traceBack | string | Detailed context — IDs, parameters, state snapshot |
level | LogLevel (@io-oidis-commons/Io/Oidis/Commons/Enums/LogLevel.js) | e.g. INFO, WARNING, ERROR |
tags | string[] | Searchable tags for filtering |
onBehalf | string | User ID for attribution (who triggered this action) |
autoLogIt | boolean | Also output via LogIt (default: depends on level) |
The TraceBack Pattern
The traceBack field is where you put the details that make debugging possible later. Think of it as the "evidence" attached to the log entry:
SystemLog.Trace({
message: "Invoice generated",
traceBack: [
`InvoiceId: ${invoice.Id}`,
`CustomerId: ${customer.Id}`,
`Amount: ${invoice.Total} EUR`,
`Items: ${invoice.Lines.length}`,
`Period: ${invoice.PeriodStart} - ${invoice.PeriodEnd}`
].join(", "),
level: LogLevel.INFO,
tags: ["invoice", "billing"],
onBehalf: currentUserId
});
LogEntry Model
Each SystemLog entry is persisted as a LogEntry model in MongoDB:
| Field | Type | Description |
|---|---|---|
Message | string | Primary log message |
TraceBack | string | Detailed context |
Level | LogLevel | INFO, WARNING, ERROR |
Tags | string[] | Searchable metadata |
OnBehalf | string | User ID (who triggered it) |
Created | Date | Auto-timestamp |
Configuration
SystemLog behavior can be configured via commons.jsonp:
{
systemLog: {
enabled: true,
rotationDays: 90,
maxEntries: 100000
}
}
Frontend Access
The frontend can read SystemLog entries via SystemLogConnector from io-oidis-hub-gui, but cannot write to the audit trail. Writing is backend-only by design — this prevents client-side tampering with the audit record.
LogIt vs SystemLog — Decision Guide
| Scenario | Use | Why |
|---|---|---|
| Debugging during development | LogIt | Temporary, no persistence needed |
| Catch block error logging | LogIt | Standard error capture |
| User performed critical action | SystemLog | Audit trail with user attribution |
| External API call result | SystemLog | Track integrations for debugging reports |
| Data modification (create/update/delete) | SystemLog | Accountability and rollback investigation |
| Authentication events | SystemLog | Security audit |
| Scheduled task execution | SystemLog | Operations monitoring |
| Frontend UI debugging | LogIt | SystemLog is backend-only |
| Startup/initialization messages | LogIt | No audit value |
| Performance metrics | SystemLog | Historical analysis |
Rule of Thumb
LogIt is for developers during development. SystemLog is for operations teams investigating issues in production.
If the information would be valuable days or weeks after it happened — use SystemLog. If it's only useful right now while you're looking at it — use LogIt.
Practical Examples
Backend Service Method
@Extern()
public async SubmitOrder($input : IOrderInput) : Promise<IOrderResult> {
try {
const record : OrderRecord = await OrderRecord.FindById($input.id);
if (ObjectValidator.IsEmptyOrNull(record)) {
LogIt.Warning("Order not found: " + $input.id);
return {success: false, message: "Record not found"};
}
record.Period = $input.period;
await record.Save();
// Audit trail — this is a critical business operation
await SystemLog.Trace({
level : LogLevel.INFO,
message : "Order submitted to ERP",
onBehalf : await this.context.getUserId(),
tags : ["order", "submit"],
traceBack: `OrderId: ${record.Id}, Group: ${record.GroupId}, Period: ${record.Period}`
});
return {success: true};
} catch (ex) {
LogIt.Error("Order submission failed", ex);
await SystemLog.Error("Order submission failed: " + ex.message);
throw ex;
}
}
Log Rotation (Scheduled)
// Typically called from a scheduled task or admin endpoint
public async PerformMaintenance() : Promise<void> {
SystemLog.Info("Starting log rotation", "maintenance");
await SystemLog.Clean(90); // Keep 90 days
SystemLog.Info("Log rotation complete", "maintenance");
}
Summary
| Feature | LogIt | SystemLog |
|---|---|---|
| Side | Frontend + Backend | Backend only |
| Persistence | None (console only) | MongoDB collection |
| User attribution | No | Yes (onBehalf) |
| Tags/search | No | Yes |
| Rotation | N/A | Yes (Clean()) |
| Performance impact | Minimal | DB write per entry |
| Use for | Development debugging | Production audit trail |
| Package | io-oidis-commons | io-oidis-hub |
See also: ObjectValidator | StringUtils | Conventions