Introduction
SysLog is a resident module that provides a common logging service for RISC OS applications. It is intended for programs which need persistent diagnostic logging without each application having to implement its own log-file management, log rotation, buffering, or session grouping.
The module writes plain text log files in a directory chosen by the SysLog$LogDir system variable. Each application normally logs to its own file, and the user controls the amount of information retained for each file through the SysLog options file.
The original SysLog distribution was supplied as the !SysLog application. Running that application sets SysLog$Dir and SysLog$LogDir, then loads the module. RISC OS Select and some later systems already include a compatible SysLog module, so a separate application is not always required.
Overview
SysLog is designed around three goals:
- give applications a simple API for recording timestamped text, formatted values, and binary data;
- let the user, rather than the application, decide how much information each log should retain; and
- make heavy logging practical by buffering output, recovering from write failures, and rotating oversized log files automatically.
Messages are filtered by priority. Lower numeric values are more important. Each log has a threshold, and only messages whose priority is strictly lower than that threshold are recorded. The default threshold is 125, so messages at priority 0 to 124 are logged unless the user overrides that default.
SysLog also supports session logging. Instead of writing directly to the main log file, an application may open a temporary session log, write a sequence of related entries to it, and then merge the whole session into the main log in a single operation. This keeps concurrent or repeated activities together in the final log.
When a write to a log fails, SysLog records the failure in the SysLog log and backs off from touching the failing log for a short period. The retry period starts at one second, increases by a factor of 1.5 after each further failure, and is capped at ten minutes. This prevents repeated filing-system errors from dominating the machine.
Technical Details
Log Names and Files
Each normal log is identified by a log name. The name is used directly as the leafname of the file stored in SysLog$LogDir, so the name must be a valid RISC OS leafname and must not exceed ten characters. The implementation rejects empty names and names containing ., :, *, #, $, &, @, ^, %, or \.
The original SysLog documentation reserves the following names for SysLog itself:
- any name beginning with -;
- any name beginning with SysLog; and
- Old.
Applications should treat those names as unavailable even though the module only enforces the general leafname rules directly.
The intended convention is that each application has its own log, usually named after the application without any leading !. Very large applications may use more than one log, but they should still choose names that are stable and recognisable to the user.
The original documentation also recommended allocating distributed log names formally so that unrelated applications did not collide on the same log file. The module itself does not enforce such a registry, but applications should still avoid reusing names already associated with other software.
Priorities and Thresholds
Message priorities are integers in the range 0 to 255, where 0 is the highest priority and 255 is the lowest. Log thresholds are compared using a strict less-than test:
- if message_priority < log_threshold, the message is recorded;
- if message_priority >= log_threshold, the message is discarded without error.
A threshold of 256 therefore disables ordinary logging for that log while still allowing callers to query or alter the threshold. The command parser for *SysLog accepts thresholds in the range 0 to 256.
In practice, priorities around 100 suit ordinary day-to-day information. Non-critical chatter should generally avoid the very high-importance range below about 50. The original guidance characterised priority 10 as comparable to Government declares war, priority 0 as Armageddon imminent, and priority 255 as Water detected in ocean. Those examples are informal, but they express the intended spread of the scale clearly.
Applications should choose priorities based on the effect of the condition on the user, not merely its technical cause. An out-of-memory condition can be a startup-fatal error in one context and an unimportant transient condition in another.
Log File Format
The basic logging primitive stores one line of text terminated by a line-feed. SWI SysLog_LogMessage prepends a textual timestamp, a three-digit priority field, and indentation spaces. SWI SysLog_LogUnstamped stores the supplied text without that prefix. The module stops copying at the first zero byte, line-feed, or carriage return in the supplied string.
Indented logging is cumulative per log. The indent level starts at zero, SWI SysLog_Indent increments it up to a maximum of 32, SWI SysLog_UnIndent decrements it down to zero, and SWI SysLog_NoIndent resets it immediately.
Options File
SysLog reads its configuration from the plain text file <SysLog$Dir>.Options when the module initialises. The standard !SysLog.!Boot file sets SysLog$Dir to the application directory, so the distributed options file lives in !SysLog.Options.
Each options line begins with a log name. The special name * supplies defaults used for logs that do not have a matching explicit line.
The recognised syntax is:
logname [Reserve kilobytes | NoReserve] [Sessions | NoSessions]
[threshold [max_size_kilobytes [timeout_centiseconds]]]
The supplied sample file documents only the first four fields, but the source also accepts an optional fifth numeric field. That final number becomes the flush timeout, measured in centiseconds, for that log. If it is omitted, the default is 50 centiseconds, or half a second.
The options have these effects:
- Reserve causes SysLog to keep extra space allocated at the end of the file. The value is stored in bytes, but the file syntax is in kilobytes.
- NoReserve disables reserved-space management for that log.
- Sessions enables temporary session logs for the log.
- NoSessions causes session logging requests to fall back to direct logging in the main log.
- threshold sets the log threshold used by priority filtering.
- max_size_kilobytes sets the maximum size of the main log before it is rotated to Old.<logname>.
- timeout_centiseconds sets the inactivity period after which buffered output is flushed automatically.
SysLog expands SysLog$Dir and SysLog$LogDir once at startup and caches the options file in memory. Editing the file does not affect the running module until it is reloaded.
Buffering and Rotation
Each log may have an in-memory buffer. SysLog chooses the buffer size from the current threshold:
- threshold 0 to 100: 1024 bytes;
- 101 to 125: 2048 bytes;
- 126 to 150: 4096 bytes;
- 151 to 175: 8192 bytes;
- 176 to 200: 16384 bytes; and
- 201 to 256: 32768 bytes.
That choice makes the lowest-priority, most verbose logs the cheapest to use. Buffers are allocated lazily, flushed after the per-log timeout expires, and released after ten minutes of inactivity or when SWI SysLog_LogComplete is called.
When a main log exceeds its configured maximum size, SysLog truncates it to the last real byte of data, deletes any existing Old.<logname>, renames the current log to that old-file name, and starts a fresh current log. Temporary session logs are handled differently: if a session log grows too large and SysLog knows its parent log, the temporary file is deleted and recreated instead of being rotated.
Session Logs
Session logs are created by SWI SysLog_OpenSessionLog. Despite the historic documentation calling the return value a session log handle, the returned handle is actually a pointer to a temporary log name string. That temporary log name begins with - and names the temporary file used under SysLog$LogDir.
When a session log is opened, SysLog writes the parent log name and a blank line at the start of the temporary file, then writes a session marker line. When the session is closed by SWI SysLog_CloseSessionLog, SysLog appends the whole temporary file to the main log and deletes the temporary file.
Applications can usually treat the returned value opaquely:
- if the requested priority is not being logged, SWI SysLog_OpenSessionLog returns 0 and later SWIs ignore that null handle;
- if sessions are disabled for the parent log, SWI SysLog_OpenSessionLog returns the original log name instead of creating a temporary file; and
- SWI SysLog_CloseSessionLog safely does nothing when given an ordinary log name instead of a temporary session name.
At module initialisation and finalisation SysLog scans the log directory for stray temporary files whose names begin with -. Any it finds are merged back into their parent logs automatically.
IRQ Mode
Normally SysLog may flush buffers and touch the filing system while handling a log request, so the logging SWIs are not safe from interrupt handlers. Calling SWI SysLog_IRQMode with a non-zero value places the module in IRQ mode. In that mode SWI SysLog_LogMessage, SWI SysLog_LogUnstamped, SWI SysLog_LogData, and SWI SysLog_LogFormatted only append to in-memory buffers.
If a buffer fills while IRQ mode is active, the new message is dropped and a missed-message count is recorded. Once IRQ mode is no longer active, SysLog inserts a line reporting how many log lines were missed.
IRQ mode is reference-counted. Each non-zero call increments the counter and each zero call decrements it down to zero. Applications which enable IRQ mode must disable it again when the critical section ends.
The following SWIs are always safe from interrupt code because they do not need to flush buffered data:
Formatted Logging and the C Veneer
SWI SysLog_LogFormatted provides a low-level formatter primarily for assembler code. It builds a line in an internal scratch buffer and then writes it as a normal stamped log message. The format language is deliberately small and is described in the SWI definition.
The original source distribution also supplies a C veneer in !SysLog.C-veneer. The header h.syslog declares one non-X and one X form for each exported SWI, plus the convenience functions syslogf and xsyslogf. Those two helper functions format a string with vsprintf and then call SWI SysLog_LogMessage.
The C veneer documentation warns that its temporary formatting buffer is only 1024 bytes long. The assembled veneer source actually reserves 1536 bytes, but portable code should continue to treat 1024 bytes as the safe upper limit because the SysLog text interfaces themselves truncate logged strings to 1024 bytes.
The veneer exports the following entry points:
- non-X calls: syslog_logmessage, syslog_getloglevel, syslog_flushlog, syslog_setloglevel, syslog_logunstamped, syslog_indent, syslog_unindent, syslog_noindent, syslog_opensessionlog, syslog_closesessionlog, syslog_logdata, syslog_readerrormessage, syslog_logcomplete, syslog_irqmode, and syslogf;
- X calls: xsyslog_logmessage, xsyslog_getloglevel, xsyslog_flushlog, xsyslog_setloglevel, xsyslog_logunstamped, xsyslog_indent, xsyslog_unindent, xsyslog_noindent, xsyslog_opensessionlog, xsyslog_closesessionlog, xsyslog_logdata, xsyslog_readerrormessage, xsyslog_logcomplete, xsyslog_irqmode, and xsyslogf.
The non-X forms are usually more convenient for application code because they return ordinary values directly. The X forms are intended for code that must handle errors explicitly, such as modules and other low-level components.
System Variables
SysLog expands SysLog$Dir during module initialisation to locate the options file as <SysLog$Dir>.Options. The distributed !SysLog.!Boot file sets this variable to the application directory before loading the module.
SysLog writes one plain file per main log in this directory, together with any temporary session files whose names begin with - and the rotated files named Old.<logname>. The distributed !SysLog.!Boot file sets this variable to <Obey$Dir>.Logs.
Service Calls
| R1 | = | &27 |
| R0 | preserved | |
| R1 | preserved | |
SysLog handles Service_Reset by clearing the stored Wimp task handle for its desktop helper task. This ensures that the module can participate in later desktop startup after a soft reset.
| R1 | = | &49 |
| R0 | = | Pointer to the command string Desktop_SysLog if the service is claimed. |
| R1 | = | 0 if the service is claimed; otherwise preserved. |
If the SysLog desktop helper is not already running, SysLog claims Service_StartWimp and returns the command Desktop_SysLog. The Wimp can then start that helper task with Wimp_StartTask.
| R1 | = | &4A |
| R0 | preserved | |
| R1 | preserved | |
SysLog uses Service_StartedWimp to recover from a desktop startup attempt which claimed Service_StartWimp but did not finish initialising. If the saved task handle still contains the temporary -1 marker, SysLog resets it to zero so the desktop task can be started again later.
| R0 | = | Standard Service_WimpCloseDown argument. |
| R1 | = | &53 |
| R2 | = | Standard Service_WimpCloseDown task handle argument. |
| R0 | preserved | |
| R1 | preserved | |
| R2 | preserved | |
When the Wimp is closing down a task, SysLog responds to Service_WimpCloseDown by flushing all known logs. This reduces the chance of losing buffered diagnostic output during desktop shutdown or task termination.
SWI Calls
| R0 | = | Pointer to a log name, or a session handle returned by SWI SysLog_OpenSessionLog. |
| R1 | = | Pointer to the text to log. Copying stops at the first zero byte, line-feed, or carriage return. |
| R2 | = | Message priority from 0 to 255. |
If the target log's threshold is higher than the supplied priority, SysLog records a line containing a timestamp, the three-digit priority, indentation spaces, and the supplied text. Text longer than 1024 bytes is truncated.
If logging has been disabled with *SysLog, if the threshold excludes the message, or if R0 is zero, the SWI returns without error and writes nothing.
SYS "SysLog_LogMessage", "MyApp", "Connected", 100
| R0 | = | Pointer to a log name, or a session handle. |
| R0 | = | Current threshold for the log. |
This SWI returns the threshold against which message priorities are compared. Messages are logged only when their priority is strictly less than the value returned here.
Applications can use this SWI to avoid constructing expensive diagnostic text that would be discarded immediately.
| R0 | = | Pointer to a log name, a session handle, or 0 to flush every known log. |
SysLog writes any buffered output for the selected log to disc and closes the underlying file. Passing zero flushes all logs and finally flushes the SysLog log itself.
| R0 | = | Pointer to a log name, or a session handle. |
| R1 | = | New threshold, normally 0 to 256. |
This SWI changes the threshold stored for the selected log. If the log has open session logs, SysLog applies the new threshold to those session logs as well.
The original SysLog documentation treats this SWI as a debugging aid and recommends that distributed applications do not use it to override the user's logging choices permanently.
| R0 | = | Pointer to a log name, or a session handle. |
| R1 | = | Pointer to the text to log. |
| R2 | = | Message priority from 0 to 255. |
This SWI behaves like SWI SysLog_LogMessage except that it writes the supplied text directly, without adding a timestamp, priority, or indentation prefix.
| R0 | = | Pointer to a log name, or a session handle. |
The indentation level affects later calls to SWI SysLog_LogMessage and SWI SysLog_LogFormatted for the same log. The implementation caps the level at 32.
| R0 | = | Pointer to a log name, or a session handle. |
If the indentation level is non-zero, SysLog subtracts one from it. Otherwise this SWI does nothing.
| R0 | = | Pointer to a log name, or a session handle. |
This SWI clears any indentation previously established by SWI SysLog_Indent. Programs which use indentation typically call it during startup to ensure a known state.
| R0 | = | Pointer to the parent log name. |
| R1 | = | Priority for the session. |
| R0 | = | 0 if the session priority would be filtered out, a pointer to the parent log name if sessions are disabled for that log, or a pointer to a temporary session log name otherwise. |
This SWI begins a new session log. The returned value is intended to be passed unchanged to later SysLog SWIs which accept a log name or session handle.
When a temporary session is created, SysLog inherits the parent log's current threshold, timeout, and maximum size for the temporary log.
| R0 | = | Session handle returned by SWI SysLog_OpenSessionLog, or 0. |
If R0 names a temporary session log, SysLog writes an end marker, appends the temporary file to the parent log, deletes the temporary file, and discards the session log state. If R0 is zero or names an ordinary main log, this SWI does nothing.
| R0 | = | Pointer to a log name, or a session handle. |
| R1 | = | Message priority. |
| R2 | = | Pointer to the first byte of data. |
| R3 | = | Number of bytes to log. |
| R4 | = | Offset value shown at the start of each line, or -1 to omit the offset field. |
SysLog writes the block as a sequence of stamped lines, eight bytes per line, in a format similar to *Dump. Non-printable bytes appear as full stops in the ASCII area.
Blocks larger than 10 KB are rejected with Error_SysLog_DataBlockTooBig. This protects the machine from spending excessive time writing what is likely to be an accidental dump request.
| R0 | = | Pointer to a log name, or a session handle. |
| R1 | = | Pointer to the format string. |
| R2 | = | Message priority. |
| R3 | = | Pointer to a block containing saved values for R0 to R3, R8 to R12, and R14. |
| R4 - R7 | = | Immediate values for registers R4 to R7. |
This SWI expands a restricted format language:
- %% inserts a literal percent sign;
- %<reg>a inserts an IPv4 address in dotted decimal, taking the register value as a network-byte-order address;
- %<reg>c inserts a character;
- %<reg>d inserts a signed decimal integer;
- %<reg>e inserts the text from SWI SysLog_ReadErrorMessage for that register value;
- %<reg>f inserts a file handle in hexadecimal and, where available, the corresponding file name;
- %<reg>p dereferences a pointer and must be followed by another type character; multiple p characters add further levels of indirection;
- %<reg>s inserts a control-terminated string;
- %<reg>x inserts an unsigned hexadecimal integer; and
- %<reg>z inserts a zero-terminated string.
Valid register numbers are 0 to 12 and 14. Register 13 is not available. Null pointers are explicitly accepted and are logged as (null).
If the format string is malformed, SysLog does not return an error. Instead it logs a diagnostic line beginning SysLog: Error in format string: at priority 50.
Assembler code normally wraps this SWI in a macro or helper routine which saves the relevant registers, points R1 at an in-line format string, and passes the saved-register block in R3. The original SysLog documentation supplied wrappers for both BBC BASIC assembler and ObjAsm sources, and that style remains the normal way to use this SWI from assembler.
| R0 | = | Error number. |
| R0 | = | Pointer to a static message string. |
This SWI maps the supplied error number to one of SysLog's built-in messages. The table covers error 0 and a set of socket-related errors including Connection timed out, Connection refused, and No route to host. Any number beyond the end of the table returns Unknown Error.
| R0 | = | Pointer to a log name. |
This SWI flushes any buffered data for the selected log and then frees the buffer associated with it. It is intended for the case where the caller knows the log will not be used again for some time, such as application shutdown.
If this SWI is not used, SysLog releases idle buffers automatically after ten minutes.
| R0 | = | 0 to disable IRQ mode, non-zero to enable it. |
This SWI increments or decrements SysLog's IRQ-mode nesting count. While the count is non-zero, the logging SWIs that normally need filing-system access write only to in-memory buffers.
Error Messages
This error is returned when a caller supplies an empty log name, a name longer than ten characters, or a name containing a disallowed leafname character.
This error is reported when SWI SysLog_CloseSessionLog tries to merge a temporary session file whose leading parent-log record cannot be read back correctly.
This error is returned by SWI SysLog_LogData when R3 is larger than 10240 bytes.
This error is returned when the SWI decoder is given an offset beyond the set of operations implemented by the module.
Commands
<log_name> | - | The log to write to or reconfigure. |
<priority> | - | Message priority in the range 0 to 255. |
<message> | - | Text to log. |
<new_threshold> | - | Temporary threshold to apply immediately, normally in the range 0 to 256. |
*SysLog has three forms. The message form writes a single timestamped line to the named log. The threshold form changes the in-memory threshold for that log immediately. The ON and OFF forms enable or disable SysLog globally.
Global disable is not persisted. While disabled, the logging SWIs and command forms silently ignore new log messages.
With no parameter, *SysLog_Flush flushes all currently known logs to disc. With ON, SysLog enters forced-flush mode and writes each log line to disc immediately after buffering it. With OFF, normal buffered operation resumes.
Forced-flush mode is useful while debugging crashes, but it makes logging much slower.
This command exists so that SysLog can claim Service_StartWimp and ask the desktop to start its helper task. It is not intended to be run directly by users; the distributed help text explicitly recommends using *Desktop instead.
Security Issues
SysLog writes plain text files containing application-controlled data. Programs should therefore avoid logging passwords, long-term secrets, or private user content unless the user has explicitly asked for that level of diagnostics.
The binary dump and formatted logging SWIs can expose memory contents, pointer targets, socket error information, and file names. They are valuable for debugging, but they should be treated as diagnostic interfaces rather than as a safe audit trail format.
Examples
Application Startup
The simplest usage pattern is to choose a stable log name, query the threshold if constructing the message would be expensive, and then log the important events:
IF SYS("SysLog_GetLogLevel", "MyApp") > 100 THEN
SYS "SysLog_LogMessage", "MyApp", "Initialising network stack", 100
ENDIF
Scoped Session Logging
Session logging lets an application group all the activity for one connection, job, or transaction and then merge it back into the main log as a block:
session = SysLog_OpenSessionLog("NewsBite", 110)
SysLog_LogMessage(session, "Connection accepted", 110)
SysLog_LogData(session, 120, packet, packet_length, 0)
SysLog_CloseSessionLog(session)
Assembler LogFormatted Wrapper
The usual assembler style is to package SWI SysLog_LogFormatted behind a small macro or helper routine:
STMFD R13!,{R0-R3,R8-R12,R14}
ADR R0,txt_logname
ADR R1,%ft90
MOV R2,#200
MOV R3,R13
SWI SysLog_LogFormatted
B %ft91
90
= "stricmp: comparing %0z and %1z",0
ALIGN
91
LDMFD R13!,{R0-R3,R8-R12,R14}
This keeps the call site short and ensures that the register block passed in R3 matches the format-string references.
Using the C Veneer
The supplied header and veneer object let C code call SysLog without writing manual SWI veneers:
#include "kernel.h"
#include "syslog"
if (syslog_getloglevel("MyApp") > 100)
syslog_logmessage("MyApp", "Starting up", 100);
syslogf("MyApp", 90, "Connected peer %s on handle %d", host, handle);
Programs should link against the supplied veneer object that matches their APCS requirements. The original distribution provided both a 26-bit/APCS-3 veneer and an APCS-32 veneer object.
Representative Options File
The distributed options file uses one default line and then selective overrides:
* NoReserve Sessions 125 256 Termite NoSessions NewsBite 125 NewsBite-d 256 1024
That example means that most logs inherit a threshold of 125, no reserved space, sessions enabled, and a 256 KB maximum size; the listed logs then override individual parts of that policy.