4.4. Logging

The ns-3 logging facility can be used to monitor or debug the progress of simulation programs. Logging output can be enabled by program statements in your main() program or by the use of the NS_LOG environment variable.

Logging statements are not compiled into optimized builds of ns-3. To use logging, one must use the default or debug build profiles of ns-3.

The project makes no guarantee about whether logging output will remain the same over time. Users are cautioned against building simulation output frameworks on top of logging code, as the output and the way the output is enabled may change over time.

4.4.1. Overview

ns-3 logging statements are typically used to log various program execution events, such as the occurrence of simulation events or the use of a particular function.

For example, this code snippet is from TcpSocketBase::EnterCwr() and informs the user that the model is reducing the congestion window and changing state:

NS_LOG_INFO("Enter CWR recovery mode; set cwnd to " << m_tcb->m_cWnd << ", ssthresh to "
                                                    << m_tcb->m_ssThresh << ", recover to "
                                                    << m_recover);

If logging has been enabled for the Ipv4L3Protocol component at a severity of INFO or above (see below about log severity), the statement will be printed out; otherwise, it will be suppressed.

The logging implementation is enabled in debug and default builds, but disabled in all other build profiles, so that it does not impact the execution speed of more optimized profiles.

You can try the example program log-example.cc in src/core/example with various values for the NS_LOG environment variable to see the effect of the options discussed below.

4.4.2. Enabling Output

There are two ways that users typically control log output. The first is by setting the NS_LOG environment variable; e.g.:

$ NS_LOG="*" ./ns3 run first

will run the first tutorial program with all logging output. (The specifics of the NS_LOG format will be discussed below.)

This can be made more granular by selecting individual components:

$ NS_LOG="Ipv4L3Protocol" ./ns3 run first

The output can be further tailored with prefix options.

The second way to enable logging is to use explicit statements in your program, such as in the first tutorial program:

int
main(int argc, char *argv[])
{
  LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
  LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
  ...

(The meaning of LOG_LEVEL_INFO, and other possible values, will be discussed below.)

4.4.3. NS_LOG Syntax

The NS_LOG environment variable contains a list of log components and options. Log components are separated by `:’ characters:

$ NS_LOG="<log-component>:<log-component>..."

Options for each log component are given as flags after each log component:

$ NS_LOG="<log-component>=<option>|<option>...:<log-component>..."

Options control the severity and level for that component, and whether optional information should be included, such as the simulation time, simulation node, function name, and the symbolic severity.

4.4.3.1. Log Components

Generally a log component refers to a single source code .cc file, and encompasses the entire file.

Some helpers have special methods to enable the logging of all components in a module, spanning different compilation units, but logically grouped together, such as the ns-3 wifi code:

WifiHelper wifiHelper;
wifiHelper.EnableLogComponents();

The NS_LOG log component wildcard `*’ will enable all components.

To see what log components are defined, any of these will work:

$ NS_LOG="print-list" ./ns3 run ...

$ NS_LOG="foo"  # a token not matching any log-component

The first form will print the name and enabled flags for all log components which are linked in; try it with scratch-simulator. The second form prints all registered log components, then exit with an error.

4.4.3.2. Severity and Level Options

Individual messages belong to a single “severity class,” set by the macro creating the message. In the example above, NS_LOG_INFO(..) creates the message in the LOG_INFO severity class.

The following severity classes are defined as enum constants:

Severity Class Meaning
LOG_NONE The default, no logging
LOG_ERROR Serious error messages only
LOG_WARN Warning messages
LOG_INFO Info about the model changing state
LOG_FUNCTION Function tracing
LOG_LOGIC For tracing key decision points or branches in a function
LOG_DEBUG For use in debugging

Typically one wants to see messages at a given severity class and higher. This is done by defining inclusive logging “levels”:

Level Meaning
LOG_LEVEL_ERROR Only LOG_ERROR severity class messages.
LOG_LEVEL_WARN LOG_WARN and above.
LOG_LEVEL_INFO LOG_INFO and above.
LOG_LEVEL_FUNCTION LOG_FUNCTION and above.
LOG_LEVEL_LOGIC LOG_LOGIC and above.
LOG_LEVEL_DEBUG LOG_DEBUG and above.
LOG_LEVEL_ALL All severity classes.
LOG_ALL Synonym for LOG_LEVEL_ALL

The severity class and level options can be given in the NS_LOG environment variable by these tokens:

Class Level
error level_error
warn level_warn
info level_info
function level_function
logic level_logic
debug level_debug
level_all
all
*

Using a severity class token enables log messages at that severity only. For example, NS_LOG="*=warn" won’t output messages with severity error. NS_LOG="*=level_debug" will output messages at severity levels debug and above.

Severity classes and levels can be combined with the `|’ operator: NS_LOG="*=level_warn|debug" will output messages at severity levels error, warn and debug, but not info, function, or logic.

The NS_LOG severity level wildcard `*’ and all are synonyms for level_all.

For log components merely mentioned in NS_LOG

$ NS_LOG="<log-component>:..."

the default severity is LOG_LEVEL_ALL.

4.4.3.3. Prefix Options

A number of prefixes can help identify where and when a message originated, and at what severity.

The available prefix options (as enum constants) are

Prefix Symbol Meaning
LOG_PREFIX_FUNC Prefix the name of the calling function.
LOG_PREFIX_TIME Prefix the simulation time.
LOG_PREFIX_NODE Prefix the node id.
LOG_PREFIX_LEVEL Prefix the severity level.
LOG_PREFIX_ALL Enable all prefixes.

The prefix options are described briefly below.

The options can be given in the NS_LOG environment variable by these tokens:

Token Alternate
prefix_func func
prefix_time time
prefix_node node
prefix_level level
prefix_all
all
*

For log components merely mentioned in NS_LOG

$ NS_LOG="<log-component>:..."

the default prefix options are LOG_PREFIX_ALL.

4.4.3.3.1. Severity Prefix

The severity class of a message can be included with the options prefix_level or level. For example, this value of NS_LOG enables logging for all log components (`*’) and all severity classes (=all), and prefixes the message with the severity class (|prefix_level).

$ NS_LOG="*=all|prefix_level" ./ns3 run scratch-simulator
Scratch Simulator
[ERROR] error message
[WARN] warn message
[INFO] info message
[FUNCT] function message
[LOGIC] logic message
[DEBUG] debug message

4.4.3.3.2. Time Prefix

The simulation time can be included with the options prefix_time or time. This prints the simulation time in seconds.

4.4.3.3.3. Node Prefix

The simulation node id can be included with the options prefix_node or node.

4.4.3.3.4. Function Prefix

The name of the calling function can be included with the options prefix_func or func.

4.4.3.4. NS_LOG Wildcards

The log component wildcard `*’ will enable all components. To enable all components at a specific severity level use *=<severity>.

The severity level option wildcard `*’ is a synonym for all. This must occur before any `|’ characters separating options. To enable all severity classes, use <log-component>=*, or <log-component>=*|<options>.

The option wildcard `*’ or token all enables all prefix options, but must occur after a `|’ character. To enable a specific severity class or level, and all prefixes, use <log-component>=<severity>|*.

The combined option wildcard ** enables all severities and all prefixes; for example, <log-component>=**.

The uber-wildcard *** enables all severities and all prefixes for all log components. These are all equivalent:

$ NS_LOG="***" ...      $ NS_LOG="*=all|*" ...        $ NS_LOG="*=*|all" ...
$ NS_LOG="*=**" ...     $ NS_LOG="*=level_all|*" ...  $ NS_LOG="*=*|prefix_all" ...
$ NS_LOG="*=*|*" ...

Be advised: even the trivial scratch-simulator produces over 46K lines of output with NS_LOG="***"!

4.4.4. How to add logging to your code

Adding logging to your code is very simple:

  1. Invoke the NS_LOG_COMPONENT_DEFINE(...); macro inside of namespace ns3.

Create a unique string identifier (usually based on the name of the file and/or class defined within the file) and register it with a macro call such as follows:

namespace ns3 {

NS_LOG_COMPONENT_DEFINE("Ipv4L3Protocol");
...

This registers Ipv4L3Protocol as a log component.

(The macro was carefully written to permit inclusion either within or outside of namespace ns3, and usage will vary across the codebase, but the original intent was to register this outside of namespace ns3 at file global scope.)

  1. Add logging statements (macro calls) to your functions and function bodies.

In case you want to add logging statements to the methods of your template class (which are defined in an header file):

  1. Invoke the NS_LOG_TEMPLATE_DECLARE; macro in the private section of your class declaration. For instance:
template <typename Item>
class Queue : public QueueBase
{
...
private:
  std::list<Ptr<Item>> m_packets;           //!< the items in the queue
  NS_LOG_TEMPLATE_DECLARE;                  //!< the log component
};

This requires you to perform these steps for all the subclasses of your class.

  1. Invoke the NS_LOG_TEMPLATE_DEFINE(...); macro in the constructor of your class by providing the name of a log component registered by calling the NS_LOG_COMPONENT_DEFINE(...); macro in some module. For instance:
template <typename Item>
Queue<Item>::Queue()
  : NS_LOG_TEMPLATE_DEFINE("Queue")
{
}
  1. Add logging statements (macro calls) to the methods of your class.

In case you want to add logging statements to a static member template (which is defined in an header file):

  1. Invoke the NS_LOG_STATIC_TEMPLATE_DEFINE (...); macro in your static method by providing the name of a log component registered by calling the NS_LOG_COMPONENT_DEFINE(...); macro in some module. For instance:
template <typename Item>
void
NetDeviceQueue::PacketEnqueued(Ptr<Queue<Item>> queue,
                               Ptr<NetDeviceQueueInterface> ndqi,
                               uint8_t txq, Ptr<const Item> item)
{

  NS_LOG_STATIC_TEMPLATE_DEFINE("NetDeviceQueueInterface");
...
  1. Add logging statements (macro calls) to your static method.

4.4.4.1. Logging Macros

The logging macros and associated severity levels are

Severity Class Macro
LOG_NONE (none needed)
LOG_ERROR NS_LOG_ERROR(...);
LOG_WARN NS_LOG_WARN(...);
LOG_INFO NS_LOG_INFO(...);
LOG_FUNCTION NS_LOG_FUNCTION(...);
LOG_LOGIC NS_LOG_LOGIC(...);
LOG_DEBUG NS_LOG_DEBUG(...);

The macros function as output streamers, so anything you can send to std::cout, joined by << operators, is allowed:

void MyClass::Check(int value, char * item)
{
  NS_LOG_FUNCTION(this << arg << item);
  if (arg > 10)
    {
      NS_LOG_ERROR("encountered bad value " << value <<
                   " while checking " << name << "!");
    }
  ...
}

Note that NS_LOG_FUNCTION automatically inserts a `,’ (comma-space) separator between each of its arguments. This simplifies logging of function arguments; just concatenate them with << as in the example above.

4.4.4.2. Unconditional Logging

As a convenience, the NS_LOG_UNCOND(...); macro will always log its arguments, even if the associated log-component is not enabled at any severity. This macro does not use any of the prefix options. Recall that logging is only enabled in debug, default and relwithdebinfo builds, so this macro will only produce output in the same builds.

The ns-3 model libraries do not typically use the NS_LOG_UNCOND(...) macro; it is provided for users for assistance with debugging.

4.4.4.3. Guidelines

  • Start every significant class method with NS_LOG_FUNCTION(this << args...); This enables easy function call tracing.
    • Exception 1: don’t log operators or explicit copy constructors, since these will cause infinite recursion and stack overflow.
    • Exception 2: For simple methods such as getters, avoid function logging because it tends to overload the logging output.
    • For methods without arguments use the same form: NS_LOG_FUNCTION(this);
    • For static functions:
      • With arguments use NS_LOG_FUNCTION(...); as normal.
      • Without arguments use NS_LOG_FUNCTION_NOARGS();
  • Use NS_LOG_ERROR for serious error conditions that probably invalidate the simulation execution. Note that in ns-3, we typically abort the simulation under such conditions rather than log it as an error (which might go undetected if the user is not using logging). The NS_ABORT_MSG_IF/UNLESS(cond,msg) macros and variants, as well as the lower-level NS_FATAL_ERROR(msg) macro, can be used to terminate the simulation with an error message.
  • Use NS_LOG_WARN for unusual conditions that are not considered invalid. An example might be that some resource has been exhausted (e.g., the DHCP server has run out of addresses to allocate). Please give some hints as to the nature of the problem and how it might be corrected.
  • Use NS_LOG_INFO for events that cause a state change in the model. Avoid using it for logging periodic events that are not causing a state change (e.g., a Wi-Fi beacon is sent, but all nodes are already associated to the access point). Try to be efficient in using it; for instance, sending a message is usually an important state change event, but try to capture this event with one single log message at the INFO level rather than multiple. If multiple log messages are desired to fully capture the event and all of its consequences, use DEBUG level for the additional messages. The intent of this log level is to allow a user to examine the normal operation of a model without becoming overwhelmed by the output.
  • NS_LOG_LOGIC is used to trace important logic branches or decision points within a function, without dumping all details of the variable states, called function return values, individual iterations, etc. It may be useful to think of it as a less granular level of function logging than DEBUG, and may not be used by all models (some authors may choose to only use DEBUG level for full logging).
  • NS_LOG_DEBUG is usually used for full voluminous debugging, and contains much more information than NS_LOG_INFO, such as the detailed execution logic of functions and the values that variables take within those functions.
  • Test that your logging changes do not break the code. Run some example programs with all log components turned on (e.g. NS_LOG="***").
  • Use a unary operator (preferred) or an explicit cast for any variable of type uint8_t or int8_t, e.g., NS_LOG_DEBUG("Variable i is " << +i);. e.g., NS_LOG_DEBUG("Variable i is " << static_cast<int>(i)); or Without the cast, the integer is interpreted as a char, and the result will be most likely not in line with the expectations. This is a well documented C++ ‘feature’.

4.4.5. Controlling timestamp precision

Timestamps are printed out in units of seconds. When used with the default ns-3 time resolution of nanoseconds, the default timestamp precision is 9 digits, with fixed format, to allow for 9 digits to be consistently printed to the right of the decimal point. Example:

+0.000123456s RandomVariableStream:SetAntithetic(0x805040, 0)

When the ns-3 simulation uses higher time resolution such as picoseconds or femtoseconds, the precision is expanded accordingly; e.g. for picosecond:

+0.000123456789s RandomVariableStream:SetAntithetic(0x805040, 0)

When the ns-3 simulation uses a time resolution lower than microseconds, the default C++ precision is used.

An example program at src/core/examples/sample-log-time-format.cc demonstrates how to change the timestamp formatting.

The maximum useful precision is 20 decimal digits, since Time is signed 64 bits.

4.4.6. Asserts

The ns-3 assert facility can be used to validate that invariant conditions are met during execution. If the condition is not met an error message is given and the program stops, printing the location of the failed assert.

The assert implementation is enabled in debug and default builds, but disabled in all other build profiles to improve execution speed.

4.4.6.1. How to add asserts to your code

There is only one macro one should use:

NS_ASSERT_MSG(condition, message);

The condition should be the invariant you want to test, as a boolean expression. The message should explain what the condition means and/or the possible source of the error.

There is a variant available without a message, NS_ASSERT(condition), but we recommend using the message variant in ns-3 library code, as a well-crafted message can help users figure out how to fix the underlying issue with their script.

In either case if the condition evaluates to false the assert will print an error message to std::cerr containing the following information:

  • Error message: “NS_ASSERT failed, “
  • The condition expression: “cond=”condition
  • The message: “msg=”message
  • The simulation time and node, as would be printed by logging. These are printed independent of the flags or prefix set on any logging component.
  • The file and line containing the assert: “file=``file``, line=``line``

Here is an example which doesn’t assert:

$ ./ns3 run assert-example
[0/2] Re-checking globbed directories...
ninja: no work to do.
NS_ASSERT_MSG example
  if an argument is given this example will assert.

and here is an example which does:

$ ./ns3 run assert-example -- foo
[0/2] Re-checking globbed directories...
ninja: no work to do.
NS_ASSERT_MSG example
  if an argument is given this example will assert.
NS_ASSERT failed, cond="argc == 1", msg="An argument was given, so we assert", file=/Users/barnes26/Code/netsim/ns3/repos/ns-3-dev/src/core/examples/assert-example.cc, line=44
NS_FATAL, terminating
terminate called without an active exception
Command 'build/debug/src/core/examples/ns3-dev-assert-example-debug foo' died with <Signals.SIGABRT: 6>.

You can try the example program assert-example.cc in src/core/example with or without arguments to see the action of NS_ASSERT_MSG.