28.19 The Ice::Logger Interface
Depending on the setting of various properties (see Chapter 26), the Ice run time produces trace, warning, or error messages. These messages are written via the
Ice::Logger interface:
module Ice {
local interface Logger {
void print(string message);
void trace(string category, string message);
void warning(string message);
void error(string message);
};
};
A default logger is instantiated when you create a communicator. The default logger logs to the standard error output. The
trace operation accepts a
category parameter in addition to the error message; this allows you to separate trace output from different subsystems by sending the output through a filter.
module Ice {
local interface Communicator {
Logger getLogger();
};
};
To install a logger other than the default one, you can pass it in an InitializationData parameter to
initialize (see
Section 28.3) when you create a communicator. You can also install a logger using configuration properties and the Ice plugin facility (see
Section 28.19.4).
Changing the Logger object that is attached to a communicator allows you to integrate Ice messages into your own message handling system. For example, for a complex application, you might have an already existing logging framework. To integrate Ice messages into that framework, you can create your own
Logger implementation that logs messages to the existing framework.
When you destroy a communicator, its logger is not destroyed. This means that you can safely use a logger even beyond the lifetime of its communicator.
28.19.3 Built‑In Loggers
For convenience, Ice provides two platform-specific logger implementations: one that logs its messages to the Unix
syslog facility, and another that uses the Windows event log. You can activate the
syslog implementation by setting the
Ice.UseSyslog property. On Windows, subclasses of
Ice::Service use the Windows application event log by default (see
Section 8.3.2).
The syslog logger implementation is available in C++, Java, and C#, whereas the Windows event log implementation is only available in C++.
Installing a custom logger using the Ice plugin facility has several advantages. Because the logger plugin is specified by a configuration property and loaded dynamically by the Ice run time, an application requires no code changes in order to utilize a custom logger implementation. Furthermore, a logger plugin takes precedence over the per-process logger (see
Section 28.19.5) and the logger supplied in the
InitializationData argument during communicator initialization, meaning you can use a logger plugin to override the logger that an application installs by default.
class MyLoggerI : public Ice::Logger {
public:
virtual void print(const std::string& message);
virtual void trace(const std::string& category,
const std::string& message);
virtual void warning(const std::string& message);
virtual void error(const std::string& message);
// ...
};
extern "C"
{
ICE_DECLSPEC_EXPORT Ice::Plugin*
createLogger(const Ice::CommunicatorPtr& communicator,
const std::string& name,
const Ice::StringSeq& args)
{
Ice::LoggerPtr logger = new MyLoggerI;
return new Ice::LoggerPlugin(communicator, logger);
}
}
The factory function can have any name you wish; we used createLogger in this example. See
Section 28.24.1 for more information on plugin factory functions.
The definition of LoggerPlugin is shown below:
namespace Ice {
class LoggerPlugin {
public:
LoggerPlugin(const CommunicatorPtr&, const LoggerPtr&);
virtual void initialize();
virtual void destroy();
};
}
The constructor installs your logger into the given communicator. The initialize and
destroy methods are empty, but you can subclass
LoggerPlugin and override these methods if necessary.
The plugin’s name in this example is MyLogger; again, you can use any name you wish. The value of the property represents the plugin’s entry point, in which
mylogger is the abbreviated form of its shared library or DLL, and
createLogger is the name of the factory function.
If the configuration file containing this property is shared by programs in multiple implementation languages, you can use an alternate syntax that is loaded only by the Ice for C++ run time:
Refer to Appendix C for more information on the
Ice.Plugin properties.
public class MyLoggerI implements Ice.Logger {
public void print(String message) { ... }
public void trace(String category, String message) { ... }
public void warning(String message) { ... }
public void error(String message) { ... }
// ...
}
public class MyLoggerPluginFactoryI implements Ice.PluginFactory {
public Ice.Plugin create(Ice.Communicator communicator,
String name, String[] args)
{
Ice.Logger logger = new MyLoggerI();
return new Ice.LoggerPlugin(communicator, logger);
}
}
The definition of LoggerPlugin is shown below:
package Ice;
public class LoggerPlugin implements Plugin {
public LoggerPlugin(Communicator communicator, Logger logger)
{
// ...
}
public void initialize() { }
public void destroy() { }
}
The constructor installs your logger into the given communicator. The initialize and
destroy methods are empty, but you can subclass
LoggerPlugin and override these methods if necessary.
The plugin’s name in this example is MyLogger; again, you can use any name you wish. The value of the property is the name of the factory class.
If the configuration file containing this property is shared by programs in multiple implementation languages, you can use an alternate syntax that is loaded only by the Ice for Java run time:
Refer to Appendix C for more information on the
Ice.Plugin properties.
// C#
public class MyLoggerI : Ice.Logger {
public void print(string message) { ... }
public void trace(string category, string message) { ... }
public void warning(string message) { ... }
public void error(string message) { ... }
// ...
}
// C#
public class MyLoggerPluginFactoryI : Ice.PluginFactory {
public Ice.Plugin create(Ice.Communicator communicator,
string name, string[] args)
{
Ice.Logger logger = new MyLoggerI();
return new Ice.LoggerPlugin(communicator, logger);
}
}
The factory class can have any name you wish; in this example, we used MyLoggerPluginFactoryI. See
Section 28.24.1 for more information on plugin factories. Typically the logger implementation and the factory are compiled into a single assembly.
The definition of LoggerPlugin is shown below:
// C#
namespace Ice {
public class LoggerPlugin : Plugin {
public LoggerPlugin(Communicator communicator, Logger logger)
{
// ...
}
public void initialize() { }
public void destroy() { }
}
}
The constructor installs your logger into the given communicator. The initialize and
destroy methods are empty, but you can subclass
LoggerPlugin and override these methods if necessary.
The plugin’s name in this example is MyLogger; again, you can use any name you wish. The value of the property is the entry point for the factory, consisting of an assembly name followed by the name of the factory class.
If the configuration file containing this property is shared by programs in multiple implementation languages, you can use an alternate syntax that is loaded only by the Ice for .NET run time:
Refer to Appendix C for more information on the
Ice.Plugin properties.
Ice allows you to install a per-process custom logger. This logger is used by all communicators that do not have their own specific logger established at the time a communicator is created.
You can set a per-process logger in C++ by calling Ice::setProcessLogger, and you can retrieve the per-process logger by calling
Ice::getProcessLogger:
LoggerPtr getProcessLogger();
void setProcessLogger(const LoggerPtr&);
If you call getProcessLogger without having called
setProcessLogger first, the Ice run time installs a default per-process logger. Note that if you call
setProcessLogger, only communicators created after that point will use this per-process logger; communicators created earlier use the logger that was in effect at the time they were created. (This also means that you can call
setProcessLogger multiple times; communicators created after that point will use whatever logger was established by the last call to
setProcessLogger.)
getProcessLogger and
setProcessLogger are language-specific APIs that are not defined in Slice. Therefore, for Java and C#, these methods appear in the
Ice.Util class.
For applications that use the Application or
Service convenience classes and do not explicitly configure a logger, these classes set a default per-process logger that uses the
Ice.ProgramName property as a prefix for log messages. The
Application class is described in the server-side language mapping chapters; more information on the
Service class can be found in
Section 8.3.2.
The Ice run time supplies a collection of utility classes that make use of the logger facility simpler and more convenient. Each of the logger’s four operations has a corresponding helper class:
namespace Ice {
class Print {
public:
Print(const LoggerPtr&);
void flush();
...
};
class Trace {
public:
Trace(const LoggerPtr&, const std::string&);
void flush();
...
};
class Warning {
public:
Warning(const LoggerPtr&);
void flush();
...
};
class Error {
public:
Error(const LoggerPtr&);
void flush();
...
};
}
if (errorCondition) {
Error err(communicator‑>getLogger());
err << "encountered error condition: " << errorCondition;
}
The Ice run time defines the necessary stream insertion operators so that you can treat an instance of a helper class as if it were a standard C++ output stream. When the helper object is destroyed, its destructor logs the message you have composed. If you want to log more than one message using the same helper object, invoke the
flush method on the object to log what you have composed so far and reset the object for a new message.