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.
| Constant | Default | Meaning |
|---|---|---|
| Fnd_Log_UseBatch | True | Buffer entries before writing |
| Fnd_Log_FlushInterval | 9 | Seconds before a buffer becomes due; 0 writes immediately |
| Fnd_Log_FlushEntryLimit | 1000 | Maximum entries before flushing |
| Fnd_Log_FlushCharLimit | 64000 | Maximum rendered characters before flushing |
| Fnd_Log_OptionDestinations | Fnd_Log_ToLocalFile | Destination mask |
| Fnd_Log_IncludeSeverity | False | Include severity column |
| Fnd_Log_IncludeMachineName | False | Include 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.