123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809 |
|
/*******************************************************************************
copyright: Copyright (c) 2004 Kris Bell. All rights reserved
license: BSD style: $(LICENSE)
version: May 2004 : Initial release
version: Oct 2004: Hierarchy moved due to circular dependencies
version: Apr 2008: Lazy delegates removed due to awkward usage
author: Kris
Simplified, pedestrian usage:
---
import tango.util.log.Config;
Log ("hello world");
Log ("temperature is {} degrees", 75);
---
Generic usage:
Loggers are named entities, sometimes shared, sometimes specific to
a particular portion of code. The names are generally hierarchical in
nature, using dot notation (with '.') to separate each named section.
For example, a typical name might be something like "mail.send.writer"
---
import tango.util.log.Log;
auto log = Log.lookup ("mail.send.writer");
log.info ("an informational message");
log.error ("an exception message: {}", exception);
etc ...
---
It is considered good form to pass a logger instance as a function or
class-ctor argument, or to assign a new logger instance during static
class construction. For example: if it were considered appropriate to
have one logger instance per class, each might be constructed like so:
---
private Logger log;
static this()
{
log = Log.lookup (nameOfThisClassOrStructOrModule);
}
---
Messages passed to a Logger are assumed to be either self-contained
or configured with "{}" notation a la Layout & Stdout:
---
log.warn ("temperature is {} degrees!", 101);
---
Note that an internal workspace is used to format the message, which
is limited to 2000 bytes. Use "{.256}" truncation notation to limit
the size of individual message components, or use explicit formatting:
---
char[4096] buf = void;
log.warn (log.format (buf, "a very long message: {}", someLongMessage));
---
To avoid overhead when constructing arguments passed to formatted
messages, you should check to see whether a logger is active or not:
---
if (log.warn)
log.warn ("temperature is {} degrees!", complexFunction());
---
tango.log closely follows both the API and the behaviour as documented
at the official Log4J site, where you'll find a good tutorial. Those
pages are hosted over
<A HREF="http://logging.apache.org/log4j/docs/documentation.html">here</A>.
*******************************************************************************/
module tango.util.log.Log;
private import tango.sys.Common;
private import tango.time.Clock;
private import tango.core.Exception;
private import tango.io.model.IConduit;
private import tango.text.convert.Format;
private import tango.util.log.model.ILogger;
/*******************************************************************************
Platform issues ...
*******************************************************************************/
version (GNU)
{
private import tango.core.Vararg;
alias void* Arg;
alias va_list ArgList;
}
else version (DigitalMars)
{
private import tango.core.Vararg;
alias void* Arg;
alias va_list ArgList;
version(X86_64) version = DigitalMarsX64;
}
else
{
alias void* Arg;
alias void* ArgList;
}
/*******************************************************************************
Pull in additional functions from the C library
*******************************************************************************/
extern (C)
{
private int memcmp (const(void) *, const(void) *, size_t);
}
version (Win32)
{
private extern(Windows) int QueryPerformanceCounter(ulong *count);
private extern(Windows) int QueryPerformanceFrequency(ulong *frequency);
}
/*******************************************************************************
These represent the standard LOG4J event levels. Note that
Debug is called Trace here, because debug is a reserved word
in D
*******************************************************************************/
alias ILogger.Level Level;
/*******************************************************************************
Manager for routing Logger calls to the default hierarchy. Note
that you may have multiple hierarchies per application, but must
access the hierarchy directly for root() and lookup() methods within
each additional instance.
*******************************************************************************/
public struct Log
{
// support for old API
public alias lookup getLogger;
// trivial usage via opCall
public alias formatln opCall;
// internal use only
private static __gshared Hierarchy base;
private static __gshared Time beginTime;
version (Win32)
{
private static __gshared double multiplier;
private static __gshared ulong timerStart;
}
private struct Pair {const(char)[] name; Level value;}
private static __gshared Level [immutable(char)[]] map;
private static __gshared Pair[] Pairs =
[
{"TRACE", Level.Trace},
{"Trace", Level.Trace},
{"trace", Level.Trace},
{"INFO", Level.Info},
{"Info", Level.Info},
{"info", Level.Info},
{"WARN", Level.Warn},
{"Warn", Level.Warn},
{"warn", Level.Warn},
{"ERROR", Level.Error},
{"Error", Level.Error},
{"error", Level.Error},
{"Fatal", Level.Fatal},
{"FATAL", Level.Fatal},
{"fatal", Level.Fatal},
{"NONE", Level.None},
{"None", Level.None},
{"none", Level.None},
];
// logging-level names
private immutable char[][] LevelNames =
[
"Trace", "Info", "Warn", "Error", "Fatal", "None"
];
/***********************************************************************
Initialize the base hierarchy
***********************************************************************/
shared static this ()
{
base = new Hierarchy ("tango");
foreach (p; Pairs)
map[p.name] = p.value;
version (Posix)
{
beginTime = Clock.now;
}
version (Win32)
{
ulong freq;
if (! QueryPerformanceFrequency (&freq))
throw new PlatformException ("high-resolution timer is not available");
QueryPerformanceCounter (&timerStart);
multiplier = cast(double) TimeSpan.TicksPerSecond / freq;
beginTime = Clock.now;
}
}
/***********************************************************************
Return the level of a given name
***********************************************************************/
static Level convert (const(char[]) name, Level def=Level.Trace)
{
auto p = name in map;
if (p)
return *p;
return def;
}
/***********************************************************************
Return the current time
***********************************************************************/
@property static Time time ()
{
version (Posix)
{
return Clock.now;
}
version (Win32)
{
ulong now;
QueryPerformanceCounter (&now);
return beginTime + TimeSpan(cast(long)((now - timerStart) * multiplier));
}
}
/***********************************************************************
Return the root Logger instance. This is the ancestor of
all loggers and, as such, can be used to manipulate the
entire hierarchy. For instance, setting the root 'level'
attribute will affect all other loggers in the tree.
***********************************************************************/
@property static Logger root ()
{
return base.root;
}
/***********************************************************************
Return an instance of the named logger. Names should be
hierarchical in nature, using dot notation (with '.') to
separate each name section. For example, a typical name
might be something like "tango.io.Stdout".
If the logger does not currently exist, it is created and
inserted into the hierarchy. A parent will be attached to
it, which will be either the root logger or the closest
ancestor in terms of the hierarchical name space.
***********************************************************************/
static Logger lookup (const(char[]) name)
{
return base.lookup (name);
}
/***********************************************************************
Return text name for a log level
***********************************************************************/
static const(char)[] convert (int level)
{
assert (level >= Level.Trace && level <= Level.None);
return LevelNames[level];
}
/***********************************************************************
Return the singleton hierarchy.
***********************************************************************/
static Hierarchy hierarchy ()
{
return base;
}
/***********************************************************************
Pedestrian usage support, as an alias for Log.root.info()
***********************************************************************/
static void formatln (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
version(GNU) {} else va_start(ap, __va_argsave);
scope(exit) va_end(ap);
root.format (Level.Info, fmt, _arguments, ap);
}
else
root.format (Level.Info, fmt, _arguments, _argptr);
}
/***********************************************************************
Initialize the behaviour of a basic logging hierarchy.
Adds a StreamAppender to the root node, and sets
the activity level to be everything enabled.
***********************************************************************/
static void config (OutputStream stream, bool flush = true)
{
root.add (new AppendStream (stream, flush));
}
}
/*******************************************************************************
Loggers are named entities, sometimes shared, sometimes specific to
a particular portion of code. The names are generally hierarchical in
nature, using dot notation (with '.') to separate each named section.
For example, a typical name might be something like "mail.send.writer"
---
import tango.util.log.Log;format
auto log = Log.lookup ("mail.send.writer");
log.info ("an informational message");
log.error ("an exception message: {}", exception.toString);
etc ...
---
It is considered good form to pass a logger instance as a function or
class-ctor argument, or to assign a new logger instance during static
class construction. For example: if it were considered appropriate to
have one logger instance per class, each might be constructed like so:
---
private Logger log;
static this()
{
log = Log.lookup (nameOfThisClassOrStructOrModule);
}
---
Messages passed to a Logger are assumed to be either self-contained
or configured with "{}" notation a la Layout & Stdout:
---
log.warn ("temperature is {} degrees!", 101);
---
Note that an internal workspace is used to format the message, which
is limited to 2048 bytes. Use "{.256}" truncation notation to limit
the size of individual message components. You can also use your own
formatting buffer:
---
log.buffer (new char[](4096));
log.warn ("a very long warning: {}", someLongWarning);
---
Or you can use explicit formatting:
---
char[4096] buf = void;
log.warn (log.format (buf, "a very long warning: {}", someLongWarning));
---
To avoid overhead when constructing argument passed to formatted
messages, you should check to see whether a logger is active or not:
---
if (log.enabled (log.Warn))
log.warn ("temperature is {} degrees!", complexFunction());
---
The above will be handled implicitly by the logging system when
macros are added to the language (used to be handled implicitly
via lazy delegates, but usage of those turned out to be awkward).
tango.log closely follows both the API and the behaviour as documented
at the official Log4J site, where you'll find a good tutorial. Those
pages are hosted over
<A HREF="http://logging.apache.org/log4j/docs/documentation.html">here</A>.
*******************************************************************************/
public class Logger : ILogger
{
alias Level.Trace Trace; // shortcut to Level values
alias Level.Info Info; // ...
alias Level.Warn Warn; // ...
alias Level.Error Error; // ...
alias Level.Fatal Fatal; // ...
alias append opCall; // shortcut to append
/***********************************************************************
Context for a hierarchy, used for customizing behaviour
of log hierarchies. You can use this to implement dynamic
log-levels, based upon filtering or some other mechanism
***********************************************************************/
interface Context
{
/// return a label for this context
@property const const(char)[] label ();
/// first arg is the setting of the logger itself, and
/// the second arg is what kind of message we're being
/// asked to produce
const bool enabled (Level setting, Level target);
}
/***********************************************************************
***********************************************************************/
private Logger next,
parent;
private Hierarchy host_;
private const(char)[] name_;
private Level level_;
private bool additive_;
private Appender appender_;
private char[] buffer_;
private size_t buffer_size_;
/***********************************************************************
Construct a LoggerInstance with the specified name for the
given hierarchy. By default, logger instances are additive
and are set to emit all events.
***********************************************************************/
private this (Hierarchy host, const(char[]) name)
{
host_ = host;
level_ = Level.Trace;
additive_ = true;
name_ = name;
}
/***********************************************************************
Is this logger enabed for the specified Level?
***********************************************************************/
final bool enabled (Level level = Level.Fatal)
{
return host_.context.enabled (level_, level);
}
/***********************************************************************
Is trace enabled?
***********************************************************************/
final bool trace ()
{
return enabled (Level.Trace);
}
/***********************************************************************
Append a trace message
***********************************************************************/
final void trace (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
format (Level.Trace, fmt, _arguments, ap);
}
else
format (Level.Trace, fmt, _arguments, _argptr);
}
/***********************************************************************
Is info enabled?
***********************************************************************/
final bool info ()
{
return enabled (Level.Info);
}
/***********************************************************************
Append an info message
***********************************************************************/
final void info (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
format (Level.Info, fmt, _arguments, ap);
}
else
format (Level.Info, fmt, _arguments, _argptr);
}
/***********************************************************************
Is warn enabled?
***********************************************************************/
final bool warn ()
{
return enabled (Level.Warn);
}
/***********************************************************************
Append a warning message
***********************************************************************/
final void warn (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
format (Level.Warn, fmt, _arguments, ap);
}
else
format (Level.Warn, fmt, _arguments, _argptr);
}
/***********************************************************************
Is error enabled?
***********************************************************************/
final bool error ()
{
return enabled (Level.Error);
}
/***********************************************************************
Append an error message
***********************************************************************/
final void error (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
format (Level.Error, fmt, _arguments, ap);
}
else
format (Level.Error, fmt, _arguments, _argptr);
}
/***********************************************************************
Is fatal enabled?
***********************************************************************/
final bool fatal ()
{
return enabled (Level.Fatal);
}
/***********************************************************************
Append a fatal message
***********************************************************************/
final void fatal (const(char[]) fmt, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
format (Level.Fatal, fmt, _arguments, ap);
}
else
format (Level.Fatal, fmt, _arguments, _argptr);
}
/***********************************************************************
Return the name of this Logger (sans the appended dot).
***********************************************************************/
@property final const(char)[] name ()
{
size_t i = name_.length;
if (i > 0)
--i;
return name_[0 .. i];
}
/***********************************************************************
Return the Level this logger is set to
***********************************************************************/
final Level level ()
{
return level_;
}
/***********************************************************************
Set the current level for this logger (and only this logger).
***********************************************************************/
final Logger level (Level l)
{
return level (l, false);
}
/***********************************************************************
Set the current level for this logger, and (optionally) all
of its descendents.
***********************************************************************/
final Logger level (Level level, bool propagate)
{
level_ = level;
if (propagate)
foreach (log; host_)
if (log.isChildOf (name_))
log.level_ = level;
return this;
}
/***********************************************************************
Is this logger additive? That is, should we walk ancestors
looking for more appenders?
***********************************************************************/
@property final const bool additive ()
{
return additive_;
}
/***********************************************************************
Set the additive status of this logger. See bool additive().
***********************************************************************/
@property final Logger additive (bool enabled)
{
additive_ = enabled;
return this;
}
/***********************************************************************
Add (another) appender to this logger. Appenders are each
invoked for log events as they are produced. At most, one
instance of each appender will be invoked.
***********************************************************************/
final Logger add (Appender another)
{
assert (another);
another.next = appender_;
appender_ = another;
return this;
}
/***********************************************************************
Remove all appenders from this Logger
***********************************************************************/
final Logger clear ()
{
appender_ = null;
return this;
}
/***********************************************************************
Get the current formatting buffer (null if none).
***********************************************************************/
@property final char[] buffer ()
{
return buffer_;
}
/***********************************************************************
Set the current formatting buffer.
Set to null to use the default internal buffer.
***********************************************************************/
@property final Logger buffer (char[] buf)
{
buffer_ = buf;
buffer_size_ = buf.length;
return this;
}
/***********************************************************************
Get time since this application started
***********************************************************************/
@property final const TimeSpan runtime ()
{
return Clock.now - Log.beginTime;
}
/***********************************************************************
Send a message to this logger via its appender list.
***********************************************************************/
final Logger append (Level level, lazy const(char[]) exp)
{
if (host_.context.enabled (level_, level))
{
LogEvent event;
// set the event attributes and append it
event.set (host_, level, exp, name.length ? name_[0..$-1] : "root");
append (event);
}
return this;
}
/***********************************************************************
Send a message to this logger via its appender list.
***********************************************************************/
private void append (LogEvent event)
{
// combine appenders from all ancestors
auto links = this;
Appender.Mask masks = 0;
do {
auto appender = links.appender_;
// this level have an appender?
while (appender)
{
auto mask = appender.mask;
// have we visited this appender already?
if ((masks & mask) is 0)
// is appender enabled for this level?
if (appender.level <= event.level)
{
// append message and update mask
appender.append (event);
masks |= mask;
}
// process all appenders for this node
appender = appender.next;
}
// process all ancestors
} while (links.additive_ && ((links = links.parent) !is null));
}
/***********************************************************************
Return a formatted string from the given arguments
***********************************************************************/
final char[] format (char[] buffer, const(char[]) formatStr, ...)
{
version (DigitalMarsX64)
{
va_list ap;
va_start(ap, __va_argsave);
scope(exit) va_end(ap);
return Format.vprint (buffer, formatStr, _arguments, ap);
}
else
return Format.vprint (buffer, formatStr, _arguments, _argptr);
}
/***********************************************************************
Format and emit text from the given arguments
***********************************************************************/
final Logger format (Level level, const(char[]) fmt, TypeInfo[] types, ArgList args)
{
if (types.length)
{
if (buffer_ is null)
formatWithDefaultBuffer(level, fmt, types, args);
else
formatWithProvidedBuffer(level, fmt, types, args);
}
else
append (level, fmt);
return this;
}
private void formatWithDefaultBuffer(Level level, const(char)[] fmt, TypeInfo[] types, ArgList args)
{
char[2048] tmp = void;
formatWithBuffer(level, fmt, types, args, tmp);
}
private void formatWithProvidedBuffer(Level level, const(char)[] fmt, TypeInfo[] types, ArgList args)
{
formatWithBuffer(level, fmt, types, args, buffer_);
buffer_.length = buffer_size_;
}
private void formatWithBuffer(Level level, const(char)[] fmt, TypeInfo[] types, ArgList args, char[] buf)
{
append (level, Format.vprint (buf, fmt, types, args));
}
/***********************************************************************
See if the provided Logger name is a parent of this one. Note
that each Logger name has a '.' appended to the end, such that
name segments will not partially match.
***********************************************************************/
private final bool isChildOf (const(char[]) candidate)
{
auto len = candidate.length;
// possible parent if length is shorter
if (len < name_.length)
// does the prefix match? Note we append a "." to each
// (the root is a parent of everything)
return (len is 0 ||
memcmp (&candidate[0], &name_[0], len) is 0);
return false;
}
/***********************************************************************
See if the provided Logger is a better match as a parent of
this one. This is used to restructure the hierarchy when a
new logger instance is introduced
***********************************************************************/
private final bool isCloserAncestor (Logger other)
{
auto name = other.name_;
if (isChildOf (name))
// is this a better (longer) match than prior parent?
if ((parent is null) || (name.length >= parent.name_.length))
return true;
return false;
}
}
/*******************************************************************************
The Logger hierarchy implementation. We keep a reference to each
logger in a hash-table for convenient lookup purposes, plus keep
each logger linked to the others in an ordered group. Ordering
places shortest names at the head and longest ones at the tail,
making the job of identifying ancestors easier in an orderly
fashion. For example, when propagating levels across descendents
it would be a mistake to propagate to a child before all of its
ancestors were taken care of.
*******************************************************************************/
private class Hierarchy : Logger.Context
{
private Logger root_;
private const(char)[] name_,
address_;
private Logger.Context context_;
private Logger[char[]] loggers;
/***********************************************************************
Construct a hierarchy with the given name.
***********************************************************************/
this (const(char[]) name)
{
name_ = name;
address_ = "network";
// insert a root node; the root has an empty name
root_ = new Logger (this, "");
context_ = this;
}
/**********************************************************************
**********************************************************************/
final const const(char)[] label ()
{
return "";
}
/**********************************************************************
**********************************************************************/
final const bool enabled (Level level, Level test)
{
return test >= level;
}
/**********************************************************************
Return the name of this Hierarchy
**********************************************************************/
@property final const const(char)[] name ()
{
return name_;
}
/**********************************************************************
Set the name of this Hierarchy
**********************************************************************/
@property final void name (const(char[]) name)
{
name_ = name;
}
/**********************************************************************
Return the address of this Hierarchy. This is typically
attached when sending events to remote monitors.
**********************************************************************/
@property final const const(char)[] address ()
{
return address_;
}
/**********************************************************************
Set the address of this Hierarchy. The address is attached
used when sending events to remote monitors.
**********************************************************************/
@property final void address (const(char[]) address)
{
address_ = address;
}
/**********************************************************************
Return the diagnostic context. Useful for setting an
override logging level.
**********************************************************************/
@property final Logger.Context context ()
{
return context_;
}
/**********************************************************************
Set the diagnostic context. Not usually necessary, as a
default was created. Useful when you need to provide a
different implementation, such as a ThreadLocal variant.
**********************************************************************/
@property final void context (Logger.Context context)
{
context_ = context;
}
/***********************************************************************
Return the root node.
***********************************************************************/
@property final Logger root ()
{
return root_;
}
/***********************************************************************
Return the instance of a Logger with the provided label. If
the instance does not exist, it is created at this time.
Note that an empty label is considered illegal, and will be
ignored.
***********************************************************************/
final Logger lookup (const(char[]) label)
{
if (label.length)
return inject (label, (const(char[]) name)
{return new Logger (this, name);});
return null;
}
/***********************************************************************
traverse the set of configured loggers
***********************************************************************/
final int opApply (scope int delegate(ref Logger) dg)
{
int ret;
for (auto log=root; log; log = log.next)
if ((ret = dg(log)) != 0)
break;
return ret;
}
/***********************************************************************
Return the instance of a Logger with the provided label. If
the instance does not exist, it is created at this time.
***********************************************************************/
private Logger inject (const(char[]) label, scope Logger delegate(const(char[]) name) dg)
{
synchronized(this)
{
// try not to allocate unless you really need to
char[255] stack_buffer;
char[] buffer = stack_buffer;
if (buffer.length < label.length + 1)
buffer.length = label.length + 1;
buffer[0 .. label.length] = label[];
buffer[label.length] = '.';
auto name = buffer[0 .. label.length + 1];
auto l = name in loggers;
if (l is null)
{
// don't use the stack allocated buffer
if (name.ptr is stack_buffer.ptr)
name = name.dup;
// create a new logger
auto li = dg(name);
l = &li;
// insert into linked list
insert (li);
// look for and adjust children. Don't force
// property inheritance on existing loggers
update (li);
// insert into map
loggers [name.idup] = li;
}
return *l;
}
}
/***********************************************************************
Loggers are maintained in a sorted linked-list. The order
is maintained such that the shortest name is at the root,
and the longest at the tail.
This is done so that updateLoggers() will always have a
known environment to manipulate, making it much faster.
***********************************************************************/
private void insert (Logger l)
{
Logger prev,
curr = root;
while (curr)
{
// insert here if the new name is shorter
if (l.name.length < curr.name.length)
if (prev is null)
throw new IllegalElementException ("invalid hierarchy");
else
{
l.next = prev.next;
prev.next = l;
return;
}
else
// find best match for parent of new entry
// and inherit relevant properties (level, etc)
propagate (l, curr, true);
// remember where insertion point should be
prev = curr;
curr = curr.next;
}
// add to tail
prev.next = l;
}
/***********************************************************************
Propagate hierarchical changes across known loggers.
This includes changes in the hierarchy itself, and to
the various settings of child loggers with respect to
their parent(s).
***********************************************************************/
private void update (Logger changed, bool force=false)
{
foreach (logger; this)
propagate (logger, changed, force);
}
/***********************************************************************
Propagate changes in the hierarchy downward to child Loggers.
Note that while 'parent' is always changed, the adjustment of
'level' is selectable.
***********************************************************************/
private void propagate (Logger logger, Logger changed, bool force=false)
{
// is the changed instance a better match for our parent?
if (logger.isCloserAncestor (changed))
{
// update parent (might actually be current parent)
logger.parent = changed;
// if we don't have an explicit level set, inherit it
// Be careful to avoid recursion, or other overhead
if (force)
logger.level_ = changed.level();
}
}
}
/*******************************************************************************
Contains all information about a logging event, and is passed around
between methods once it has been determined that the invoking logger
is enabled for output.
Note that Event instances are maintained in a freelist rather than
being allocated each time, and they include a scratchpad area for
EventLayout formatters to use.
*******************************************************************************/
package struct LogEvent
{
private const(char)[] msg_,
name_;
private Time time_;
private Level level_;
private Hierarchy host_;
/***********************************************************************
Set the various attributes of this event.
***********************************************************************/
void set (Hierarchy host, Level level, const(char[]) msg, const(char[]) name)
{
time_ = Log.time;
level_ = level;
host_ = host;
name_ = name;
msg_ = msg;
}
/***********************************************************************
Return the message attached to this event.
***********************************************************************/
const const(char)[] toString ()
{
return msg_;
}
/***********************************************************************
Return the name of the logger which produced this event
***********************************************************************/
@property const const(char)[] name ()
{
return name_;
}
/***********************************************************************
Return the logger level of this event.
***********************************************************************/
@property const Level level ()
{
return level_;
}
/***********************************************************************
Return the hierarchy where the event was produced from
***********************************************************************/
@property Hierarchy host ()
{
return host_;
}
/***********************************************************************
Return the time this event was produced, relative to the
start of this executable
***********************************************************************/
@property const TimeSpan span ()
{
return time_ - Log.beginTime;
}
/***********************************************************************
Return the time this event was produced relative to Epoch
***********************************************************************/
@property const const(Time) time ()
{
return time_;
}
/***********************************************************************
Return time when the executable started
***********************************************************************/
@property const(Time) started ()
{
return Log.beginTime;
}
/***********************************************************************
Return the logger level name of this event.
***********************************************************************/
@property const(char)[] levelName ()
{
return Log.LevelNames[level_];
}
/***********************************************************************
Convert a time value (in milliseconds) to ascii
***********************************************************************/
static char[] toMilli (char[] s, TimeSpan time)
{
assert (s.length > 0);
long ms = time.millis;
size_t len = s.length;
do {
s[--len] = cast(char)(ms % 10 + '0');
ms /= 10;
} while (ms && len);
return s[len..s.length];
}
}
/*******************************************************************************
Base class for all Appenders. These objects are responsible for
emitting messages sent to a particular logger. There may be more
than one appender attached to any logger. The actual message is
constructed by another class known as an EventLayout.
*******************************************************************************/
public class Appender
{
alias int Mask;
private Appender next_;
private Level level_;
private Layout layout_;
private static __gshared Layout generic;
/***********************************************************************
Interface for all logging layout instances
Implement this method to perform the formatting of
message content.
***********************************************************************/
interface Layout
{
void format (LogEvent event, scope size_t delegate(const(void)[]) dg);
}
/***********************************************************************
Return the mask used to identify this Appender. The mask
is used to figure out whether an appender has already been
invoked for a particular logger.
***********************************************************************/
@property abstract const Mask mask ();
/***********************************************************************
Return the name of this Appender.
***********************************************************************/
@property abstract const const(char)[] name ();
/***********************************************************************
Append a message to the output.
***********************************************************************/
abstract void append (LogEvent event);
/***********************************************************************
Create an Appender and default its layout to LayoutSimple.
***********************************************************************/
this ()
{
layout_ = generic;
}
/***********************************************************************
Create an Appender and default its layout to LayoutSimple.
***********************************************************************/
shared static this ()
{
generic = new LayoutTimer;
}
/***********************************************************************
Return the current Level setting
***********************************************************************/
@property final Level level ()
{
return level_;
}
/***********************************************************************
Return the current Level setting
***********************************************************************/
@property final Appender level (Level l)
{
level_ = l;
return this;
}
/***********************************************************************
Static method to return a mask for identifying the Appender.
Each Appender class should have a unique fingerprint so that
we can figure out which ones have been invoked for a given
event. A bitmask is a simple an efficient way to do that.
***********************************************************************/
protected Mask register (const(char)[] tag)
{
static Mask mask = 1;
static Mask[immutable(char)[]] registry;
Mask* p = tag in registry;
if (p)
return *p;
else
{
auto ret = mask;
registry [tag] = mask;
if (mask < 0)
throw new IllegalArgumentException ("too many unique registrations");
mask <<= 1;
return ret;
}
}
/***********************************************************************
Set the current layout to be that of the argument, or the
generic layout where the argument is null
***********************************************************************/
@property void layout (Layout how)
{
layout_ = how ? how : generic;
}
/***********************************************************************
Return the current Layout
***********************************************************************/
@property Layout layout ()
{
return layout_;
}
/***********************************************************************
Attach another appender to this one
***********************************************************************/
@property void next (Appender appender)
{
next_ = appender;
}
/***********************************************************************
Return the next appender in the list
***********************************************************************/
@property Appender next ()
{
return next_;
}
/***********************************************************************
Close this appender. This would be used for file, sockets,
and such like.
***********************************************************************/
void close ()
{
}
}
/*******************************************************************************
An appender that does nothing. This is useful for cutting and
pasting, and for benchmarking the tango.log environment.
*******************************************************************************/
public class AppendNull : Appender
{
private Mask mask_;
/***********************************************************************
Create with the given Layout
***********************************************************************/
this (Layout how = null)
{
mask_ = register (name);
layout (how);
}
/***********************************************************************
Return the fingerprint for this class
***********************************************************************/
@property override final const Mask mask ()
{
return mask_;
}
/***********************************************************************
Return the name of this class
***********************************************************************/
@property override final const const(char)[] name ()
{
return this.classinfo.name;
}
/***********************************************************************
Append an event to the output.
***********************************************************************/
override final void append (LogEvent event)
{
layout.format (event, (const(void)[]){return cast(size_t) 0;});
}
}
/*******************************************************************************
Append to a configured OutputStream
*******************************************************************************/
public class AppendStream : Appender
{
private Mask mask_;
private bool flush_;
private OutputStream stream_;
/***********************************************************************
Create with the given stream and layout
***********************************************************************/
this (OutputStream stream, bool flush = false, Appender.Layout how = null)
{
assert (stream);
mask_ = register (name);
stream_ = stream;
flush_ = flush;
layout (how);
}
/***********************************************************************
Return the fingerprint for this class
***********************************************************************/
@property override final const Mask mask ()
{
return mask_;
}
/***********************************************************************
Return the name of this class
***********************************************************************/
@property override const const(char)[] name ()
{
return this.classinfo.name;
}
/***********************************************************************
Append an event to the output.
***********************************************************************/
override final void append (LogEvent event)
{
version(Win32)
immutable char[] Eol = "\r\n";
else
immutable char[] Eol = "\n";
synchronized (stream_)
{
layout.format (event, (const(void)[] content){return stream_.write(content);});
stream_.write (Eol);
if (flush_)
stream_.flush();
}
}
}
/*******************************************************************************
A simple layout comprised only of time(ms), level, name, and message
*******************************************************************************/
public class LayoutTimer : Appender.Layout
{
/***********************************************************************
Subclasses should implement this method to perform the
formatting of the actual message content.
***********************************************************************/
void format (LogEvent event, scope size_t delegate(const(void)[]) dg)
{
char[20] tmp = void;
dg (event.toMilli (tmp, event.span));
dg (" ");
dg (event.levelName);
dg (" [");
dg (event.name);
dg ("] ");
dg (event.host.context.label);
dg ("- ");
dg (event.toString());
}
}
/*******************************************************************************
*******************************************************************************/
debug (Log)
{
import tango.io.Console;
void main()
{
Log.config (Cerr.stream);
auto log = Log.lookup ("fu.bar");
log.level = log.Trace;
// traditional usage
log.trace ("hello {}", "world");
char[100] buf;
log (log.Trace, log.format(buf, "hello {}", "world"));
// formatted output
/* /
auto format = Log.format;
log.info (format ("blah{}", 1));
// snapshot
auto snap = Log.snapshot (log, Level.Error);
snap.format ("arg{}; ", 1);
snap.format ("arg{}; ", 2);
//log.trace (snap.format ("error! arg{}", 3));
snap.flush;
*/
}
}
|