#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System;
using System.Collections.Generic;
using System.Text;
namespace Duplicati.Library.Logging
{
///
/// The different types of messages
///
public enum LogMessageType
{
///
/// The message should only be shown if it is explicitly requested
///
ExplicitOnly,
///
/// The message is a profiling message
///
Profiling,
///
/// Messages that are normally not wanted for display
///
Verbose,
///
/// The message is from a retry
///
Retry,
///
/// The message is informative but does not indicate problems
///
Information,
///
/// The message is from dry-run output
///
DryRun,
///
/// The message is a warning, meaning that later errors may be related to this message
///
Warning,
///
/// The message indicates an error
///
Error,
}
///
/// This static class is used to write log messages
///
public static class Log
{
///
/// The key used to assign the current scope into the current call-context
///
private const string LOGICAL_CONTEXT_KEY = "Duplicati:LoggingEntry";
///
/// The root scope
///
private static readonly LogScope m_root = new LogScope(null, new LogTagFilter(LogMessageType.Error, null, null), null, true);
///
/// The stored log instances
///
private static readonly Dictionary m_log_instances = new Dictionary();
///
/// Static lock object to provide thread safe logging
///
private static readonly object m_lock = new object();
///
/// Gets the lock instance used to protect the logging calls
///
public static object Lock { get { return m_lock; } }
///
/// Gets a log tag that reflects the type
///
/// The log-tag for the type.
/// The type to get the tag for.
public static string LogTagFromType()
{
return LogTagFromType(typeof(T));
}
///
/// Gets a log tag that reflects the type
///
/// The log-tag for the type.
/// The type to get the tag for.
public static string LogTagFromType(Type t)
{
return t.Namespace + "." + t.Name;
}
///
/// Gets a common list of items to include in the help-url
///
/// The basic help url items.
/// A flag indicating if the link is for commandline
private static List GetBasicHelpUrlItems(bool fromCommandLine)
{
var items = new List();
items.Add("version=" + System.Uri.EscapeDataString(typeof(Log).Assembly.GetName().Version.ToString()));
items.Add("cli=" + (fromCommandLine ? "t" : "f"));
// TODO: Add OS type? mono version? install id? app-name?
return items;
}
///
/// Encodes a list of query string values and appends them to the help url
///
/// The help URL.
/// The items to include.
private static string EncodeHelpUrl(IEnumerable items)
{
var query = string.Join("&", items);
if (!string.IsNullOrWhiteSpace(query))
query = "?" + query;
return "https://help.duplicati.com/" + query;
}
///
/// Creates a link to look up further information on a particular message
///
/// The help link.
/// The id to use.
/// A flag indicating if the link is for commandline
public static string CreateHelpLink(string id, bool fromCommandLine)
{
var items = GetBasicHelpUrlItems(fromCommandLine);
if (!string.IsNullOrWhiteSpace(id))
items.Add("id=" + System.Uri.EscapeDataString(id));
return EncodeHelpUrl(items);
}
///
/// Creates a link to look up further information on a particular message
///
/// The help link.
/// The log tag, if any.
/// The message id, if any.
/// The exception id, if any.
/// A flag indicating if the link is for commandline
public static string CreateHelpLink(string tag, string messageid, string exceptionid, bool fromCommandLine)
{
var items = GetBasicHelpUrlItems(fromCommandLine);
if (!string.IsNullOrWhiteSpace(tag))
items.Add("tag=" + System.Uri.EscapeDataString(tag));
if (!string.IsNullOrWhiteSpace(messageid))
items.Add("msgid=" + System.Uri.EscapeDataString(messageid));
if (!string.IsNullOrWhiteSpace(exceptionid))
items.Add("exid=" + System.Uri.EscapeDataString(exceptionid));
return EncodeHelpUrl(items);
}
///
/// Writes an explicit message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The message format arguments
public static void WriteExplicitMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.ExplicitOnly, tag, id, null, message, arguments);
}
///
/// Writes an explicit message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The exception to log
/// The message format arguments
public static void WriteExplicitMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.ExplicitOnly, tag, id, ex, message, arguments);
}
///
/// Writes a verbose message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The message format arguments
public static void WriteVerboseMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Verbose, tag, id, null, message, arguments);
}
///
/// Writes a verbose message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The exception to log
/// The message format arguments
public static void WriteVerboseMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Verbose, tag, id, ex, message, arguments);
}
///
/// Writes a profiling message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The message format arguments
public static void WriteProfilingMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Profiling, tag, id, null, message, arguments);
}
///
/// Writes a dry-run message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The message format arguments
public static void WriteDryrunMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.DryRun, tag, id, null, message, arguments);
}
///
/// Writes a retry message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The exception to attach
/// The message format arguments
public static void WriteRetryMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Retry, tag, id, ex, message, arguments);
}
///
/// Writes an information message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The message format arguments
public static void WriteInformationMessage(string tag, string id, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Information, tag, id, null, message, arguments);
}
///
/// Writes a warning message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The exception to attach
/// The message format arguments
public static void WriteWarningMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Warning, tag, id, ex, message, arguments);
}
///
/// Writes an error message to the current log destination
///
/// The message to write
/// The tag-type for this message
/// The message id
/// The exception to attach
/// The message format arguments
public static void WriteErrorMessage(string tag, string id, Exception ex, string message, params object[] arguments)
{
WriteMessage(LogMessageType.Error, tag, id, ex, message, arguments);
}
///
/// Writes a message to the current log destination
///
/// The message to write
/// The type of the message
/// The tag-type for this message
/// The message id
public static void WriteMessage(LogMessageType type, string tag, string id, string message, params object[] arguments)
{
WriteMessage(type, tag, id, null, message, arguments);
}
///
/// Writes a message to the current log destination
///
/// The message to write
/// The type of the message
/// An exception value
/// The tag-type for this message
/// The message id
/// The arguments to format the log message with
public static void WriteMessage(LogMessageType type, string tag, string id, Exception ex, string message, params object[] arguments)
{
var msg = new LogEntry(message, arguments, type, tag, id, ex);
lock (m_lock)
{
var cs = CurrentScope;
while (cs != null && !cs.IsolatingScope)
{
cs.WriteMessage(msg);
cs = cs.Parent;
}
}
}
///
/// Starts a new scope, that can be closed by disposing the returned instance
///
/// Flag indicating if the scope should be detached from the parent
/// The new scope.
public static IDisposable StartIsolatingScope(bool detached)
{
lock (m_lock)
{
var scope = StartScope(null, null, true);
if (detached)
DetachCurrentScope(scope);
return scope;
}
}
///
/// Detaches the current scope, such that new scopes do not chain onto this
///
/// The current scope.
public static IDisposable DetachCurrentScope(IDisposable scope)
{
lock (m_lock)
{
if (CurrentScope == scope && scope != null && CurrentScope.Parent != null)
CurrentScope = CurrentScope.Parent;
}
return scope;
}
///
/// Starts a new scope, that can be closed by disposing the returned instance
///
/// The new scope.
public static IDisposable StartScope()
{
return StartScope((ILogDestination)null, null);
}
///
/// Starts a new scope, that can be stopped by disposing the returned instance
///
/// The log target
/// The log level
/// The new scope.
public static IDisposable StartScope(ILogDestination log, LogMessageType level)
{
return StartScope(log, new LogTagFilter(level, null, null));
}
///
/// Starts a new scope, that can be stopped by disposing the returned instance
///
/// The log target
/// The log filter
/// The new scope.
public static IDisposable StartScope(ILogDestination log, ILogFilter filter = null, bool isolating = false)
{
return new LogScope(log, filter, CurrentScope, isolating);
}
///
/// Starts a new scope, that can be stopped by disposing the returned instance
///
/// The log target
/// The log filter
/// The new scope.
public static IDisposable StartScope(Action log, Func filter = null)
{
return new LogScope(new FunctionLogDestination(log), filter == null ? null : new FunctionFilter(filter), CurrentScope, false);
}
///
/// Starts the scope.
///
/// The scope to start.
internal static void StartScope(LogScope scope)
{
CurrentScope = scope;
}
///
/// Closes the scope.
///
/// The scope to finish.
internal static void CloseScope(LogScope scope)
{
lock(m_lock)
{
if (CurrentScope == scope && scope != m_root)
CurrentScope = scope.Parent;
m_log_instances.Remove(scope.InstanceID);
}
}
///
/// Gets or sets the current log destination in a call-context aware fashion
///
internal static LogScope CurrentScope
{
get
{
lock (m_lock)
{
var cur = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(LOGICAL_CONTEXT_KEY) as string;
if (cur == null || cur == m_root.InstanceID)
return m_root;
LogScope sc;
if (!m_log_instances.TryGetValue(cur, out sc))
throw new Exception("Unable to find log in lookup table, this may be caused by attempting to transport call contexts between AppDomains (eg. with remoting calls)");
return sc;
}
}
private set
{
lock (m_lock)
{
if (value != null)
{
m_log_instances[value.InstanceID] = value;
System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(LOGICAL_CONTEXT_KEY, value.InstanceID);
}
else
{
System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(LOGICAL_CONTEXT_KEY, null);
}
}
}
}
}
}