Ideas for logging/tracing

I have a couple of ideas about making function traces nicer: 1. Use of PP_SEQ interface to "parse" function name and arguments int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); // ... } Function name can be extracted with BOOST_PP_SEQ_ELEM(0, fn), the arguments with BOOST_PP_SEQ_POP_FRONT(fn). BOOST_TRACE_MEM_FUN macro could expand to: tracer tracer_line_N(this, "foo", "(buf, len)", resolve_args (buf, len)); 2. In addition to text format, produce XML output. This opens up a possibility for XSLT convertion to UML sequence and collaboration diagrams (XMI). -- Alexander Nasonov

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); ^^^^^^^^
You can't do this without variadic macros (ala C99). We should have these in C++ eventually, but we don't right now and the pp-lib doesn't support them. Regards, Paul Mensonides

Paul Mensonides wrote:
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) );
^^^^^^^^
You can't do this without variadic macros (ala C99). We should have these in C++ eventually, but we don't right now and the pp-lib doesn't support them.
I think he's just using BOOST_PP_SEQ_TAIL to get the arguments. This means that they're never directly accessed by the sequence macros. For example: #define ARGUMENT_STRING(fn) BOOST_PP_STRINGIZE(BOOST_PP_SEQ_TAIL(fn)) ARGUMENT_STRING((foo)(buf, len)) // Expands to "(buf, len)" That works on g++, although I don't know how portable it is. Daniel

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Daniel James
Paul Mensonides wrote:
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) );
^^^^^^^^
You can't do this without variadic macros (ala C99). We should have these in C++ eventually, but we don't right now and the pp-lib doesn't support them.
I think he's just using BOOST_PP_SEQ_TAIL to get the arguments. This means that they're never directly accessed by the sequence macros.
SEQ_TAIL *is* a sequence macro.
For example:
#define ARGUMENT_STRING(fn) BOOST_PP_STRINGIZE(BOOST_PP_SEQ_TAIL(fn))
ARGUMENT_STRING((foo)(buf, len)) // Expands to "(buf, len)"
That works on g++, although I don't know how portable it is.
It works because of a poorly-designed gcc extension. It shouldn't. The only thing that you can do with a sequence that contains a non-unary element is process the elements before it. You cannot touch it in any way; you cannot even test whether or not it is non-unary. In the case of SEQ_TAIL, it has to go through the non-unary element to tell where the end is which should result in the wrong number of arguments (2) to a macro (which takes 1). Even with variadics, it should *still* be an error because the library isn't currently designed to handle it. (I.e. there is not a single instance of ... in any parameter list of any macro in the entire library.) Regards, Paul Mensonides

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Paul Mensonides
In the case of SEQ_TAIL, it has to go through the non-unary element to tell where the end is which should result in the wrong number of arguments (2) to a macro (which takes 1).
Sorry, I was thinking of extracting the last element. SEQ_TAIL happens to work here because it doesn't need to access (in any way) the tail of the sequence. This is not guaranteed by the library, and the implementation detail should not be exploited. Regards, Paul Mensonides

Paul Mensonides wrote:
-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); ^^^^^^^^
You can't do this without variadic macros (ala C99). We should have these in C++ eventually, but we don't right now and the pp-lib doesn't support them.
I thought I can't but BOOST_PP_POP_FRONT( (foo)(buf, len) ) works on gcc and sunpro! BOOST_PP_ELEM(1, (foo)(buf, len) ) of course doesn't work but BOOST_PP_ELEM(0, (foo)(buf, len) ) do. -- Alexander Nasonov

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); ^^^^^^^^
You can't do this without variadic macros (ala C99). We should have these in C++ eventually, but we don't right now and the pp-lib doesn't support them.
I thought I can't but BOOST_PP_POP_FRONT( (foo)(buf, len) ) works on gcc and sunpro!
That might work because there is no need to ever touch (buf, len). That is not guaranteed, however.
BOOST_PP_ELEM(1, (foo)(buf, len) ) of course doesn't work but BOOST_PP_ELEM(0, (foo)(buf, len) ) do.
Again, this happens to work because the implementation doesn't happen to touch (buf, len) at all. Once again, this is not guaranteed to work. More generally, without both variadic macros and a sequence implementation designed to handle arbitrary-arity elements, (foo)(buf, len) is not a sequence. There are, of course, alternatives, such as (foo)((buf)(len)). Regards, Paul Mensonides

Paul Mensonides wrote:
[mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov I thought I can't but BOOST_PP_POP_FRONT( (foo)(buf, len) ) works on gcc and sunpro!
That might work because there is no need to ever touch (buf, len). That is not guaranteed, however.
Is it right that it's not guaranteed only because it doesn't conform to sequence interface? -- Alexander Nasonov

-----Original Message----- [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
Paul Mensonides wrote:
That might work because there is no need to ever touch (buf, len). That is not guaranteed, however.
Is it right that it's not guaranteed only because it doesn't conform to sequence interface?
Yes. Firstly, sequences contain unary elements. In the larger scheme of things a sequence according to the pp-lib is a "unary sequence". Other sequences are possible (such as binary sequences) without variadics, but there is no support for them in the pp-lib. Variadic sequences, meaning sequences whose elements are any arity, can only be supported with C99's variadic macros. Even so, there is no support for them in the pp-lib (yet). Secondly, because of how broken many preprocessors are, the pp-lib does quite a few major hacks (a.k.a. workarounds) to make things work. It is doing a lot of things that aren't necessary from a theoretical point of view, but are necessary in practicality. Because of that, adding side guarantees or allowances is difficult. That might not be the case here, but there is no real reason to add such a guarantee. Regards, Paul Mensonides

Paul Mensonides wrote:
-----Original Message----- [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov Is it right that it's not guaranteed only because it doesn't conform to sequence interface?
Yes. Firstly, sequences contain unary elements. In the larger scheme of things a sequence according to the pp-lib is a "unary sequence". Other sequences are possible (such as binary sequences) without variadics, but there is no support for them in the pp-lib. Variadic sequences, meaning sequences whose elements are any arity, can only be supported with C99's variadic macros. Even so, there is no support for them in the pp-lib (yet). Secondly, because of how broken many preprocessors are, the pp-lib does quite a few major hacks (a.k.a. workarounds) to make things work. It is doing a lot of things that aren't necessary from a theoretical point of view, but are necessary in practicality. Because of that, adding side guarantees or allowances is difficult. That might not be the case here, but there is no real reason to add such a guarantee.
No problem, I can copy/paste/rename required parts of pp :) -- Alexander Nasonov

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
That might not be the case here, but there is no real reason to add such a guarantee.
No problem, I can copy/paste/rename required parts of pp :)
Doing so just propagates a bad design (which is what it appears to be). What exactly are you doing that requires this behavior, because I guarantee there is a better, more structured, way to do it that doesn't involve ill-formed constructs or invalid sequences. Regards, Paul Mensonides

Paul Mensonides wrote:
-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
That might not be the case here, but there is no real reason to add such a guarantee.
No problem, I can copy/paste/rename required parts of pp :)
Doing so just propagates a bad design (which is what it appears to be). What exactly are you doing that requires this behavior, because I guarantee there is a better, more structured, way to do it that doesn't involve ill-formed constructs or invalid sequences.
Paul, I understand your worry and I'm not going to use pp seq interface to extract arguments tuple, I'm just trying to understand whether it's possible to: 1. extract x from (x)(a,b,c) 2. remove (x) from (x)(a,b,c) I don't think my design is really bad, if, after all, it can be expressed in valid C++. Compare: BOOST_TRACE_MEM_FUN( (foo)((buf)(len)) ); BOOST_TRACE_MEM_FUN( (foo)(2, (buf,len)) ); // These two are only for pp funs ;-) BOOST_TRACE_MEM_FUN( foo, (buf, len) ); // Comma is unnatural and looks like a typo BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); // Valid C++ call syntax. Brackets around foo can be explained in // docs. -- Alexander Nasonov

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Alexander Nasonov
Doing so just propagates a bad design (which is what it appears to be). What exactly are you doing that requires this behavior, because I guarantee there is a better, more structured, way to do it that doesn't involve ill-formed constructs or invalid sequences.
Paul, I understand your worry and I'm not going to use pp seq interface to extract arguments tuple, I'm just trying to understand whether it's possible to:
1. extract x from (x)(a,b,c) 2. remove (x) from (x)(a,b,c)
Yes, it is certainly possible. The simplest way, minus workarounds, is: #define CAT(a, b) PRIMITIVE_CAT(a, b) #define PRIMITIVE_CAT(a, b) a ## b #define SPLIT(i, im) PRIMITIVE_CAT(SPLIT_, i)(im) #define SPLIT_0(a, b) a #define SPLIT_1(a, b) b #define EXTRACT(data) SPLIT(0, EXTRACT_I data) #define EXTRACT_I(x) x, #define REMOVE(data) SPLIT(1, EXTRACT_I data)
I don't think my design is really bad, if, after all, it can be expressed in valid C++. Compare:
My only concern is that you are currently using sequence primitives on data structures that aren't sequences. They happen to work in this case. It is roughly equivalent to calling an STL algorithm with an iterator range where the end iterator cannot be reached from the begin iterator. In some cases it may well work, but there are no guarantees. With the preprocessor specifically, it is an undetectable failure to achieve a precondition. Reliance on undocumented and unguaranteed behavior *is* bad design.
BOOST_TRACE_MEM_FUN( (foo)((buf)(len)) ); BOOST_TRACE_MEM_FUN( (foo)(2, (buf,len)) ); // These two are only for pp funs ;-)
BOOST_TRACE_MEM_FUN( foo, (buf, len) ); // Comma is unnatural and looks like a typo
BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); // Valid C++ call syntax. Brackets around foo can be explained in // docs.
Personally, I think that TRACE_MEM_FUN(foo, (buf, len)) is the most natural as well as the easiest to port, unless you need (at some point) access to the elements inside (buf, len), in which case I'd prefer TRACE_MEM_FUN(foo, (buf)(len)). You have two separate pieces of data, which can be supplied in multiple ways--as two distinct parameters or as a data structure. If it is passed as a data structure, it is arguably better not to use a "custom" data structure format just for slightly prettier syntax (IYO). If you use two separate parameters, it avoids the need to extract or remove anything. If you are going to use a data structure, I'd go with (foo)((buf)(len)) or (foo)((buf, len)). I'd choose the first if you ever are going to need to do anything with the two (or more) elements. Later, when variadics are generally available in C++, you can make this interface significantly better. Until then, however, nothing is going to be syntactically "perfect". Regards, Paul Mensonides

Hi Alexander, Sorry for butting in earlier, I should have just let you speak for yourself. Anyway, you might want to try doing this instead: #define BOOST_TRACE_MEM_FUN(return_type, arguments) \ tracer trace_line_N(this, \ BOOST_PP_STRINGIZE(return_type), \ BOOST_PP_STRINGIZE(arguments), \ resolve_args arguments) BOOST_TRACE_MEM_FUN(foo, (buf, len)); Daniel

Alexander Nasonov wrote:
I have a couple of ideas about making function traces nicer:
1. Use of PP_SEQ interface to "parse" function name and arguments
int X::foo(char* buf, size_t len) { BOOST_TRACE_MEM_FUN( (foo)(buf, len) ); // ... }
[snip] I have been using some helper macros for tracing which use the boost PP library. Basically it looks liket this: int X::foo(char* buf, size_t len) { some_log_object << STREAM_VARS_SEQ((buf)(len)); } This expands to "buf=" << buf << ",len=" << len What I like most is that you can specify expressions during tracing like this: STREAM_VARS_SEQ((buf[0])(2*len)) which of course gets you "buf[0]=" << buf[0] << ",2*len" << 2*len While usage is not really restricted to tracing, that where it gets used currently. Markus

Markus Sch?pflin wrote:
I have been using some helper macros for tracing which use the boost PP library.
Basically it looks liket this:
int X::foo(char* buf, size_t len) { some_log_object << STREAM_VARS_SEQ((buf)(len)); }
This is fine except that it doesn't look like a valid function call. IMO, function call style is easier to remember.
This expands to
"buf=" << buf << ",len=" << len
What I like most is that you can specify expressions during tracing like this: STREAM_VARS_SEQ((buf[0])(2*len)) which of course gets you
"buf[0]=" << buf[0] << ",2*len" << 2*len
While usage is not really restricted to tracing, that where it gets used currently.
This is nice for general things but not that nice for call traces. They are special case of logging with some specific requirements. Some of them might be: - strace/ltrace format - function decls - Object identities (== dynamic_cast<void*>(this) for poly classes) primarily for sequence diagrams Uniform interface could let you switch between different formats without rewriting any tracing code. -- Alexander Nasonov
participants (4)
-
Alexander Nasonov
-
Daniel James
-
Markus Schöpflin
-
Paul Mensonides