- Development
- European Robotics Forum 2011 Workshop on the Orocos Toolchain
- European Robotics Forum 2012: workshops
- Geometric relations semantics
- KDL wiki
- Kuka LBR user group
- Links of Orocos components
- OCL v1.x wiki
- RTT v1.x wiki
- Toolchain v2.x
- Wiki for site admins
- iTaSC wiki
Using real-time logging
Table of Contents
This page collects notes and issues on the use of real-time logging. Its contents will eventually become the documentation for this feature.
This feature has been integrated in the Orocos 1.x and 2.x branches but is still considered under development. However, if you need a real-time logging infrastructure (ie text messages to users), this is exactly where you need to be. If you need a real-time data stream logging of ports, use the OCL's Reporting NetCDFReporting Component instead.
It is noted in the text where Orocos 1.x and 2.x differ.
Restrictions and issues
Restrictions
Startup the logging components first: Logging events prior to logging service's configure() will be dropped. The problem is that the logging service connects categories and appenders, and is it itself a component. So until it is configured, and the connections are all made, no appenders are available to deal with the event. Therefore you are suggested to put your appender components and the logging service in a separate deployment XML or script file which is loaded first. This will allow your application components to use logging from the start (component creation). See the ocl/logging/tests/data availablility XML deployment files for examples. OCL's deployer can execute in order multiple XML or script files.Categories can not be created in real-time: They live on the normal heap via new/delete. Create all categories in your component's constructor or during configureHook() or similar.
NDC's are not supported. They involve std::string and std::vector which we currently can't replace.
Works only with OCL's deployers: If you use a non-deployer mechanism to bring up your system, you will need to add code to ensure that the log4cpp framework creates our OCL::Category objects, and not the default (non-real-time) log4cpp::Category objects. This should be done early in your application, prior to any components and categories being created.
log4cpp::HierarchyMaintainer::set_category_factory( OCL::logging::Category::createOCLCategory);
Issues
On the ML it was requested to log when events have been lost. There are two places that this would need to be implemented, both annotated with TODO's in the code.- When creation of the OCL::String objects in a LoggingEvent exhausts the memory pool
- When the buffer between a category and its appenders is full
This is not currently dealt with, but could be in future implementations.
In RTT/OCL 1.x, multiple appenders connected to the same category will, receive only some of the incoming logging events. This is as each appender will pop different elements from the category's buffer. This issue has been solved in 2.x.
The size of the buffer between a category and its appenders is currently fixed (see ocl/logging/Category.cpp). This will be fixed lateron on the 2.x branch. Note that that fixed size plus the default consumption rate of the FileAppender means you can exhaust the default TLSF memory pool in very short order. For a complex application (~40 components, 400 Hz cycle rate) we increased the default buffer size to 200, increased the memory pool to 10's of kilobytes (or megabytes) and increased the FileAppender consumption rate to 500 messages per second.
Viewing logs
We can use standard log viewers for Log4j in two ways:
- Use FileAppender which writes log lines to a file and let the viewers read that file
- Use Log4cxxAppender which creates a network socket to which Log4cxx/Log4j viewers can connect.
These log viewers are compatible:
- LogSaw (Eclipse-based) http://logsaw.sourceforge.net/
- ChainSaw (Part of Log4j) Old site: http://logui.sourceforge.net/ New site: http://logging.apache.org/chainsaw/index.html
Complete application example
As at October 2010, assumes you are using for RTT 1.x:
- log4cpp from http://gitorious.org/orocos-toolchain/log4cpp
- RTT from http://github.com/snrkiwi/orocos-rtt/tree/next-rtlogging
- OCL from http://github.com/snrkiwi/orocos-ocl/tree/next-logging
And for RTT 2.x, use the Orocos Toolchain 2.2 or later from :
- log4cpp from http://gitorious.org/orocos-toolchain/log4cpp
- RTT from http://gitorious.org/orocos-toolchain/rtt
- OCL from http://gitorious.org/orocos-toolchain/ocl
then build in the following order, with these options ON:
- log4cpp (default options)
- RTT: ENABLE_RT_MALLOC, OS_RT_MALLOC
- OCL: BUILD_RTALLOC, BUILD_LOGGING
The deployer now defaults to a 20k real-time memory pool (see OCL CMake option ORO_DEFAULT_RTALLOC_SIZE), all Orocos RTT::Logger calls end up inside of log4cpp, and the default for RTT::Logger logging events is to log to a file "orocos.log". Same as always. But now you can configure all logging in one place!
IMPORTANT Be aware that there are two logging hierarchies at work here:
- a non-real-time, log4cpp-based logging in use by RTT::Logger (currently only for RTT 1.x)
- a real-time, OCL::Logging-based (with log4cpp underneath) in use by application code
In time, hopefully these two will evolve into just the latter.
Required Build flags
We're assuming here that you used 'orocreate-pkg' to setup a new application. So you're using the UseOrocos CMake macros.
- Your application's manifest.xml must depend on ocl.
- Your application's CMakeLists.txt must include the line : orocos_use_package(ocl-logging)
Both steps will make sure that your libraries link with the Orocos logging libraries and that include files are found.
Configuring real-time memory pool size
The deployer's have command line options for this
deployer-macosx --rtalloc-mem-size 10k deployer-corba-macosx --rtalloc-mem-size 30m deployer-corba-macosx --rtalloc 10240 # understands shortened, but unique, options
Configuring RTT::Logger logging
NOTE: this feature is not available on the official release. Skip to the next section (Configuring OCL::logging) if you're not using the log4cpp branch of the RTT
You can use any of log4cpp's configurator approaches to configure, but the deployer's already know about PropertyConfigurator's. You can pass a log4cpp property file to the deployer and that will be used to configure the first of the hierarchies above - the non-real-time, logging used by RTT::Logger. For example
deployer-macosx --rtt-log4cpp-config-file /z/l/log4cpp.conf
# root category logs to application (this level is also the default for all # categories who's level is NOT explicitly set in this file) log4j.rootCategory=DEBUG, applicationAppender # orocos setup log4j.category.org.orocos.rtt=INFO, orocosAppender log4j.additivity.org.orocos.rtt=false # do not also log to parent categories log4j.appender.orocosAppender=org.apache.log4j.FileAppender log4j.appender.orocosAppender.fileName=orocos-log4cpp.log log4j.appender.orocosAppender.layout=org.apache.log4j.PatternLayout log4j.appender.orocosAppender.layout.ConversionPattern=%d{%Y%m%dT%T.%l} [%-5p] %m%n
IMPORTANT Note the direction of the category name, from org to rtt. This is specific to log4cpp and other log4j-style frameworks. Using a category "rtt.orocos.org" and sub-category "scripting.rtt.orocos.org" won't do what you, nor log4cpp, expect.
Configuring OCL::logging (XML)
This is how you would setup logging from a Deployer XML file. If you prefer to use a script, see the next section.
See ocl/logging/tests/xxx.xml for complete examples and more detail, but in short
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "cpf.dtd"> <properties> <simple name="Import" type="string"> <value>liborocos-logging</value> </simple> <simple name="Import" type="string"> <value>libTestComponent</value> </simple> <struct name="TestComponent" type="OCL::logging::test::Component"> <struct name="Activity" type="Activity"> <simple name="Period" type="double"><value>0.5</value></simple> <simple name="Priority" type="short"><value>0</value></simple> <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple> </struct> <simple name="AutoConf" type="boolean"><value>1</value></simple> <simple name="AutoStart" type="boolean"><value>1</value></simple> </struct> <struct name="AppenderA" type="OCL::logging::FileAppender"> <struct name="Activity" type="Activity"> <simple name="Period" type="double"><value>0.5</value></simple> <simple name="Priority" type="short"><value>0</value></simple> <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple> </struct> <simple name="AutoConf" type="boolean"><value>1</value></simple> <simple name="AutoStart" type="boolean"><value>1</value></simple> <struct name="Properties" type="PropertyBag"> <simple name="Filename" type="string"><value>appendera.log</value></simple> <simple name="LayoutName" type="string"><value>pattern</value></simple> <simple name="LayoutPattern" type="string"><value>%d [%t] %-5p %c %x - %m%n</value></simple> </struct> </struct> <struct name="LoggingService" type="OCL::logging::LoggingService"> <struct name="Activity" type="Activity"> <simple name="Period" type="double"><value>0.5</value></simple> <simple name="Priority" type="short"><value>0</value></simple> <simple name="Scheduler" type="string"><value>ORO_SCHED_OTHER</value></simple> </struct> <simple name="AutoConf" type="boolean"><value>1</value></simple> <simple name="AutoStart" type="boolean"><value>1</value></simple> <struct name="Properties" type="PropertyBag"> <struct name="Levels" type="PropertyBag"> <simple name="org.orocos.ocl.logging.tests.TestComponent" type="string"><value>info</value></simple> </struct> <struct name="Appenders" type="PropertyBag"> <simple name="org.orocos.ocl.logging.tests.TestComponent" type="string"><value>AppenderA</value></simple> </struct> </struct> <struct name="Peers" type="PropertyBag"> <simple type="string"><value>AppenderA</value></simple> </struct> </struct> </properties>
Run this XML file, save it in 'setup_logging.xml' and use it:
deployer-gnulinux -s setuplogging.xml
Configuring OCL::logging (Lua)
This is how you would setup logging from a Lua script file. If you prefer to use a XML, see the previous section.
require("rttlib") -- Set this to true to write the property files the first time. write_props=false tc = rtt.getTC() depl = tc:getPeer("deployer") -- Create components. Enable BUILD_LOGGING and BUILD_TESTS for this to -- work. depl:loadComponent("TestComponent","OCL::logging::test::Component") depl:setActivity("TestComponent", 0.5, 0, 0) depl:loadComponent("AppenderA", "OCL::logging::FileAppender") depl:setActivity("AppenderA", 0.5, 0, 0) depl:loadComponent("LoggingService", "OCL::logging::LoggingService") depl:setActivity("LoggingService", 0.5, 0, 0) test = depl:getPeer("TestComponent") aa = depl:getPeer("AppenderA") ls = depl:getPeer("LoggingService") depl:addPeer("AppenderA","LoggingService") -- Load marshalling service to read/write components depl:loadService("LoggingService","marshalling") depl:loadService("AppenderA","marshalling") if write_props then ls:provides("marshalling"):writeProperties("logging_properties.cpf") aa:provides("marshalling"):writeProperties("appender_properties.cpf") print("Wrote property files. Edit them and set write_props=false") os.exit(0) else ls:provides("marshalling"):loadProperties("logging_properties.cpf") aa:provides("marshalling"):loadProperties("appender_properties.cpf") end test:configure() aa:configure() ls:configure() test:start() aa:start() ls:start()
To run this script, save it in 'setup_logging.lua' and do:
rttlua-gnulinux -i setup_logging.lua
Using OCL::Logging in C++
The component itself uses logging like the following simplified example// TestComponent.hpp #include <ocl/LoggingService.hpp> #include <ocl/Category.hpp> class Component : public RTT::TaskContext { ... /// Our logging category OCL::logging::Category* logger; };
// TestComponent.cpp #include <rtt/rt_string.hpp> Component::Component(std::string name) : RTT::TaskContext(name), logger(dynamic_cast<OCL::logging::Category*>( &log4cpp::Category::getInstance("org.orocos.ocl.logging.tests.TestComponent"))) { } bool Component::startHook() { bool ok = (0 != logger); if (!ok) { log(Error) << "Unable to find existing OCL category '" << categoryName << "'" << endlog(); } return ok; } void Component::updateHook() { // RTT 1.X logger->error(OCL::String("Had an error here")); logger->debug(OCL::String("Some debug data ...")); // RTT 2.X logger->error(RTT::rt_string("Had an error here")); logger->debug(RTT::rt_string("Some debug data ...")); logger->getRTStream(log4cpp::Priority::DEBUG) << "Some debug data and a double value " << i; }
IMPORTANT YOu must dynamic_cast to an OCL::logging::Category* to get the logger, as shown in the constructor above. Failure to do this can lead to trouble. You must also use explicitly use OCL::String() syntax when logging. Failure to do this produces compiler errors, as otherwise the system defaults to std::string and then you are no longer real-time. See the FAQ below for more description.
And the output of the above looks something like this:
// file orocos.log, from RTT::Logger configured with log4cpp 20100414T09:50:11.844 [INFO] ControlTask 'HMI' found CORBA Naming Service. 20100414T09:50:11.845 [WARN] ControlTask 'HMI' already bound to CORBA Naming Service.
20100414T21:41:22.539 [INFO ] components.HMI Started servicing::HMI 20100414T21:41:23.039 [DEBUG] components.Robot Motoman robot started 20100414T21:41:42.539 [INFO ] components.ConnectionMonitor Connected
20100414T21:41:41.982 [INFO ] org.orocos.rtt Thread created with scheduler type '1', priority 0 and period 0. 20100414T21:41:41.982 [INFO ] org.orocos.rtt Creating Proxy interface for HMI 20100414T21:41:42.016 [DEBUG] org.me.myapp Connections made successfully 20100414T21:41:44.595 [DEBUG] org.me.myapp.Robot Request position hold
The last one is the most interesting. All RTT::Logger calls have been sent to the same appender as the application logs to. This means you can use the exact same logging statements in both your components (when they use OCL::Logging) and in your GUI code (when they use log4cpp directly). Less maintenance, less hassle, only one (more) tool to learn. The configuration file for the last example looks something like
# root category logs to application (this level is also the default for all # categories who's level is NOT explicitly set in this file) log4j.rootCategory=DEBUG, applicationAppender # orocos setup log4j.category.org.orocos.rtt=INFO, applicationAppender log4j.additivity.org.orocos.rtt=false # do not also log to parent categories # application setup log4j.category.org.me =INFO, applicationAppender log4j.additivity.org.me=false # do not also log to parent categories log4j.category.org.me.gui=WARN log4j.category.org.me.gui.Robot=DEBUG log4j.category.org.me.gui.MainWindow=INFO log4j.appender.applicationAppender=org.apache.log4j.FileAppender log4j.appender.applicationAppender.fileName=application.log log4j.appender.applicationAppender.layout=org.apache.log4j.PatternLayout log4j.appender.applicationAppender.layout.ConversionPattern=%d{%Y%m%dT%T.%l} [%-5p] %c %m%n
Technical details
- We rely on a real-time allocator called TLSF.
- There is a several kilobyte overhead for TLSF's bookkeeping (~3k on 32-bit Ubuntu, ~6k on 64-bit Snow Leopard). You must take this into account, although the standard OCL TLSF pool size (256k) should cover your needs.
- Only the OCL::String (in 1.x) and RTT::rt_string (in 2.x) objects in OCL::logging::LoggingEvent objects use the real-time memory pool.
- When you create a category, all parent categories up to the root are created. For example, "org.me.myapp.cat1" causes creation of five (5) categories: "org.me.myapp.cat1", "org.me.myapp", "org.me", "org", and "" (the root category) (presuming none of these already exist). These all occur on the normal heap (see below).
- Currently, exhausting the real-time memory pool results in logging events being silently dropped (also, see next item).
- For real-time performance, ensure that TLSF is built with MMAP and SBRK support OFF in RTT's CMake options (-DOS_RT_MALLOC_MMAP=OFF -DOS_RT_MALLOC_SBRK=OFF).
- TLSF use with multiple threads is currently supported only for non-macosx platforms. Use on macosx will exhibit (understandable) corruption in the TLSF bookkeeping (causes assert's).
FAQ
Logging statements are not recorded
Q: You are logging and everything seems fine, but you get no output to file/socket/stdout (depending on what your appender is).A: Make sure you are using an OCL::logging::Category* and not a log4cpp::Category. The latter will silently compile and run, but it will discard all logging statements. This situation can also mask the fact that you are accidentally using std::string and not OCL::String. For example
log4cpp::Category* logger = log4cpp::Category::getInstance(name); logger->debug("Hello world")
OCL::logging::Category* logger = dynamic_cast<OCL::logging::Category*>(&log4cpp::Category::getInstance(name)); logger->debug("Hello world")
/path/to/log4cpp/include/log4cpp/Category.hh: In member function ‘virtual bool MyComponent::configureHook()’: /path/to/log4cpp/include/log4cpp/Category.hh:310: error: ‘void log4cpp::Category::debug(const char*, ...)’ is inaccessible /path/to/my/source/MyComponent.cpp:64: error: within this context
OCL::logging::Category* logger = dynamic_cast<OCL::logging::Category*>(&log4cpp::Category::getInstance(name)); logger->debug(OCL::String("Hello world"))
»
- Printer-friendly version
- Login or register to post comments