diff --git a/TimberWinR.ServiceHost/Program.cs b/TimberWinR.ServiceHost/Program.cs index e234bd3..5dc2755 100644 --- a/TimberWinR.ServiceHost/Program.cs +++ b/TimberWinR.ServiceHost/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; @@ -20,9 +21,9 @@ namespace TimberWinR.ServiceHost private static void Main(string[] args) { Arguments arguments = new Arguments(); - + HostFactory.Run(hostConfigurator => - { + { string cmdLine = Environment.CommandLine; hostConfigurator.Service(serviceConfigurator => @@ -55,23 +56,23 @@ namespace TimberWinR.ServiceHost } } - + internal class TimberWinRService { readonly CancellationTokenSource _cancellationTokenSource; readonly CancellationToken _cancellationToken; readonly Task _serviceTask; private readonly Arguments _args; - private TcpInputListener _nlogListener; + private TcpInputListener _nlogListener; public TimberWinRService(Arguments args) { _args = args; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; - _serviceTask = new Task(RunService, _cancellationToken); + _serviceTask = new Task(RunService, _cancellationToken); } - + public void Start() { _serviceTask.Start(); @@ -82,21 +83,16 @@ namespace TimberWinR.ServiceHost _cancellationTokenSource.Cancel(); _nlogListener.Shutdown(); } - + /// /// The Main body of the Service Worker Thread /// private void RunService() { TimberWinR.Manager manager = new TimberWinR.Manager(_args.ConfigFile); - - // logaggregator.vistaprint.svc - //var outputRedis = new RedisOutput(new string[] { "tstlexiceapp006.vistaprint.svc", "tstlexiceapp007.vistaprint.svc" }, _cancellationToken); + var outputRedis = new RedisOutput(manager, new string[] { "logaggregator.vistaprint.svc" }, _cancellationToken); - // var outputRedis = new RedisOutput(new string[] { "prdlexicelgs001.vistaprint.svc" }, _cancellationToken); - var outputRedis = new RedisOutput(new string[] { "logaggregator.vistaprint.svc" }, _cancellationToken); - _nlogListener = new TcpInputListener(_cancellationToken, 5140); outputRedis.Connect(_nlogListener); @@ -105,18 +101,18 @@ namespace TimberWinR.ServiceHost var elistner = new IISW3CInputListener(iisw3cConfig, _cancellationToken); outputRedis.Connect(elistner); } - + foreach (Configuration.WindowsEvent eventConfig in manager.Config.Events) { var elistner = new WindowsEvtInputListener(eventConfig, _cancellationToken); outputRedis.Connect(elistner); } - - //while (!_cancellationTokenSource.IsCancellationRequested) - //{ - // System.Threading.Thread.Sleep(1000); - //} + foreach (var logConfig in manager.Config.Logs) + { + var elistner = new TailFileInputListener(logConfig, _cancellationToken); + outputRedis.Connect(elistner); + } } } } diff --git a/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj b/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj index e94bcd8..98e50fb 100644 --- a/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj +++ b/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj @@ -60,6 +60,11 @@ TimberWinR + + + PreserveNewest + + + + + + + + + + + + + + MMM d HH:mm:ss + MMM dd HH:mm:ss + ISO8601 + + + MMM d HH:mm:ss + MMM dd HH:mm:ss + ISO8601 + + + + diff --git a/TimberWinR.UnitTests/Configuration.cs b/TimberWinR.UnitTests/Configuration.cs index a31dda9..ff93ddf 100644 --- a/TimberWinR.UnitTests/Configuration.cs +++ b/TimberWinR.UnitTests/Configuration.cs @@ -257,7 +257,7 @@ namespace TimberWinR.UnitTests bool splitLongLines = false; string iCheckpoint; - TimberWinR.Configuration.TextLog log = c.Logs.ToArray()[0]; + TimberWinR.Configuration.TailFileInput log = c.Logs.ToArray()[0]; Assert.AreEqual(name, log.Name); Assert.AreEqual(location, log.Location); diff --git a/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj b/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj index 0f2b2b6..0481388 100644 --- a/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj +++ b/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj @@ -47,9 +47,6 @@ - - Designer - diff --git a/TimberWinR.UnitTests/testconf.xml b/TimberWinR.UnitTests/testconf.xml index 156753b..7de93cc 100644 --- a/TimberWinR.UnitTests/testconf.xml +++ b/TimberWinR.UnitTests/testconf.xml @@ -17,7 +17,7 @@ - + diff --git a/TimberWinR.UnitTests/testconf.xsd b/TimberWinR.UnitTests/testconf.xsd deleted file mode 100644 index 6f9811a..0000000 --- a/TimberWinR.UnitTests/testconf.xsd +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - - - - - - - - iis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TimberWinR/Configuration.cs b/TimberWinR/Configuration.cs index a4d2f1b..2afb1c0 100644 --- a/TimberWinR/Configuration.cs +++ b/TimberWinR/Configuration.cs @@ -9,6 +9,7 @@ using System.IO; using System.Globalization; using TimberWinR.Inputs; using System.Xml.Schema; +using NLog; namespace TimberWinR { @@ -29,7 +30,7 @@ namespace TimberWinR public MissingRequiredAttributeException(XElement e, string attributeName) : base( string.Format("{0}:{1} Missing required attribute \"{2}\" for element <{3}>", e.Document.BaseUri, - ((IXmlLineInfo)e).LineNumber, attributeName, e.Name.ToString())) + ((IXmlLineInfo) e).LineNumber, attributeName, e.Name.ToString())) { } } @@ -39,7 +40,7 @@ namespace TimberWinR public InvalidAttributeNameException(XAttribute a) : base( string.Format("{0}:{1} Invalid Attribute Name <{2} {3}>", a.Document.BaseUri, - ((IXmlLineInfo)a).LineNumber, a.Parent.Name, a.Name.ToString())) + ((IXmlLineInfo) a).LineNumber, a.Parent.Name, a.Name.ToString())) { } } @@ -48,8 +49,10 @@ namespace TimberWinR { public InvalidAttributeDateValueException(XAttribute a) : base( - string.Format("{0}:{1} Invalid date format given for attribute. Format must be \"yyyy-MM-dd hh:mm:ss\". <{2} {3}>", a.Document.BaseUri, - ((IXmlLineInfo)a).LineNumber, a.Parent.Name, a.ToString())) + string.Format( + "{0}:{1} Invalid date format given for attribute. Format must be \"yyyy-MM-dd hh:mm:ss\". <{2} {3}>", + a.Document.BaseUri, + ((IXmlLineInfo) a).LineNumber, a.Parent.Name, a.ToString())) { } } @@ -59,7 +62,7 @@ namespace TimberWinR public InvalidAttributeIntegerValueException(XAttribute a) : base( string.Format("{0}:{1} Integer value not given for attribute. <{2} {3}>", a.Document.BaseUri, - ((IXmlLineInfo)a).LineNumber, a.Parent.Name, a.ToString())) + ((IXmlLineInfo) a).LineNumber, a.Parent.Name, a.ToString())) { } } @@ -69,7 +72,7 @@ namespace TimberWinR public InvalidAttributeValueException(XAttribute a) : base( string.Format("{0}:{1} Invalid Attribute Value <{2} {3}>", a.Document.BaseUri, - ((IXmlLineInfo)a).LineNumber, a.Parent.Name, a.ToString())) + ((IXmlLineInfo) a).LineNumber, a.Parent.Name, a.ToString())) { } } @@ -79,25 +82,45 @@ namespace TimberWinR public InvalidElementNameException(XElement e) : base( string.Format("{0}:{1} Invalid Element Name <{2}> <{3}>", e.Document.BaseUri, - ((IXmlLineInfo)e).LineNumber, e.Parent.Name, e.ToString())) + ((IXmlLineInfo) e).LineNumber, e.Parent.Name, e.ToString())) { } } private static List _events = new List(); - public IEnumerable Events { get { return _events; } } - private static List _logs = new List(); - public IEnumerable Logs { get { return _logs; } } + public IEnumerable Events + { + get { return _events; } + } + + private static List _logs = new List(); + + public IEnumerable Logs + { + get { return _logs; } + } private static List _iislogs = new List(); - public IEnumerable IIS { get { return _iislogs; } } + + public IEnumerable IIS + { + get { return _iislogs; } + } private static List _iisw3clogs = new List(); - public IEnumerable IISW3C { get { return _iisw3clogs; } } + + public IEnumerable IISW3C + { + get { return _iisw3clogs; } + } private static List _groks = new List(); - public IEnumerable Groks { get { return _groks; } } + + public IEnumerable Groks + { + get { return _groks; } + } public Configuration(string xmlConfFile) { @@ -107,23 +130,46 @@ namespace TimberWinR parseConfFilter(xmlConfFile); } - static void validateWithSchema(string xmlConfFile, string xsdSchema) + private static void validateWithSchema(string xmlConfFile, string xsdSchema) { XDocument config = XDocument.Load(xmlConfFile, LoadOptions.SetLineInfo | LoadOptions.SetBaseUri); // Ensure that the xml configuration file provided obeys the xsd schema. XmlSchemaSet schemas = new XmlSchemaSet(); schemas.Add("", XmlReader.Create(new StringReader(xsdSchema))); - - bool errors = false; + bool errorsFound = false; config.Validate(schemas, (o, e) => { - Console.WriteLine("{0}", e.Message); - errors = true; - }); - Console.WriteLine("The XML configuration file provided {0}", errors ? "did not validate." : "validated."); + errorsFound = true; + LogManager.GetCurrentClassLogger().Error(e.Message); + }, true); + + if (errorsFound) + DumpInvalidNodes(config.Root); } + static void DumpInvalidNodes(XElement el) + { + if (el.GetSchemaInfo().Validity != XmlSchemaValidity.Valid) + LogManager.GetCurrentClassLogger().Error("Invalid Element {0}", + el.AncestorsAndSelf() + .InDocumentOrder() + .Aggregate("", (s, i) => s + "/" + i.Name.ToString())); + foreach (XAttribute att in el.Attributes()) + if (att.GetSchemaInfo().Validity != XmlSchemaValidity.Valid) + LogManager.GetCurrentClassLogger().Error("Invalid Attribute {0}", + att + .Parent + .AncestorsAndSelf() + .InDocumentOrder() + .Aggregate("", + (s, i) => s + "/" + i.Name.ToString()) + "/@" + att.Name.ToString() + ); + foreach (XElement child in el.Elements()) + DumpInvalidNodes(child); + } + + static void parseConfInput(string xmlConfFile) { XDocument config = XDocument.Load(xmlConfFile, LoadOptions.SetLineInfo | LoadOptions.SetBaseUri); @@ -248,7 +294,7 @@ namespace TimberWinR // Parse parameters. Params_TextLog args = parseParams_Log(e.Attributes()); - TextLog log = new TextLog(name, location, fields, args); + TailFileInput log = new TailFileInput(name, location, fields, args); _logs.Add(log); } @@ -841,7 +887,19 @@ namespace TimberWinR throw new MissingRequiredAttributeException(e, attributeName); } p.WithMatch(val); + + attributeName = "field"; + try + { + val = e.Attribute(attributeName).Value; + } + catch + { + throw new MissingRequiredAttributeException(e, attributeName); + } + p.WithField(val); break; + case "AddField": string name, value; attributeName = "name"; @@ -964,7 +1022,7 @@ namespace TimberWinR } } - public class TextLog + public class TailFileInput { public string Name { get; private set; } public string Location { get; private set; } @@ -976,7 +1034,7 @@ namespace TimberWinR public bool SplitLongLines { get; private set; } public string ICheckpoint { get; private set; } - public TextLog(string name, string location, List fields, Params_TextLog args) + public TailFileInput(string name, string location, List fields, Params_TextLog args) { Name = name; Location = location; @@ -1421,6 +1479,7 @@ namespace TimberWinR public class Grok { public string Match { get; private set; } + public string Field { get; private set; } public Pair AddField { get; private set; } public bool DropIfMatch { get; private set; } public string RemoveField { get; private set; } @@ -1428,6 +1487,7 @@ namespace TimberWinR public Grok(Params_Grok args) { Match = args.Match; + Field = args.Field; AddField = args.AddField; DropIfMatch = args.DropIfMatch; RemoveField = args.RemoveField; @@ -1452,6 +1512,7 @@ namespace TimberWinR public class Params_Grok { public string Match { get; private set; } + public string Field { get; private set; } public Pair AddField { get; private set; } public bool DropIfMatch { get; private set; } public string RemoveField { get; private set; } @@ -1459,10 +1520,17 @@ namespace TimberWinR public class Builder { private string match; + private string field; private Pair addField; private bool dropIfMatch = false; private string removeField; + public Builder WithField(string value) + { + field = value; + return this; + } + public Builder WithMatch(string value) { match = value; @@ -1492,6 +1560,7 @@ namespace TimberWinR return new Params_Grok() { Match = match, + Field = field, AddField = addField, DropIfMatch = dropIfMatch, RemoveField = removeField diff --git a/TimberWinR/Inputs/IISW3CInputListener.cs b/TimberWinR/Inputs/IISW3CInputListener.cs index 4ff292a..1674e4d 100644 --- a/TimberWinR/Inputs/IISW3CInputListener.cs +++ b/TimberWinR/Inputs/IISW3CInputListener.cs @@ -92,7 +92,7 @@ namespace TimberWinR.Inputs json.Add(new JProperty(field.Name, v)); } json.Add(new JProperty("type", "Win32-IISLog")); - ProcessJson(json.ToString()); + ProcessJson(json); } } // Close the recordset diff --git a/TimberWinR/Inputs/InputListener.cs b/TimberWinR/Inputs/InputListener.cs index 0ad72ec..e0ef091 100644 --- a/TimberWinR/Inputs/InputListener.cs +++ b/TimberWinR/Inputs/InputListener.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,7 +10,7 @@ namespace TimberWinR.Inputs public abstract class InputListener { public CancellationToken CancelToken { get; set; } - public event Action OnMessageRecieved; + public event Action OnMessageRecieved; public InputListener(CancellationToken token) @@ -17,10 +18,11 @@ namespace TimberWinR.Inputs this.CancelToken = token; } - protected void ProcessJson(string message) - { + + protected void ProcessJson(JObject json) + { if (OnMessageRecieved != null) - OnMessageRecieved(message); + OnMessageRecieved(json); } } } diff --git a/TimberWinR/Inputs/TailFileInputListener.cs b/TimberWinR/Inputs/TailFileInputListener.cs new file mode 100644 index 0000000..2d5c23f --- /dev/null +++ b/TimberWinR/Inputs/TailFileInputListener.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.IO; +using Interop.MSUtil; + +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using NLog; + +using LogQuery = Interop.MSUtil.LogQueryClassClass; +using TextLineInputFormat = Interop.MSUtil.COMTextLineInputContextClass; +using LogRecordSet = Interop.MSUtil.ILogRecordset; + +namespace TimberWinR.Inputs +{ + /// + /// Tail a file. + /// + public class TailFileInputListener : InputListener + { + private int _pollingIntervalInSeconds = 1; + private TimberWinR.Configuration.TailFileInput _arguments; + + public TailFileInputListener(TimberWinR.Configuration.TailFileInput arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 1) + : base(cancelToken) + { + _arguments = arguments; + _pollingIntervalInSeconds = pollingIntervalInSeconds; + var task = new Task(FileWatcher, cancelToken); + task.Start(); + } + + private void FileWatcher() + { + var oLogQuery = new LogQuery(); + + var checkpointFileName = Path.Combine(System.IO.Path.GetTempPath(), + string.Format("{0}.lpc", Guid.NewGuid().ToString())); + + var iFmt = new TextLineInputFormat() + { + iCodepage = _arguments.ICodepage, + splitLongLines = _arguments.SplitLongLines, + iCheckpoint = checkpointFileName, + recurse = _arguments.Recurse + }; + + // Create the query + var query = string.Format("SELECT * FROM {0}", _arguments.Location); + + var firstQuery = true; + // Execute the query + while (!CancelToken.IsCancellationRequested) + { + try + { + var rs = oLogQuery.Execute(query, iFmt); + Dictionary colMap = new Dictionary(); + for (int col=0; col - /// Tail a file. - /// - class TextLine - { - - } -} diff --git a/TimberWinR/Inputs/WindowsEvtInputListener.cs b/TimberWinR/Inputs/WindowsEvtInputListener.cs index e1d6b88..738dbc5 100644 --- a/TimberWinR/Inputs/WindowsEvtInputListener.cs +++ b/TimberWinR/Inputs/WindowsEvtInputListener.cs @@ -83,7 +83,7 @@ namespace TimberWinR.Inputs json.Add(new JProperty(field.Name, v)); } json.Add(new JProperty("type", "Win32-Eventlog")); - ProcessJson(json.ToString()); + ProcessJson(json); } } // Close the recordset diff --git a/TimberWinR/Outputs/OutputSender.cs b/TimberWinR/Outputs/OutputSender.cs index 9e1d459..8445cbb 100644 --- a/TimberWinR/Outputs/OutputSender.cs +++ b/TimberWinR/Outputs/OutputSender.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; +using Newtonsoft.Json.Linq; using TimberWinR.Inputs; namespace TimberWinR.Outputs @@ -23,6 +24,6 @@ namespace TimberWinR.Outputs listener.OnMessageRecieved += MessageReceivedHandler; } - protected abstract void MessageReceivedHandler(string jsonMessage); + protected abstract void MessageReceivedHandler(JObject jsonMessage); } } diff --git a/TimberWinR/Outputs/Redis.cs b/TimberWinR/Outputs/Redis.cs index 2672d38..b7c5961 100644 --- a/TimberWinR/Outputs/Redis.cs +++ b/TimberWinR/Outputs/Redis.cs @@ -6,14 +6,18 @@ using System.Net.Sockets; using System.Text; using System.Threading; using ctstone.Redis; +using Newtonsoft.Json.Linq; using NLog; using System.Threading.Tasks; +using RapidRegex.Core; +using System.Text.RegularExpressions; +using System.Globalization; namespace TimberWinR.Outputs { public class RedisOutput : OutputSender - { - private readonly string _logstashIndexName; + { + private readonly string _logstashIndexName; private readonly int _port; private readonly int _timeout; private readonly object _locker = new object(); @@ -21,6 +25,7 @@ namespace TimberWinR.Outputs readonly Task _consumerTask; private readonly string[] _redisHosts; private int _redisHostIndex; + private TimberWinR.Manager _manager; /// /// Get the next client @@ -30,36 +35,37 @@ namespace TimberWinR.Outputs { if (_redisHostIndex >= _redisHosts.Length) _redisHostIndex = 0; - + int numTries = 0; while (numTries < _redisHosts.Length) { try { RedisClient client = new RedisClient(_redisHosts[_redisHostIndex], _port, _timeout); - + _redisHostIndex++; if (_redisHostIndex >= _redisHosts.Length) _redisHostIndex = 0; - + return client; } catch (Exception ex) { - + } numTries++; - } + } return null; } - public RedisOutput(string[] redisHosts, CancellationToken cancelToken, string logstashIndexName = "logstash", int port = 6379, int timeout = 10000) + public RedisOutput(TimberWinR.Manager manager, string[] redisHosts, CancellationToken cancelToken, string logstashIndexName = "logstash", int port = 6379, int timeout = 10000) : base(cancelToken) { + _manager = manager; _redisHostIndex = 0; _redisHosts = redisHosts; - _jsonQueue = new List(); + _jsonQueue = new List(); _port = port; _timeout = timeout; _logstashIndexName = logstashIndexName; @@ -67,18 +73,69 @@ namespace TimberWinR.Outputs _consumerTask.Start(); } - + /// /// Forward on Json message to Redis Logstash queue /// /// - protected override void MessageReceivedHandler(string jsonMessage) + protected override void MessageReceivedHandler(JObject jsonMessage) { - LogManager.GetCurrentClassLogger().Info(jsonMessage); + if (_manager.Config.Groks != null) + ProcessGroks(jsonMessage); + + var message = jsonMessage.ToString(); + LogManager.GetCurrentClassLogger().Info(message); lock (_locker) { - _jsonQueue.Add(jsonMessage); + _jsonQueue.Add(message); + } + } + + private void ProcessGroks(JObject json) + { + foreach (var grok in _manager.Config.Groks) + { + JToken token = null; + if (json.TryGetValue(grok.Field, StringComparison.OrdinalIgnoreCase, out token)) + { + string text = token.ToString(); + if (!string.IsNullOrEmpty(text)) + { + string expr = grok.Match; + var resolver = new RegexGrokResolver(); + var pattern = resolver.ResolveToRegex(expr); + var match = Regex.Match(text, pattern); + if (match.Success) + { + var regex = new Regex(pattern); + var namedCaptures = regex.MatchNamedCaptures(text); + foreach (string fieldName in namedCaptures.Keys) + { + + if (fieldName == "timestamp") + { + string value = namedCaptures[fieldName]; + DateTime ts; + if (DateTime.TryParse(value, out ts)) + json.Add(fieldName, ts.ToUniversalTime()); + else if (DateTime.TryParseExact(value, new string[] + { + "MMM dd hh:mm:ss", + "MMM dd HH:mm:ss", + "MMM dd h:mm", + "MMM dd hh:mm", + }, CultureInfo.InvariantCulture, DateTimeStyles.None, out ts)) + json.Add(fieldName, ts.ToUniversalTime()); + else + json.Add(fieldName, (JToken)namedCaptures[fieldName]); + } + else + json.Add(fieldName, (JToken)namedCaptures[fieldName]); + } + } + } + } } } @@ -120,7 +177,7 @@ namespace TimberWinR.Outputs { } } - client.EndPipe(); + client.EndPipe(); break; } else @@ -131,9 +188,9 @@ namespace TimberWinR.Outputs } } } - catch(Exception ex) + catch (Exception ex) { - LogManager.GetCurrentClassLogger().Error(ex); + LogManager.GetCurrentClassLogger().Error(ex); } } } diff --git a/TimberWinR/TimberWinR.csproj b/TimberWinR/TimberWinR.csproj index 222dc8d..5a9d9c8 100644 --- a/TimberWinR/TimberWinR.csproj +++ b/TimberWinR/TimberWinR.csproj @@ -46,7 +46,8 @@ ..\packages\NLog.3.1.0.0\lib\net40\NLog.dll - + + False ..\packages\RapidRegex.Core.1.0.0.0\lib\net40\RapidRegex.Core.dll @@ -68,7 +69,7 @@ - + @@ -81,7 +82,9 @@ - + + PreserveNewest + diff --git a/TimberWinR/configSchema.xsd b/TimberWinR/configSchema.xsd index e4302ff..cd4bd01 100644 --- a/TimberWinR/configSchema.xsd +++ b/TimberWinR/configSchema.xsd @@ -260,6 +260,7 @@ + diff --git a/packages/RapidRegex.Core.1.0.0.0/lib/net40/RapidRegex.Core.dll b/packages/RapidRegex.Core.1.0.0.0/lib/net40/RapidRegex.Core.dll index e5e975d..6ba5e8e 100644 Binary files a/packages/RapidRegex.Core.1.0.0.0/lib/net40/RapidRegex.Core.dll and b/packages/RapidRegex.Core.1.0.0.0/lib/net40/RapidRegex.Core.dll differ