14 Commits

Author SHA1 Message Date
Tommy Parnell
5165224c65 change DEPRECATION notice 2016-01-20 08:06:25 -05:00
Ryan Breen
fd9306e24e Update README.md
Add deprecation notice.
2015-11-05 14:53:57 -05:00
Greg Lutz
050ae7cf84 Merge pull request #59 from schmidt4brains/feature/AddHttpsBasicAuthSupport
Added support for HTTPS authentication to Found clusters
2015-09-11 06:46:29 -04:00
Doug Schmidt
a99b04e1b1 Added support for HTTPS authentication to Found clusters
Elasticsearch's newly acquired found.io hosted clusters require HTTPS basic authentication in order to feed the index.

This feature branch adds optional support for HTTPS according to https://www.elastic.co/guide/en/found/current/elk-and-found.html#_using_logstash

When SSL is true, a non-empty username and password are required to authenticate against an Elasticsearch cluster.
2015-08-31 17:15:36 -07:00
Greg Lutz
dddbf6ae0d logo 2015-07-13 11:26:50 -04:00
Greg Lutz
147e243478 Update WindowsEvents.md 2015-06-19 11:47:45 -04:00
Eric Fontana
7cf1230d7a Merge pull request #52 from Cimpress-MCP/add_statsd_output
Added StatsD outputter and smart shutdown code.
2015-05-18 07:00:36 -04:00
Eric Fontana
4fe5cec9e2 More doc tweaks to explain where it will show up. 2015-05-15 11:17:20 -04:00
Eric Fontana
9308f91316 I just realized this closed issue #36 2015-05-15 11:06:21 -04:00
Eric Fontana
28d20199ab Updated Release notes and bumped version number to 1.3.26.0 2015-05-15 11:05:06 -04:00
Eric Fontana
5692d2ec42 Switched to use Nuget component for StatsD instead of including the code. 2015-05-15 10:41:09 -04:00
Eric Fontana
4dbb926698 Remove internal names. 2015-05-15 09:57:11 -04:00
Eric Fontana
92f23c1117 Added StatsD outputter and smart shutdown code. 2015-05-15 09:53:24 -04:00
Eric Fontana
cd31cf47f5 Merge pull request #51 from Cimpress-MCP/issue49
Release candidate 1.3.25.0
2015-04-30 13:14:23 -04:00
31 changed files with 3078 additions and 58 deletions

View File

@@ -2,6 +2,6 @@
<packages>
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net40" />
<package id="NUnit.Runners" version="2.6.4" />
<package id="RapidRegex.Core" version="1.0.0.2" targetFramework="net40" />
<package id="RapidRegex.Core" version="1.0.0.4" targetFramework="net40" />
<package id="System.Linq.Dynamic" version="1.0.4" targetFramework="net40" />
</packages>

View File

@@ -2,6 +2,14 @@ TimberWinR
==========
A Native Windows to Redis/Elasticsearch Logstash Agent which runs as a service.
## Development and Roadmap
TimberWinR's development has been and in the short term will continue to be on a hiatus. The project's ownership has been recently transferred, and thus the project will be slow to pick up steam again. Currently a roadmap is being established, and a vision for the project is currently being formulated.
#### Getting Involved
If you wish to be involved with TimberWinR in an on-going basis file an issue.
## Why have TimberWinR?
TimberWinR is a native .NET implementation utilizing Microsoft's [LogParser](http://technet.microsoft.com/en-us/scriptcenter/dd919274.aspx). This means
no JVM/JRuby is required, and LogParser does all the heavy lifting. TimberWinR collects
@@ -9,14 +17,14 @@ the data from LogParser and ships it to Logstash via Redis (or can ship direcly
## Release Notes
[View Version History](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/ReleaseNotes.md)
[View Version History](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/ReleaseNotes.md)
## Basics
TimberWinR uses a configuration file to control how the logs are collected, filtered and shipped off.
These are broken down into:
1. Inputs (Collect data from different sources)
2. Filters (Are applied to all Inputs)
3. Outputs (Redis, Elasticsearch or Stdout)
3. Outputs (e.g. Redis, Elasticsearch, Stdout, StatsD)
### Support ###
Please use the TimberWinR Google Group for discussion and support:
@@ -30,27 +38,27 @@ Latest Build:
## Inputs
The current supported Input format sources are:
1. [Logs](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Logs.md) (Files, a.k.a Tailing a file)
2. [Tcp](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/TcpInput.md) (listens on TCP port for JSON messages)
3. [IISW3C](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/IISW3CInput.md)(Internet Information Services W3C Format)
4. [WindowsEvents](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/WindowsEvents.md) (Windows Event Viewer)
5. [Stdin](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/StdinInput.md) (Standard Input for Debugging)
6. [W3C](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/W3CInput.md)(Internet Information Services W3C Advanced/Custom Format)
7. [Udp](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/UdpInput.md) (listens for UDP on port for JSON messages)
8. [TailFiles](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/TailFiles.md) (Tails log files efficiently)
8. [Generator](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Generator.md) (Generate logs for testing *New*)
1. [Logs](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/Logs.md) (Files, a.k.a Tailing a file)
2. [Tcp](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/TcpInput.md) (listens on TCP port for JSON messages)
3. [IISW3C](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/IISW3CInput.md)(Internet Information Services W3C Format)
4. [WindowsEvents](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/WindowsEvents.md) (Windows Event Viewer)
5. [Stdin](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/StdinInput.md) (Standard Input for Debugging)
6. [W3C](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/W3CInput.md)(Internet Information Services W3C Advanced/Custom Format)
7. [Udp](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/UdpInput.md) (listens for UDP on port for JSON messages)
8. [TailFiles](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/TailFiles.md) (Tails log files efficiently)
8. [Generator](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/Generator.md) (Generate logs for testing *New*)
## Codecs
The current list of supported codecs are:
1. [Multiline](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md)
1. [Multiline](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md)
## Filters
The current list of supported filters are:
1. [Grok](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/GrokFilter.md)
2. [Mutate](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/MutateFilter.md)
3. [Date](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/DateFilter.md)
4. [Json](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/JsonFilter.md)
5. [GeoIP](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/GeoIPFilter.md)
1. [Grok](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/GrokFilter.md)
2. [Mutate](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/MutateFilter.md)
3. [Date](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/DateFilter.md)
4. [Json](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/JsonFilter.md)
5. [GeoIP](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/GeoIPFilter.md)
Note that there are now two syntaxes for filters, array and single, if you want more than one filter of the same
type then you must use the array tag instead of the singular tag. i.e:
@@ -104,10 +112,11 @@ Since TimberWinR only ships to Redis and Elasticsearch, the format generated by
represented as a JSON Property or Array.
## Outputs
1. [Redis](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/RedisOutput.md)
2. [Elasticsearch](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/ElasticsearchOutput.md)
3. [Stdout](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/StdoutOutput.md)
4. [File](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/FileOutput.md)
1. [Redis](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/RedisOutput.md)
2. [Elasticsearch](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/ElasticsearchOutput.md)
3. [Stdout](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/StdoutOutput.md)
4. [File](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/FileOutput.md)
5. [StatsD](https://github.com/tparnell8/TimberWinR/blob/master/TimberWinR/mdocs/StatsD.md)
## Sample Configuration
TimberWinR reads a JSON configuration file, an example file is shown here:

View File

@@ -131,6 +131,8 @@ namespace TimberWinR.ServiceHost
private readonly Arguments _args;
private TimberWinR.Diagnostics.Diagnostics _diags;
private TimberWinR.Manager _manager;
public bool StartingUp { get; set; }
public bool Started { get; set; }
public TimberWinRService(Arguments args)
{
@@ -147,6 +149,8 @@ namespace TimberWinR.ServiceHost
public void Stop()
{
WaitForStartupToComplete();
_cancellationTokenSource.Cancel();
if (_diags != null)
_diags.Shutdown();
@@ -155,14 +159,31 @@ namespace TimberWinR.ServiceHost
_manager.Shutdown();
}
// If you bounce the service too quickly, the shutdown can occur
// before the service has started, which results in a hang, this blocks until
// all thread have properly started (waiting up to 10 seconds max)
private void WaitForStartupToComplete()
{
int tries = 100; // 10 seconds max
if (StartingUp)
{
while (!Started && tries-- >= 0)
{
Thread.Sleep(100);
}
}
}
/// <summary>
/// The Main body of the Service Worker Thread
/// </summary>
private void RunService()
{
StartingUp = true;
_manager = new TimberWinR.Manager(_args.ConfigFile, _args.LogLevel, _args.LogfileDir, _args.LiveMonitor, _cancellationToken);
if (_args.DiagnosticPort > 0)
_diags = new Diagnostics.Diagnostics(_manager, _cancellationToken, _args.DiagnosticPort);
Started = true;
}
}
}

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.25.0")]
[assembly: AssemblyFileVersion("1.3.25.0")]
[assembly: AssemblyVersion("1.3.26.0")]
[assembly: AssemblyFileVersion("1.3.26.0")]

View File

@@ -38,9 +38,16 @@
<ApplicationIcon>timberwinr.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="RapidRegex.Core, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="RapidRegex.Core, Version=1.0.0.4, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\RapidRegex.Core.1.0.0.2\lib\net40\RapidRegex.Core.dll</HintPath>
<HintPath>..\packages\RapidRegex.Core.1.0.0.4\lib\net40\RapidRegex.Core.dll</HintPath>
</Reference>
<Reference Include="StatsdClient, Version=1.0.0.19, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\StatsdClient.1.0.0.19\lib\net35\StatsdClient.dll</HintPath>
</Reference>
<Reference Include="StatsdClient.Configuration">
<HintPath>..\packages\StatsdClient.1.0.0.19\lib\net35\StatsdClient.Configuration.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="RapidRegex.Core" version="1.0.0.2" targetFramework="net40" />
<package id="RapidRegex.Core" version="1.0.0.4" targetFramework="net40" />
<package id="StatsdClient" version="1.0.0.19" targetFramework="net40" />
<package id="Topshelf" version="3.1.4" targetFramework="net40" />
</packages>

View File

@@ -27,6 +27,9 @@ namespace TimberWinR.TestGenerator
[Option("resultsFile", HelpText = "Expected results Results json file")]
public string ExpectedResultsFile { get; set; }
[Option("totalMessages", DefaultValue = 0, HelpText = "The total number of messages to send to the output(s)")]
public int TotalMessages { get; set; }
[Option('n', "numMessages", DefaultValue = 1000, HelpText = "The number of messages to send to the output(s)")]
public int NumMessages { get; set; }

View File

@@ -350,7 +350,7 @@ namespace TimberWinR.TestGenerator
var mbc = outputToken["queuedMessageCount"].Value<int>();
var smc = outputToken["sentMessageCount"].Value<int>();
// LogManager.GetCurrentClassLogger().Info("Queued: {0}, Sent: {1}", mbc, smc);
//LogManager.GetCurrentClassLogger().Info("Output: {2} Queued: {0}, Sent: {1}", mbc, smc, outputToken.ToString());
completed = mbc == 0 && smc >= _totalMessagesToSend;
}
@@ -504,6 +504,8 @@ namespace TimberWinR.TestGenerator
static Task[] RunGenerators(CommandLineOptions options)
{
_totalMessagesToSend = options.TotalMessages;
_monitorTask = Task.Factory.StartNew(() =>
{
using (var syncHandle = new ManualResetEventSlim())

View File

@@ -126,6 +126,18 @@
<Content Include="results5.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="test7-tw.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="test7.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="results7.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="sample-apache.log">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TimberWinR\TimberWinR.csproj">

View File

@@ -0,0 +1,13 @@
{
"Results": {
"Inputs": [
{
"udp": {
"test1: message sent count": "[messages] == 80000",
"test2: average cpu": "[avgCpuUsage] <= 30",
"test3: maximum memory": "[maxMemUsage] <= 30"
}
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
{
"test": "Test 2",
"arguments": {
"--start": "",
"--testFile": "test2.json",
"--testDir": "test2",
"--timberWinRConfig": "test2-tw.json",
"--numMessages": 1234,
"--logLevel": "trace",
"--logLevel": "debug",
"--udp": "5140",
"--jroll": ["r1.jlog", "r2.jlog"],
"--json": ["1.jlog", "2.jlog", "3.jlog", "4.jlog"],

View File

@@ -0,0 +1,37 @@
{
"TimberWinR": {
"Inputs": {
"TailFiles": [
{
"interval": 5,
"logSource": "apache log files",
"location": "..\\sample-apache.log",
"recurse": -1
}
]
},
"Filters": [
{
"grok": {
"type": "Win32-TailLog",
"match": [
"Text",
"%{COMBINEDAPACHELOG}"
]
}
}
],
"Outputs": {
"StatsD": [
{
"type": "Win32-TailLog",
"namespace": "timberwinrtest",
"port": 8125,
"host": "devlexicesnu003.mycompany.svc",
"increment": ["apache.response.%{response}"],
"count": ["apache.bytes", "%{bytes}"]
}
]
}
}
}

View File

@@ -0,0 +1,13 @@
{
"test": "Test 7",
"arguments": {
"--totalMessages": 2223,
"--start": "",
"--testFile": "test7.json",
"--testDir": "test7",
"--timberWinRConfig": "test7-tw.json",
"--numMessages": 1234,
"--logLevel": "debug",
"--resultsFile": "results7.json"
}
}

View File

@@ -1,4 +1,6 @@
namespace TimberWinR.UnitTests.Parser
using TimberWinR.Outputs;
namespace TimberWinR.UnitTests.Parser
{
using System;
@@ -51,5 +53,51 @@
Assert.AreEqual("someindex-" + DateTime.UtcNow.ToString("yyyy.MM.dd"), result);
}
[Test]
public void Given_no_ssl_then_validate_does_not_throw()
{
parser.Ssl = false;
Assert.That(() => parser.Validate(), Throws.Nothing);
}
[Test]
public void Given_ssl_and_no_username_then_validate_throws()
{
parser.Ssl = true;
parser.Password = "pass";
Assert.That(() => parser.Validate(), Throws.Exception.InstanceOf<ElasticsearchOutputParameters.ElasticsearchBasicAuthException>());
}
[Test]
public void Given_ssl_and_no_password_then_validate_throws()
{
parser.Ssl = true;
parser.Username = "user";
Assert.That(() => parser.Validate(), Throws.Exception.InstanceOf<ElasticsearchOutputParameters.ElasticsearchBasicAuthException>());
}
[Test]
public void Given_ssl_and_username_and_password_then_validate_does_not_throw()
{
parser.Ssl = true;
parser.Username = "user";
parser.Password = "pass";
Assert.That(() => parser.Validate(), Throws.Nothing);
}
[Test]
[TestCase("host", 1234, false, null, null, "http://host:1234/")]
[TestCase("host", 1234, true, "user", "pass", "https://user:pass@host:1234/")]
[TestCase("host", 1234, true, "user:", "pass@", "https://user%3A:pass%40@host:1234/")]
public void ComposeUri_Matches_Expected(string host, int port, bool ssl, string username, string password, string expectedUri)
{
var uri = ElasticsearchOutput.ComposeUri(host, port, ssl, username, password);
Assert.That(uri.ToString(), Is.EqualTo(expectedUri));
}
}
}

View File

@@ -39,6 +39,12 @@ namespace TimberWinR
get { return _events; }
}
private List<StatsDOutputParameters> _statsdOutputs = new List<StatsDOutputParameters>();
public IEnumerable<StatsDOutputParameters> StatsDOutputs
{
get { return _statsdOutputs; }
}
private List<RedisOutputParameters> _redisOutputs = new List<RedisOutputParameters>();
public IEnumerable<RedisOutputParameters> RedisOutputs
{
@@ -265,6 +271,8 @@ namespace TimberWinR
if (x.TimberWinR.Outputs != null)
{
if (x.TimberWinR.Outputs.StatsD != null)
c._statsdOutputs.AddRange(x.TimberWinR.Outputs.StatsD.ToList());
if (x.TimberWinR.Outputs.Redis != null)
c._redisOutputs.AddRange(x.TimberWinR.Outputs.Redis.ToList());
if (x.TimberWinR.Outputs.Elasticsearch != null)
@@ -307,6 +315,7 @@ namespace TimberWinR
_events = new List<WindowsEvent>();
_iisw3clogs = new List<IISW3CLogParameters>();
_logs = new List<LogParameters>();
_statsdOutputs = new List<StatsDOutputParameters>();
_redisOutputs = new List<RedisOutputParameters>();
_elasticsearchOutputs = new List<ElasticsearchOutputParameters>();
_stdoutOutputs = new List<StdoutOutputParameters>();

View File

@@ -165,6 +165,15 @@ namespace TimberWinR
if (OnConfigurationProcessed != null)
OnConfigurationProcessed(config);
if (config.StatsDOutputs != null)
{
foreach (var ro in config.StatsDOutputs)
{
var output = new StatsDOutput(this, ro, cancelToken);
Outputs.Add(output);
}
}
if (config.RedisOutputs != null)
{
foreach (var ro in config.RedisOutputs)

View File

@@ -29,6 +29,9 @@ namespace TimberWinR.Outputs
{
private TimberWinR.Manager _manager;
private readonly int _port;
private readonly bool _ssl;
private readonly string _username;
private readonly string _password;
private readonly int _interval;
private readonly int _flushSize;
private readonly int _idleFlushTimeSeconds;
@@ -57,8 +60,8 @@ namespace TimberWinR.Outputs
var nodes = new List<Uri>();
foreach (var host in _hosts)
{
var url = string.Format("http://{0}:{1}", host, _port);
nodes.Add(new Uri(url));
var uri = ComposeUri(host, _port, _ssl, _username, _password);
nodes.Add(uri);
}
var pool = new StaticConnectionPool(nodes.ToArray());
var settings = new ConnectionSettings(pool)
@@ -73,6 +76,13 @@ namespace TimberWinR.Outputs
return client;
}
public static Uri ComposeUri(string host, int port, bool ssl, string username, string password)
{
return ssl
? new Uri(string.Format("https://{0}:{1}@{2}:{3}", Uri.EscapeDataString(username), Uri.EscapeDataString(password), host, port))
: new Uri(string.Format("http://{0}:{1}", host, port));
}
public ElasticsearchOutput(TimberWinR.Manager manager, Parser.ElasticsearchOutputParameters parameters, CancellationToken cancelToken)
: base(cancelToken, "Elasticsearch")
{
@@ -86,6 +96,9 @@ namespace TimberWinR.Outputs
_timeout = parameters.Timeout;
_manager = manager;
_port = parameters.Port;
_ssl = parameters.Ssl;
_username = parameters.Username;
_password = parameters.Password;
_interval = parameters.Interval;
_hosts = parameters.Host;
_jsonQueue = new List<JObject>();
@@ -111,6 +124,9 @@ namespace TimberWinR.Outputs
new JProperty("messages", _sentMessages),
new JProperty("queuedMessageCount", _jsonQueue.Count),
new JProperty("port", _port),
new JProperty("ssl", _ssl),
new JProperty("username", _username),
new JProperty("password", _password),
new JProperty("flushSize", _flushSize),
new JProperty("idleFlushTime", _idleFlushTimeSeconds),
new JProperty("interval", _interval),

View File

@@ -0,0 +1,368 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using CSRedis;
using Nest;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using System.Threading.Tasks;
using RapidRegex.Core;
using System.Text.RegularExpressions;
using System.Globalization;
using StatsdClient;
using TimberWinR.Parser;
namespace TimberWinR.Outputs
{
public class StatsDOutput : OutputSender
{
public int QueueDepth
{
get { return _jsonQueue.Count; }
}
public long SentMessages
{
get { return _sentMessages; }
}
private readonly int _port;
public string _host { get; set; }
private readonly int _interval;
private readonly object _locker = new object();
private readonly List<JObject> _jsonQueue;
private TimberWinR.Manager _manager;
private long _sentMessages;
private long _errorCount;
private readonly int _maxQueueSize;
private readonly bool _queueOverflowDiscardOldest;
private readonly int _flushSize;
private readonly int _idleFlushTimeSeconds;
private readonly int _numThreads;
private Parser.StatsDOutputParameters _params;
public bool Stop { get; set; }
public override JObject ToJson()
{
var json = new JObject(
new JProperty("statsd",
new JObject(
new JProperty("errors", _errorCount),
new JProperty("sentMessageCount", _sentMessages),
new JProperty("queuedMessageCount", _jsonQueue.Count),
new JProperty("port", _port),
new JProperty("threads", _numThreads),
new JProperty("flushSize", _flushSize),
new JProperty("idleFlushTime", _idleFlushTimeSeconds),
new JProperty("maxQueueSize", _maxQueueSize),
new JProperty("overflowDiscardOldest", _queueOverflowDiscardOldest),
new JProperty("interval", _interval),
new JProperty("host", _host)
)));
return json;
}
public StatsDOutput(TimberWinR.Manager manager, Parser.StatsDOutputParameters parameters, CancellationToken cancelToken)
: base(cancelToken, "StatsD")
{
_params = parameters;
_manager = manager;
_port = parameters.Port;
_host = parameters.Host;
_interval = parameters.Interval;
_flushSize = parameters.FlushSize;
_idleFlushTimeSeconds = parameters.IdleFlushTimeInSeconds;
_maxQueueSize = parameters.MaxQueueSize;
_queueOverflowDiscardOldest = parameters.QueueOverflowDiscardOldest;
_numThreads = parameters.NumThreads;
_jsonQueue = new List<JObject>();
var metricsConfig = new MetricsConfig
{
StatsdServerName = _host,
Prefix = parameters.Namespace,
};
StatsdClient.Metrics.Configure(metricsConfig);
for (int i = 0; i < _numThreads; i++)
{
Task.Factory.StartNew(StatsDSender, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
}
public override string ToString()
{
return string.Format("StatsD Host: {0} Port: {1}", _host, _port);
}
/// <summary>
/// Forward on Json message to Redis Logstash queue
/// </summary>
/// <param name="jsonMessage"></param>
protected override void MessageReceivedHandler(JObject jsonMessage)
{
if (_manager.Config.Filters != null)
{
if (ApplyFilters(jsonMessage))
return;
}
var message = jsonMessage.ToString();
LogManager.GetCurrentClassLogger().Trace(message);
lock (_locker)
{
if (_jsonQueue.Count >= _maxQueueSize)
{
// If we've exceeded our queue size, and we're supposed to throw out the oldest objects first,
// then remove as many as necessary to get us under our limit
if (_queueOverflowDiscardOldest)
{
LogManager.GetCurrentClassLogger()
.Warn("Overflow discarding oldest {0} messages", _jsonQueue.Count - _maxQueueSize + 1);
_jsonQueue.RemoveRange(0, (_jsonQueue.Count - _maxQueueSize) + 1);
}
// Otherwise we're in a "discard newest" mode, and this is the newest message, so just ignore it
else
{
LogManager.GetCurrentClassLogger()
.Warn("Overflow discarding newest message: {0}", message);
return;
}
}
_jsonQueue.Add(jsonMessage);
}
}
private bool ApplyFilters(JObject json)
{
bool drop = false;
foreach (var filter in _manager.Config.Filters)
{
if (!filter.Apply(json))
{
LogManager.GetCurrentClassLogger().Debug("{0}: Dropping: {1}", Thread.CurrentThread.ManagedThreadId, json.ToString());
drop = true;
}
}
// Check for matching type (if defined).
if (!drop && !string.IsNullOrEmpty(_params.InputType) && json["type"] != null)
{
string msgType = json["type"].ToString();
if (!string.IsNullOrEmpty(msgType) && msgType != _params.InputType)
return true;
}
return drop;
}
// Places messages back into the queue (for a future attempt)
private void interlockedInsert(List<JObject> messages)
{
lock (_locker)
{
Interlocked.Increment(ref _errorCount);
_jsonQueue.InsertRange(0, messages);
if (_jsonQueue.Count > _maxQueueSize)
{
LogManager.GetCurrentClassLogger().Warn("Exceeded maximum queue depth");
}
}
}
//
// Pull off messages from the Queue, batch them up and send them all across
//
private void StatsDSender()
{
DateTime lastFlushTime = DateTime.MinValue;
using (var syncHandle = new ManualResetEventSlim())
{
// Execute the query
while (!Stop)
{
if (!CancelToken.IsCancellationRequested)
{
try
{
int messageCount = 0;
List<JObject> messages = new List<JObject>();
// Lets get whats in the queue
lock (_locker)
{
messageCount = _jsonQueue.Count;
// Time to flush?
if (messageCount >= _flushSize || (DateTime.UtcNow - lastFlushTime).Seconds >= _idleFlushTimeSeconds)
{
messages = _jsonQueue.Take(messageCount).ToList();
_jsonQueue.RemoveRange(0, messageCount);
if (messages.Count > 0)
_manager.IncrementMessageCount(messages.Count);
}
}
TransmitStats(messages);
if (!Stop)
syncHandle.Wait(TimeSpan.FromMilliseconds(_interval), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (ThreadAbortException)
{
break;
}
catch (Exception ex)
{
Interlocked.Increment(ref _errorCount);
LogManager.GetCurrentClassLogger().Error(ex);
}
}
}
}
}
protected string ExpandField(string fieldName, JObject json)
{
foreach (var token in json.Children())
{
string replaceString = "%{" + token.Path + "}";
fieldName = fieldName.Replace(replaceString, json[token.Path].ToString());
}
return fieldName;
}
private string BuildMetricPath(string metric, JObject json)
{
return string.Format("{0}.{1}", ExpandField(_params.Sender, json), ExpandField(metric, json));
}
private void TransmitStats(List<JObject> messages)
{
// We've got some to send.
if (messages.Count > 0)
{
do
{
try
{
int numMessages = messages.Count;
foreach (var m in messages)
{
SendMetrics(m);
}
messages.RemoveRange(0, numMessages);
Interlocked.Add(ref _sentMessages, numMessages);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
interlockedInsert(messages); // Put the messages back into the queue
break;
}
} while (messages.Count > 0);
}
}
// Process all the metrics for this json
private void SendMetrics(JObject m)
{
if (_params.Gauges != null && _params.Gauges.Length > 0)
DoGauges(m);
if (_params.Counts != null && _params.Counts.Length > 0)
DoCounts(m);
if (_params.Timings != null && _params.Timings.Length > 0)
DoTimings(m);
if (_params.Increments != null && _params.Increments.Length > 0)
DoIncrements(m);
if (_params.Decrements != null && _params.Decrements.Length > 0)
DoDecrements(m);
}
// Process the Gauges
private void DoGauges(JObject json)
{
for (int i=0; i<_params.Gauges.Length; i += 2)
{
string metricPath = BuildMetricPath(_params.Gauges[i], json);
string gaugeName = ExpandField(_params.Gauges[i + 1], json);
int value;
if (int.TryParse(gaugeName, out value))
{
Metrics.Gauge(metricPath, value);
}
}
}
// Process the Gauges
private void DoTimings(JObject json)
{
for (int i = 0; i < _params.Timings.Length; i += 2)
{
string metricPath = BuildMetricPath(_params.Timings[i], json);
string timingName = ExpandField(_params.Timings[i + 1], json);
int value;
if (int.TryParse(timingName, out value))
{
Metrics.Timer(metricPath, value, _params.SampleRate);
}
}
}
// Process the Counts
private void DoCounts(JObject json)
{
for (int i = 0; i < _params.Counts.Length; i += 2)
{
string metricPath = BuildMetricPath(_params.Counts[i], json);
string countName = ExpandField(_params.Counts[i + 1], json);
int value;
if (int.TryParse(countName, out value))
{
Metrics.Counter(metricPath, value, _params.SampleRate);
}
}
}
// Process the Increments
private void DoIncrements(JObject json)
{
foreach (var metric in _params.Increments)
{
string metricPath = BuildMetricPath(metric, json);
Metrics.Counter(metricPath, 1,_params.SampleRate);
}
}
// Process the Increments
private void DoDecrements(JObject json)
{
foreach (var metric in _params.Increments)
{
string metricPath = BuildMetricPath(metric, json);
Metrics.Counter(metricPath, -1, _params.SampleRate);
}
}
}
}

View File

@@ -34,6 +34,7 @@ namespace TimberWinR.Outputs
JObject json = new JObject(
new JProperty("stdout",
new JObject(
new JProperty("queuedMessageCount", _jsonQueue.Count),
new JProperty("sentMessageCount", _sentMessages))));
return json;
@@ -67,7 +68,7 @@ namespace TimberWinR.Outputs
foreach (JObject obj in messages)
{
Console.WriteLine(obj.ToString());
_sentMessages++;
Interlocked.Increment(ref _sentMessages);
}
}
catch (Exception ex)

View File

@@ -543,8 +543,98 @@ namespace TimberWinR.Parser
}
}
public class ElasticsearchOutputParameters
public class StatsDOutputParameters : IValidateSchema
{
public class StatsDGaugeHashException : Exception
{
public StatsDGaugeHashException()
: base("StatsD output 'gauge' must be an array of pairs.")
{
}
}
public class StatsDCountHashException : Exception
{
public StatsDCountHashException()
: base("StatsD output 'count' must be an array of pairs.")
{
}
}
[JsonProperty(PropertyName = "type")]
public string InputType { get; set; }
[JsonProperty(PropertyName = "sender")]
public string Sender { get; set; }
[JsonProperty(PropertyName = "namespace")]
public string Namespace { get; set; }
[JsonProperty(PropertyName = "host")]
public string Host { get; set; }
[JsonProperty(PropertyName = "port")]
public int Port { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
[JsonProperty(PropertyName = "flush_size")]
public int FlushSize { get; set; }
[JsonProperty(PropertyName = "idle_flush_time")]
public int IdleFlushTimeInSeconds { get; set; }
[JsonProperty(PropertyName = "max_queue_size")]
public int MaxQueueSize { get; set; }
[JsonProperty(PropertyName = "queue_overflow_discard_oldest")]
public bool QueueOverflowDiscardOldest { get; set; }
[JsonProperty(PropertyName = "threads")]
public int NumThreads { get; set; }
[JsonProperty(PropertyName = "sample_rate")]
public double SampleRate { get; set; }
[JsonProperty(PropertyName = "increment")] // Array: metric names
public string[] Increments { get; set; }
[JsonProperty(PropertyName = "decrement")] // Array: metric names
public string[] Decrements { get; set; }
[JsonProperty(PropertyName = "gauge")] // Hash: metric_name => gauge
public string[] Gauges { get; set; }
[JsonProperty(PropertyName = "count")] // Hash: metric_name => count
public string[] Counts { get; set; }
[JsonProperty(PropertyName = "timing")] // Hash: metric_name => count
public string[] Timings { get; set; }
public StatsDOutputParameters()
{
SampleRate = 1;
Port = 8125;
Host = "localhost";
Interval = 5000;
FlushSize = 5000;
IdleFlushTimeInSeconds = 10;
QueueOverflowDiscardOldest = true;
MaxQueueSize = 50000;
NumThreads = 1;
Namespace = "timberwinr";
Sender = System.Environment.MachineName.ToLower() + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
@"SYSTEM\CurrentControlSet\services\Tcpip\Parameters")
.GetValue("Domain", "")
.ToString().ToLower();
}
public void Validate()
{
if (Gauges != null && Gauges.Length % 2 != 0)
throw new StatsDGaugeHashException();
if (Counts != null && Counts.Length % 2 != 0)
throw new StatsDCountHashException();
}
}
public class ElasticsearchOutputParameters : IValidateSchema
{
public class ElasticsearchBasicAuthException : Exception
{
public ElasticsearchBasicAuthException()
: base("Elasticsearch 'username' and 'password' properties must be set when SSL is enabled.")
{
}
}
const string IndexDatePattern = "(%\\{(?<format>[^\\}]+)\\})";
[JsonProperty(PropertyName = "host")]
@@ -553,6 +643,12 @@ namespace TimberWinR.Parser
public string Index { get; set; }
[JsonProperty(PropertyName = "port")]
public int Port { get; set; }
[JsonProperty(PropertyName = "ssl")]
public bool Ssl { get; set; }
[JsonProperty(PropertyName = "username")]
public string Username { get; set; }
[JsonProperty(PropertyName = "password")]
public string Password { get; set; }
[JsonProperty(PropertyName = "timeout")]
public int Timeout { get; set; }
[JsonProperty(PropertyName = "threads")]
@@ -580,6 +676,9 @@ namespace TimberWinR.Parser
IdleFlushTimeInSeconds = 10;
Protocol = "http";
Port = 9200;
Ssl = false;
Username = string.Empty;
Password = string.Empty;
Index = "";
Host = new string[] { "localhost" };
Timeout = 10000;
@@ -629,6 +728,11 @@ namespace TimberWinR.Parser
return typeName;
}
public void Validate()
{
if (Ssl && (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)))
throw new ElasticsearchBasicAuthException();
}
}
public class RedisOutputParameters
@@ -668,6 +772,8 @@ namespace TimberWinR.Parser
}
}
public class StdoutOutputParameters
{
[JsonProperty(PropertyName = "interval")]
@@ -729,6 +835,9 @@ namespace TimberWinR.Parser
[JsonProperty("File")]
public FileOutputParameters[] File { get; set; }
[JsonProperty("StatsD")]
public StatsDOutputParameters[] StatsD { get; set; }
}
public class InputSources

View File

@@ -3,6 +3,11 @@
A Native Windows to Redis/Elasticsearch Logstash Agent which runs as a service.
Version / Date
### 1.3.26.0 - 2015-05-15
1. Added StatsD outputter
2. Fixed shutdown hang if shutdown was received before service was fully started up.
3. Closed issue [#36](https://github.com/Cimpress-MCP/TimberWinR/issues/36)
### 1.3.25.0 - 2015-04-30
1. Fixed Issue [#49](https://github.com/Cimpress-MCP/TimberWinR/issues/49)
2. Fixed potential non-thread safe when renaming properties

View File

@@ -60,13 +60,21 @@
<Reference Include="NLog">
<HintPath>..\packages\NLog.3.2.0.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="RapidRegex.Core">
<HintPath>..\packages\RapidRegex.Core.1.0.0.2\lib\net40\RapidRegex.Core.dll</HintPath>
<Reference Include="RapidRegex.Core, Version=1.0.0.4, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\RapidRegex.Core.1.0.0.4\lib\net40\RapidRegex.Core.dll</HintPath>
</Reference>
<Reference Include="RestSharp">
<HintPath>..\packages\RestSharp.105.0.0\lib\net4\RestSharp.dll</HintPath>
</Reference>
<Reference Include="StatsdClient">
<HintPath>..\packages\StatsdClient.1.0.0.19\lib\net35\StatsdClient.dll</HintPath>
</Reference>
<Reference Include="StatsdClient.Configuration">
<HintPath>..\packages\StatsdClient.1.0.0.19\lib\net35\StatsdClient.Configuration.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Linq.Dynamic">
<HintPath>..\packages\System.Linq.Dynamic.1.0.4\lib\net40\System.Linq.Dynamic.dll</HintPath>
@@ -114,6 +122,7 @@
<Compile Include="Manager.cs" />
<Compile Include="Outputs\Elasticsearch.cs" />
<Compile Include="Outputs\OutputSender.cs" />
<Compile Include="Outputs\StatsD.cs" />
<Compile Include="Outputs\Redis.cs" />
<Compile Include="Outputs\File.cs" />
<Compile Include="Outputs\Stdout.cs" />
@@ -139,6 +148,7 @@
</Content>
<None Include="mdocs\Codec.md" />
<None Include="mdocs\DateFilter.md" />
<None Include="mdocs\StatsD.md" />
<None Include="mdocs\Filters.md" />
<None Include="mdocs\GeoIPFilter.md" />
<None Include="mdocs\Generator.md" />

View File

@@ -3,7 +3,7 @@
The Elasticsearch output passes on data directly to Elasticsearch.
## Parameters
The following parameters are allowed when configuring the Redis output.
The following parameters are allowed when configuring the Elasticsearch output.
| Parameter | Type | Description | Details | Default |
| :-------------|:---------|:------------------------------------------------------------| :--------------------------- | :-- |
@@ -14,6 +14,9 @@ The following parameters are allowed when configuring the Redis output.
| *interval* | integer | Interval in milliseconds to sleep during batch sends | Interval | 5000 |
| *max_queue_size* | integer | Maximum Elasticsearch queue depth | | 50000 |
| *port* | integer | Elasticsearch port number | This port must be open | 9200 |
| *ssl* | bool | If true, use an HTTPS connection to Elasticsearch. See [this page] (https://www.elastic.co/guide/en/found/current/elk-and-found.html#_using_logstash) for a configuration example. | *username* and *password* are also required for HTTPS connections. | false |
| *username* | string | Username for Elasticsearch credentials. | Required for HTTPS connection. | |
| *password* | string | Password for Elasticsearch credentials. | Required for HTTPS connection. | |
| *queue_overflow_discard_oldest* | bool | If true, discard oldest messages when max_queue_size reached otherwise discard newest | | true |
| *threads* | [string] | Number of Threads | Number of worker threads processing messages | 1 |
| *enable_ping* | bool | If true, pings the server to test for keep alive | | false |

View File

@@ -33,7 +33,7 @@ The following operations are allowed when mutating a field.
| *remove_field* | property:array |If the filter is successful, remove arbitrary fields from this event. Field names can be dynamic and include parts of the event using the %{field} syntax.
| *remove_tag* | property:array |If the filter is successful, remove arbitrary tags from this event. Field names can be dynamic and include parts of the event using the %{field} syntax.
| *rename* | property:array |Rename one or more fields
| *type* | property:string |Type to which this filter applyes, if empty, applies to all types.
| *type* | property:string |Type to which this filter applies, if empty, applies to all types.
## Operation Details
### match

View File

@@ -0,0 +1,94 @@
# Output: StatsD
The StatsD output passes on data directly to StatsD. (https://github.com/etsy/statsd)
## Parameters
The following parameters are allowed when configuring the StatsD output.
| Parameter | Type | Description | Details | Default |
| :-------------|:---------|:------------------------------------------------------------| :--------------------------- | :-- |
| *count* | string | Array of (metric_name, gauge name) pairs counted | Must come in pairs | |
| *decrement* | string | Array of metrics to be decremented | | |
| *flush_size* | integer | Maximum number of messages before flushing | | 50000 |
| *gauge* | string | Array of (metric_name, gauge name) pairs gauged | Must come in pairs | |
| *host* | string | Hostname or IP of StatsD server | localhost | |
| *idle_flush_time* | integer | Maximum number of seconds elapsed before triggering a flush | | 10 |
| *increment* | string | Array of metrics to be incremented | | |
| *interval* | integer | Interval in milliseconds to sleep between sends | Interval | 5000 |
| *max_queue_size* | integer | Maximum StatsD queue depth | | 50000 |
| *namespace* | string | Namespace for stats | timberwinr | |
| *port* | integer | StatsD port number | This port must be open | 8125 |
| *queue_overflow_discard_oldest* | bool | If true, discard oldest messages when max_queue_size reached otherwise discard newest | | true |
| *sample_rate* | integer | StatsD sample rate | | 1 |
| *sender* | string | Sender name | FQDN | |
| *threads* | string | Number of Threads processing messages | | 1 |
| *timing* | string | Array of (metric_name, timing_name) pairs timed | Must come in pairs | |
| *type* | string |Type to which this filter applies, if empty, applies to all types.
### Example Usage
Example Input: Tail an apache log file, and record counts for bytes and increments for response codes.
sample-apache.log (snip)
```
180.76.5.25 - - [13/May/2015:17:02:26 -0700] "GET /frameset.htm HTTP/1.1" 404 89 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" "www.redlug.com"
208.115.113.94 - - [13/May/2015:17:03:55 -0700] "GET /robots.txt HTTP/1.1" 200 37 "-" "Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)" "redlug.com"
208.115.113.94 - - [13/May/2015:17:03:55 -0700] "GET /robots.txt HTTP/1.1" 200 37 "-" "Mozilla/5.0 (compatible; DotBot/1.1; http://www.opensiteexplorer.org/dotbot, help@moz.com)" "www.redlug.com"
```
Note: [COMBINEDAPACHELOG](https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns) is a standard
Grok Pattern.
TimberWinR configuration
```json
{
"TimberWinR": {
"Inputs": {
"TailFiles": [
{
"interval": 5,
"logSource": "apache log files",
"location": "..\\sample-apache.log",
"recurse": -1
}
]
},
"Filters": [
{
"grok": {
"type": "Win32-TailLog",
"match": [
"Text",
"%{COMBINEDAPACHELOG}"
]
}
}
],
"Outputs": {
"StatsD": [
{
"type": "Win32-TailLog",
"port": 8125,
"host": "stats.mycompany.svc",
"increment": ["apache.response.%{response}"],
"count": ["apache.bytes", "%{bytes}"]
}
]
}
}
}
```
Assuming your FQDN is something like mymachine.mycompany.com, you should see the following in Graphite:
```
stats.counters.timberwinr.mymachine.mycompany.com.apache.bytes.count
stats.counters.timberwinr.mymachine.mycompany.com.apache.bytes.rate
stats.counters.timberwinr.mymachine.mycompany.com.apache.response.200.count
stats.counters.timberwinr.mymachine.mycompany.com.apache.response.200.rate
stats.counters.timberwinr.mymachine.mycompany.com.apache.response.404.count
stats.counters.timberwinr.mymachine.mycompany.com.apache.response.404.rate
...
...
```

View File

@@ -65,3 +65,5 @@ After a successful parse of an event, the following fields are added:
| Message | STRING | The full event message |
| Data | STRING | The binary data associated with the event |

View File

@@ -7,8 +7,9 @@
<package id="NEST" version="1.3.1" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.5" targetFramework="net40" />
<package id="NLog" version="3.2.0.0" targetFramework="net40" />
<package id="RapidRegex.Core" version="1.0.0.2" targetFramework="net40" />
<package id="RapidRegex.Core" version="1.0.0.4" targetFramework="net40" />
<package id="RestSharp" version="105.0.0" targetFramework="net40" />
<package id="StatsdClient" version="1.0.0.19" targetFramework="net40" />
<package id="System.Linq.Dynamic" version="1.0.4" targetFramework="net40" />
<package id="Topshelf" version="3.1.4" targetFramework="net40" />
</packages>

View File

@@ -54,6 +54,8 @@
<File Id="default.json" Source="$(var.TimberWinR.ServiceHost.TargetDir)\default.json" />
<File Id="Newtonsoft.Json.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\Newtonsoft.Json.dll" />
<File Id="Nlog.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\Nlog.dll" />
<File Id="StatsdClient.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\StatsdClient.dll" />
<File Id="StatsdClient.Configuration.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\StatsdClient.Configuration.dll" />
<File Id="RestSharp.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\RestSharp.dll" />
<File Id="RapidRegex.Core.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\RapidRegex.Core.dll" />
<File Id="TimberWinR.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\TimberWinR.dll" />

View File

@@ -2,6 +2,6 @@ $packageName = 'TimberWinR-${version}' # arbitrary name for the package, used in
$installerType = 'msi' #only one of these: exe, msi, msu
$scriptPath = $(Split-Path $MyInvocation.MyCommand.Path)
$fileFullPath = Join-Path $scriptPath 'TimberWinR-${version}.0.msi'
$silentArgs = '{CC4DF908-07C4-4BD8-A9FA-6E6AC315E30B} /quiet'
$silentArgs = '{E001D138-669B-4604-88C5-02C756461C15} /quiet'
$validExitCodes = @(0) #please insert other valid exit codes here, exit codes for ms http://msdn.microsoft.com/en-us/library/aa368542(VS.85).aspx
UnInstall-ChocolateyPackage "$packageName" "$installerType" "$silentArgs" "fileFullPath" -validExitCodes $validExitCodes

BIN
timberwinr.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB