Foundation 6

10. Logging

Fnd_Log writes tab-separated entries to a file, to a file on the server, or to the [Fnd_Log] table in the host, from any process, batched through workers. This chapter is the host-facing contract of the module as of Foundation 6.

The existing Fnd_Log_* methods remain available. Hosts can also instantiate cs.fnd.log.new(); inside Foundation use cs.log.new(). Class instances delegate to the same methods and process state. This API supports 4D 20; no shared singleton is required. A later 21 branch can add singleton access without changing .new().

Changes in behaviour

Batching is now enabled by default. Entries flush after approximately 9 seconds, 1,000 entries, or 64,000 characters. This reduces file writes but delays visibility in the file; an unexpected exit can lose pending memory buffers. The timer is checked twice a second, and a busy worker processes its flush after queued work.

Timestamps now use local time with milliseconds (YYYY-MM-DDThh:mm:ss.mmm), using the existing Arnaud de Montard conversion. They have no timezone offset suffix. Severity and machine-name columns are independently optional and off by default. Text columns remain tab-separated with CRLF line endings. Existing extension and rollover conventions remain in force.

Existing calls

Fnd_Log_Enable(True)
Fnd_Log_AddEntry("Import started"; "customers.csv")
Fnd_Log_Enable(False)

Enable remains Boolean-only, scoped to the current process, and returns its Boolean state. Enabled writes every severity. Disabled suppresses all entries unless an explicit severity override permits an entry.

Severity overrides

// Application default, normally set at startup.
Fnd_Log_OverrideLevel(Fnd_Log_LevelWarning; True)

// This process inherits it until explicitly overridden.
Fnd_Log_Enable(False)
Fnd_Log_AddEntry("Import failed"; \
    New object(Fnd_Log_OptionLevel; Fnd_Log_LevelError))

// Suppress even errors in this process, or restore inheritance.
Fnd_Log_OverrideLevel(Fnd_Log_LevelNone)
Fnd_Log_OverrideLevel(Fnd_Log_LevelInherit)

The initial application override is Fnd_Log_LevelNone. Default Level (0) never bypasses disabled logging. Explicit levels are Info (1), Warning (2), Error (4). The getter returns the effective threshold. A process setting takes precedence over the application default. True as the second argument selects application scope; False selects process scope. Enabled logging bypasses thresholds.

Named logs and destinations

$options_o:=New object(Fnd_Log_OptionDestinations; Fnd_Log_ToRecord)
Fnd_Log_DeclareLog("Audit"; ""; ""; $options_o; True)
Fnd_Log_UseLog("Audit")
Fnd_Log_AddEntry("Import completed")
Fnd_Log_UseLog  // Return to the default file log

Declaration arguments are label, optional filename, optional folder, optional settings object, optional dedicated-worker Boolean (default False). Empty filename or folder uses defaults. Redeclaring an existing label does not change its settings. Selecting an unknown label declares it with defaults. Fnd_Log_UseLog is shared. Direct filename/path setters clear the named selection and use application settings.

Destinations are a mask: Fnd_Log_ToLocalFile (1), Fnd_Log_ToServerFile (2), Fnd_Log_ToRecord (4). Add distinct flags to combine them. The default is local file. Standalone local+server file selections are written once.

An entry can override label and/or destinations without altering process selection:

Fnd_Log_AddEntry("One audit entry"; \
    New object(Fnd_Log_OptionLabel; "Audit"))
Fnd_Log_AddEntry("One record"; \
    New object(Fnd_Log_OptionDestinations; Fnd_Log_ToRecord))

Entry destinations replace the selected log's mask. Zero (Fnd_Log_ToDefault) leaves the selected log's destinations in effect. Text arguments remain columns; objects supply metadata, numeric arguments supply severity, and Boolean arguments supply flushNow. Last metadata value wins.

Configuration and output

Fnd_Log_Config reads a copy of application settings. Pass an object to update defaults for unnamed logs and future declarations; existing declarations retain their settings. The same keys are accepted in declaration options.

ConstantDefaultMeaning
Fnd_Log_UseBatchTrueBuffer entries before writing
Fnd_Log_FlushInterval9Seconds before a buffer becomes due; 0 writes immediately
Fnd_Log_FlushEntryLimit1000Maximum entries before flushing
Fnd_Log_FlushCharLimit64000Maximum rendered characters before flushing
Fnd_Log_OptionDestinationsFnd_Log_ToLocalFileDestination mask
Fnd_Log_IncludeSeverityFalseInclude severity column
Fnd_Log_IncludeMachineNameFalseInclude source machine column
Fnd_Log_Config(New object(Fnd_Log_IncludeSeverity; True))
Fnd_Log_Config(New object(Fnd_Log_UseBatch; False))  // Immediate writing

When enabled, machine name follows the timestamp; severity follows machine name (or timestamp if machine name is disabled). Severity values render as -, info, warn, error. Fnd_Log_Extension remains the run-wide extension preference.

Flush, close and workers

Logging is best effort under 4D's worker execution model. Queueing an entry or flush does not guarantee completion before termination. Normal-operation tests verify routing and observed writes; they do not establish guaranteed delivery.

Fnd_Log_Flush(label) queues a flush for one log. With no argument it queues flushes for all registered logs. It returns before writing completes and is not a disk durability guarantee. Fnd_Log_CloseLog flushes and closes through the same owning workers. Fnd_Log_OptionFlushNow requests a flush after that entry.

A dedicated worker isolates a busy log's queue. The logger names and registers it with Fnd_Shell_ExcludeFromQuit. Aliases of the same physical path share the first assigned writer, so a later declaration cannot move a file with queued writes. Server-file writers are allocated on the server; client paths are not sent there as filesystem targets. Dedicated workers do not imply CPU scheduling priority.

Call Fnd_Log_Shutdown after producers have stopped, from outside any logging worker, before exiting a host that does not use Foundation's shell quit sequence. It stops the timer and queues a flush, close and stop on each local writer, then returns without waiting. The shell quit sequence does this automatically. A client queues a server flush but does not stop server workers used by other clients. Shutdown is best effort: a stalled worker cannot hold up the caller. Application exit may interrupt unfinished writes, and late record-failure fallbacks to another worker may be lost. Stop producers first and do not resume logging during exit. Completion markers are used only by tests, with bounded waits.

Fnd_Log table

Requesting Fnd_Log_ToRecord requires the host Fnd_Log table. There is no separate record-enable switch. Fnd_Log_CreateTable is an explicit developer operation; the logger never calls it. Run it on an interpreted local host or interpreted server, not from a remote client. Its result reports success, created, restartRequired and message. Restart/reopen after creating the schema.

The schema matches the separate component: UUID primary key ID, logTimestamp, logLabel, logLevel, destinationMask, processName, message, renderedLine. message holds the first column (up to 255 characters); renderedLine retains the complete formatted entry. Record data access uses ORDA.

Missing/incompatible schema or failed record saves preserve the original entry and failure explanation in the server's default file, or the standalone default file. An interpreted host also receives Fnd_Gen_BugAlert with corrective guidance. Compiled hosts receive no dialog. Fallback handling bypasses record dispatch.

Class examples

$logger_o:=cs.fnd.log.new()
$logger_o.enable(True)
$logger_o.useLog("Audit")
$logger_o.addEntry("Import completed")
$logger_o.addEntry(New collection("Import"; "customers.csv"); \
    New object(Fnd_Log_OptionLevel; Fnd_Log_LevelInfo))
$logger_o.flush()

The class accepts text or a collection of text columns, plus an options object. Its other functions are config, declareLog, label, overrideLevel, closeLog, createTable, shutdown, extension, fileName, path, maximumSize and info.