Overview
The Sedona Framework includes a built-in logging facility with the following features:
- Simple syntax to embed logging in your source code
- Ability to selectively enable/disable logging at runtime
- Ability to selectively compile logging in or out of an scode image
Log Levels
There are five logging severity levels:
Log.NONE
: all logging is disabledLog.ERROR
: indicates a failure conditionLog.WARNING
: indicates an unexpected conditionLog.MESSAGE
: indicates something of interestLog.TRACE
: used to embed debug tracing
Log Definition
The sys::Log
class is the
primary API used for logging. The Log
class is a const
class like Type
or Slot
. This means you can't
directly allocate instances yourself. Instead we use the
define
keyword to define a Log instance:
class MyService { define Log log }
Like other defines, the log is treated much like a static field.
During compilation all the log instances are compiled into the scode
as const data much like Kit
instances. The APIs for
log reflection follow a similar pattern to kit reflection:
class Sys { define int logsLen const static Log[logsLen] logs static inline byte[logsLen] logLevels static Log log(int id) static Log findLog(Str qname) } const class Log { define int NONE = 0 define int ERROR = 1 define int WARNING = 2 define int MESSAGE = 3 define int TRACE = 4 int level() bool isError() bool isWarning() bool isMessage() bool isTrace() OutStream error(Str msg) OutStream warning(Str msg) OutStream message(Str msg) OutStream trace(Str msg) const short id const Str qname }
Note that the logging levels are stored separately from the
Log
instances themselves. This is because the log objects
are readonly and stored in the code section. The log levels must
be in dynamic memory to allow runtime modification.
Log Naming
All logs are identified by a qname (qualified name), which is based on the define field's qname:
- If the define field is named "log", then the log qname is the qname of the declaring type
- If the define field ends in "Log", then the log qname is the field's qname minus the "Log" suffix
- If none of the above applies, then log's qname is the field's qname
For example this class in a kit named "acme":
class MyService { define Log log // log qname is "acme::MyService" define Log reqLog // log qname is "acme::MyService.req" define Log stuff // log qname is "acme::MyService.stuff" }
By convention, your primary Log definition should be named "log" to match rule 1. Sub-logging for extra tracing should use names with a "Log" suffix.
Logging
To embed logging into your code, call one of following the logging methods:
sys::Log.error
sys::Log.warning
sys::Log.message
sys::Log.trace
All of the logging methods take a Str
and return an
OutStream
, which permits string
interpolation:
// these lines of code log.message("Started!") log.error("Cannot open port=$port") log.trace("Received $numBytes bytes from $addr") // would print something like -- MESSAGE [acme::MyServer] Started! -- ERROR [acme::MyServer] Cannot open port 8080 -- TRACE [acme::MyServer] Received 5 bytes from 32
A few points to note:
- Don't add a trailing newline to your message, one will be added automatically
- Don't include any severity or log identity in your message string, this information is automatically included (the actual format is implementation specific)
- You are required to call the logging methods on the field define itself. For example you can't assign the log reference to a local variable, then use the local variable to call one of the logging methods - this will result in a compile time error. The reason for this restriction is that it enables the compiler to generate level checking and conditional compilation checking.
Runtime Configuration
Log levels are stored in the Sys.logLevels
field.
This array is indexed by Log.id
and stores the
current level as a number between 0 and 4 (Log.NONE to Log.TRACE):
// change my logging level to trace Sys.logLevels[log.id] = Log.TRACE
The current level defines the maximum severity that is
logged. For example a level or WARNING will log calls
to error
and warning
, but not
calls to message
and trace
. All
log levels default to MESSAGE on startup.
The compiler automatically inserts code that jumps over a logging statement if the current log level is set lower. This means the method call and any embedded expressions are efficiently skipped:
// this code log.trace("This is the ${count++} time") // is semantically equivalent to if (log.isTrace()) log.trace("This is the ${count++} time")
WebService Spy Page
If a Sedona device is running the web::WebService
, then you
can use the spy page at "<device IP>/spy/logs"
to change the
log levels for a SVM at runtime.
Compile-time Configuration
TODO: this feature is not implemented yet