Added Multiline codec closed issue #23

This commit is contained in:
Eric Fontana
2015-01-12 12:35:20 -05:00
parent 884efffb25
commit dec51efccd
17 changed files with 623 additions and 36 deletions

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.18.0")]
[assembly: AssemblyFileVersion("1.3.18.0")]
[assembly: AssemblyVersion("1.3.19.0")]
[assembly: AssemblyFileVersion("1.3.19.0")]

View File

@@ -0,0 +1,13 @@
multiline1 \
ml1_1 \
ml1_2 \
ml1_2
singleline1
singleline2
multiline2 \
ml2_1 \
ml2_2
multiline3 \
ml3_1 \
ml3_2
singleline3

View File

@@ -0,0 +1,19 @@
2015-01-07 13:14:26,572 TEST DEBUG [THREAD : 25] - Sending message to TServer - tcp://10.1111.11.111:1111
'RequestAttachUserData' ('30')
message attributes:
AttributeConnID [long] = 00890
AttributeReferenceID [int] = 88
AttributeThisDN [str] = "2214"
AttributeUserData [bstr] = KVList:
'ActivityID' [str] = "1-XXXXXX"
2015-01-07 13:14:26,574 TEST DEBUG [THREAD : 25] - Writing message RequestAttachUserData in 'proxy1' via '.StatePrimary proxy: proxy1'
2015-01-07 13:14:26,575 TEST DEBUG [THREAD : 25] - sending RequestAttachUserData to Test.Platform.Commons.Connection.CommonConnection
2015-01-07 13:20:31,665 TEST DEBUG [THREAD : SelectorThread] - Proxy got message 'EventOnHook' ('87')
message attributes:
AttributeEventSequenceNumber [long] = 4899493
Time = ComplexClass(TimeStamp):
AttributeTimeinuSecs [int] = 573000
AttributeTimeinSecs [int] = 1420644031
AttributeThisDN [str] = "2214"
. Processing with state .StatePrimary proxy: proxy1
2015-01-07 14:14:26,666 TEST DEBUG [THREAD : 25] - sending RequestAttachUserData to Test.Platform.Commons.Connection.CommonConnection

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NUnit.Framework;
using TimberWinR.Inputs;
using TimberWinR.Parser;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
namespace TimberWinR.UnitTests
{
[TestFixture]
public class MultilineTests
{
[Test(Description = "Test using next")]
public void TestMultiline1()
{
using (StreamReader sr = new StreamReader("Multiline1.txt"))
{
List<JObject> events = new List<JObject>();
Console.SetIn(sr);
Stdin sin = new Stdin();
sin.Codec = new Codec();
sin.Codec.Pattern = "\\\\$";
sin.Codec.What = Codec.WhatType.next;
sin.Codec.Type = Codec.CodecType.multiline;
var cancelTokenSource = new CancellationTokenSource();
using (var syncHandle = new ManualResetEventSlim())
{
try
{
StdinListener sl = new StdinListener(sin, cancelTokenSource.Token);
sl.OnMessageRecieved += o =>
{
events.Add(o);
if (events.Count >= 6)
cancelTokenSource.Cancel();
};
if (!cancelTokenSource.Token.IsCancellationRequested)
syncHandle.Wait(TimeSpan.FromSeconds(10000), cancelTokenSource.Token);
}
catch (OperationCanceledException oex)
{
}
}
Assert.AreEqual(events.Count, 6);
Assert.AreEqual(events[0]["message"].ToString(), "multiline1 \\\nml1_1 \\\nml1_2 \\\nml1_2 ");
Assert.AreEqual(events[1]["message"].ToString(), "singleline1");
Assert.AreEqual(events[2]["message"].ToString(), "singleline2");
Assert.AreEqual(events[3]["message"].ToString(), "multiline2 \\\nml2_1 \\\nml2_2");
Assert.AreEqual(events[4]["message"].ToString(), "multiline3 \\\nml3_1 \\\nml3_2");
Assert.AreEqual(events[5]["message"].ToString(), "singleline3");
}
}
[Test(Description = "Test using previous")]
public void TestMultiline2()
{
using (StreamReader sr = new StreamReader("Multiline2.txt"))
{
List<JObject> events = new List<JObject>();
Console.SetIn(sr);
Stdin sin = new Stdin();
sin.Codec = new Codec();
sin.Codec.Pattern = "^(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2},\\d{3})(.*)$";
sin.Codec.What = Codec.WhatType.previous;
sin.Codec.Type = Codec.CodecType.multiline;
sin.Codec.Negate = true;
var cancelTokenSource = new CancellationTokenSource();
using (var syncHandle = new ManualResetEventSlim())
{
try
{
StdinListener sl = new StdinListener(sin, cancelTokenSource.Token);
sl.OnMessageRecieved += o =>
{
events.Add(o);
if (events.Count >= 4)
cancelTokenSource.Cancel();
};
if (!cancelTokenSource.Token.IsCancellationRequested)
syncHandle.Wait(TimeSpan.FromSeconds(10000), cancelTokenSource.Token);
}
catch (OperationCanceledException oex)
{
}
}
Assert.AreEqual(events.Count, 4);
Assert.AreEqual(events[0]["message"].ToString(), "2015-01-07 13:14:26,572 TEST DEBUG [THREAD : 25] - Sending message to TServer - tcp://10.1111.11.111:1111\n'RequestAttachUserData' ('30')\nmessage attributes:\nAttributeConnID [long] = 00890\nAttributeReferenceID [int] = 88\nAttributeThisDN [str] = \"2214\"\nAttributeUserData [bstr] = KVList: \n\t\t'ActivityID' [str] = \"1-XXXXXX\"");
Assert.AreEqual(events[1]["message"].ToString(), "2015-01-07 13:14:26,574 TEST DEBUG [THREAD : 25] - Writing message RequestAttachUserData in 'proxy1' via '.StatePrimary proxy: proxy1'");
Assert.AreEqual(events[2]["message"].ToString(), "2015-01-07 13:14:26,575 TEST DEBUG [THREAD : 25] - sending RequestAttachUserData to Test.Platform.Commons.Connection.CommonConnection");
Assert.AreEqual(events[3]["message"].ToString(), "2015-01-07 13:20:31,665 TEST DEBUG [THREAD : SelectorThread] - Proxy got message 'EventOnHook' ('87')\nmessage attributes:\nAttributeEventSequenceNumber [long] = 4899493\nTime = ComplexClass(TimeStamp):\n\tAttributeTimeinuSecs [int] = 573000\n\tAttributeTimeinSecs [int] = 1420644031\nAttributeThisDN [str] = \"2214\"\n. Processing with state .StatePrimary proxy: proxy1");
}
}
}
}

View File

@@ -64,6 +64,7 @@
<Compile Include="Inputs\IisW3CRowReaderTests.cs" />
<Compile Include="JsonFilterTests.cs" />
<Compile Include="GrokFilterTests.cs" />
<Compile Include="MultilineTests.cs" />
<Compile Include="Parser\ElasticsearchOutputTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestBase.cs" />
@@ -82,6 +83,14 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Multiline2.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Multiline1.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Configuration;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
@@ -16,9 +18,150 @@ using NLog;
using LogQuery = Interop.MSUtil.LogQueryClassClass;
using TextLineInputFormat = Interop.MSUtil.COMTextLineInputContextClass;
using LogRecordSet = Interop.MSUtil.ILogRecordset;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
public class LogsFileDatabase
{
private static readonly object _locker = new object();
private List<LogsFileDatabaseEntry> Entries { get; set; }
private string DatabaseDirectory { get; set; }
public string DatabaseFileName
{
get { return Path.Combine(DatabaseDirectory, ".timberwinrdb"); }
}
public static Manager Manager { get; set; }
private static LogsFileDatabase instance;
private bool ExistingFile(string logName)
{
lock (_locker)
{
return ExistingFileTest(logName);
}
}
private bool ExistingFileTest(string logName)
{
var existingEntry = (from e in Entries where e.FileName == logName select e).FirstOrDefault();
return existingEntry != null;
}
private void RemoveFileEntry(string logName)
{
lock (_locker)
{
var existingEntry = (from e in Entries where e.FileName == logName select e).FirstOrDefault();
if (existingEntry != null)
{
Entries.Remove(existingEntry);
WriteDatabaseFileNoLock();
}
}
}
private LogsFileDatabaseEntry AddFileEntry(string logName, TextLineInputFormat fmt)
{
LogsFileDatabaseEntry de = new LogsFileDatabaseEntry();
lock (_locker)
{
var lq = new LogQuery();
FileInfo fi = new FileInfo(logName);
de.FileName = logName;
de.Size = fi.Length;
de.SampleTime = DateTime.UtcNow;
de.CreationTime = fi.CreationTimeUtc;
if (fi.Exists)
{
var qcount = string.Format("SELECT max(Index) as MaxRecordNumber FROM {0}", logName);
var rcount = lq.Execute(qcount, fmt);
var qr = rcount.getRecord();
var lrn = (Int64)qr.getValueEx("MaxRecordNumber");
de.MaxRecords = lrn;
}
Entries.Add(de);
WriteDatabaseFileNoLock();
}
return de;
}
public static LogsFileDatabaseEntry AddLogFile(string logName, TextLineInputFormat fmt)
{
Instance.RemoveFileEntry(logName); // Remove if already exists, otherwise ignores.
return Instance.AddFileEntry(logName, fmt);
}
public static LogsFileDatabase Instance
{
get
{
if (instance == null)
{
instance = new LogsFileDatabase(Manager.LogfileDir);
lock (_locker)
{
if (!Directory.Exists(instance.DatabaseDirectory))
{
Directory.CreateDirectory(instance.DatabaseDirectory);
}
if (File.Exists(instance.DatabaseFileName))
instance.ReadDatabaseNoLock();
else
instance.WriteDatabaseFileNoLock();
}
}
return instance;
}
}
private void ReadDatabaseNoLock()
{
JsonSerializer serializer = new JsonSerializer();
if (File.Exists(DatabaseFileName))
Entries = JsonConvert.DeserializeObject<List<LogsFileDatabaseEntry>>(File.ReadAllText(DatabaseFileName));
}
private void WriteDatabaseFileNoLock()
{
File.WriteAllText(DatabaseFileName, JsonConvert.SerializeObject(instance.Entries), Encoding.UTF8);
}
private void ReadDatabaseLock()
{
lock (_locker)
{
ReadDatabaseNoLock();
}
}
private void WriteDatabaseLock()
{
lock (_locker)
{
WriteDatabaseFileNoLock();
}
}
private LogsFileDatabase(string databaseDirectory)
{
DatabaseDirectory = databaseDirectory;
Entries = new List<LogsFileDatabaseEntry>();
}
}
public class LogsFileDatabaseEntry
{
public string FileName { get; set; }
public Int64 MaxRecords { get; set; }
public DateTime CreationTime { get; set; }
public DateTime SampleTime { get; set; }
public long Size { get; set; }
}
/// <summary>
/// Tail a file.
/// </summary>
@@ -31,6 +174,8 @@ namespace TimberWinR.Inputs
private Dictionary<string, DateTime> _logFileCreationTimes;
private Dictionary<string, DateTime> _logFileSampleTimes;
private Dictionary<string, long> _logFileSizes;
private Codec _codec;
private List<string> _multiline { get; set; }
public bool Stop { get; set; }
@@ -39,6 +184,7 @@ namespace TimberWinR.Inputs
{
Stop = false;
_codec = arguments.Codec;
_logFileMaxRecords = new Dictionary<string, Int64>();
_logFileCreationTimes = new Dictionary<string, DateTime>();
_logFileSampleTimes = new Dictionary<string, DateTime>();
@@ -64,15 +210,18 @@ namespace TimberWinR.Inputs
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("log",
new JObject(
new JProperty("messages", _receivedMessages),
new JProperty("type", InputType),
new JProperty("location", _arguments.Location),
new JProperty("logSource", _arguments.LogSource),
new JProperty("codepage", _arguments.CodePage),
new JProperty("splitLongLines", _arguments.SplitLongLines),
new JProperty("recurse", _arguments.Recurse),
new JProperty("files",
new JArray(from f in _logFileMaxRecords.Keys
select new JValue(f))),
@@ -90,9 +239,96 @@ namespace TimberWinR.Inputs
select new JValue(f)))
)));
if (_codec != null)
{
var cp = new JProperty("codec",
new JArray(
new JObject(
new JProperty("type", _codec.Type.ToString()),
new JProperty("what", _codec.What.ToString()),
new JProperty("negate", _codec.Negate),
new JProperty("multilineTag", _codec.MultilineTag),
new JProperty("pattern", _codec.Pattern))));
json.Add(cp);
}
return json;
}
// return true to cancel codec
private void applyMultilineCodec(string msg)
{
if (_codec.Re == null)
_codec.Re = new Regex(_codec.Pattern);
Match match = _codec.Re.Match(msg);
bool isMatch = (match.Success && !_codec.Negate) || (!match.Success && _codec.Negate);
switch (_codec.What)
{
case Codec.WhatType.previous:
if (isMatch)
{
if (_multiline == null)
_multiline = new List<string>();
_multiline.Add(msg);
}
else // No Match
{
if (_multiline != null)
{
string single = string.Join("\n", _multiline.ToArray());
_multiline = null;
JObject jo = new JObject();
jo["message"] = single;
jo.Add("tags", new JArray(_codec.MultilineTag));
AddDefaultFields(jo);
ProcessJson(jo);
_receivedMessages++;
}
_multiline = new List<string>();
_multiline.Add(msg);
}
break;
case Codec.WhatType.next:
if (isMatch)
{
if (_multiline == null)
_multiline = new List<string>();
_multiline.Add(msg);
}
else // No match
{
if (_multiline != null)
{
_multiline.Add(msg);
string single = string.Join("\n", _multiline.ToArray());
_multiline = null;
JObject jo = new JObject();
jo["message"] = single;
jo.Add("tags", new JArray(_codec.MultilineTag));
AddDefaultFields(jo);
ProcessJson(jo);
_receivedMessages++;
}
else
{
JObject jo = new JObject();
jo["message"] = msg;
AddDefaultFields(jo);
ProcessJson(jo);
_receivedMessages++;
}
}
break;
}
}
private void FileWatcher(string fileToWatch)
{
var iFmt = new TextLineInputFormat()
@@ -199,10 +435,15 @@ namespace TimberWinR.Inputs
}
string msg = json["Text"].ToString();
if (!string.IsNullOrEmpty(msg))
{
if (_codec != null && _codec.Type == Codec.CodecType.multiline)
applyMultilineCodec(msg);
else
{
ProcessJson(json);
_receivedMessages++;
}
}
var lrn = (Int64)record.getValueEx("Index");
_logFileMaxRecords[fileName] = lrn;

View File

@@ -1,21 +1,27 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
public class StdinListener : InputListener
{
private Thread _listenThread;
private Codec _codec;
private List<string> _multiline { get; set; }
public StdinListener(CancellationToken cancelToken)
public StdinListener(TimberWinR.Parser.Stdin arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-Console")
{
_codec = arguments.Codec;
_listenThread = new Thread(new ThreadStart(ListenToStdin));
_listenThread.Start();
}
@@ -24,6 +30,21 @@ namespace TimberWinR.Inputs
{
JObject json = new JObject(
new JProperty("stdin", "enabled"));
if (_codec != null)
{
var cp = new JProperty("codec",
new JArray(
new JObject(
new JProperty("type", _codec.Type.ToString()),
new JProperty("what", _codec.What.ToString()),
new JProperty("negate", _codec.Negate),
new JProperty("multilineTag", _codec.MultilineTag),
new JProperty("pattern", _codec.Pattern))));
json.Add(cp);
}
return json;
}
@@ -43,15 +64,87 @@ namespace TimberWinR.Inputs
if (line != null)
{
string msg = ToPrintable(line);
if (_codec != null && _codec.Type == Codec.CodecType.multiline)
applyMultilineCodec(msg);
else
{
JObject jo = new JObject();
jo["message"] = msg;
AddDefaultFields(jo);
ProcessJson(jo);
}
else
break;
}
}
Finished();
}
// return true to cancel codec
private void applyMultilineCodec(string msg)
{
if (_codec.Re == null)
_codec.Re = new Regex(_codec.Pattern);
Match match = _codec.Re.Match(msg);
bool isMatch = (match.Success && !_codec.Negate) || (!match.Success && _codec.Negate);
switch (_codec.What)
{
case Codec.WhatType.previous:
if (isMatch)
{
if (_multiline == null)
_multiline = new List<string>();
_multiline.Add(msg);
}
else // No Match
{
if (_multiline != null)
{
string single = string.Join("\n", _multiline.ToArray());
_multiline = null;
JObject jo = new JObject();
jo["message"] = single;
jo.Add("tags", new JArray(_codec.MultilineTag));
AddDefaultFields(jo);
ProcessJson(jo);
}
_multiline = new List<string>();
_multiline.Add(msg);
}
break;
case Codec.WhatType.next:
if (isMatch)
{
if (_multiline == null)
_multiline = new List<string>();
_multiline.Add(msg);
}
else // No match
{
if (_multiline != null)
{
_multiline.Add(msg);
string single = string.Join("\n", _multiline.ToArray());
_multiline = null;
JObject jo = new JObject();
jo["message"] = single;
jo.Add("tags", new JArray(_codec.MultilineTag));
AddDefaultFields(jo);
ProcessJson(jo);
}
else
{
JObject jo = new JObject();
jo["message"] = msg;
AddDefaultFields(jo);
ProcessJson(jo);
}
}
break;
}
}
}
}

View File

@@ -62,6 +62,8 @@ namespace TimberWinR
public Manager(string jsonConfigFile, string logLevel, string logfileDir, CancellationToken cancelToken)
{
LogsFileDatabase.Manager = this;
StartedOn = DateTime.UtcNow;
var vfi = new FileInfo(jsonConfigFile);
@@ -99,6 +101,11 @@ namespace TimberWinR
LogManager.GetCurrentClassLogger()
.Info("TimberWinR Version {0}", GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString());
LogManager.GetCurrentClassLogger()
.Info("Database Directory: {0}", LogsFileDatabase.Instance.DatabaseFileName);
try
{
// Is it a directory?
@@ -211,7 +218,7 @@ namespace TimberWinR
foreach (var stdin in Config.Stdins)
{
var elistner = new StdinListener(cancelToken);
var elistner = new StdinListener(stdin, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);

View File

@@ -13,6 +13,7 @@ using System.Threading.Tasks;
using RapidRegex.Core;
using System.Text.RegularExpressions;
using System.Globalization;
using TimberWinR.Parser;
namespace TimberWinR.Outputs
{

View File

@@ -93,7 +93,10 @@ namespace TimberWinR.Outputs
protected override void MessageReceivedHandler(Newtonsoft.Json.Linq.JObject jsonMessage)
{
if (_manager.Config.Filters != null)
ApplyFilters(jsonMessage);
{
if (ApplyFilters(jsonMessage))
return;
}
var message = jsonMessage.ToString();
LogManager.GetCurrentClassLogger().Debug(message);
@@ -104,12 +107,17 @@ namespace TimberWinR.Outputs
}
}
private void ApplyFilters(JObject json)
private bool ApplyFilters(JObject json)
{
bool drop = false;
foreach (var filter in _manager.Config.Filters)
{
filter.Apply(json);
if (!filter.Apply(json))
drop = true;
}
return drop;
}
}

View File

@@ -54,7 +54,6 @@ namespace TimberWinR.Parser
protected bool EvaluateCondition(JObject json, string condition)
{
var cond = condition;
IList<string> keys = json.Properties().Select(pn => pn.Name).ToList();
@@ -255,12 +254,49 @@ namespace TimberWinR.Parser
public class Stdin : IValidateSchema
{
[JsonProperty(PropertyName = "codec")]
public Codec Codec { get; set; }
public void Validate()
{
}
}
public class Codec
{
public enum CodecType
{
singleline,
multiline
};
public enum WhatType
{
previous,
next
};
[JsonProperty(PropertyName = "type")]
public CodecType Type { get; set; }
[JsonProperty(PropertyName = "pattern")]
public string Pattern { get; set; }
[JsonProperty(PropertyName = "what")]
public WhatType What { get; set; }
[JsonProperty(PropertyName = "negate")]
public bool Negate { get; set; }
[JsonProperty(PropertyName = "multiline_tag")]
public string MultilineTag { get; set; }
public Regex Re { get; set; }
public Codec()
{
Negate = false;
MultilineTag = "multiline";
}
}
public class Log : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
@@ -277,6 +313,8 @@ namespace TimberWinR.Parser
public int Interval { get; set; }
[JsonProperty(PropertyName = "logSource")]
public string LogSource { get; set; }
[JsonProperty(PropertyName = "codec")]
public Codec Codec { get; set; }
public Log()
{
@@ -763,6 +801,7 @@ namespace TimberWinR.Parser
}
}
public partial class Json : LogstashFilter
{
public class JsonMissingSourceException : Exception

View File

@@ -4,6 +4,10 @@ A Native Windows to Redis/Elasticsearch Logstash Agent which runs as a service.
Version History
### 1.3.19.0 - 01/12/2015
1. Added support for Multiline codecs for Stdin and Logs listeners, addresses issue #23
### 1.3.18.0 - 12/22/2014
1. Fixed bug introduced in 1.3.17.0 which changed the meaning of the delay for Elasticsearch, Redis and Stdout

View File

@@ -122,6 +122,7 @@
<Content Include="GeoLite2City.mmdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="mdocs\Codec.md" />
<None Include="mdocs\DateFilter.md" />
<None Include="mdocs\Filters.md" />
<None Include="mdocs\GeoIPFilter.md" />

31
TimberWinR/mdocs/Codec.md Normal file
View File

@@ -0,0 +1,31 @@
# Codec
## Parameters
The following parameters are allowed when configuring the Codec.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *type* | enum |Codec type 'multiline' | Must be 'multiline' | |
| *pattern* | regex |Regular expression to be matched | Must be legal .NET Regex | |
| *what* | enum |Value can be previous or next | If the pattern matched, does event belong to the next or previous event? | |
Example Input: Mutliline input log file
```json
{
"TimberWinR": {
"Inputs": {
"Logs": [
{
"location": "C:\\Logs1\\multiline.log",
"recurse": -1,
"codec": {
"type": "multiline",
"pattern": "(^.+Exception: .+)|(^\\s+at .+)|(^\\s+... \\d+ more)|(^\\s*Caused by:.+)",
"what": "previous"
}
}
}
}
}
```

View File

@@ -11,6 +11,7 @@ The following parameters are allowed when configuring WindowsEvents.
| *recurse* | integer |Max subdirectory recursion level. | 0 disables subdirectory recursion; -1 enables unlimited recursion. | 0 |
| *splitLongLines* | boolean |Behavior when event messages or event category names cannot be resolved. |When a text line is longer than 128K characters, the format truncates the line and either discards the remaining of the line (when this parameter is set to "false"), or processes the remainder of the line as a new line (when this parameter is set to "true").| false |
| *iCodepage* | integer |Codepage of the text file. | 0 is the system codepage, -1 is UNICODE. | 0 |
| [codec](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md) | object | Codec to use |
Example Input: Monitors all files (recursively) located at C:\Logs1\ matching *.log as a pattern. I.e. C:\Logs1\foo.log, C:\Logs1\Subdir\Log2.log, etc.

View File

@@ -26,4 +26,5 @@ A field: "type": "Win32-Stdin" is automatically appended, and the entire JSON is
| ---- |:-----| :-----------------------------------------------------------------------|
| type | STRING |Win32-Stdin |
| message | STRING | The message typed in |
| [codec](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md) | object | Codec to use |