Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
		Suppression of Messages in MessageLogger
		----------------------------------------

Basics of message suppression
-----------------------------

By definition, suppression of messages means that some subset of
messages issued by <LogWhatever> will never (in the case of multithread
operation) even be put on the queue for the MessageLoggerScribe thread
to look at.  (In single-thread mode, no calls to any messafge service logging
functions are made.)  

To suppress a message, the software must know, "at the top", that no message 
of this ilk can possibly be reacted to. 

For practical purposes, this has several consequences:

1. Savings of activity:  
   a. Overhead of adding to the queue, waking the scribe thread, consuming
      the message is eliminated.
   b. Preparation of the header and the extended message id never happens.
   c. Message is not "shopped" to the various destinations, saving 
      various map lookups.  This includes the statistics destinations.
   d. Of course no actual I/O or  is done. 

2. Granularity of choice:
   a. Suppression cannot distinguish between one destination and another.
   b. Suppression knows nothing about the category of the message.
   c. Suppression does know about the severity level of the message.
   c. Suppression CAN depend on which module is running.

3. Effect:
   a. A suppressed message cannot contribute to any statistics destination.
   b. There cannot be a way to over-ride suppression at the destination level.

Other configuration-specified preferences can cause various messages to be 
ignored by various destinations, but that is moot to the issue of suppresssion
because to decide to ignore a message, the destination has to see the message,
thus the message must have made it to the MessageLoggerScribe.

Suppression can be controlled by compile-time defines, by thresholds and
module specifications set up in the configuration file, or by a combination 
of both.  

Although by definition the activities above are elided, there is potential for
avoiding (or incurring) various other burdens.  In particular, the message
content itself might take non-trivial time to build up.  It is desirable that
in the case of a suppressed message, this work be avoided:

4. Further savings of activity:
   a. The various operator<<'s appearing in the message-issuing statement  
      are not executed.
   b. Any functions called to prepare for those operator<<'s are not called.
   
If suppression is dictated by a compile-time define, then a fifth savings can 
be imagined:

5. Savings of space:  
   a. The code for the various operator<<'s and or functions appearing in the 
      statement issuing the message does not even apear in the executable.  

The non-execution of functions appearing in a statement involving a 
suppressed LogDebug, while quite desirable to our community, violates C++
semantics about expression evaluation.  The user "has a right to expect"
that in A(b) << foo(), the function foo() is called and any side effects
will happen.  Creating a situation in which these semantics are violated 
can be accomplished via a pre-preocessor macro or define (and I suspect 
ONLY that way).  The reason we are somewhat comfortable with this violation 
of C++ expression evaluation semantics is that this is the identical situation
occuring in the context of the assert macro.  Users are familiar with the fact
that using a function with side effects inside an assert is poison, and 
nobody takes arms against the writer of assert on that issue.

Enabling LogDebug by compile-time define
----------------------------------------

In the new code, LogDebug and LogTrace are by default always suppressed.
To enable them, one must define EDM_ML_DEBUG.  

To do this on the build command, one does

scram b clean; scram b USER_CXXFLAGS="-DEDM_ML_DEBUG"

There is a bit of a subtlety.  Scram does check for changes in these user flags 
when deciding what does and does not require recompilation.  But this implies
that everything will be recompiled.  In principle you would rather recompile
only files that included MessageLogger.h (however, we notice that is **is**
pretty much everything).  It would be even better to recompile only files that
use LogDebug or LogTrace.  This we cannot force the build system to do.  Thus
we either need to do scram v clean first (as in the above mantra) or touch
MessageLogger.h.

A consequence is that if some irrelevant file has advanced to a new version,
then without using the versioning capabilities of cvs, there is no way USING
THE SCRAM COMMAND LINE ALONE to just enable LogDebug for your files without
moving to the new version of the irrelevant files.  

Of course, one can place a 
  #define EDM_ML_DEBUG
into one's own code, and that will enable LogDebug for just that file.

Status before Sep 2010
----------------------

In the original idea for LogDebug, a compile-time define would cause LogDebug 
to become a complete no-op, resulting in code that was identical to the code 
with the LogDebug statement commented out.  This turned out not to be as easy 
as originally anticipated, and indeed the original code missed the mark in
(at least) two ways:

1. When the suppression was specified at compile time, the code for 
   functions needed to prepare the data composing the message was still
   being generated, and the code implementing the functions to prepare 
   the message was still being executed, because the Suppress_LogDebug_ class 
   was still doing operator<<, treated in this way:
   
    Suppress_LogDebug_ &operator<< (T const & t) { return *this; }	
    
   That implies that if the rhs of the operator<< is foo(), then foo() must
   be called since from the compiler's perspective foo() might have side 
   effects. 

2. When the suppression was specified dynamically, the compiler inherently 
   could not avoid generating the code for foo(), but it also would be 
   executing foo(), and that (wee shall see) could have been avoided.

Although some care was taken to expose, by inlining, as much of the mechanism 
as possible so that a good compiler could know to throw everything away, the 
compiler does not know (and cannot be told) that the individual streaming 
operators and/or the functions called to compute the data being streamed are 
"pure."   Empirically, therefore, LogDebug occupied non-negligible time (in 
some cases) even when suppressed.

The solution to this dilemma, at least for the compile-time case, lies in
a lexical technique suggested by C. Jones.  This forces the pre-preocessor to
generate a statement such that the possibly-expensive function and operator<< 
calls are REQUIRED (by the C++ standard) to be elided by the compiler.

In the dynamic case, the solution is to not to leave the realm of the
macros until after the decision-point about suppression.  This complicates
matters due to the two-levels of macro, but is not conceptually any different.

Given that LogDebug() behaves logically like a ctor, it is less than 
trivial to implement these techniques; more about that below in the Coding 
Details section.


Details of how suppression booleans are maintained
--------------------------------------------------

I will use edm::LogInfo as an example; the WARNING level loggers behave
similarly, ERROR level messages cannot be suppressed, and LogDebug has a 
special macro structure discussed below for placing the line number into 
the message.

Let's start from the business end (how the suppression boolean is used)
and trace back to see how it is modified and finally how it is set up
and how modification policy is established.

The variable obtained by edm::MessageDrop::instance()->infoEnabled 
is the relevant suppression boolean.  It is used in the ctor of LogInfo
to decide whether to initialize a unique_ptr to a MessageSender with
an actual MessageSender, or with 0.  

In MessageLogger/interface/MessageLogger.h:
class LogInfo				
{
public:
  explicit LogInfo( std::string const & id ) 
    : ap ( edm::MessageDrop::instance()->infoEnabled ? 
      new MessageSender(ELinfo,id) : 0 )
  { }
// ...
}

The act of destroying the MessageSender, which occurs when in the dtor of
LogInfo this unique_ptr is destructed, is what triggers preparation of the
ErrorObject and placing that ErrorObject onto the MessageLoggerQueue.  
That is, if the MessageSender is constructed, then INEVITABLY the message 
will appear on the queue, and such a message by definition was not suppressed.

How does edm::MessageDrop::instance()->infoEnabled evolve?

If we look in MessageService/src/MessageLogger.cc (note that the other header
we looked at was in MessageLogger, not MessageService), we see that various
calls to the following helper functions are set up as callbacks:

establishModuleCtor  is called by
  preModuleConstruction
  preSourceConstruction
  
unEstablishModule is called by 
  preModuleConstruction
  preSourceConstruction
  
establishModule is called by
  preModuleEndJob
  preModuleEndRun
  preModuleEndLumi
  preModule
  preModuleEndLumi
  preModuleEndRun  
  preModuleEndJob
      
unEstablishModule is called by
  preModuleEndJob
  preModuleEndRun  
  preModuleEndLumi
  preModule
  preModuleEndLumi
  preModuleEndRun  
  preModuleEndJob
  
So we see that whenever code for some module is running, establishModule
with that module descriptor will have been called.

In establishModuleCtor and establishModule, values are set up for 
messageDrop->debugEnabled, messageDrop->infoEnabled, and warningEnabled.

The code first searches for this moduleLable() (label, not name) in the map 
suppression_levels.  If it fines a suppression level for this module,
then it sets each of enabling variables (for example infoEnabled) true if 
the indicated supression level is less than the corresponding severity level.
For example, if suppression_levels["thisModule"] = ELsev_info (or higher),
then infoEnabled is set false; if it is ELsev_debug then infoEnabled is
set true.  If the module label is not found in the list, then  infoEnabled
and warningEnabled are true.

Current code (as of 9/17/10) treats debugEnabled differently:  First,
it sets up debugEnabled according to two booleans anyDebugEnabledand
everyDebugEnabled (the usage is the obvious) and the map
debugEnabledModules_ (if the bools don't settle the issue and the map 
contains the module lable, then debugEnabled is set true).  Then if 
the suppression_levels map dose not contain this module label, that 
value of debugEnabled is left holding.

When the module is unEstablished, the value goes back to the original
infoEnabled, as stored in nonModule_infoEnabled (similarly for the others.
nonModule_debugEnabled, nonModule_infoEnabled, and nonModule_warningEnabled 
are given initial values (false, true, true) as a matter of good C++ practice, 
but these initial values are not used.  On the other hand, initial values of
messageDrop->debugEnabled, infoEnabled, and warningEnabled  (which are relevant
for messages issued outside of all modules) are set true in MessageDrop.cc.

So far, we see that infoEnabled is used to decide whether to create a 
MessageSender, and that infoEnabled is established each time a module is 
entered, based on suppression_levels.  All that remains is to see how
suppression_levels (and, for logDebug, anyDebugEnabled, everyDebugEnabled, 
and debugEnabledModules_) get set up.

In MessageLogger.cc, the ctor of MessageLogger sets suppression_levels
based on the three vectors suppressDebug, suppressInfo and suppressWarning.
Each of those, in turn, were obtained as a vstring from the configuration
(parameters "suppressDebug", "suppressInfo", and "suppressWarning").
Similarly, debugEnabledModules_ is obtained from debugModules, which is
obtained from the vstring parameter "debugModules".  anyDebugEnabled is
set based on debugModules being empty, and if this is the case, 
MessageDrop::debugEnabled  is also set false (which will be relevant 
when outside of all modules). Finally, if any of the values in debugModules
is "*", then everyDebugEnabled_ is set true.
   
Current (until Sep 2010) LogDebug Suppression
--------------------------------------------

The code defining the (macro) LogDebug looked like this:

#ifdef EDM_MESSAGELOGGER_SUPPRESS_LOGDEBUG 
#define LogDebug(id) edm::Suppress_LogDebug_()
#define LogTrace(id) edm::Suppress_LogDebug_()
#else
#define LogDebug(id)                                 \
  ( !edm::MessageDrop::debugEnabled )                \
    ?  edm::LogDebug_()                              \
    :  edm::LogDebug_(id, __FILE__, __LINE__)
// ...

class Suppress_LogDebug_ 
{ 
public:
  template< class T >
    Suppress_LogDebug_ &operator<< (T const & t) { return *this; }	
};  // Suppress_LogDebug_

This implies that **even if debugEnabled is false** a line like

LogDebug ("cat") << foo(objectA) << objectB;

will force the compiler, to properly execute any side effects of operator<<
and of foo(objectA), to do the operator<<'s and to call foo(), even though 
the user's intent was surely to skip them.  While operator<<, taken from
SuppressLogDebug_, is trivial, foo() probably is not.

Similarly, if debugEnabled is false when LogDebug is not suppressed by the 
define, then even though no MessageSender will be created, the operator<<
and foo() calls to construct the message still will be executed.  

Placing line numbers into a message
-----------------------------------

In order to place line number and source file name into a message, 
the LogDebug token **must** be a macro -- nobody is interested in
the line and file of the place in MessageLogger.h or .cc that the
ctor of some LogDebug class appears.

In retrospect, it was this forcing of the usse of a macro that enticed 
us to provide assert-like behavior in terms of eliding LogDebug in the
debug-disabled case.  For code in the MessageFacility package, where users
have requested forms of LogInfo etc. having line numbers and file names,
this same issue will arise again, and we should carefully think out what
we and theusers want to do. 

Coding details in moving to optimal suppression
-----------------------------------------------

For LogDebug (and LogTrace) ONLY we wish to have a general suppression 
variable which does not do anything with the messageDrop singleton and 
is controlled solely by a define.  

The goal is to prevent the execution of operator<< and foo(), and ideally to 
prevent even the inclusion of non-executed code for the operator<< and foo(), 
when the user does something like

  LogDebug ("cat") << foo(objectA )<< objectB;

and the LogDebug is suppressed.  Part one of this goal is to achieve that
optimization when the information indicating suppression is via a macro.
Part two is to (if we can) achieve the optimization under control of a 
run-time variable.

The trick for part one (suggested by C. Jones) exploits the lexical 
properties of the preprocssor, to constuct a statement that contains
a ?: conditional, with the predicate being compile-time true, and the
operator<< constructs coming out as the tail end of the (unexecuted)
if-false statement. Thus:

  #define LogDebug(id) true ? edm::SuppressLogDebug() :edm::SuppressLogDebug() 

such that the user statement becomes 

  true ? edm::SuppressLogDebug() :edm::SuppressLogDebug() 
				  		<< foo(objectA) << objectB;

and this is equivalent (known at compile time) to

  edm::SuppressLogDebug();

which, we notice does not even contain the code for foo(objectA) or any
operator<<.

Part two can also be accomplished, although in that case the code for the
operator<<'s and foo() obviously will be generated.  Consider the 
following two-tier construct.
  
  #define MIGHTSUPPRESSLOGDEBUG(id) disabling_variable ? \\
          SuppressLogDebug_() ? LogDebug_(id)

  #ifdef DISABLING_SYMBOL 
  #define LogDebug(id) true ? SuppressLogDebug_() : SuppressLogDebug_() 
  #else
  #define LogDebug(id) MIGHTSUPPRESSLOGDEBUG(id)
  #endif
					  
Then if DISABLING_SYMBOL is true, the user statement becomes					  

  true ? edm::SuppressLogDebug() :edm::SuppressLogDebug() 
  						<<foo(objectA) << objectB;

which we already saw satisfies the part 1 goal.  If DISABLING_SYMBOL is false,
the user statement becomes

  disabling_variable ? edm::SuppressLogDebug_() 
  : LogDebug_(id) << foo(objectA) << objectB;
  
Clearly, if disabling_variable is false, you get the proper LogDebug_ call with
the associated streaming.  If disabling_variable is true, you get a ctor of
SuppressLogDebug_() as the entire statement; the anonymous object immediately
goes out of scpe when tthe statement ends, and thus nothing is done.  So this
satisfies part two.  

We should choose symbol names that are appropriate for the CMS EDM, of course.
It has already been specified that the define the user sees to enable 
LogDebug should be EDM_ML_DEBUG.  Since this is now to be disconnected from
NDEBUG and other such symbols, we can use it directly.  The  
MIGHTSUPPRESSLOGDEBUG is an internal to the MessageLogger, but those too
should start with EDM_ML_; a good name is EDM_ML_ENABLED_LOGDEBUG.  But we
don't even need that extra level of "indirection" since we are already 
going to a #define for LogDebug(id).  So the code would look like:

  #ifndef EDM_ML_DEBUG 
  #define LogDebug(id) true ? edm::Suppress_LogDebug_() : edm::Suppress_LogDebug_() 
  #else
  #define LogDebug(id) \\
  	isDebugEnabled() ? edm::Suppress_LogDebug_() : \\
	 edm::LogDebug_(id, __FILE__, __LINE__) 
  #endif

However, the dynamic case of this (if EDM_ML_DEBUG is defined) is not quite
correct C++, since the two possible values for the ?: operator have different
types which cannot be converted to one another.  


Here is the "Aha!" moment:  Since in each of the cases where debug is disabled,
there is nothing in the expression after Suppress_LogDebug_(), there would
be no harm in using LogDebug() -- the default ctor -- instead.   The insight 
is that even though nothing was done to make the operator<< be effortless,
that is fine because the operator<< won't appear in executed code!

  #ifndef EDM_ML_DEBUG 
  #define LogDebug(id) true ? edm::Suppress_LogDebug_() : edm::Suppress_LogDebug_() 
  #else
  #define LogDebug(id) \\
  	isDebugEnabled() ? edm::LogDebug() : \\
	 edm::LogDebug_(id, __FILE__, __LINE__) 
  #endif

Finally, because these techniques use preprocessor macros, we need to 
look at what happens if the user statement is broken into multiple lines.
As our example:

  LogDebug ("cat") << foo(objectA )
                   << objectB;

In the absense of EDM_ML_DEBUG this would become

  true ?edm::SuppressLogDebug_() : edm::SuppressLogDebug_() << foo(objectA)
                    << objectB;

which is fine.  If EDM_ML_DEBUG is defined it becomes

  !debugEnabled ? edm::SuppressLogDebug_() : \\
  edm::LogDebug_(id, __FILE__, __LINE__) << foo(objectA) 
	             << objectB;

which is also fine.

Effect on "message blocks"
--------------------------

A technique recommended if the user needs to prepare a message in a loop
or with conditional code looks like:

  if (isInfoEnabled()) { 
    edm::LogInfo m("cat");
    for (i=0; i<imax; ++i) m << foo(i);
  } // m goes out of scope, causing the message to be sent.
  
Let's see what happens when we use that with LogDebug instead, in light of 
these macros.     

  if (isDebugEnabled()) { 
    LogDebug m("cat");
    for (i=0; i<imax; ++i) m << foo(i);
  } // m goes out of scope, causing the message to be sent.
  
becomes  a pre-processing error whether or not EDM_ML_DEBUG is defined 
(since LogDebug is a macro requiring an argument, but LogDebug m("cat")
breaks that up at the m).  Pre-processor changes in the next version of C++
might allow us to do something more robust, but we need to provide a mantra
that works now.

We will need to recommend a different mantra for LogDebug.  The following
will work:

  #ifdef EDM_ML_DEBUG
  if (isDebugEnabled()) { 
    edm::LogDebug_ m (LogDebug ("cat"));
    for (i=0; i<imax; ++i) m << foo(i);
  } // m goes out of scope, causing the message to be sent.
  #endif
  
If EDM_ML_DEBUG is defined that becomes 

  if (isDebugEnabled()) { 
    edm::LogDebug_ m (edm::LogDebug_("cat", __FILE__, __LINE__));
    for (i=0; i<imax; ++i) m << foo(i);
  } // m goes out of scope, causing the message to be sent.
  
while if EDM_ML_DEBUG is not defined that becomes nothing at all.

We will need to document this technique difference for LogDebug.

By the way, is right to inline the functions isDebugEnabled(), 
isInfoEnabled() and isWarningEnabled(), to make this quicker.
In particular, isDebugEnabled() is important since it will be in the 
path of dynamically suppressed LogDebug's.

Strategy for testing EDM_ML_DEBUG
---------------------------------

Existing tests (as of Sep 2010) assumed LogDebug would be enabled buy default.
This was about to change, so naively the tests would need to be modified to 
reflect the change.  

A first step was to temporarily define EDM_ML_DEBUG in MessageLogger.h
just to see that existing unit tests wre not directly broken by the changes
made, in circumstances where the behavior should not have changed.  That worked
fine.  

Next we want to see whether our mantra for users to enable LogDebug will work.
So we do a build with the right define on the build line and see if the unit
tests still pass.  
  scram b clean; scram b USER_CXXFLAGS="-DEDM_ML_DEBUG"
worked.  Without the clean, scram does not 

Next, we eliminated that temporary define and had to look at the outputs of all
unit tests that broke.  For the ones breaking in the now-proper way, we
moved the new correct outputs into the output area.  But this is not sufficient:
it would leave us with unit tests that completely skip testiong LogDebug.

The last step is to create tests that work with LogDebug enabled, and there
are two sorts of these tests:  a) Those where LogDebugs are still suppressed
by configuration; b) those where LogDebugs should cause output.  Fortunately,
once we enable LogDebug, both sorts are tested by our suite, because typical
unit tests produce both a file with LogDebug, and file(s) with higher
thresholds.  But how to force the define of DEDM_ML_DEBUG in some parts
of the build but not others?  That can be done in the buildfile, via 
flags lines.  We choose some of the unit tests to do those on.  

Where is it determined that u1, for instance, works with UnitTestClient_A?
Certainly not in u1.cpp or u1.sh, which are trivial.  It is in u1_cfg.py,
where we have 
  process.sendSomeMessages = cms.EDAnalyzer("UnitTestClient_A")
So if we wish to create a debug-enabled test corresponding to u1, we should
be able to copy u1_cfg.py to u1d_cfg.py, change the UnitTestClient_A to
UnitTestClient_Ad, and in the Buildfile.xml, have something like the lines
  <library   file="UnitTestClient_A.cc" name="UnitTestClient_A">
    <flags   EDM_PLUGIN="1" -DEDM_ML_DEBUG/>
    <use   name="FWCore/MessageLogger"/>
    <use   name="FWCore/Framework"/>
  </library>
Unfortunately that does not work (parse error on -DEDM_ML_DEBUG) and after a few
more tries, it seemed wrong to continue wandering around without documentation.
Instead of changing the build file, we can make a new client source  
UnitTestClient_Ad.cc, identical to UnitTestClient_A.cc but with 
#define EDM_ML_DEBUG at the top.


Optimization when no threshold is low enough
--------------------------------------------

There was as of 9/21/110 no mechanism to disable LogInfo if all thresholds are 
higher than logInfo, and similarly for logWarning (not sure if the issue
is relevant for logDebug).  This could be a huge savings.  Not easy to do,
but should not be too tricky.  

The tough part is that while looking for suppressInfo in the configuration 
is easy enough, seeing whether any destination has a high enough threshold
is involved, so that it needs to be done in the configuration code that is
down in the scribe.  So the strategy will have to be setting up a vairiable
that is used in the scribe thread to set the field in the MessageDrop, just
as when a suppressed module is entered.  

The appropriate variables really are statics in MessageDrop, and we name them
debugAlwaysSuppressed, infoAlwaysSuppressed, warningAlwaysSuppressed.
The usage for the LogInfo and LogWarning cases is that the ctor looks like
  explicit LogInfo( std::string const & id ) 
    : ap ( (!MessageDrop::infoAlwaysSuppressed 		// Change log 21
            && edm::MessageDrop::instance()->infoEnabled) ?
           new MessageSender(ELinfo,id) : 0 )
Observe that since !MessageDrop::infoAlwaysSuppressed is checked first, 
in the important case of universal suppression, the overhead for the
MessageDrop::instance() call is not incurred -- this is the secondary 
performance improvement.  (The main improvement is that in config files which
do not explicitly suppress INFO, but where no thresholds are as low as INFO,
the info messages get suppressed anyway.)

The usage is a bit different for the debug case:  The define line (in the
case where EDM_ML_DEBUG is defined so that LogDebug is not out of the question)
becomes:

#define LogDebug(id) \\
(edm::MessageDrop::debugAlwaysSuppressed \\
|| !edm::MessageDrop::debugEnabled) ? \\
edm::LogDebug_() : edm::LogDebug_(id, __FILE__, __LINE__) 

and, since if  the logDebug is going to be suppressed (for whichever reason)
the default ctor of LogDebug_ is called, which sets the class instance variable
debugEnabled to false, nothing needs to be changed in the LogDebug_ class.
(For reasons of paranoia, in the meaty ctor for LogDebug_, we do initialize 
debugEnabled to !debugAlwaysSuppressed rather than true, but this may be
overkill.)

That leaves the issue of how (and where) to establish the values of 
debugAlwaysSuppressed and its bretheren.  Since for EVERY destination 
configure_dest (in the scribe) sets the threshold severity explicitly,
this is a common choke point, at which we can adjust these variables.
What that implies is that they must start off true, and become false 
when some destination is configured to have a low enough threshold.
Actually, just in case some path avoids configuration altogether, we
start these off as false by initialization, change them to true once
configure_ordinary_destinations() is reached, and then change them in
configure_dest() if and when a low enough threshold is encountered.

Having implemented these changes, we now wish to find a clean way of
testing whether the changes were effective.  This is harder than it
looks, since the suprressed messages were not going to have any effect
even before this performance-only improvement.  And one would like to
make this a test that can be part of the test suite.

The technique to do this testing is as follows:

1) Add an extra class in MessageLogger.h, namely 
   LogWarningThatSuppressesLikeLogInfo.  To protect
   against some user using this, hide it in a sub-namespace
   edm::edmtest.  This class does its test for suppression
   using infoEnabled but uses a severity level of LogWarning.
   
2) Create a unit test that does some LogWarning's and some 
   LogInfo's, and also some LogWarningThatSuppressesLikeLogInfo's.
   
3) Create a cfg file (A) with one destination,which sets a 
   threshold of LogWarning, and another (B) with two destinations, one
   of which has threshold of LogWarning, the other LogInfo.  Each 
   uses this new unit test.
   
4) In cmsRun (A), no trace of the LogWarningThatSuppressesLikeLogInfo
   should appear.  In cmsRun (B), it should appear in the both destinations,
   as a %MSG-w message (warning).

Possible further improvement opportunities
------------------------------------------

The initial value of MessageDrop->debugEnabled (in MessageDrop.cc) is
true.  This is perhaps relevant in cases where debug is overall enabled,
and one is not in any module.  So you run with debug on, and if the 
framework itself has LogDebugs in it, you can't suppress them without
suppressing all debug messages.  It might be better to make this false.
(Trivial change to make.)  It might also be desirable to have some way
to turn on/off non-module LogDebugs.  However, this issue is very likely 
moot since users don't write non-module code, and probably framework 
people are not leaving many LogDebugs around.

---

Currently, LogDebug depends on the define EDM_ML_DEBUG at header time.
So the user who wants to define this symbol must do so before the header,
and has control only on a per-file basis.  It might be better to give users
finer control by putting the #ifdef inside the definition of LogDebug, as in
  #define LogDebug(id) \\
  #ifndef EDM_ML_DEBUG \\
    true ? edm::Suppress_LogDebug_() : edm::Suppress_LogDebug_() \\
  #else \\
  #define LogDebug(id) \\
  	isDebugEnabled() ? edm::LogDebug() : \\
	 edm::LogDebug_(id, __FILE__, __LINE__) \\
  #endif
This of course is not quite right, and the business of line ends and
continuations might be a severe pain in the neck, which is why we have not 
made this improvement.

---

This is not strictly speaking only a suppression issue but is in the same code
area.  Currently every time a module is entered or exitted, a call to
MessageDrop::instance() is made to tell where to put information such as
the suppression variables and the module label.  Although nobody is currently
griping about these calls, they may be taking non-negligible time.
If the user could declare "no need for module names in messages" then
when there is also no ML configuration information that is module sensitive 
(that is, any suppress or enable directives use "*"), then we can do without
this instance() call.  Some simple decisions would need to be made, such as 
what module name to output or whether to modify ELoutput to eliminate the 
module name when this option is chosen; and the option would have to be coded
into the configuration and validation and the documentation and into the 
establish_module routines (or into the code that puts the establish_module
routine onto the callback stack in the first place; probably the whole callback
would now be superfluous).

This would be several day's work but might be worthwhile.

---