diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe index 9f8781d..6bb79fe 100644 Binary files a/.nuget/NuGet.exe and b/.nuget/NuGet.exe differ diff --git a/README.md b/README.md index c568d07..5c3c642 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ service/environment. - `NotToBeDisplayedDomainList`(optional) - It will be used when logging host name by excluding these strings in service name attribute e.g. domain: ".xyz.com", host: "abc.xyz.com" will be logged as "abc" only - `ExcludedPathList`(optional) - Path list that is not needed for tracing. Each item must start with "/". +- `Create128BitTraceId` - Create new traces using 128 bit (32 hex character) traceId ```csharp @@ -166,3 +167,5 @@ ZipkinTracer is (c) Medidata Solutions Worldwide and owned by its major contribu * [Kenya Matsumoto](https://github.com/kenyamat) * [Brent Villanueva](https://github.com/bvillanueva-mdsol) * [Laszlo Schreck](https://github.com/lschreck-mdsol) +* [Jordi Carres](https://github.com/jcarres-mdsol) +* [Herry Kurniawan](https://github.com/hkurniawan-mdsol) diff --git a/src/Medidata.ZipkinTracer.Core/App_Packages/LibLog.4.2/LibLog.cs b/src/Medidata.ZipkinTracer.Core/App_Packages/LibLog.4.2/LibLog.cs index 45e6da7..22f356c 100644 --- a/src/Medidata.ZipkinTracer.Core/App_Packages/LibLog.4.2/LibLog.cs +++ b/src/Medidata.ZipkinTracer.Core/App_Packages/LibLog.4.2/LibLog.cs @@ -422,11 +422,6 @@ interface ILogProvider static class LogProvider { #if !LIBLOG_PROVIDERS_ONLY - /// - /// The disable logging environment variable. If the environment variable is set to 'true', then logging - /// will be disabled. - /// - public const string DisableLoggingEnvironmentVariable = "Medidata.ZipkinTracer.Core_LIBLOG_DISABLE"; private const string NullLogProvider = "Current Log Provider is not set. Call SetCurrentLogProvider " + "with a non-null value first."; private static dynamic s_currentLogProvider; @@ -517,15 +512,17 @@ static ILog GetCurrentClassLogger() /// Gets a logger for the specified type. /// /// The type whose name will be used for the logger. + /// If the type is null then this name will be used as the log name instead /// An instance of #if LIBLOG_PUBLIC public #else internal #endif - static ILog GetLogger(Type type) + static ILog GetLogger(Type type, string fallbackTypeName = "System.Object") { - return GetLogger(type.FullName); + // If the type passed in is null then fallback to the type name specified + return GetLogger(type != null ? type.FullName : fallbackTypeName); } /// @@ -541,7 +538,7 @@ static ILog GetLogger(Type type) static ILog GetLogger(string name) { ILogProvider logProvider = CurrentLogProvider ?? ResolveLogProvider(); - return logProvider == null + return logProvider == null ? NoOpLogger.Instance : (ILog)new LoggerExecutionWrapper(logProvider.GetLogger(name), () => IsDisabled); } @@ -693,15 +690,6 @@ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception { return false; } -#if !LIBLOG_PORTABLE - var envVar = Environment.GetEnvironmentVariable(LogProvider.DisableLoggingEnvironmentVariable); - - if (envVar != null && envVar.Equals("true", StringComparison.OrdinalIgnoreCase)) - { - return false; - } -#endif - if (messageFunc == null) { return _logger(logLevel, null); @@ -757,7 +745,7 @@ internal abstract class LogProviderBase : ILogProvider protected LogProviderBase() { - _lazyOpenNdcMethod + _lazyOpenNdcMethod = new Lazy(GetOpenNdcMethod); _lazyOpenMdcMethod = new Lazy(GetOpenMdcMethod); @@ -871,6 +859,55 @@ internal class NLogLogger { private readonly dynamic _logger; + private static Func _logEventInfoFact; + + private static readonly object _levelTrace; + private static readonly object _levelDebug; + private static readonly object _levelInfo; + private static readonly object _levelWarn; + private static readonly object _levelError; + private static readonly object _levelFatal; + + static NLogLogger() + { + try + { + var logEventLevelType = Type.GetType("NLog.LogLevel, NLog"); + if (logEventLevelType == null) + { + throw new InvalidOperationException("Type NLog.LogLevel was not found."); + } + + var levelFields = logEventLevelType.GetFieldsPortable().ToList(); + _levelTrace = levelFields.First(x => x.Name == "Trace").GetValue(null); + _levelDebug = levelFields.First(x => x.Name == "Debug").GetValue(null); + _levelInfo = levelFields.First(x => x.Name == "Info").GetValue(null); + _levelWarn = levelFields.First(x => x.Name == "Warn").GetValue(null); + _levelError = levelFields.First(x => x.Name == "Error").GetValue(null); + _levelFatal = levelFields.First(x => x.Name == "Fatal").GetValue(null); + + var logEventInfoType = Type.GetType("NLog.LogEventInfo, NLog"); + if (logEventInfoType == null) + { + throw new InvalidOperationException("Type NLog.LogEventInfo was not found."); + } + MethodInfo createLogEventInfoMethodInfo = logEventInfoType.GetMethodPortable("Create", + logEventLevelType, typeof(string), typeof(Exception), typeof(IFormatProvider), typeof(string), typeof(object[])); + ParameterExpression loggerNameParam = Expression.Parameter(typeof(string)); + ParameterExpression levelParam = Expression.Parameter(typeof(object)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); + MethodCallExpression createLogEventInfoMethodCall = Expression.Call(null, + createLogEventInfoMethodInfo, + levelCast, loggerNameParam, exceptionParam, + Expression.Constant(null, typeof(IFormatProvider)), messageParam, Expression.Constant(null, typeof(object[]))); + _logEventInfoFact = Expression.Lambda>(createLogEventInfoMethodCall, + loggerNameParam, levelParam, messageParam, exceptionParam).Compile(); + } + catch { } + } + internal NLogLogger(dynamic logger) { _logger = logger; @@ -885,6 +922,43 @@ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception } messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + if (_logEventInfoFact != null) + { + if (IsLogLevelEnable(logLevel)) + { + var nlogLevel = this.TranslateLevel(logLevel); + Type s_callerStackBoundaryType; +#if !LIBLOG_PORTABLE + StackTrace stack = new StackTrace(); + Type thisType = GetType(); + Type knownType0 = typeof(LoggerExecutionWrapper); + Type knownType1 = typeof(LogExtensions); + //Maybe inline, so we may can't found any LibLog classes in stack + s_callerStackBoundaryType = null; + for (var i = 0; i < stack.FrameCount; i++) + { + var declaringType = stack.GetFrame(i).GetMethod().DeclaringType; + if (!IsInTypeHierarchy(thisType, declaringType) && + !IsInTypeHierarchy(knownType0, declaringType) && + !IsInTypeHierarchy(knownType1, declaringType)) + { + if (i > 1) + s_callerStackBoundaryType = stack.GetFrame(i - 1).GetMethod().DeclaringType; + break; + } + } +#else + s_callerStackBoundaryType = null; +#endif + if (s_callerStackBoundaryType != null) + _logger.Log(s_callerStackBoundaryType, _logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception)); + else + _logger.Log(_logEventInfoFact(_logger.Name, nlogLevel, messageFunc(), exception)); + return true; + } + return false; + } + if(exception != null) { return LogException(logLevel, messageFunc, exception); @@ -937,6 +1011,19 @@ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception return false; } + private static bool IsInTypeHierarchy(Type currentType, Type checkType) + { + while (currentType != null && currentType != typeof(object)) + { + if (currentType == checkType) + { + return true; + } + currentType = currentType.GetBaseTypePortable(); + } + return false; + } + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] private bool LogException(LogLevel logLevel, Func messageFunc, Exception exception) { @@ -1006,6 +1093,27 @@ private bool IsLogLevelEnable(LogLevel logLevel) return _logger.IsTraceEnabled; } } + + private object TranslateLevel(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return _levelTrace; + case LogLevel.Debug: + return _levelDebug; + case LogLevel.Info: + return _levelInfo; + case LogLevel.Warn: + return _levelWarn; + case LogLevel.Error: + return _levelError; + case LogLevel.Fatal: + return _levelFatal; + default: + throw new ArgumentOutOfRangeException("logLevel", logLevel, null); + } + } } } @@ -1129,7 +1237,9 @@ internal class Log4NetLogger private readonly object _levelError; private readonly object _levelFatal; private readonly Func _isEnabledForDelegate; - private readonly Action _logDelegate; + private readonly Action _logDelegate; + private readonly Func _createLoggingEvent; + private Action _loggingEventPropertySetter; [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "ILogger")] internal Log4NetLogger(dynamic logger) @@ -1155,38 +1265,123 @@ internal Log4NetLogger(dynamic logger) { throw new InvalidOperationException("Type log4net.Core.ILogger, was not found."); } - MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); ParameterExpression instanceParam = Expression.Parameter(typeof(object)); UnaryExpression instanceCast = Expression.Convert(instanceParam, loggerType); - ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); ParameterExpression levelParam = Expression.Parameter(typeof(object)); - ParameterExpression messageParam = Expression.Parameter(typeof(string)); UnaryExpression levelCast = Expression.Convert(levelParam, logEventLevelType); - MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); - _isEnabledForDelegate = Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam).Compile(); + _isEnabledForDelegate = GetIsEnabledFor(loggerType, logEventLevelType, instanceCast, levelCast, instanceParam, levelParam); + + Type loggingEventType = Type.GetType("log4net.Core.LoggingEvent, log4net"); + + _createLoggingEvent = GetCreateLoggingEvent(instanceParam, instanceCast, levelParam, levelCast, loggingEventType); + + _logDelegate = GetLogDelegate(loggerType, loggingEventType, instanceCast, instanceParam); - // Action Log = - // (logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Write(callerStackBoundaryDeclaringType, level, message, exception); } + _loggingEventPropertySetter = GetLoggingEventPropertySetter(loggingEventType); + } + + private static Action GetLogDelegate(Type loggerType, Type loggingEventType, UnaryExpression instanceCast, + ParameterExpression instanceParam) + { + //Action Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => { ((ILogger)logger).Log(new LoggingEvent(callerStackBoundaryDeclaringType, logger.Repository, logger.Name, level, message, exception)); } MethodInfo writeExceptionMethodInfo = loggerType.GetMethodPortable("Log", - typeof(Type), - logEventLevelType, - typeof(string), - typeof(Exception)); - ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + loggingEventType); + + ParameterExpression loggingEventParameter = + Expression.Parameter(typeof(object), "loggingEvent"); + + UnaryExpression loggingEventCasted = + Expression.Convert(loggingEventParameter, loggingEventType); + var writeMethodExp = Expression.Call( instanceCast, writeExceptionMethodInfo, - callerStackBoundaryDeclaringTypeParam, - levelCast, - messageParam, - exceptionParam); - _logDelegate = Expression.Lambda>( - writeMethodExp, - instanceParam, - callerStackBoundaryDeclaringTypeParam, - levelParam, - messageParam, - exceptionParam).Compile(); + loggingEventCasted); + + var logDelegate = Expression.Lambda>( + writeMethodExp, + instanceParam, + loggingEventParameter).Compile(); + + return logDelegate; + } + + private static Func GetCreateLoggingEvent(ParameterExpression instanceParam, UnaryExpression instanceCast, ParameterExpression levelParam, UnaryExpression levelCast, Type loggingEventType) + { + ParameterExpression callerStackBoundaryDeclaringTypeParam = Expression.Parameter(typeof(Type)); + ParameterExpression messageParam = Expression.Parameter(typeof(string)); + ParameterExpression exceptionParam = Expression.Parameter(typeof(Exception)); + + PropertyInfo repositoryProperty = loggingEventType.GetPropertyPortable("Repository"); + PropertyInfo levelProperty = loggingEventType.GetPropertyPortable("Level"); + + ConstructorInfo loggingEventConstructor = + loggingEventType.GetConstructorPortable(typeof(Type), repositoryProperty.PropertyType, typeof(string), levelProperty.PropertyType, typeof(object), typeof(Exception)); + + //Func Log = + //(logger, callerStackBoundaryDeclaringType, level, message, exception) => new LoggingEvent(callerStackBoundaryDeclaringType, ((ILogger)logger).Repository, ((ILogger)logger).Name, (Level)level, message, exception); } + NewExpression newLoggingEventExpression = + Expression.New(loggingEventConstructor, + callerStackBoundaryDeclaringTypeParam, + Expression.Property(instanceCast, "Repository"), + Expression.Property(instanceCast, "Name"), + levelCast, + messageParam, + exceptionParam); + + var createLoggingEvent = + Expression.Lambda>( + newLoggingEventExpression, + instanceParam, + callerStackBoundaryDeclaringTypeParam, + levelParam, + messageParam, + exceptionParam) + .Compile(); + + return createLoggingEvent; + } + + private static Func GetIsEnabledFor(Type loggerType, Type logEventLevelType, + UnaryExpression instanceCast, + UnaryExpression levelCast, + ParameterExpression instanceParam, + ParameterExpression levelParam) + { + MethodInfo isEnabledMethodInfo = loggerType.GetMethodPortable("IsEnabledFor", logEventLevelType); + MethodCallExpression isEnabledMethodCall = Expression.Call(instanceCast, isEnabledMethodInfo, levelCast); + + Func result = + Expression.Lambda>(isEnabledMethodCall, instanceParam, levelParam) + .Compile(); + + return result; + } + + private static Action GetLoggingEventPropertySetter(Type loggingEventType) + { + ParameterExpression loggingEventParameter = Expression.Parameter(typeof(object), "loggingEvent"); + ParameterExpression keyParameter = Expression.Parameter(typeof(string), "key"); + ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value"); + + PropertyInfo propertiesProperty = loggingEventType.GetPropertyPortable("Properties"); + PropertyInfo item = propertiesProperty.PropertyType.GetPropertyPortable("Item"); + + // ((LoggingEvent)loggingEvent).Properties[key] = value; + var body = + Expression.Assign( + Expression.Property( + Expression.Property(Expression.Convert(loggingEventParameter, loggingEventType), + propertiesProperty), item, keyParameter), valueParameter); + + Action result = + Expression.Lambda> + (body, loggingEventParameter, keyParameter, + valueParameter) + .Compile(); + + return result; } public bool Log(LogLevel logLevel, Func messageFunc, Exception exception, params object[] formatParameters) @@ -1201,7 +1396,14 @@ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception return false; } - messageFunc = LogMessageFormatter.SimulateStructuredLogging(messageFunc, formatParameters); + string message = messageFunc(); + + IEnumerable patternMatches; + + string formattedMessage = + LogMessageFormatter.FormatStructuredMessage(message, + formatParameters, + out patternMatches); // determine correct caller - this might change due to jit optimizations with method inlining if (s_callerStackBoundaryType == null) @@ -1227,10 +1429,28 @@ public bool Log(LogLevel logLevel, Func messageFunc, Exception exception } var translatedLevel = TranslateLevel(logLevel); - _logDelegate(_logger, s_callerStackBoundaryType, translatedLevel, messageFunc(), exception); + + object loggingEvent = _createLoggingEvent(_logger, s_callerStackBoundaryType, translatedLevel, formattedMessage, exception); + + PopulateProperties(loggingEvent, patternMatches, formatParameters); + + _logDelegate(_logger, loggingEvent); + return true; } + private void PopulateProperties(object loggingEvent, IEnumerable patternMatches, object[] formatParameters) + { + IEnumerable> keyToValue = + patternMatches.Zip(formatParameters, + (key, value) => new KeyValuePair(key, value)); + + foreach (KeyValuePair keyValuePair in keyToValue) + { + _loggingEventPropertySetter(loggingEvent, keyValuePair.Key, keyValuePair.Value); + } + } + private static bool IsInTypeHierarchy(Type currentType, Type checkType) { while (currentType != null && currentType != typeof(object)) @@ -1373,7 +1593,7 @@ private static MemberInitExpression GetWriteLogExpression(Expression message, Expression severityParameter, ParameterExpression logNameParameter) { var entryType = LogEntryType; - MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), + MemberInitExpression memberInit = Expression.MemberInit(Expression.New(entryType), Expression.Bind(entryType.GetPropertyPortable("Message"), message), Expression.Bind(entryType.GetPropertyPortable("Severity"), severityParameter), Expression.Bind( @@ -1489,12 +1709,15 @@ protected override OpenMdc GetOpenMdcMethod() private static Func GetPushProperty() { - Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + Type ndcContextType = Type.GetType("Serilog.Context.LogContext, Serilog") ?? + Type.GetType("Serilog.Context.LogContext, Serilog.FullNetFx"); + MethodInfo pushPropertyMethod = ndcContextType.GetMethodPortable( "PushProperty", typeof(string), typeof(object), typeof(bool)); + ParameterExpression nameParam = Expression.Parameter(typeof(string), "name"); ParameterExpression valueParam = Expression.Parameter(typeof(object), "value"); ParameterExpression destructureObjectParam = Expression.Parameter(typeof(bool), "destructureObjects"); @@ -1849,7 +2072,12 @@ static TraceEventTypeValues() internal static class LogMessageFormatter { - private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); + //private static readonly Regex Pattern = new Regex(@"\{@?\w{1,}\}"); +#if LIBLOG_PORTABLE + private static readonly Regex Pattern = new Regex(@"(?[^\d{][^ }]*)}"); +#else + private static readonly Regex Pattern = new Regex(@"(?[^ :{}]+)(?:[^}]+)?}", RegexOptions.Compiled); +#endif /// /// Some logging frameworks support structured logging, such as serilog. This will allow you to add names to structured data in a format string: @@ -1871,24 +2099,8 @@ public static Func SimulateStructuredLogging(Func messageBuilder return () => { string targetMessage = messageBuilder(); - int argumentIndex = 0; - foreach (Match match in Pattern.Matches(targetMessage)) - { - int notUsed; - if (!int.TryParse(match.Value.Substring(1, match.Value.Length -2), out notUsed)) - { - targetMessage = ReplaceFirst(targetMessage, match.Value, - "{" + argumentIndex++ + "}"); - } - } - try - { - return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); - } - catch (FormatException ex) - { - throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); - } + IEnumerable patternMatches; + return FormatStructuredMessage(targetMessage, formatParameters, out patternMatches); }; } @@ -1901,10 +2113,62 @@ private static string ReplaceFirst(string text, string search, string replace) } return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); } + + public static string FormatStructuredMessage(string targetMessage, object[] formatParameters, out IEnumerable patternMatches) + { + if (formatParameters.Length == 0) + { + patternMatches = Enumerable.Empty(); + return targetMessage; + } + + List processedArguments = new List(); + patternMatches = processedArguments; + + foreach (Match match in Pattern.Matches(targetMessage)) + { + var arg = match.Groups["arg"].Value; + + int notUsed; + if (!int.TryParse(arg, out notUsed)) + { + int argumentIndex = processedArguments.IndexOf(arg); + if (argumentIndex == -1) + { + argumentIndex = processedArguments.Count; + processedArguments.Add(arg); + } + + targetMessage = ReplaceFirst(targetMessage, match.Value, + "{" + argumentIndex + match.Groups["format"].Value + "}"); + } + } + try + { + return string.Format(CultureInfo.InvariantCulture, targetMessage, formatParameters); + } + catch (FormatException ex) + { + throw new FormatException("The input string '" + targetMessage + "' could not be formatted using string.Format", ex); + } + } } internal static class TypeExtensions { + internal static ConstructorInfo GetConstructorPortable(this Type type, params Type[] types) + { +#if LIBLOG_PORTABLE + return type.GetTypeInfo().DeclaredConstructors.FirstOrDefault + (constructor => + constructor.GetParameters() + .Select(parameter => parameter.ParameterType) + .SequenceEqual(types)); +#else + return type.GetConstructor(types); +#endif + } + internal static MethodInfo GetMethodPortable(this Type type, string name) { #if LIBLOG_PORTABLE diff --git a/src/Medidata.ZipkinTracer.Core/Helpers/ParserHelper.cs b/src/Medidata.ZipkinTracer.Core/Helpers/ParserHelper.cs new file mode 100644 index 0000000..00cbfa1 --- /dev/null +++ b/src/Medidata.ZipkinTracer.Core/Helpers/ParserHelper.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; + +namespace Medidata.ZipkinTracer.Core.Helpers +{ + internal static class ParserHelper + { + /// + /// Checks if string value can be converted to 128bit (Guid) or 64bit (long) + /// + /// + /// + internal static bool IsParsableTo128Or64Bit(this string value) + { + if (value.IsParsableToGuid()) return true; + return value.IsParsableToLong(); + } + + /// + /// Checks if hex string value is parsable to Guid + /// + /// + /// + internal static bool IsParsableToGuid(this string value) + { + Guid result; + return Guid.TryParseExact(value, "N", out result); + } + + /// + /// Checks if hex string value is parsable to long + /// + /// + /// + internal static bool IsParsableToLong(this string value) + { + long result; + return !string.IsNullOrWhiteSpace(value) && long.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result); + } + } +} diff --git a/src/Medidata.ZipkinTracer.Core/IZipkinConfig.cs b/src/Medidata.ZipkinTracer.Core/IZipkinConfig.cs index 6f4b17e..a4bd6f3 100644 --- a/src/Medidata.ZipkinTracer.Core/IZipkinConfig.cs +++ b/src/Medidata.ZipkinTracer.Core/IZipkinConfig.cs @@ -20,6 +20,8 @@ public interface IZipkinConfig IList NotToBeDisplayedDomainList { get; set; } + bool Create128BitTraceId { get; set; } + bool ShouldBeSampled(IOwinContext context, string sampled); void Validate(); diff --git a/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.csproj b/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.csproj index b1fb947..5050e4d 100644 --- a/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.csproj +++ b/src/Medidata.ZipkinTracer.Core/Medidata.ZipkinTracer.Core.csproj @@ -1,5 +1,6 @@  + Debug @@ -13,6 +14,8 @@ 512 .\ true + + true @@ -36,8 +39,8 @@ ..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll True - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True @@ -56,8 +59,9 @@ - + + @@ -99,6 +103,12 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + +