A simple yet powerful .NET logging library.
Tacitus logger helps to organize your logging process to save vital execution information of your application to the variety of logging destinations.
Attention: TacitusLogger is currently in Alpha phase. This means you should not use it in any production code.
The NuGet package:
PM> Install-Package TacitusLogger
Dependencies:
- NET Standard >= 1.3
- Newtonsoft.Json >= 9.0.1
Due to implemented convention over configuration principle, using TacitusLogger could be as simple as:
// Configure and build your logger.
ILogger logger = LoggerBuilder.Logger().ForAllLogs()
.File().WithPath(@".\logs.txt").Add()
.BuildLogger();
// Write your logs.
logger.LogError("Something bad happened!");
// Or write them asynchronously.
await logger.LogErrorAsync("Something bad happened!");
TacitusLogger provides an opportunity to:
- Use several built-in, various extension and custom log destinations.
- Group log destinations to several units called log groups.
- Configure each log group with rule predicate to filter out logs that is eligible to be sent to this log group.
- Configure each log group with log cache.
- Select built-in or use custom destination feeding strategy for each log group.
- Select built-in or use custom exception handling strategy for the logger.
- Use built-in and custom log contributors to add runtime info to your logs.
- Use built-in and custom log transformers to modify all logs from single place.
- Use built-in or custom log ID generator to enrich logs with IDs.
- Use built-in or custom log serializers to get different textual representation of the log for each log destination.
- Change some settings like logger log level, log group status in runtime without restarting the app.
- More examples
- Ways to write logs
- Examples of logger configuration
- Custom log text template
- Several log groups
- Constant log group status
- Log group with status that can be changed at runtime
- Logger with log caching
- Logger with custom log cache
- Logger with several groups each with its own log cache
- Logger with log contributors
- Logger with custom log contributor
- Logger with log transformers
- Logger with custom log transformers
- Logger destination feeding strategy
- Logger custom destination feeding strategy
- Logger custom log creation strategy
- Logger exception handling strategy
- Logger exception handling strategy of type Log
- Configuring logger with the custom exception handling strategy
- Logger with constant log level
- Logger with mutable log level that can be changed at runtime
- Custom log serializer implementation
- GUID based log ID
- Null log ID
- Custom log ID generator implementation
- More advanced configuration
- Main components and definitions
- General log flow of TacitusLogger
In TacitusLogger there are several convenient styles to write logs to the logger:
logger.LogInfo("Operation has completed.");
logger.LogError("Something bad has happened!");
logger.Log("UserController", LogType.Info, "Request from the client", new { data1 = "", data2 = "" });
logger.Log("UserService", LogType.Error, "Some error occurred");
logger.Error("Some error occurred")
.From(this)
.WithEx(exception)
.Tagged("Error", "Exception", "Bug")
.Log();
await logger.Error("Some error occurred")
.From(this)
.WithEx(exception)
.Tagged("Error", "Exception", "Bug")
.LogAsync();
Log error = Log.Error("Some error occurred")
.From(this)
.WithEx(exception)
.Tagged("Error", "Exception", "Bug");
error.To(logger);
Log error = Log.Error("Some error occurred")
.From(this)
.WithEx(exception)
.Tagged("Error", "Exception", "Bug");
await error.ToAsync(logger);
In the simplest form, logger could contain a single log group that proceeds all logs. The following code snippet the logger with a single log group containing one ConsoleDestination
is configured to take all logs:
ILogger logger = LoggerBuilder.Logger().ForAllLogs()
.Console().Add()
.BuildLogger();
Actually, more precise syntax for this configuration would be like the following:
ILogger logger = LoggerBuilder.Logger().NewLogGroup()
.ForAllLogs()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
Nevertheless, when you have a logger that contains only one log group, you can omit NewLogGroup()
and BuildLogGroup()
.
In this example logger is configured with single log group containing two log destinations - Console and Debug:
ILogger logger = LoggerBuilder.Logger("App1").ForAllLogs()
.Console().Add()
.Debug().Add()
.BuildLogger();
Same example:
ILoggerBuilder loggerBuilder = LoggerBuilder.Logger("App1");
ILogGroupDestinationsBuilder groupDestinations = loggerBuilder.ForAllLogs();
groupDestinations.Console().Add();
groupDestinations.Debug().Add();
groupDestinations.BuildLogGroup();
Logger logger = loggerBuilder.BuildLogger();
In this example file destination is configured with file path template that will be used to save logs. Template contains placeholders $Source
, $LogType
, $LogDate(dd-MM-yyyy)
that will be replaced with according properties of log models thus forming various file paths. If file exists it will be appended, otherwise, a new file will be created with all preceding directories. When selecting file path template make sure the application has the according write permissions.
ILogger logger = LoggerBuilder.Logger("Main logger").ForAllLogs()
.File().WithPath(@".\$Source\$LogType logs\Logs-$LogDate(dd-MM-yyyy).log")
.Add()
.BuildLogger();
ILogger logger = LoggerBuilder.Logger().ForAllLogs()
.Console().WithSimpleTemplateLogText("Log of type: $LogType, Description: $Description, Date: $LogDate")
.Add()
.BuildLogger();
Here a logger with two log groups is configured. First log group named Not important
receives Info
type logs and sends them to console and debug destinations. Second log group - Important
writes Error
, Failure
and Critical
logs to file and console.
ILogger logger = LoggerBuilder.Logger().NewLogGroup("Not important")
.ForInfoLogs()
.Console().Add()
.Debug().Add()
.BuildLogGroup()
.NewLogGroup("Important")
.ForRule(x => x.LogTypeIsIn(LogType.Error, LogType.Failure, LogType.Critical))
.File().WithPath(@".\ImportantLogs.txt").Add()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
Same configuration as above:
var loggerBuilder = LoggerBuilder.Logger();
var notImpLogs = loggerBuilder.NewLogGroup("Not important");
var notImpLogsDestinations = notImpLogs.ForInfoLogs();
notImpLogsDestinations.Console().Add();
notImpLogsDestinations.Debug().Add();
notImpLogsDestinations.BuildLogGroup();
var impLogsGroup = loggerBuilder.NewLogGroup("Important");
var impLogsDestinations = impLogsGroup.ForRule(x => x.LogTypeIsIn(LogType.Error, LogType.Failure, LogType.Critical));
impLogsDestinations.File().WithPath(@".\ImportantLogs.txt").Add();
impLogsDestinations.Console().Add();
impLogsDestinations.BuildLogGroup();
ILogger logger = loggerBuilder.BuildLogger();
In the following code snippet log group "group1"
is configured to be inactive:
var logGroupStatus = LogGroupStatus.Inactive;
ILogger logger = LoggerBuilder.Logger().NewLogGroup("group1")
.SetStatus(logGroupStatus)
.ForInfoLogs()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
In this example value provider of type TacitusLogger.MutableSetting<LogGroupStatus>
is used to manage the value for log group status. After building the logger the value provider used to change log group status to Inactive
at runtime:
MutableSetting<LogGroupStatus> logGroupStatus = Setting<LogGroupStatus>.From.Variable(LogGroupStatus.Active);
ILogger logger = LoggerBuilder.Logger().NewLogGroup("group1")
.SetStatus(logGroupStatus)
.ForInfoLogs()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
logger.LogInfo("This log will be processed by group1");
logGroupStatus.SetValue(LogGroupStatus.Inactive);
logger.LogInfo("This log will not, because group1 is now inactive");
The following example configures log caching for the log group "Groups with cache"
: Cache size to 20 logs and cache time to 60 seconds.
ILogger logger = LoggerBuilder.Logger("Main logger").NewLogGroup("Groups with cache")
.WithCaching(20, 60000, isActive: true)
.ForAllLogs()
.Console().Add()
.BuildLogger();
In this example the custom log cache implementation is registered with the log group "Groups with cache"
.
ILogCache customLogCache = new MyCustomLogCache();
ILogger logger = LoggerBuilder.Logger("Main logger").NewLogGroup("Groups with cache")
.WithCaching(customLogCache, isActive: true)
.ForAllLogs()
.Console().Add()
.BuildLogger();
Each log group can have its own caching configuration:
ILogger logger = LoggerBuilder.Logger("Main logger").NewLogGroup("Group1")
.WithCaching(100)
.ForAllLogs()
.File()
.WithPath(@".\AllLogs.txt")
.Add()
.BuildLogGroup()
.NewLogGroup("Group2")
.WithCaching(10)
.ForErrorLogs()
.File()
.WithPath(@".\Errors.txt")
.Add()
.BuildLogGroup()
.BuildLogger();
The following code snippet adds StackTraceContributor
log contributor to the logger. Log contributors add additional specific data to each log:
ILogger logger = LoggerBuilder.Logger()
.Contributors()
.StackTrace()
.BuildContributors()
.NewLogGroup("group1")
.ForAllLogs()
.File()
.WithPath(@".\logs.txt")
.Add()
.BuildLogGroup()
.BuildLogger();
LogContributorBase customContributor = new MyCustomContributor();
ILogger logger = LoggerBuilder.Logger()
.LogContributors()
.Custom(customContributor)
.BuildContributors()
.NewLogGroup("group1")
.ForAllLogs()
.File().WithPath(@".\logs.txt").Add()
.BuildLogGroup()
.BuildLogger();
The following code snippet adds TacitusLogger.Transformers.StringsManualTransformer
log transformer to the logger. Log transformers allow to modify all logs before they are sent to log groups. In this case StringsManualTransformer
is used to modify all strings to lower case (Note that no null checks are performed inside the delegate, that is because it StringsManualTransformer
filters out null strings and does not sends them to the delegate):
ILogger logger = LoggerBuilder.Logger()
.Transformers()
.StringsManual((ref string s) => s.ToLower(), true, "Lowercase")
.BuildTransformers()
.NewLogGroup("group1")
.ForAllLogs()
.Console()
.Add()
.BuildLogGroup()
.BuildLogger();
In the following example in addition to the transformer from the previous example, another custom transformer is added to the logger:
var myCustomLogTransformer = new MyCustomLogTransformer("some transformer");
ILogger logger = LoggerBuilder.Logger()
.Transformers()
.StringsManual((ref string s) => s.ToLower(), true, "Lowercase")
.Custom(myCustomLogTransformer, true)
.BuildTransformers()
.NewLogGroup("group1")
.ForAllLogs()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
Each log group has its own destination feeding strategy which defines how log group sends provided logs to its destinations. There are two built-in strategies - Greedy and FirstSuccess. If not specified, greedy is the default strategy for all log groups. The following example configures two log groups with feeding strategies specified explicitly:
ILogger logger = LoggerBuilder.Logger()
.NewLogGroup("non-greedy group")
.WithDestinationFeeding(DestinationFeedingStrategy.FirstSuccess)
.ForAllLogs()
.File().WithPath(@".\logs1.txt").Add()
.File().WithPath(@".\logs2.txt").Add()
.BuildLogGroup()
.NewLogGroup("greedy group")
.WithDestinationFeeding(DestinationFeedingStrategy.Greedy)
.ForErrorLogs()
.File().WithPath(@".\errors.txt").Add()
.Console().Add()
.BuildLogGroup()
.BuildLogger();
It is possible to implement your own destination feeding strategy and use it with log groups as in the following code snippet:
DestinationFeedingStrategyBase customDestinationFeedingStrategy = new CustomDestinationFeedingStrategy();
ILogger logger = LoggerBuilder.Logger()
.NewLogGroup("group1")
.WithDestinationFeeding(customDestinationFeedingStrategy)
.ForAllLogs()
.File()
.WithPath(@".\logs1.txt")
.Add()
.File()
.WithPath(@".\logs2.txt")
.Add()
.BuildLogGroup()
.BuildLogger();
Creation strategy defines how an instance of TacitusLogger.LogModel
is created. You can override the default log creation strategy by creating your own one and providing it to logger builder:
LogCreationStrategyBase customLogCreationStrategy = new CustomLogCreationStrategy();
ILogger logger = LoggerBuilder.Logger()
.WithLogCreation(customLogCreationStrategy)
.ForAllLogs()
.Console().Add()
.BuildLogger();
Exception handling strategy defines how the logger copes with its own exceptions. There are three built-in strategies: Silent
, Log
and Rethrow
. If not specified, Silent
is the default strategy for all loggers. In the following code snippet the logger is explicitly coed with the exception handling strategy:
ILogger logger = LoggerBuilder.Logger()
.WithExceptionHandling(ExceptionHandling.Rethrow)
.ForAllLogs()
.Console().Add()
.BuildLogger();
In the following example the exception handling strategy of type Log
is used. Please note, that along to the exception handling strategy the diagnostics destination also should be set as a place where the error logs from the strategy will be directed; otherwise, Log
exception handling strategy will behave just like the Silent
:
FileDestination diagnosticsDestination = new FileDestination("./Logger-Errors.txt");
ILogger logger = LoggerBuilder.Logger()
.WithDiagnostics(diagnosticsDestination)
.WithExceptionHandling(ExceptionHandling.Log)
.ForAllLogs()
.Console().Add()
.BuildLogger();
You can implement and register your own exception handling strategy as shown in the following example:
ExceptionHandlingStrategyBase customExceptionHandlingStrategy = new CustomExceptionHandlingStrategy();
ILogger logger = LoggerBuilder.Logger()
.WithExceptionHandling(customExceptionHandlingStrategy)
.ForAllLogs()
.Console().Add()
.BuildLogger();
Log level shows the minimal value of log type that will be proceeded by the logger. The following example sets log level to Warning
which means that logs with log level less than Warning
(that are Info
, Success
and Event
) will be ignored by the logger.
LogLevel logLevel = LogLevel.Warning;
ILogger logger = LoggerBuilder.Logger()
.WithLogLevel(logLevel)
.ForAllLogs()
.Console().Add()
.BuildLogger();
If you intend to modify logger's log level during the runtime, you can use VariableValueProvider<LogLevel>
wrapper as shown in the following example. Using this class allows you to modify log level without restarting the application.
MutableSetting<LogLevel> logLevel = Setting<LogLevel>.From.Variable(LogLevel.Info);
ILogger logger = LoggerBuilder.Logger()
.WithLogLevel(logLevel)
.ForAllLogs()
.Console().Add()
.BuildLogger();
logger.LogInfo("This log will be processed by logger");
logLevel.SetValue(LogLevel.None);
logger.LogInfo("This log will be ignored");
Log serializers are used by destinations that need some textual representation of log models. For example ConsoleDestination
needs that log text to send it to Console, FileDestination
- to generate file path and log text to save it to the file etc.
ILogSerializer myCustomLogSerializer = new MyCustomLogSerializer();
ILogger logger = LoggerBuilder.Logger().ForAllLogs()
.File().WithLogSerializer(myCustomLogSerializer)
.Add()
.BuildLogger();
Log ID generator is used by logger to add string
log IDs to log models. The default log ID generator for the logger is TacitusLogger.LogIdGenerators.GuidLogIdGenerator
and it will be added if no log ID generator is specified explicitly. Nevertheless, if you want to add it with some customizations like different GUID format or substring length you may register it explicitly as shown in the following example:
ILogger logger = LoggerBuilder.Logger().WithGuidLogId("N", 6)
.ForAllLogs()
.File().WithPath(@".\logs.txt").Add()
.BuildLogger();
In some cases you do not want your logs to contain any log IDs. In this case you may consider registering the logger with TacitusLogger.LogIdGenerators.NullLogIdGenerator
. NullLogIdGenerator
is a typical implementation of null object pattern and always generates NULL log IDs:
ILogger logger = LoggerBuilder.Logger().WithNullLogId()
.ForAllLogs()
.File().WithPath(@".\logs.txt").Add()
.BuildLogger();
If you need some custom log ID generation logic you can create and register your own log ID generator as in the following example:
ILogIdGenerator myCustomLogIdGenerator = new MyCustomLogIdGenerator();
ILogger logger13 = LoggerBuilder.Logger().WithLogIdGenerator(myCustomLogIdGenerator)
.ForAllLogs()
.File().WithPath(@".\logs.txt").Add()
.BuildLogger();
This is more advanced logger configuration that combines most of the previous ones:
var debugLogGroupStatus = LogGroupStatus.Inactive;
var userServiceLogGroupStatus = LogGroupStatus.Inactive;
ILogger logger = LoggerBuilder.Logger("App1 logs")
.Contributors()
.StackTrace()
.BuildContributors()
.Transformers()
.StringsManual((ref string s) => s.ToLower(), true, "Lowercase")
.BuildTransformers()
.WithExceptionHandling(ExceptionHandling.Silent)
.WithLogCreation(LogCreation.Standard)
.WithLogIdGenerator(new MyCustomLogIdGenerator())
.NewLogGroup("Debug")
.SetStatus(debugLogGroupStatus)
.ForAllLogs()
.Debug()
.WithSimpleTemplateLogText("$LogTypeLog: $Description | Date: $LogDate | Id: $LogId(8)")
.Add()
.BuildLogGroup()
.NewLogGroup("UserService logs")
.SetStatus(userServiceLogGroupStatus)
.ForRule(x => x.Context != null && x.Context.Contains("UserService"))
.File()
.WithPath(@".\UserServiceLogs-$LogDate(dd-MM-yyyy).log")
.WithExtendedTemplateLogText()
.Add()
.BuildLogGroup()
.NewLogGroup("Informational logs")
.WithCaching(30, 60000)
.ForRule(x => x.LogType == LogType.Info || x.LogType == LogType.Event || x.LogType == LogType.Warning)
.File()
.WithPath(@".\InformationalLogs-$LogDate(dd-MM-yyyy).log")
.WithGeneratorFuncLogText(x => $"Id: {x.LogId} | {x.LogTypeName}Log: {x.Description} | Date: {x.LogDate.ToString("dd-MM-yyyy hh.mm.ss")}")
.Add()
.Console()
.WithGeneratorFuncLogText(x => $"{x.LogTypeName}Log: {x.Description}")
.Add()
.BuildLogGroup()
.NewLogGroup("Important logs")
.ForRule(x => x.LogType == LogType.Error || x.LogType == LogType.Critical)
.File()
.WithPath(@".\$LogTypeLogs-$LogDate(dd-MM-yyyy).log")
.WithGeneratorFuncLogText(x => $"{x.LogTypeName}Log: {x.Description} | Date: {x.LogDate.ToString("dd-MM-yyyy hh.mm.ss")}")
.Add()
.BuildLogGroup()
.BuildLogger();
The central definition in TacitusLogger library is the Logger - an implementation of TacitusLogger.ILogger
interface:
public interface ILogger
{
string Log(Log log);
Task<string> LogAsync(Log log, CancellationToken cancellationToken = default(CancellationToken));
}
TacitusLogger contains an only implementation of this interface - TacitusLogger.Logger
class which is the default logger. In all further documentation unless explicitly stated otherwise, when we say logger we mean exactly this implementation of the TacitusLogger.ILogger
interface.
Logger is the main entry point for all writing logs and usually it is the only dependency you want to inject into your classes. Logger's creation could be an expensive operation (depending on used components) and it's lifetime should be singleton. Its implementation of ILogger
interface if fully thread safe, which means you are free to use singleton logger as an ILogger
from several threads. As a contrary, logger's configuration (adding log groups, destinations etc) is not thread safe.
TacitusLogger.Log
class contains user-provided information about the logging event. These are the properties shown in the following listing:
public class Log
{
// Constructors go here...
public string Context { get; }
public LogType Type { get; }
public string Description { get; }
public IList<string> Tags { get; }
public IList<LogItem> Items { get; }
// Other members go here...
}
Lets discuss them briefly:
TacitusLogger.LogType
is an enum
defining the following names:
public enum LogType
{
Info = 10,
Success = 11,
Event = 12,
Warning = 20,
Error = 30,
Failure = 31,
Critical = 32
}
Every log has its type. Log type classify log events to 7 categories that show what type of event is being described:
Log Type | Value | Description |
---|---|---|
Info | 10 | Informational logs. |
Success | 11 | Logs representing successful operation. |
Event | 12 | Logs representing some unexceptional event occurrence. |
Warning | 20 | Warning logs. |
Error | 30 | Error logs. |
Failure | 31 | Logs representing failure. |
Critical | 32 | Logs representing some critical situations. |
As with all Log
properties, log type meaning is closely related to user's interpretations - it is up to user, who decides, for example, what is difference between Error logs and Failure logs.
There is another enum closely related with LogType
- LogLevel
that will be discussed #####.
String property containing log event description.
Log context is a string property that is used to provide information related to the place and circumstances of the logging event. It can contain any string representation of what user considers as "context of the logging event" - usually these are class and method names, or some custom piece of data. Well chosen log context easily localize "the place" of the event thus easing troubleshooting.
Tags are supposed as short strings that beautify the log with additional information useful in the following classification and research. Every log contains a Tags
property - a list of tags, that can contain any number of tag strings, meaningful to logger user. For convenience, TacitusLogger comes with common tag strings in class TacitusLogger.Common.Tags
.
In the most circumstances log description string is not enough to describe the log event: Usually you want to include additional information in form of textual data, various objects, exceptions and so on. In TacitusLogger this could be achieved by means of Log items. Every log object contains a list of LogItem
objects which store some peace of named data. Every log item has Name
property that describes the item and Value
property that contains related data.
public class LogItem
{
public LogItem(string name, object value);
public string Name { get; }
public object Value { get; }
// Methods go here
}
Final representation of log item's value could be various and completely depends on log serializer. Usually, when textual representation is needed, log items values are represented in JSON format.
While log event contains user provided information about the event, log model contains full information about the writing log ready to be consumed by log destinations. Log model is defined by TacitusLogger.LogModel
class, which have the following fields:
public class LogModel
{
// Other members goes here
public string LogId;
public string Context;
public string[] Tags;
public string Source;
public LogType LogType;
public string Description;
public LogItem[] LogItems;
public DateTime LogDate;
// Other members goes here
}
As you can see, additional information includes: LogId
, Source
and LogDate
. Besides that, LogItems
may contain additional items if logger is configured with log contributors (for additional information see log contributors section of this document).
Often when dealing with logs you want them to contain some sort of ID information to identify them later. This can be achieved with string LogId
property and log ID generators. When creating LogModel object from provided Log
object, the logger (More precisely, log creation strategy assigned to logger) populates LogId
property with string ID using assigned log ID generator. More on log ID generators and log creation strategies see below.
Source property represents more global information about where the logging event is originated from. In default implementation this is the name of the logger which has produced current log. This means that if you have several loggers (probably, from the different applications) which send logs to the same destination, you can name them in some way that will help you when dealing with logs.
LogDate
, obviously, is a DateTime field that contains date of creation of log model. As with log id, this field is set by log creation strategy assigned to each logger. Depending on settings, it could hold local or UTC date and time.
In TacitusLogger log destination is an object that represents a target where the log will be written to. It could be a file system, console, email, database etc. Log destinations are feeded with LogModel
models and encapsulate all logic related to how these models will be transformed to specific form (file record, console output, email body and subject, db record etc) and sent to the specified targets - the logger have no idea about destination-specific logic. It is important to note that log destinations NEVER mutate provided log models. Violating this rule when implementing your custom destinations can lead to various undesirable consequences. You can register as many log destinations as you need with a single logger.
All log destinations implement the TacitusLogger.Destinations.ILogDestination
interface:
public interface ILogDestination : IDisposable
{
void Send(LogModel[] logs);
Task SendAsync(LogModel[] logs, CancellationToken cancellationToken = default(CancellationToken));
}
ILogDestination
interface contains two methods, both of them takes the LogModel[]
models array. Second method is an asynchronous counterpart of first one and takes an optional cancellation token parameter.
The TacitusLogger has 4 built-in log destinations:
TacitusLogger.Destinations.Console.ConsoleDestination
TacitusLogger.Destinations.File.FileDestination
TacitusLogger.Destinations.Debug.DebugDestination
TacitusLogger.Destinations.TextWriter.TextWriterDestination
First one, ConsoleDestination
, writes logs to the standard output device. FileDestination
writes to file system, DebugDestination
writes to the System.Diagnostics.Debug
, TextWriterDestination
as the names implies, writes to the given System.IO.TextWriter
.
Logger does not own and manage log destinations by self. There are intermediate players - log groups which we are going to introduce next.
Log groups represents an additional layer in TacitusLogger which gives the potential to configure loggers to more advanced log flow including log filtering and caching. Each log group contains log group status which can be used to toggle the group on-off. Every log group implements the TacitusLogger.LogGroupBase
abstract class:
public abstract class LogGroupBase: IDisposable
{
public abstract string Name { get; }
public abstract LogGroupStatus Status { get; }
public abstract bool IsEligible(LogModel log);
public abstract void Send(LogModel log);
public abstract Task SendAsync(LogModel log, CancellationToken cancellationToken = default(CancellationToken));
public virtual void Dispose();
}
TacitusLogger contains an only implementation of this interface - TacitusLogger.LogGroup
class which is the default implementation. In all further documentation unless explicitly stated otherwise, when we say log group we mean exactly this implementation of the TacitusLogger.LogGroupBase
abstract class.
Every log group has its name that can be useful during the configuration process. If log group name was not specified during the creation of log group, a random GUID-based name will be used. Name should be unique within the given logger which means no two log groups with the same name can be attached to the same logger. Log groups names do not affect the log models in any way.
Every log group has its status of enum type TacitusLogger.LogGroupStatus
:
public enum LogGroupStatus
{
Active = 0,
Inactive
}
Only active groups are considered by logger for log sending, inactive groups are ignored.
If not specified explicitly, by default, all log groups are created with Active
status.
The main purpose of log groups is to group log destinations by filtering rule. Here is how it works. Every log group contains a rule predicate which is simply a delegate of type TacitusLogger.LogModelFunc<bool>
that takes LogModel
object as a single parameter and returns the Boolean
. When the Log
object has been received and LogModel
object has been constructed, the logger iterates over all active log groups and checks their eligibility to take the log model by calling its IsEligible()
method. It is inside this method where the log rule predicate is executed and its Boolean
result is returned to the logger.
The strategy of sending logs to destinations inside a single log group is implemented by means of destination feeding strategies assigned to that log group. List of possible strategies could be found in TacitusLogger.DestinationFeeding
enum:
public enum DestinationFeeding
{
Greedy = 0,
FirstSuccess
}
Currently, there are two destination feeding strategies out of box in TacitusLogger - Greedy
and FirstSuccess
strategies. If not specified explicitly, by default, the greedy strategy is used in all log groups and as its name implies, it sends logs to all log destinations registered with the given log group. As the opposite, the FirstSuccess strategy keeps sending logs to destinations until first successful send operation. Sending operation is considered successful if no exception was thrown by log destination methods. In other words, it iterates through log destinations list of its log group consequently sending log models to destinations until it reaches the first destination that does not throw an exception or until destinations list is exhausted depending which happens first. After that, it stops iterating and returns, leaving all subsequent log destinations unfed if there are any.
You can also implement your own feeding strategy by implementing TacitusLogger.Strategies.DestinationFeeding.DestinationFeedingStrategyBase
abstract class:
public abstract class DestinationFeedingStrategyBase
{
public abstract void Feed(LogModel[] logs, IList<ILogDestination> logDestinations);
public abstract Task FeedAsync(LogModel[] logs, IList<ILogDestination> logDestinations, CancellationToken cancellationToken = default(CancellationToken));
}
This strategy could be useful if you have several destination one of which is primary and others just in case if first one fails to write log.
Also, you should take into consideration that some times a destination could fail silently without throwing. For example a destination that sends mails using SMTP server may not know that the mail it sent failed to reach the recipient and will not throw any exception. In such circumstances the FirstSuccess
strategy, obviously, will behave not as expected to.
Another thing that is done on the log group level is log caching. Usually when there are a lot of logs per minute, sending each log to log destinations could be expensive. Especially if writing to destinations is related with opening-closing and holding resources. This is where log caching can help. When log group has cache activated, it serves logs differently. Instead of sending each log to destinations immediately after receiving, log group places them into the cache. Each time the cache receives a log model it checks fulfillment of some condition (for example the specific amount of logs have been cached or specified cache time has been expired) and if the check succeeds it immediately flushes returning all collected logs which are then sent to destinations at once within an array (saying it more precisely, not to destinations directly but to destination feeding strategy object). An important thing here to note is that sending logs to destinations at once in the form of array by no means guarantee that the given destination will not process them separately. Performance gain here is strictly related to log destination type and its implementation. For some types of destinations logs caching could be completely useless, for example for destination that sends emails, or destination that writes to console. But in many cases caching could be quite useful in terms of performance, for example if the destination writes to the file (or files) or to MongoDb database.
As it was mentioned, log caching is performed on log group level, that is every log group could have its own cache configured specifically or disabled.
By default, if not enabled and configured explicitly, log caching is disabled for all created log groups.
All log caches implement TacitusLogger.LogCaching.ILogCache
interface:
public interface ILogCache : IDisposable
{
LogModel[] AddToCache(LogModel logModel, bool forceToFlush = false);
}
TacitusLogger contains only one implementation of ILogCache
that is TacitusLogger.LogCaching.InMemoryLogCache
that is configured to flush on the specific amount of logs have been cached or specified cache time has been expired depending which happens first.
Custom implementations of ILogCache
have to implement AddToCache
in a way that:
- When called with
forceToFlush = false
it should return either log data array (including the current log) when flushed or null otherwise. The moment and reason of flushing is related to implementation. - When called with
forceToFlush = true
it should always flush. - Flushing should clear the inner collection of the cache.
To summarize, every log group has:
- Unique name that could be used during configuration.
- Status, which could be
Active
orInactive
. Only active groups are considered by logger as targets for logs, inactive log groups are ignored. - Rule predicate which is used to decide if this log group is eligible to take the given log data model.
- Destination feeding strategy which defines how the log group should feed its log destinations when provided by log data model.
- Optional log cache which delays destination feeding until some time in future, for example, until some amount of them will have been collected in cache.
After the logger gets Log
object that contains log event data provided by user, it should create LogModel
object which contains complete picture about the logging event and which will be delivered to log destinations through the TacitusLogger pipeline. The TacitusLogger currently contains only one log creation strategy - Standard
:
public enum LogCreation
{
Standard = 0,
}
This log creation strategy is implemented in TacitusLogger.Strategies.LogCreation.StandardLogCreationStrategy
.
The Standard
log creation strategy works this way: it creates an instance of LogModel
populates it with data from the Log
object, populates Source
property with the provided one, adds log date using an instance of TacitusLogger.Components.Time.ITimeProvider
and considering UseUtcTime
flag provided during the logger configuration; if logger was configured with (active) log contributors, the strategy retrieves their log items and adds to the LogItems
array of the log model.
As it was mentioned above time provider that is an implementation of TacitusLogger.Components.Time.ITimeProvider
is used by standard log creation strategy to set log dates:
public interface ITimeProvider
{
DateTime GetLocalTime();
DateTime GetUtcTime();
}
In some situations (for example while testing custom log destinations) you may want to change the default time provider that is TacitusLogger.Components.Time.SystemTimeProvider
to some custom implementation. You can do this by using ResetTimeProvider
method of TacitusLogger.Strategies.LogCreation.StandardLogCreationStrategy
object.
If you need some custom logic for creating LogModel
instances, you can create and register your own implementation of log creation strategy by deriving from TacitusLogger.Strategies.LogCreation.LogCreationStrategyBase
abstract class:
public abstract class LogCreationStrategyBase
{
protected ILogIdGenerator _logIdGenerator;
protected IList<LogContributorBase> _logContributors;
protected ExceptionHandlingStrategyBase _exceptionHandlingStrategy;
public LogCreationStrategyBase();
public ILogIdGenerator LogIdGenerator { get; }
public IEnumerable<LogContributorBase> LogContributors { get; }
public ExceptionHandlingStrategyBase ExceptionHandlingStrategy { get; }
public abstract LogModel CreateLogModel(Log log, string source);
public abstract Task<LogModel> CreateLogModelAsync(Log log, string source, CancellationToken cancellationToken = default(CancellationToken));
public void InitStrategy(ILogIdGenerator logIdGenerator, IList<LogContributorBase> logContributors, ExceptionHandlingStrategyBase exceptionHandlingStrategy);
}
As you see from the above snippet, to do this you need to implement two abstract methods - CreateLogModel
and CreateLogModelAsync
.
As it was mentioned all logs contain LogId field of type string
. It is not mandatory for logs to have this field populated - it can contain null
value for all logs. Nevertheless, in most situations you want your logs to contain some sort of ID to identify logs. Log ID generator as the name implies, used to generate string log IDs that are added to logs. If not specified explicitly, TacitusLogger.LogIdGenerators.GuidLogIdGenerator
is used with the logger by default.
All log ID generators implement TacitusLogger.LogIdGenerators.ILogIdGenerator
interface:
public interface ILogIdGenerator: IDisposable
{
string Generate(LogModel logModel);
Task<string> GenerateAsync(LogModel logModel, CancellationToken cancellationToken = default(CancellationToken));
}
Log ID generator is used by log creation strategy when creating LogModel
objects from Log
objects. With default log creation strategy, by the time when log ID generator methods are called and provided with LogModel
object, the latter already contains all other data with the log ID being the only missing datum. This means that in your custom log ID generator implementations you can leverage all LogModel
fields if you need them during the log generation.
This interface contains two methods - the second one is an asynchronous counterpart of the first one. They take log model and returns generated log id. Please take into consideration that these methods should not mutate log model taken. They are provided with log model only to have the opportunity to use it for log id string generation.
Asynchronous counterpart is called when asynchronous method of ILogger
is called. In most cases when creating custom log id generator your log generation represents a quick synchronous operation and you do not want to implement and fill GenerateAsync(...)
method. For such cases you can inherit from SynchronousLogIdGeneratorBase
abstract class. This class has the only abstract methods to be overridden - Generate(...)
. Async counterpart of Generate(...)
method has already implemented within the abstract class itself to call Generate(...)
synchronously and to return a completed task afterwards:
public abstract class SynchronousLogIdGeneratorBase : ILogIdGenerator
{
public abstract string Generate(LogModel logModel);
public Task<string> GenerateAsync(LogModel logModel, CancellationToken cancellationToken = default(CancellationToken));
public abstract void Dispose();
}
TacitusLogger provides the ability to log different information regarding to the logger's itself health. Every logger has diagnostics manager of type TacitusLogger.Diagnostics.DiagnosticsManagerBase
that is used by logger itself or injected into its exception handling strategy. If not specified explicitly, by default, it is TacitusLogger.Diagnostics.DiagnosticsManager
. Besides the diagnostics manager, logger can be provided with diagnostics destination using SetDiagnosticsDestination(...)
method of logger class or WithDiagnostics(...)
. If set, this destination is used by diagnostics manager to send logs related to the logger itself, which could be logger start configuration description and logger errors sent by exception handling strategy.
Exception handling strategy defines how the logger should handle its own exceptions in Log(...)
and LogAsync(...)
methods. There are three built-in exception handling strategies as seen from TacitusLogger.ExceptionHandling
:
public enum ExceptionHandling
{
Silent = 0,
Log = 1,
Rethrow = 2
}
Just keep in mind that exception handling strategy handles an exceptions only in Log(...)
and LogAsync(...)
methods. Particularly, it does not affect exceptions that is thrown during configuration of the logger.
Silent
exception handling strategy is implemented inTacitusLogger.Strategies.ExceptionHandling.SilentExceptionHandlingStrategy
class. WhenSilent
strategy is used, all exceptions that flows up toLog(...)
andLogAsync(...)
methods from logger's components are swallowed.Log
exception handling strategy is implemented inTacitusLogger.Strategies.ExceptionHandling.LogExceptionHandlingStrategy
class. When using this strategy, exceptions caught inLog(...)
andLogAsync(...)
methods are send to logger's self monitoring destination, which was discussed above. This means that to use this strategy your logger should have self monitoring destination set. Otherwise,Log
strategy, not having destination to sends its logs, will behave just likeSilent
. Also consider that if self monitoring destination throws any exception when being feed byLog
strategy that exceptions will be swallowed by the strategy.Rethrow
implemented inTacitusLogger.Strategies.ExceptionHandling.RethrowExceptionHandlingStrategy
class just rethrows all exception that are caught inLog(...)
andLogAsync(...)
methods wrapped inTacitusLogger.Exceptions.LoggerException
class.
Important note: The only exception that is not affected by exception handling strategy is System.OperationCanceledException
thrown from LogAsync(...)
. It is always rethrown and there is no way to silence or log it.
If the above strategies does not fit your needs, you can create your own exception handling strategy by deriving from TacitusLogger.Strategies.ExceptionHandling.ExceptionHandlingStrategyBase
abstract class:
public abstract class ExceptionHandlingStrategyBase
{
protected DiagnosticsManagerBase _diagnosticsManager;
protected ExceptionHandlingStrategyBase();
public abstract bool ShouldRethrow { get; }
public DiagnosticsManagerBase DiagnosticsManager { get; }
public abstract void HandleException(Exception exception, string context);
public abstract Task HandleExceptionAsync(Exception exception, string context, CancellationToken cancellationToken = default(CancellationToken));
public void SetDiagnosticsManager(DiagnosticsManagerBase diagnosticsManager);
}
And register it with the logger using ResetExceptionHandlingStrategy(...)
method of the latter.
To implement a custom strategy, it is useful to briefly discuss how its methods are used by the logger instance:
When ResetExceptionHandlingStrategy(...)
method is called and provided with an instance of strategy, inside this method the SetHandler(...)
method of the strategy is called provided with reference to self monitoring destination. It is OK if by the time of calling ResetExceptionHandlingStrategy(...)
self monitoring destination has not specified yet because it is passed to SetHandler(...)
method by reference.
Every time exception is caught in Log(...)
and LogAsync(...)
methods, according (synchronous or asynchronous) handler method of strategy is called. After that, ShouldRethrow
property is examined by the logger to decide should the exception be rethrown or not.
Log serializers' responsibility is to create some textual representation of provided log model. Depending on serializer's implementation, this could be plain text, JSON or XML. Log serializers are used within log destinations, and help to decouple creating textual representation of the log from the specific log destination logic. You are not obligated to use serializers in your custom log destination but if you need some textual representation within your destination it would be a good practice to delegate the work to serializers and give users opportunity to inject them to your log destination.
Every log serializer implements TacitusLogger.Serializers.ILogSerializer
interface:
public interface ILogSerializer : IDisposable
{
string Serialize(LogModel logModel);
}
TacitusLogger has five built in log serializers out of the box:
TacitusLogger.Serializers.JsonLogSerializer
TacitusLogger.Serializers.SimpleTemplateLogSerializer
TacitusLogger.Serializers.ExtendedTemplateLogSerializer
TacitusLogger.Serializers.FilePathTemplateLogSerializer
TacitusLogger.Serializers.GeneratorFunctionLogSerializer
JsonLogSerializer
transforms log model object into its JSON representation, SimpleTemplateLogSerializer
, ExtendedTemplateLogSerializer
and FilePathTemplateLogSerializer
uses templates with placeholders to get plaintext representation of log model, GeneratorFunctionLogSerializer
uses delegate of type TacitusLogger.LogDataFunc<string>
provided by user to generate strings from log models.
Sometimes you want to make change to different settings of logger without restarting the whole application and that is where setting providers can help. Setting providers sets some property to the logger or its components in the form of injection thus providing the ability to manage the value of this property during the runtime.
All setting providers inherit from TacitusLogger.Setting<TValue>
class which TValue parameter represents the value itself:
public class Setting<TValue> : IDisposable
{
protected Setting();
public static SettingBuilder<TValue> From { get; }
public virtual TValue Value { get; }
public virtual void Dispose();
public static implicit operator Setting<TValue>(TValue value);
public static implicit operator TValue(Setting<TValue> setting);
}
As you see, derivatives of TacitusLogger.Setting<TValue>
are intended to encapsulate the logic of retrieving an instance of TValue
(from memory, config file, database etc) and/or are able to make changes to it thus affecting the components of logger which use that value during the runtime.
TacitusLogger.Setting<TValue>
does not have a public constructor, and can be created only by implicit conversion from the TValue
instance:
Setting<LogLevel> logLevel = LogLevel.All;
Setting<LogGroupStatus> logGroupStatus = LogGroupStatus.Active;
This is useful when all you need is a constant value and you want to avoid bulky expressions in your logger configurations.
If you need more control over the value of the setting, you should use derived subclasses of TacitusLogger.Setting<TValue>
. Out of the box TacitusLogger comes with a single subclass of the TacitusLogger.Setting<TValue>
, that is TacitusLogger.MutableSetting<TValue>
:
public class MutableSetting<TValue> : Setting<TValue>
{
public MutableSetting(TValue initialValue = default(TValue));
public override TValue Value { get; }
public void SetValue(TValue value);
public static implicit operator MutableSetting<TValue>(TValue value);
}
As you can see from the above code snippet, TacitusLogger.MutableSetting<TValue>
has the method SetValue()
which is the exact one that is used to change the wrapped value during the execution.
Log contributors are lightweight plugins that are attached to logger and automatically add various runtime information to every created log model as additional log items. Every contributor produces a single log item that is added to the collection of log items of log model by standard log creation strategy.
Every log contributor should inherit from TacitusLogger.Contributors.LogContributorBase
abstract class:
public abstract class LogContributorBase
{
protected LogContributorBase(string name);
public string Name { get; }
public Setting<bool> IsActive { get; }
public virtual void Dispose();
public virtual LogItem ProduceLogItem();
public void SetActive(Setting<bool> isActive);
protected abstract object GenerateLogItemData();
}
and implement GenerateLogItemData()
method that is supposed to return specific log item value.
When creating a log model, log creation strategy strategy iterates through all Active log contributors (inactive ones are ignored) and harvests log items calling ProduceLogItem()
method of each contributor.
TacitusLogger package contains four log contributors out of the box:
2. TacitusLogger.Contributors.StackTraceContributor
4. TacitusLogger.Contributors.UserDataContributor
StackTraceContributor
produces a log item with the default name Stack trace
and the value containing stack trace information at the moment of writing log.
UserDataContributor
defines an only public constructor that takes user defined name and value, and uses them to add log item to all created logs.
Log transformers are added to the logger and intended to make arbitrary modifications to log model after the latter is created and before is sent to log groups. Log transformers could be useful in various situations, such as strings localization, beautifying, making custom changes that should be applicable to all logs etc. Log transformers are function at logger level before the created log models reach the log groups. Logger can have zero or many log transformers and they are applied to log model is the same order in which they were added to the logger.
All log transformers should implement TacitusLogger.Transformers.LogTransformerBase
abstract class:
public abstract class LogTransformerBase
{
protected LogTransformerBase(string name);
public string Name { get; }
public Setting<bool> IsActive { get; }
public void SetActive(Setting<bool> isActive);
public abstract void Transform(LogModel logModel);
public abstract Task TransformAsync(LogModel logModel, CancellationToken cancellationToken = default(CancellationToken));
}
Every transformer has its name which is useful to make configuration more readable and IsActive
status of type Setting<bool>
which shows if this transformer is active or not. Only active transformers are applied by logger to logs, inactive ones are completely ignored. Being of type Setting<bool>
IsActive
status can be modified during the runtime thus allowing the logger user to toggle on/off various transformers.
When creating your own transformers, as it was said, you have to implement TacitusLogger.Transformers.LogTransformerBase
which defines two abstract methods Transform(...)
and TransformAsync(...)
which are intended to be called during the synchronous and asynchronous uses of logger: That is Transform(...)
is called within the Log(...)
method of the logger, and TransformAsync(...)
- within the LogAsync(...)
method. In most situations when creating your custom transformers your logic represents some quick synchronous manipulations over the log model and thus you do not have any special async logic to put into TransformAsync(...)
. Is such scenarios you may consider to derive from TacitusLogger.Transformers.SynchronousTransformerBase
abstract class which has already implemented TransformAsync(...)
by calling Transform(...)
method synchronously inside of it and returning completed task afterwards:
public abstract class SynchronousTransformerBase : LogTransformerBase
{
public SynchronousTransformerBase(string name);
public override Task TransformAsync(LogModel logModel, CancellationToken cancellationToken = default(CancellationToken));
}
in this case you will have to implement only the Transform(...)
method.
In some cases you may want to create a transformer that makes some custom modifications on main string properties of LogModel
object. In such cases you may want to derive from the TacitusLogger.Transformers.StringsTransformerBase
:
public abstract class StringsTransformerBase : SynchronousTransformerBase
{
public StringsTransformerBase(string name);
public override void Transform(LogModel logModel);
protected abstract void TransformString(ref string str);
}
This class, inside its Transform(...)
method goes over string properties of LogModel
and sends each by reference to TransformString(...)
method that you should implement with your string transformation logic. String properties which the class deal with are the following: Log ID, context, source, description, tags, name properties of log items. Take into consideration that this class does not deal with log item values.
TacitusLogger contains the following log transformers out of the box:
TacitusLogger.Transformers.ManualTransformer
TacitusLogger.Transformers.StringsManualTransformer
The first one - TacitusLogger.Transformers.ManualTransformer
- provides a way to inject a transformation logic into its constructor in the form of a transformer action - a delegate of type Action<LogModel>
with a single LogModel
parameter. This delegate will be called and provided with LogModel
action on each call of Transform(...)
method:
public class ManualTransformer : SynchronousTransformerBase
{
public ManualTransformer(Action<LogModel> transformerAction, string name = "Manual transformer");
public Action<LogModel> TransformerAction { get; }
public override void Transform(LogModel logModel);
}
The second one, TacitusLogger.Transformers.StringsManualTransformer
can be used for similar purposes as StringsTransformerBase
that was explained above, but with a single difference: Instead of subclassing it and implementing abstract TransformString(...)
method, you inject StringTransformerDelegate
delegate, containing the logic for modifying strings into its constructor. This delegate will be called for each string:
public class StringsManualTransformer : StringsTransformerBase
{
public StringsManualTransformer(StringDelegate transformerDelegate, string name = "Strings manual transformer");
public StringDelegate TransformerDelegate { get; }
protected override void TransformString(ref string str);
public delegate void StringDelegate(ref string str);
}
As you can see from TacitusLogger.Logger
class definition, along with other interfaces it implements TacitusLogger.Destinations.ILogDestination
:
public class Logger : ILogger, ILogDestination, IDisposable
{
//...
public void Send(LogModel[] logs);
public Task SendAsync(LogModel[] logs, CancellationToken cancellationToken = default(CancellationToken));
//...
}
This means that you can register an instance of TacitusLogger.Logger
class as a log destinations with other loggers - as destinations within their log groups and/or a self monitoring destination:
Logger loggerAsDestination1 = LoggerBuilder.Logger().ForAllLogs()
.Console().WithSimpleTemplateLogText("Log of type: $LogType, Description: $Description, Date: $LogDate").Add()
.File().WithPath(Templates.FilePath.Template1).Add()
.BuildLogger();
Logger loggerAsDestination2 = LoggerBuilder.Logger().ForAllLogs()
.Debug().Add()
.BuildLogger();
// This logger uses loggerAsDestination1 and loggerAsDestination2 as log destinations for its only log group.
Logger logger = LoggerBuilder.Logger("Main").ForAllLogs()
.CustomDestination(loggerAsDestination1)
.CustomDestination(loggerAsDestination2)
.BuildLogGroup()
.BuildLogger();
In this section we will discuss in general the journey which is made by log event and log model after the former is sent to the logger.
Synchronous flow is performed when synchronous Log(...)
method is called by user either directly or through different extension methods that finally utilize this entry point.
- An instance of
TacitusLogger.Log
class is received byLog(...)
method of the logger. - The type of the received log is compared with the logger's log level. If the log type is less that the logger's log level then the log is ignored and no further action is taken - the
Log(...)
method returns. Otherwise, subsequent steps are performed. - Using log creation strategy, a log model instance of type
TacitusLogger.LogModel
is created and filled with data from the log event instance. - Additional log items are produced using log contributors (if any) and attached to the log model.
- Log ID string is generated by the log ID generator and set to the log model.
- After the log model is created and returned from the log creation strategy, the logger applies all registered log transformers to it giving the last chance to make modifications to log model before it will be sent to log groups.
- Logger iterates through all log groups, and sends log model to active and eligible ones. Eligibility is checked by sending log model to
IsEligible(...)
method of the log group that returnsBoolean
value indicating whether or not the log group should filter out this log model. If the log model is considered eligible, it is sent to the log group. - Flow inside the log group depends on whether the log caching is active or not. If caching is not active, the log model is placed to an array and sent directly to log destination feeding strategy. Otherwise, if caching is active, log model is sent to cache using
AddToCache(...)
method of the log cache instance. This method, depending on its logic either returns null or array of all cached logs (that is said that cache flushed). In the former case, log group method returns without sending any data. In the latter case, the flushed array is sent to log destination feeding strategy. - In the log feeding strategy, log models array is eventually send to log destinations of this log group according to implementation of this feeding strategy.
- Log destinations take the array and make actions on each log model depending on their internal logic: This could include various serializations, connecting to database, file or SMTP server etc, sending data to them.
Asynchronous flow is performed when asynchronous LogAsync(...)
method is called and its result is awaited. The flow is generally similar to the synchronous one described above. The main difference is that for each involved component the async versions of methods (if any) are called and awaited. For example, instead of sync Generate(...)
method of log ID generator its async method is called - GenerateAsync(...)
; instead of sync Send(...)
method of log group its async method SendAsync(...)
is called and so on.
Destinations:
- TacitusLogger.Destinations.MongoDb - Extension destination for TacitusLogger that sends logs to MongoDb database.
- TacitusLogger.Destinations.RabbitMq - Extension destination for TacitusLogger that sends logs to the RabbitMQ exchanges.
- TacitusLogger.Destinations.Email - Extension destination for TacitusLogger that sends logs as emails using SMTP protocol.
- TacitusLogger.Destinations.EntityFramework - Extension destination for TacitusLogger that sends logs to database using Entity Framework ORM.
- TacitusLogger.Destinations.Trace - Extension destination for TacitusLogger that sends logs to System.Diagnostics.Trace listeners.
Dependency injection:
- TacitusLogger.DI.Ninject - Extension for Ninject dependency injection container that helps to configure and add TacitusLogger as a singleton.
- TacitusLogger.DI.Autofac - Extension for Autofac dependency injection container that helps to configure and add TacitusLogger as a singleton.
- TacitusLogger.DI.MicrosoftDI - Extension for Microsoft dependency injection container that helps to configure and add TacitusLogger as a singleton.
Log contributors:
- TacitusLogger.Contributors.ThreadInfo - Extension contributor for TacitusLogger that generates additional info related to the thread on which the logger method was called.
- TacitusLogger.Contributors.MachineInfo - Extension contributor for TacitusLogger that generates additional info related to the machine on which the log was produced.