creating unique message identifiers

M

mliptak

I'm trying to implement logging in my application, so that each log
message has its unique identifier, e.g.
log(identifier, text)
What I want to achieve is that the compiler screams if the log() with
'identifier' is also used in some other place in the code which would
make the 'identifier' not unique.
Is that something that can be achieved in C++?
Thanks
 
J

James Kanze

I'm trying to implement logging in my application, so that
each log message has its unique identifier, e.g.
log(identifier, text)
What I want to achieve is that the compiler screams if the
log() with 'identifier' is also used in some other place in
the code which would make the 'identifier' not unique.
Is that something that can be achieved in C++?

It depends on the type of identifier, but in general, yes,
provided log is a macro. In the general case, you need a local
static variable to achieve it, and the actual identifier won't
be known until compile time. If identifier is text, however,
you can generate it with a macro using __FILE__ and __LINE__.
 
M

mliptak

It depends on the type of identifier, but in general, yes,
provided log is a macro.  In the general case, you need a local
static variable to achieve it, and the actual identifier won't
be known until compile time.  If identifier is text, however,
you can generate it with a macro using __FILE__ and __LINE__.

What if I want to set the identifier myself, not have it generated at
compile time (using local static variable)?

I was also thinking of using the __FILE__ and __LINE__ but the problem
is this could change when the code changes, and I need to have the
identifier fixed for each message.
 
M

Maxim Yegorushkin

I'm trying to implement logging in my application, so that each log
message has its unique identifier, e.g.
log(identifier, text)

What do you need that identifier for?
What I want to achieve is that the compiler screams if the log() with
'identifier' is also used in some other place in the code which would
make the 'identifier' not unique.

It is not possible in the general case when a binary is built by
linking several object files compiled separately. In this case you
could make the linker produce an error, however, it still would not be
possible in the presence of dynamic libraries.
 
M

mliptak

I think you're still to figure out exactly what you need (i.e. the
"requirements" part of your design).  What does it mean "to have the
identifier fixed for each message"?  Do you intend to have the same
message sprinkled throughout your code and have the same identifier
accompany it?  If the messages are unique, why nave an identifier at
all?  If the messages aren't unique and can be the same, using the
exact location in your code to generate the identifier is a nice way
to ensure uniqueness of them.

What I meant by this is to have the "code" associated with particular
"text" and this association never changes (which is not true if I'd be
using __FILE__ and __LINE__).

You asked why have identifier at all.. Well another requirement is to
have the "code" unique throughout the program.

Example:

// a.cc
// ok
log(code1, "text1");
log(code2, "text2");
....
// invalid - code1 already used before
log(code1, "text3");

// b.cc
// invalid - code1 already used elsewhere
log(code1, "text4");
// ok
log(code4, "text5");
The presence of __FILE__ and __LINE__ is very convenient if you need
to later find which of the millions of lines in your code caused the
output of the message.

Another way would be a global counter of sorts.  You could create
a class that when instantiated would increment some counter and when
output would produce that counter (which it would keep as a static
data member), but then you lose control over what counter relates to
which line since generation of the counter can depend on the execution
order of the constructors for those objects.

As I mentioned before, the disadvantage of this approach is that the
codes of messages change when the source changes.
 
M

mliptak

What do you need that identifier for?

The requirement is to have the log messages searchable easily, by
tools like grep. It is a bit more convenient than search by message
text.
It is not possible in the general case when a binary is built by
linking several object files compiled separately. In this case you
could make the linker produce an error, however, it still would not be
possible in the presence of dynamic libraries.

Yes, I thought about the dynamic libraries before. In my case it is
ok if it does not work for dynamic libraries as I don't use them right
now.
 
M

mliptak

  Why don't you want to use '__FILE__' and '__LINE__'? If these
  change too often, add '__DATE__' and '__TIME__' which, together
  with your version management should be enough.

I think it is too complicated for what I really need to achieve.
What I had in mind originally was something like:

log(ERROR01, "Text of error 1");
log(ERROR02, "Text of error 2");
 
B

Bart van Ingen Schenau

What I meant by this is to have the "code" associated with particular
"text" and this association never changes (which is not true if I'd be
using __FILE__ and __LINE__).

You asked why have identifier at all.. Well another requirement is to
have the "code" unique throughout the program.

Example:

// a.cc
// ok
log(code1, "text1");
log(code2, "text2");
...
// invalid - code1 already used before
log(code1, "text3");

If that line had read
log(code1, "text1"),
would that have been OK?
// b.cc
// invalid - code1 already used elsewhere
log(code1, "text4");
// ok
log(code4, "text5");

If you want to have a consistent mapping between a numeric/symbolic ID
and a string, the worst thing you can do is require the programmer to
provide both the ID and the string.

The solution I would propose is to use only the ID in the log
statements, and use a separate mechanism (for example a table) to map
the ID to a string.
For example, something like this:

enum logID {
code1,
code2,
code4
};

char const * const logString[] = {
"text1",
"text2",
"text5"
};

void log(enum logID id)
{
char const * const text = logString[id];
// write text to log destination
}
//usage: log(code1);

To keep the mapping between the ID's and the texts consistent, you can
generate the enumeration and the table from a single source. This
could even be done with the preprocessor.

Bart v Ingen Schenau
 
P

peter koch

I think it is too complicated for what I really need to achieve.
What I had in mind originally was something like:

log(ERROR01, "Text of error 1");
log(ERROR02, "Text of error 2");

Certainly, if you search for simplicity using __FILE__ and __LINE__ is
the way to go. I once used your approach (manually and written in C
loads of years ago), but the reason not to use __LINE__ and __FILE__
was because of a restricted environment where low space overhead was
at a premium. (It was not for logging but for assertions, and in case
of an assert, the integer id was simply dumped).

/Peter
 
M

mliptak

What I meant by this is to have the "code" associated with particular
"text" and this association never changes (which is not true if I'd be
using __FILE__ and __LINE__).
You asked why have identifier at all.. Well another requirement is to
have the "code" unique throughout the program.

// a.cc
// ok
log(code1, "text1");
log(code2, "text2");
...
// invalid - code1 already used before
log(code1, "text3");

If that line had read
  log(code1, "text1"),
would that have been OK?


// b.cc
// invalid - code1 already used elsewhere
log(code1, "text4");
// ok
log(code4, "text5");

If you want to have a consistent mapping between a numeric/symbolic ID
and a string, the worst thing you can do is require the programmer to
provide both the ID and the string.

The solution I would propose is to use only the ID in the log
statements, and use a separate mechanism (for example a table) to map
the ID to a string.
For example, something like this:

enum logID {
  code1,
  code2,
  code4

};

char const * const logString[] = {
  "text1",
  "text2",
  "text5"

};

void log(enum logID id)
{
  char const * const text = logString[id];
  // write text to log destination}

//usage: log(code1);

To keep the mapping between the ID's and the texts consistent, you can
generate the enumeration and the table from a single source. This
could even be done with the preprocessor.

Bart v Ingen Schenau

Yes, I was thinking about this, but the problem is the "text" is not
only a text, but string with parameters, either:
"text param %d, %s", a, b
or:
"text param " << a << ", " << b

I think I will end up with:
void log(enum logID id, ...)
{
}
and do the vsnprintf() magic in there.
 
J

James Kanze

What if I want to set the identifier myself, not have it
generated at compile time (using local static variable)?

You mean that you want the user to provide an indentifier, that
the compiler checks for uniqueness? The compiler can't do that
per se (at least not within a single statement), but you could
easily use it as the initializer of a local static variable,
which would check for uniqueness at runtime.
I was also thinking of using the __FILE__ and __LINE__ but the
problem is this could change when the code changes, and I need
to have the identifier fixed for each message.

The real question is what the identifier is to be used for.
Most of the time, if you need each point of invocation to have a
separate identifier, it is for some sort of tracing or
debugging, in which case, __FILE__ and __LINE__ are far more
useful than anything else you could come up with.
 
J

James Kanze

What I meant by this is to have the "code" associated with
particular "text" and this association never changes (which is
not true if I'd be using __FILE__ and __LINE__).

Aha. Something like errno. Or the error codes returned by
Internet message protocols (smtp, nntp, http...).

That really requires an external registry. The simplest
solution is to maintain a list of code/message pairs, use it to
generate a mapping function, and just pass the code to log
(which uses the mapping function to obtain the message).
 
J

James Kanze

If you want to have a consistent mapping between a
numeric/symbolic ID and a string, the worst thing you can do
is require the programmer to provide both the ID and the
string.
The solution I would propose is to use only the ID in the
log statements, and use a separate mechanism (for example a
table) to map the ID to a string.
For example, something like this:
enum logID {
code1,
code2,
code4
};
char const * const logString[] = {
"text1",
"text2",
"text5"
};
void log(enum logID id)
{
char const * const text = logString[id];
// write text to log destination}
//usage: log(code1);
To keep the mapping between the ID's and the texts
consistent, you can generate the enumeration and the table
from a single source. This could even be done with the
preprocessor.
Yes, I was thinking about this, but the problem is the "text"
is not only a text, but string with parameters, either:
"text param %d, %s", a, b
or:
"text param " << a << ", " << b
I think I will end up with:
void log(enum logID id, ...)
{}
and do the vsnprintf() magic in there.

That's a good recepe for program crashes down the line. In such
cases, the usual solution is for log to return an output stream
wrapper (or even an ostream&, if you don't have to worry about
threading), log inserts the header text (the code, plus anything
you've already mapped), and the client appends the rest.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top