Why use yaolog
Log is extremely important for debug. Many cppers use
printf
to output debug info, it's easy to use, but hard to get more info and control.
On the other hand, some log libraries are powerful but too large for a simple project, and the complexity maybe increase learning costs. In my opinion, besides friendly interface,
a good log utility should achieve three aims mainly: extra info, multi-output, behavior control. Here yaolog have already implemented these features:
- printf style,
LOGA__
, LOGW__
, LOG__, LOGBIN__, LOGBIN_F__
interfaces - extra info (time, source file, function, line, etc.)
- each logger has its own behavior
- config ini file to change log behavior at run-time
- output to console, file, http server
- thread-safe
- cross-platform (Windows,
Linux)
And the most important thing is it's really easy-to-use.
Design and Implement
No 'log level'?
Log level, like "info", "warning", "error",
and so on, have been provided by many log libraries. But are these levels sufficient for your project? Maybe you just want a single level, maybe you want more levels
like "socket buffer", "UI event", "oh God help me", etc. So yaolog use 'logger object' instead of 'log level', and you can create
multi logger objects and control their behaviors respectively:
YAOLOG_CREATE("I", true, YaoUtil::LOG_TYPE_TEXT); YAOLOG_CREATE("B", true, YaoUtil::LOG_TYPE_BIN); YAOLOG_CREATE("FB", true, YaoUtil::LOG_TYPE_FORMATTED_BIN);
YAOLOG_SET_LOG_ATTR("I", true, YaoUtil::OUT_FLAG_FILE, true, false, true, true, NULL);
The first parameter is logID. You can use macros to define logID for convenience.
The interface
<p>
</p><p>All the interfaces of yaolog are macro:</p><p>
<code>
<code>YAOLOG_INIT
<code>YAOLOG_EXIT
<code>YAOLOG_CREATE
<code>YAOLOG_DISABLE_ALL
<code>YAOLOG_SET_LOG_ATTR
<code>YAOLOG_SET_LOGFILE_ATTR
<code>YAOLOG_SET_ATTR_FROM_CONFIG_FILE
<code>LOG__
<code>LOGA__
<code>LOGW__
<code>LOGBIN__
<code>LOGBIN_F__
The reason is: this method will not increase your app's size if you define _NO_YAOLOG
macro.
The log interface LOGA__
, LOGW__
, LOG__
, LOGBIN__
, LOGBIN_F__ are macros, to log
char
, wchar
, tchar
, and binary data(raw or formatted). LOG__
Expanded like this:
#define LOG__(logID, szFormat, ...) \
YaoUtil::BaseLog *p = YaoUtil::LogFactory::Get(logID);\
p->Log(__FILE__, __FUNCTION__, __LINE__, szFormat, __VA_ARGS__);
__VA_ARGS__
(##__VA_ARGS__
in Linux) is so-called Variadic Macros, it is replaced by all of the arguments that match the ellipsis,
like a 'printf macro':
LOG__("I", _T("Logging %!"), _T("tchar"));
LOGA__("I", "Logging %!", "char");
LOGW__("I", L"Logging %!", L"wchar");
Synchronous vs. Asynchronous
I need a real-time log output to file or console because some debug info require be examined synchronously, e.g. UI events, so yaolog executes the 'local' log operation immediately
in the same thread that user's. But if you specify the flag YaoUtil::OUT_FLAG_REMOTE
to a logger, it's means you want to post the log data to a http server,
and this operation execute in a background worker thread, never block the other threads. See the chart below:
Change log behavior at run-time
The behavior of a logger is refreshed by the last 'config operation', and 'config operation' in yaolog is one of the following:
- call
YAOLOG_CREATE
to create a logger and enable or disable it - call
YAOLOG_SET_LOG_ATTR
& YAOLOG_SET_LOGFILE_ATTR
to set a logger's attribute - call
YAOLOG_SET_ATTR_FROM_CONFIG_FILE
to set a logger's attribute from a config file - modify config file's content
- call
YAOLOG_DISABLE_ALL
to disable all log actions (or cancel disable), the priority of this method is higher than 1&2&3&4.
When your app is running, if you want to change a logger's behavior, just modify the config file and save, then the logger's behavior will be changed
(the premise is YAOLOG_SET_ATTR_FROM_CONFIG_FILE has ever been called in app). As the diagram described above, the background worker thread check each
logger's config file (if specified) every 2 seconds (by default), and update the loggers.
Usage
A yaolog's 'hello world' is like this:
YAOLOG_INIT;
YAOLOG_CREATE("log1", true, YaoUtil::LOG_TYPE_TEXT);
LOGA__("log1", "Hello world! My name is %s, I'm %d!", "neil", 29);
LOGW__("log1", L"Hello world! My name is %s, I'm %d!", L"neil", 29);
LOG__("log1", _T("Hello world! My name is %s, I'm %d!"), _T("neil"), 29);
YAOLOG_EXIT;
The YAOLOG_INIT
and YAOLOG_EXIT
should just call once, but no bad infection with repetitious calls.
If you want to output both to console and file:
YAOLOG_SET_LOG_ATTR("log1", true, YaoUtil::OUT_FLAG_STDOUT | YaoUtil::OUT_FLAG_FILE, true, false, true, true, NULL);
YAOLOG_SET_LOGFILE_ATTR(LOGID_I, false, false, false, "c:\\logfile", "tt.log");
You can also config an ini file and change it at run-time to control log behavior, the format of ini file is in the bottom of
yaolog.h.
YAOLOG_SET_ATTR_FROM_CONFIG_FILE("log1", "logconfig.ini");
Sometimes we need to log binary data. You must set the third parameter to YaoUtil::LOG_TYPE_BIN
or YaoUtil::LOG_TYPE_FORMATTED_BIN
in YAOLOG_CREATE
method to indicate this is a binary logger(raw or formatted),
and this attribute cannot be change later:
YAOLOG_CREATE("log2", true, YaoUtil::LOG_TYPE_BIN);
YAOLOG_SET_ATTR_FROM_CONFIG_FILE("log2", "logconfig.ini");
char buf[10] = { 0,1,2,3,4,5,6,7,8,9 };
LOGBIN__("log2", buf, 10);
YAOLOG_CREATE("log3", true, YaoUtil::LOG_TYPE_FORMATTED_BIN);
YAOLOG_SET_ATTR_FROM_CONFIG_FILE("log3", "logconfig.ini");
char data1[4] = { 0,1,2,3 };
char data2[4] = { 4,5,6,7 };
LOGBIN_F__("log3", "send", data1, 4);
LOGBIN_F__("log3", "recv", data2, 4);
yaolog can post log data to http server:
YAOLOG_SET_LOG_ATTR("log1", true, YaoUtil::OUT_FLAG_FILE | YaoUtil::OUT_FLAG_REMOTE,
true, true, true, true, "http://192.168.1.195/default.aspx");
The log data have used Base64Encode
and UrlEncode
for transfer. An HTTP server (C#) code is like this:
protected void Page_Load(object sender, EventArgs e)
{
string logID = GetValueOfKey("logID");
string isText = GetValueOfKey("isText");
string machineID = GetValueOfKey("machineID");
string logData = GetValueOfKey("logData");
if (logID.Length == 0) return;
string path_ = @"c:\rev_log\";
byte[] bytes = Convert.FromBase64String(logData);
if (isText == "0")
{
File.WriteAllBytes(path_ + logID + ".bl", bytes);
}
else
{
string s = Encoding.Default.GetString(bytes);
s = string.Format(
"logID={0}\r\nmachineID={1}\r\nlogData={2}",
logID, machineID, s);
File.AppendAllText(path_ + logID + ".log", s);
}
Response.Write("ok");
}
protected string GetValueOfKey(string key)
{
return (Request.Form[key] == null ? "" : Request.Form[key]);
}
If you want to compile yaolog to DLL (Windows), uncomment this block in
yaolog.h and define YAOLOG_EXPORTS
for the DLL itself:
Control log in the official version of your software
Generally we disable all logs in release. However, many bugs happened only in end-user's machine.
How to get debug info in this case? yaolog considers three different situations:
For simple, private projects, or not require high level security
Just set all bEnable
parameters to false (at YAOLOG_CREATE
, YAOLOG_SET_LOG_ATTR
, and in ini file).
You can use macros to do this easily. And, ini file not exists equal to disable. When a user reports a bug, told him to deploying an ini file -- file name and path are specified
by YAOLOG_SET_ATTR_FROM_CONFIG_FILE
-- so loggers will work by ini file's content. Notice: this method will not work if you don't call
YAOLOG_SET_ATTR_FROM_CONFIG_FILE
ever in your code.
For higher level security
OK, I know your code is a commercial software and do not want users view debug info via just writing an ini file! yaolog provides a solution below:
- call
YAOLOG_DISABLE_ALL(true)
to disable all logs in your release version. Then all logs are disabled and cannot be enable except
for call YAOLOG_DISABLE_ALL(false)
. - Specify a protocal to tell
server the user's machine ID -- from
YaoUtil::MachineID::GetMachineID()
-- when your app startup. And write this ID in a special file or show
it in app's about dialog, by your choice. - When a user reports a bug, told him find his machine ID to you. When his app startup again,
your server identify this ID and response log config string (by your protocol) to his app and loggers are working now.
- Here is an example of response
handler function. Notice: the behavior of a logger is set by the last 'config operation', so do not call other set functions after
OnResponse()
, or else your config in OnResponse()
will be overwrite.
void OnResponse(char *responseBuffer)
{
string logID;
bool enable = true;
...
...
if (!enable)
{
YAOLOG_DISABLE_ALL(true);
return;
}
YAOLOG_DISABLE_ALL(false);
YAOLOG_SET_LOG_ATTR(logID, ...);
YAOLOG_SET_LOGFILE_ATTR(logID, ...);
}
For highest level security
If you want no-log in release absolutely, define _NO_YAOLOG
macro. Then all log-calls will not be compiled.
History
- 2013-3-31: add ability to: 1. log formatted binary data 2. generate new log file daily
- 2013-1-24: defined
YAOLOG_EXPORTS
for compiling yaolog to
DLL. - 2013-1-14: all log-calls will not be compiled if
_NO_YAOLOG
is defined. - 2012-12-19 : bug fixed:
parsing ini file.
- 2012-12-16 : initial release.