Skip to main content

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​

console.* is banned

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:

OptionTypeDescription
messagestringPrimary log message
traceBackstringDetailed context — IDs, parameters, state snapshot
levelLogLevel (@io-oidis-commons/Io/Oidis/Commons/Enums/LogLevel.js)e.g. INFO, WARNING, ERROR
tagsstring[]Searchable tags for filtering
onBehalfstringUser ID for attribution (who triggered this action)
autoLogItbooleanAlso 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:

FieldTypeDescription
MessagestringPrimary log message
TraceBackstringDetailed context
LevelLogLevelINFO, WARNING, ERROR
Tagsstring[]Searchable metadata
OnBehalfstringUser ID (who triggered it)
CreatedDateAuto-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​

ScenarioUseWhy
Debugging during developmentLogItTemporary, no persistence needed
Catch block error loggingLogItStandard error capture
User performed critical actionSystemLogAudit trail with user attribution
External API call resultSystemLogTrack integrations for debugging reports
Data modification (create/update/delete)SystemLogAccountability and rollback investigation
Authentication eventsSystemLogSecurity audit
Scheduled task executionSystemLogOperations monitoring
Frontend UI debuggingLogItSystemLog is backend-only
Startup/initialization messagesLogItNo audit value
Performance metricsSystemLogHistorical 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​

FeatureLogItSystemLog
SideFrontend + BackendBackend only
PersistenceNone (console only)MongoDB collection
User attributionNoYes (onBehalf)
Tags/searchNoYes
RotationN/AYes (Clean())
Performance impactMinimalDB write per entry
Use forDevelopment debuggingProduction audit trail
Packageio-oidis-commonsio-oidis-hub

See also: ObjectValidator | StringUtils | Conventions