The SimbaEngine SDK includes a flexible logging system for use in development, troubleshooting, and maintenance of any ODBC driver developed with it. This article will go through how the logging system is set up in the C++ DSI. It is expected that readers have a basic understanding of the SDK and the DSI before reading this article.
The logging system uses the interface ILogger as a top-level object. In brief, this interface defines functions for logging messages at different levels, as well as the ability to change the logging level. Please see the documentation for a complete description of the functions and behaviour of ILogger.
Most functions in ILogger have 4 required parameters, and one extra variadic parameter:
– The namespace
– The class
– The method
– The message format
– … (the variadic parameters)
The most important of these are the message format and the variadic parameters. These allow for printf()-style format strings. Example:
const simba_char* query = input.c_str();
GetLog()->LogTrace("Simba", "QueryExecutor", "Prepare", "Query being prepared: %s", query);
GetLog()->LogTrace("Simba", "QueryExecutor", "Prepare", simba_string("Query being prepared: ") + input);
You should use format strings to log variables instead of manually concatenating strings together and putting them in the message format parameter. Here are a few reasons why:
- If you use a format string to log a number, the slow number to string conversions won’t occur unless necessary.
- If you are logging arbitrary user input, the user might introduce format specifiers in their input. This can break because you won’t have supplied the extra arguments required to populate format string.
- If you take the tracing example above, what would happen if the user executed a query “SELECT * from table where c1 LIKE ‘%dog’”. %d is a format specifier, but we won’t have enough parameters to satisfy it.
- Similar to item 1, if you are logging several strings the string concatenations will only occur if the line is going to be logged.
The SDK supplies a default implementation, DSIFileLogger, which logs to a text file. This enables you to get started developing your driver with a fully functional logging system out of the box. For more information please refer to Enable Logging in ODBC.
However, if you do not wish to use the logging that is provided by the SDK, then you are free to subclass ILogger and implement your own logging implementation. A common use case for this would be to tie the ODBC driver logging into an existing database logging system.
The ODBC layer accesses the DSII ILogger instances through the IDriver, the IEnvironment, the IConnection, and the IStatement. Each of these classes contains a function to get the ILogger instance for that object. The SDK, by default, returns the ILogger instance associated with the IDriver for all 4 objects. You may want to change this behaviour and provide a separate ILogger object for any of these objects. One case may be for each IConnection object to have its own ILogger to allow you to more easily debug situations that involve many simultaneous connections.
If you decide to provide your own implementation of an ILogger, simply instantiate your logger implementation and return it from the Get*Log() functions in the driver, environment, connection and/or statement classes in your DSII. The SDK will then use this logger implementation for all of its logging calls. Your ILogger implementation should allow for multi-threaded access, as they will be shared across threads if your ODBC driver is being accessed by multiple threads at the same time.