Lets make a macro based Logging

I have been attached to a software project for an Embedded Linux system. Currently, our project is infested with a lot of logs. These logs are “printed” on both stdout and a log file on target system. Also, log outputs are being put to “output” always. So I tried to find a way to keep current logging functions almost intact while extending the current functionality.

Right now, our WriteLogMsg function and VDPRINTF & DPRINTF macros looks like this:

#define DPRINTF(fmt, ...)
#define VDPRINTF(fmt, ...)

//...

void WriteLogMsg(const char *format, ...) {
    struct tm *tm_tm;
    char tmBuf[64] = {0};
    time_t ltimes;
    va_list args;

    time(&ltimes);
    tm_tm = localtime(&ltimes);
    strftime(tmBuf, sizeof(tmBuf) - 1, "%Y %b %d %H:%M:%S", tm_tm);

    DPRINTF("%s ", tmBuf);

    va_start(args, format);

    VDPRINTF(format, args);

    va_end(args);
}

This function along with two macros are sufficient for our current stage, e.g. Development. But as we move toward Release and Production the urge for a clean and understandable log format arise.

As you can see from the codes, output of these log messages lack some crucial details such as source code file, function name and line. These are most common needed details and they help a lot when debugging embedded applications.

So, back to our goals. First let’s see if we can introduce a method to write log messages without doing major changes to available codes. From top of my head, two approaches are available: Either modify implementation of WriteLogMsg or use Macros. I haven’t worked with macros although they are used in our project, so I took the challenge of implementing our goal through macros.

Supposedly, future implementation of WriteLogMsg will look like this:

void WriteLogMsg(const char *format, ...) {
    LOG_MACRO(format, args); // psudo-code
}

Let’s dive in. I will define LOG_MACRO to start. We will use __FILE__, __FUNCTION__ & __LINE__ predefined macros to get the name of source file, function & line number, accordingly:

#define LOG_MACRO(msg) printf("[%s->%s->%d] %s", __FILE__, __FUNCTION__, __LINE__, msg)

To test the output I have created a sample app as follows:

#include <stdio.h>

#define LOG_MACRO(msg) printf("[%s->%s->%d] %s", __FILE__, __FUNCTION__, __LINE__, msg)

using namespace std;

int main(int argc, char *argv[]) {
    LOG_MACRO("Hello, Log!");
    return 0;
}

This is the output:

[main.cpp->main->12] Hello, Log!

Not bad, I have the source file name, function name and line number along with log message. Mind the “12” which is the line that actually called LOG_MACRO("Hello, Log!");.

Please note: Value of “__LINE__” might be different depending on your code file.

Now, another question arise: What if I want to log formatted messages? Meaning, I would pass formatted message along with arguments to our macro. This is a common case for logging, something along LOG_MACRO("Return value: %d", rv);.

To achieve this we need to modify our macro to accept 2 parameters: formatted message and arguments.

#define LOG_MACRO(msg, ...) printf("[%s->%s->%d] %s", __FILE__, __FUNCTION__, __LINE__, msg,__VA_ARGS__)

__VA_ARGS__ stand for anything passed inside ...

Lets find out if our desired output is actually printed:

//...
int main(int argc, char *argv[]) {
    unsigned int rv = 256;
    LOG_MACRO("Return value: %d", rv);
    return 0;
}

This is the output:

[main.cpp->main->12] Return value: %d

OK, our macro cannot process passed arguments. As you saw in the beginning of this post, WriteLogMsg accepts two arguments: a formatted message and its arguments. We need to find a way to process formatted log messages. We can solve this by introducing a second macro, generate initial formatted output and then pass it to secondary macro which will produce final output:

#define NEWLINE "\n"
#define LOG_FMT "[%s->%s:%d] "
#define LOG_ARGS __FILE__, __FUNCTION__, __LINE__
#define PRINT_LOG(fmt, ...) printf(fmt, __VA_ARGS__)
#define LOG_MACRO(s, args...) PRINT_LOG(LOG_FMT s NEWLINE, LOG_ARGS, ## args)
using namespace std;

int main(int argc, char *argv[]){
    unsigned int rv = 256;
    LOG_MACRO("Return value: %d", rv);
    return 0;
} 

I have added three new macros, NEWLINE, LOG_FMT & PRINT_LOG. NEWLINE, LOG_FMT are just placeholder for new line char and our format. PRINT_LOG is the macro that will produce final output. Also, I have modified LOG_MACRO to do the initial formatting.

This is the output:

[main.cpp->main:17] Return value: 256

We have achieved our goal.

At this point I realize that I have missed another crucial detail: Time. Unfortunately, there is no predefined macro for getting time. We can get time using an inline function and use it along with our macro:

#include <time.h>
#include <string.h>
static inline char *timenow();
#define NEWLINE "\n"
#define LOG_FMT "%s [%s->%s:%d] "
#define LOG_ARGS timenow(), __FILE__, __FUNCTION__, __LINE__
#define PRINT_LOG(fmt, ...) printf(fmt, __VA_ARGS__)
#define LOG_MACRO(s, args...) PRINT_LOG(LOG_FMT s NEWLINE, LOG_ARGS, ## args)
static inline char *timenow() {
    static char buffer[64];
    time_t rawtime;
    struct tm *timeinfo;

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    strftime(buffer, 64, "%Y-%m-%d %H:%M:%S", timeinfo);

    return buffer;
}

#include <stdio.h>

using namespace std;

int main(int argc, char *argv[]){
    unsigned int rv = 256;
    LOG_MACRO("Return value: %d", rv);
    return 0;
}

This is the output:

2017-07-26 15:11:30 [main.cpp->main:12] Return value: 256

Great. but, this solution only covers the source file that our macro is present. To make available to other source files we can move logging parts to a header file and include it where needed.

Contents of miniLog.h

#ifndef MINILOG_H
#define MINILOG_H
    #include <time.h>
    #include <string.h>
    static inline char *timenow();
    #define NEWLINE "\n"
    #define LOG_FMT "%s [%s->%s:%d] "
    #define LOG_ARGS timenow(), __FILE__, __FUNCTION__, __LINE__
    #define PRINT_LOG(fmt, ...) printf(fmt, __VA_ARGS__)
    #define LOG_MACRO(s, args...) PRINT_LOG(LOG_FMT s NEWLINE, LOG_ARGS, ## args)
    static inline char *timenow() {
        static char buffer[64];
        time_t rawtime;
        struct tm *timeinfo;

        time(&rawtime);
        timeinfo = localtime(&rawtime);

        strftime(buffer, 64, "%Y-%m-%d %H:%M:%S", timeinfo);

        return buffer;
    }
#endif //MINILOG_H

Usage

#include <stdio.h>
#include "miniLog.h"

using namespace std;

int main(int argc, char *argv[]){
    unsigned int rv = 256;
    LOG_MACRO("Return value: %d", rv);
    return 0;
}

Refereences

  • https://coderwall.com/p/v6u7jq/a-simplified-logging-system-using-macros
  • https://stackoverflow.com/questions/3384912/define-log-msg-for-debugging

In this post I’ll demonstrate how to connect to remote SQL Server from Smart Device projects.

What you’ll need:

  • Visual Studio 2005/2008 Professional (Ideally with all available SP’s installed)
  • Windows CE/Mobile SDK
  • SQL Server Compact Edition 3.5 SP2. Remember, this is for desktop.
  • A running SQL Server instance

Note: SQL Server instance must be configured to accept incoming connection. Also a make sure that firewall or antivirus does not block SQL Server connection port (Usually 1433).

Run VisualStudio 2008 and create a new SmartDevice project.

In the next screen select Windows CE as target platform and choose .Net Compact Framework 3.5.

Finally, select project type, e.g. Device Application.

Now add a reference to SqlClient.dll in ‘C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.5\Devices\Client\System.Data.SqlClient.dll’

Note: As you can see SqlClient.dll is inside SQL Server Compact’s binaries for Device. Naturally, this is not for Desktop.

The following is a sample for creating and opening connection:

using(SqlConnection conn = new SqlConnection(GetConnectionString()))
{
	try
	{
		if(conn.ConnectionState != ConnectionState.Open)
			conn.Open();
	}
	catch(SqlException e)
	{
		// Log error, etc.
	}
}

Multilanguage DevExpress components

By default DevExpress components come with English, German, Spanish, Japanese & Russian language support. To change component language one needs to specify CultureInfo by doing:

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

Of course, resource files of desired language should be present alongside your binary.

So what is needed to use components in a language other than default language support? DevExpress provide translated resources - AKA SatteliteAssembly - on demand. Go to Localization, signup or login with your account. Now, press Add Translation and select your desired component version and Culture. Press Download; A download link will be sent to registered e-mail.

Download these files from provided link and put them alongside your assembly inside a folder with culture name, e.g. tr,kr & etc. Add these files to your project as Existing Items and set Copy to output to Always.

Now you are all set to use these resources, and to use, you only need to set CurrentCulture and CurrentUICulture to desired culture as shown above.

In this post I’ll demonstrate how to integrate VisualStudio build process with obfuscation.

Recently I’ve been playing with ConfuserEx. I decided to use obfuscation with ConfuserEx in my project. It’s fairly easy to obfuscate a built assembly. But for integrating with VisualStudio there are some steps. Let’s dive in!

I assume you are familiar with using ConfuserEx, if not hop on Project Wiki to get started.

Note: If you are using EntityFramework in your assembly you are going to have a bad time. I suggest move your data access components to a separate project.

After creating and configuring our ConfuserEx project we are ready to roll. Run VisualStudio and open your project. Go to your project properties and open Build Events tab. Type the following commands inside Post-build event command line:

if $(ConfigurationName) == Release (
...\ConfuserEx_bin\Confuser.CLI.exe $(SolutionDir)confuser.crproj
copy /y $(TargetDir)Confused\*.* $(TargetDir)
rmdir $(TargetDir)Confused /s /q
)

Let’s examine these commands: if $(ConfigurationName) == Release is to make sure we only obfuscate Release binaries. If your project have more than Debug/Release configuration you can specify which configuration you want to obfuscate.

...\ConfuserEx_bin\Confuser.CLI.exe $(SolutionDir)confuser.crproj is the path to Confuser.CLI.exe binary on your system followed by path of ConfuserEx project file. Remember to replace ... this with actual path in your system.

copy /y $(TargetDir)Confused\*.* $(TargetDir) copies all files (confused binaries) back to their original folder.

Finally, rmdir $(TargetDir)Confused /s /q removes Confused folder along with it’s sub-folder (if any).

That’s it, now build your project with Release configuration. If everything goes as expected your assemblies are now obfuscated. To confirm you can use any Disassemble tool.