158 Commits

Author SHA1 Message Date
Eric Fontana
736d0bd68a Fixed documentation to match desired new default of 200 2015-04-09 12:50:24 -04:00
Eric Fontana
065aaa3ec4 Removed test which caused conflict with Newtonsoft 2015-04-09 11:12:15 -04:00
Eric Fontana
835f4f6b61 Fixed topshelf reference 2015-04-09 10:50:19 -04:00
Eric Fontana
2f84f46c39 Re-based NLog package 2015-04-09 10:25:35 -04:00
Eric Fontana
77a2258aff Updated to latest versions of Packages. 2015-04-09 10:20:13 -04:00
Eric Fontana
8983916f6c Updated nuget package restore 2015-04-09 09:22:15 -04:00
Eric Fontana
775f33c843 Removed branched packages/Elasticsearch.Net.1.3.1/Elasticsearch.Net.1.3.1.nupkg 2015-04-09 09:20:55 -04:00
Eric Fontana
3b58f768f4 Trimed project 2015-04-09 09:09:57 -04:00
Eric Fontana
c0dbe8f6e9 updates 2015-04-08 13:27:40 -04:00
Eric Fontana
ef7e50b1b9 Added NuGet.exe 2015-04-08 12:25:24 -04:00
Eric Fontana
b43e26fc01 Cleaned up checked in packages. 2015-04-08 12:16:15 -04:00
Eric Fontana
7f6e563238 Enhanced to log JSON errors to Redis/Elasticsearch 2015-04-08 10:58:27 -04:00
Eric Fontana
e8a54782e4 Remove extra Start call for standalone operation. 2015-04-08 08:49:55 -04:00
Eric Fontana
4e5af689f6 Removed redundant Finished() call per Markus Review comments. 2015-04-08 08:15:17 -04:00
Eric Fontana
77f8c0d303 Bug fix for UdpInputListener, re-factored TailFile and LogsListener to use database. 2015-04-08 07:54:41 -04:00
Eric Fontana
9aeee16499 Merge pull request #34 from Cimpress-MCP/bulk_elasticsearch
No functional changes - refactored to common code for Multiline codec
2015-03-16 08:35:32 -04:00
Eric Fontana
9ef79978a6 Undo bad commit 2015-03-16 08:34:07 -04:00
Eric Fontana
47f233f863 No functional changes - refactored to common code for Multiline codec 2015-03-16 08:32:54 -04:00
Chris Baldauf
a6affcb3c9 Update broken link based on girl report.
Just updating from efontana -> cimpress-mcp organization.

http://derecho.elijah.cs.cmu.edu:8585/cimpress-mcp
2015-03-13 12:44:36 -04:00
Eric Fontana
fa1009af51 Merge pull request #33 from Cimpress-MCP/bulk_elasticsearch
Re-factored to use Elasticsearch Bulk API
2015-03-11 11:58:28 -04:00
Eric Fontana
b937d6ef45 Fixed doc to match default maxQueueSize 2015-03-11 10:29:31 -04:00
Eric Fontana
c16331ac10 Incorporated changes from reviews 2015-03-11 10:28:13 -04:00
Eric Fontana
ac10640edf Disable test for AppVeyor builds 2015-03-06 11:57:00 -05:00
Eric Fontana
e9c27c8c19 Doc tweaks 2015-03-06 10:39:20 -05:00
Eric Fontana
7be95a976e Re-factored to use Elasticsearch NEST and Bulk API 2015-03-06 10:01:50 -05:00
Eric Fontana
f2b0f1a85d Merge pull request #31 from Cimpress-MCP/mitigate_flood
Added new max_batch_count parameter to mitigate flooding when large burs...
2015-03-05 06:40:20 -05:00
Eric Fontana
eaba99144e Fixed test comment 2015-03-05 06:39:26 -05:00
Eric Fontana
3208da6488 Final changes from PR reviews 2015-03-05 06:38:58 -05:00
Eric Fontana
fb473909e7 Removed all warnings. 2015-03-04 09:50:34 -05:00
Eric Fontana
b7095471fb Added Unit tests for batchCount and enhanced Elasticsearch error reporting a bit. 2015-03-04 09:36:01 -05:00
Eric Fontana
d7fa582191 Re-factored Batchcounter logic into separate class. 2015-03-03 14:07:53 -05:00
Eric Fontana
6ea3e581fd Merge branch 'mitigate_flood' of https://github.com/Cimpress-MCP/TimberWinR.git 2015-03-03 13:25:02 -05:00
Eric Fontana
99b51d240d Updated per PR feedback 2015-03-03 13:24:55 -05:00
Eric Fontana
fd123e3a86 Merge pull request #32 from Cimpress-MCP/fix_redisHostIndex_inc
Move redisHostIndex inc to a finally
2015-03-03 13:01:02 -05:00
Ryan Breen
42741fbe1e Move redisHostIndex inc to a finally
It looks like this will just retry connections to the same host if new RedisClient throws.  Move _redisHostIndex++ to a finally.

Should we also be logging when these happen?
2015-03-03 12:54:48 -05:00
Eric Fontana
cdc2d09150 Added new max_batch_count parameter to mitigate flooding when large bursts occur. Essentially the incoming rate could exceed the outgoing rate and this will mitigate this condition. 2015-03-03 09:55:08 -05:00
Eric Fontana
2e28a50222 Merge branch 'master' of https://github.com/Cimpress-MCP/TimberWinR.git 2015-03-03 08:23:31 -05:00
Eric Fontana
92a9adeca8 Handle quit condition gracefully. 2015-03-03 08:23:25 -05:00
Eric Fontana
91cf59612c Merge pull request #30 from Cimpress-MCP/chrisbaldauf-default-diag-port-readme
Fix default diagnostic port
2015-03-02 12:17:37 -05:00
Chris Baldauf
e0aac878ff Fix default diagnostic port
I noticed that the documented default diagnostics port and the actual were different. From what I could see, 5141 was the default diag port and 5142 was the default UDP port. Feel free to correct me if I'm mistaken.
2015-02-27 13:01:37 -05:00
Eric Fontana
dc89ac996a Added missing batch_count param to the docs. 2015-02-27 11:23:37 -05:00
Eric Fontana
57b29a5425 Fixed high CPU usage problem for non-existent log files. 2015-02-27 07:37:09 -05:00
Eric Fontana
e5237e8e59 Default to current directory 2015-02-16 06:45:11 -05:00
Eric Fontana
fb61a49fe5 Bumped up TailFile interval to 60 seconds and updated docs. 2015-01-29 10:49:02 -05:00
Eric Fontana
775935683f Fixed guid 2015-01-29 10:18:38 -05:00
Eric Fontana
024fa68e34 Removed Vistaprint references 2015-01-29 10:17:46 -05:00
Eric Fontana
4654d7dbc1 Added new TailFiles listener. 2015-01-29 09:36:22 -05:00
Eric Fontana
d1e5224ba3 Added missing logSource to the docs. 2015-01-20 09:57:28 -05:00
Eric Fontana
5a34b687bb Split database into separate file 2015-01-20 09:10:18 -05:00
Eric Fontana
2982482f25 More doc updates 2015-01-20 09:06:36 -05:00
Eric Fontana
4d9aa4fd54 Formatting changes 2015-01-20 08:03:52 -05:00
Eric Fontana
5b0d28ce16 Fixed typo 2015-01-20 06:59:28 -05:00
Eric Fontana
4b255bfd27 More doc updates 2015-01-20 06:59:05 -05:00
Eric Fontana
e7a8ff3eb7 Merge branch 'master' of https://github.com/Cimpress-MCP/TimberWinR.git 2015-01-20 06:56:20 -05:00
Eric Fontana
3f227e0914 Fixed codec doc (was missing negate) 2015-01-20 06:56:15 -05:00
Eric Fontana
42301b5c9f Merge pull request #26 from Cimpress-MCP/mutate_example_json_fix
Update MutateFilter.md
2015-01-20 06:51:23 -05:00
Ryan Breen
b1c22ff0ca Update MutateFilter.md
Correct syntax of JSON example.  There was a missing comma.
2015-01-19 11:14:39 -05:00
Eric Fontana
6f3d854cbe Disabled Multiline Tests 2015-01-12 13:18:30 -05:00
Eric Fontana
dec51efccd Added Multiline codec closed issue #23 2015-01-12 12:35:20 -05:00
Eric Fontana
884efffb25 Doc tweak 2015-01-05 07:14:47 -05:00
Eric Fontana
b032ffc8fa Updated doc to include release notes link 2015-01-05 06:50:22 -05:00
Eric Fontana
7b2aee8d13 Added ReleaseNotes.md 2015-01-05 06:41:54 -05:00
Eric Fontana
d2483aa991 Put default back 2014-12-22 06:34:41 -05:00
Eric Fontana
771aa791f1 Fixed syncWait to use Milliseconds. 2014-12-22 06:29:42 -05:00
Eric Fontana
0da192167a Fixed shutdown slowness 2014-12-19 13:29:28 -05:00
Eric Fontana
1eaa5a4657 Fix default for doc 2014-12-19 11:48:03 -05:00
Eric Fontana
cc28914690 Log discarded messages and updated RedisOuput doc for new features. 2014-12-19 11:46:52 -05:00
Eric Fontana
27f3e114ef Merge pull request #21 from gcschorer/FixRedisOutputLeaks
Single array remove instead of multiple
2014-12-19 11:36:11 -05:00
gschorer
8a69b58dfd Don't be stupid about array copying 2014-12-19 11:24:02 -05:00
Eric Fontana
1063f493b5 Merge pull request #20 from gcschorer/FixRedisOutputLeaks
Batch send messages to redis
2014-12-19 11:22:53 -05:00
gschorer
afbc1e2294 Batch send messages to redis
Add a cap on the message backlog in the redis outputter. Defaults to 50k.
2014-12-19 11:08:37 -05:00
Eric Fontana
1f65e45726 Fixed shutdown problem. 2014-12-19 10:19:21 -05:00
Eric Fontana
9734f3516e Update ddoc. 2014-12-18 07:57:42 -05:00
Eric Fontana
11762b6136 Moved wait to bottom of loop. cleanup some old comments. 2014-12-18 07:51:14 -05:00
Eric Fontana
8ba2db7cf4 Added new syntax for handling multiple filters of the same type. 2014-12-18 07:35:23 -05:00
Eric Fontana
f4f8f0b50b Enhanced Json input filter to have logSource which defaults to the filename 2014-12-17 12:40:34 -05:00
Eric Fontana
9d25a8302b Reset guid for Uninstall 2014-12-16 08:23:52 -05:00
Eric Fontana
5f7b96206e Fixed reported bug when reading invalid configuration files, error was not reported. 2014-12-16 08:21:46 -05:00
Eric Fontana
20c16a1ec3 Fixed bug which caused the UDP and TCP Listeners to shut down on improperly formatted JSON 2014-12-12 09:10:49 -05:00
Eric Fontana
89684844dd Reset Guid. 2014-12-11 12:55:16 -05:00
Eric Fontana
0c283224bd Fixed bug with Grok filter not testing values correctly. 2014-12-11 12:46:22 -05:00
Eric Fontana
f1412c6b76 Fixed installer to allow downgrades. 2014-12-02 08:26:50 -05:00
Eric Fontana
ef4893691c Update LICENSE.txt
Changed to new Company name.
2014-12-01 09:03:35 -05:00
Eric Fontana
aa3b0f6440 Added icon. 2014-12-01 06:30:34 -05:00
Eric Fontana
cbf80e94e5 Merge pull request #14 from gediminasgu/iislog_timestamp_col
Elasticsearch output index name custom format
2014-12-01 06:24:43 -05:00
gediminasgu
ad21e34b29 trigger 2014-12-01 06:42:49 +02:00
gediminasgu
8e45b0399b Merge remote-tracking branch 'parent/master' into iislog_timestamp_col 2014-11-30 22:51:13 +02:00
gediminasgu
849e038550 Elasticsearch index name custom formating 2014-11-30 22:51:02 +02:00
Eric Fontana
85fcf11503 Merge pull request #13 from gediminasgu/iislog_timestamp_col
(feat): @timestamp column for IISW3C input now is parsed from date and t...
2014-11-30 14:07:39 -05:00
gediminasgu
e25024da9d Fixed interop path 2014-11-30 20:33:31 +02:00
gediminasgu
5c7f0e5ab9 Nuget added 2014-11-30 20:25:02 +02:00
Eric Fontana
5656f5b03f Added NuGet 2014-11-30 13:12:09 -05:00
gediminasgu
2c4e0700eb (feat): @timestamp column for IISW3C input now is parsed from date and time fields. 2014-11-30 19:16:04 +02:00
Eric Fontana
194ee43bfd Merge pull request #12 from gediminasgu/master
Adds mutate:remove filter to remove fields.
2014-11-28 10:23:52 -05:00
gediminasgu
9761b38fdb (feat): Adds mutate:remove filter to remove fields. 2014-11-28 16:38:43 +02:00
Eric Fontana
c67ff21859 Fixed remaining memory leaks. 2014-11-25 12:10:40 -05:00
Eric Fontana
6df3992110 Fixed Doc Links for new repo 2014-11-24 12:53:51 -05:00
Eric Fontana
05142d2a8f Re-worked Windows Event shutdown to use Thread.Abort to enable shuting down in a quicker fashion. 2014-11-21 09:06:13 -05:00
Eric Fontana
fc1d7402d0 Fixed templatization for uninstall 2014-11-19 06:43:59 -05:00
Eric Fontana
61ab116327 Fixed template 2014-11-19 06:39:36 -05:00
Eric Fontana
5485081dc6 Added missing package assembly 2014-11-18 14:05:11 -05:00
Eric Fontana
b35e195b49 Fixed memory leak with conditions. 2014-11-18 13:31:33 -05:00
Eric Fontana
c94b6ac1d7 Merged in pull request #9, updated chocolatey uninstall to extract product GUID 2014-11-11 07:51:44 -05:00
Eric Fontana
d5f76dacbc Merge pull request #9 from hardcodet/custom_index
Custom index
2014-11-11 06:54:41 -05:00
Philipp Sumi
387bd6c7f9 Elastic output improvements
FIX  Existing implementation only took one element from the queue per loop rather than clearing the queue.
ADD  Clients can submit a custom index that is being leveraged if specified. If not specified, the output reverts to a statically configured index name.
2014-11-11 12:28:32 +01:00
hardcodet
448a5613b2 Merge pull request #1 from efontana/master
Syncing with upstream
2014-11-11 12:22:40 +01:00
Eric Fontana
e28e893120 Added Interval parameter for WindowsEvents and bumped up default interval to 60 seconds. 2014-11-06 10:47:51 -05:00
Eric Fontana
a0c571b9c0 Added Udp Input type. 2014-11-04 08:01:48 -05:00
Eric Fontana
ff9026095a Merge pull request #8 from hardcodet/multiline-json
Support for continuous multi-line JSON objects
2014-11-04 06:27:08 -05:00
Philipp Sumi
7583a586fd CHG TCP listeners now accepts continous streams with multiline json snippets.
FIX   Default configuration template had Outputs on wrong (root) level, thus ignoring them.
2014-10-30 18:50:27 +01:00
Eric Fontana
17a9e382fe Always update file size 2014-10-21 13:12:41 -04:00
Eric Fontana
87dc01b385 Added additional diagnostics for logging 2014-10-21 09:42:59 -04:00
Eric Fontana
303d21d241 Handle log rolling better by checking if filesize gets smaller or creation date is greater. 2014-10-16 06:51:20 -04:00
Eric Fontana
6a483946ef Removed extra Brace 2014-10-15 11:20:27 -04:00
Eric Fontana
30f61b61f1 Fixed problem with wildcard filenames causing invalid existence check 2014-10-15 11:13:01 -04:00
Eric Fontana
bcf5195427 read entire file on log roll 2014-10-10 07:22:13 -04:00
Eric Fontana
3e3157c40b Handle tailing of files that log roll by resetting index on new file detection 2014-10-09 11:05:04 -04:00
Eric Fontana
d25b62823c Fixed problem with Uninstaller and Shutdown handling of Diagnostic service. 2014-10-01 07:47:51 -04:00
Eric Fontana
e02624a8b0 Added type to diagnostic json 2014-09-29 11:14:11 -04:00
Eric Fontana
3762d20949 Added promote option 2014-09-29 11:04:55 -04:00
Eric Fontana
8e62188566 Send "initialized" message on startup. 2014-09-29 08:25:42 -04:00
Eric Fontana
0f660fb937 Enhanced JSON filter and added remove_source and rename properties. 2014-09-29 07:38:12 -04:00
Eric Fontana
19b33a0941 Final doc change for W3CLogs 2014-09-23 06:53:02 -04:00
Eric Fontana
dbf15b2d0e Doc update 2014-09-23 06:49:50 -04:00
Eric Fontana
a18561d92f Updated main doc 2014-09-23 06:44:32 -04:00
Eric Fontana
21c4f8e3b8 Added W3C input to handle custom fields 2014-09-22 13:40:41 -04:00
Eric Fontana
69c142c63b Fixed doc copy/paste error 2014-09-22 11:41:56 -04:00
Eric Fontana
0b725f32d9 Merge branch 'master' of https://github.com/efontana/TimberWinR.git 2014-09-22 11:37:12 -04:00
Eric Fontana
e5c78f1efd Added dependent dlls 2014-09-22 11:37:01 -04:00
Eric Fontana
19d4770523 Added dependent dlls 2014-09-22 11:34:09 -04:00
Eric Fontana
5dd0f5de34 Added new dependencies 2014-09-22 11:31:44 -04:00
Eric Fontana
37f09601dd Added GeoIP Filter 2014-09-22 11:19:11 -04:00
Eric Fontana
e42d99147e Changed to use HTTP Listener 2014-09-18 08:35:36 -04:00
Eric Fontana
2e82d8d7b5 Fixed bug with multiple filters/inputs being dropped. 2014-09-16 07:04:28 -04:00
Eric Fontana
e15ded5405 Cleanup doc 2014-09-12 12:25:57 -04:00
Eric Fontana
81d57db90d Added JSON Filter. 2014-09-12 12:22:34 -04:00
Eric Fontana
60fdea78e3 Added new tests 2014-09-11 08:18:53 -04:00
Eric Fontana
0f51312081 Fixed problem with Drop and Conditionals. 2014-09-11 07:55:34 -04:00
Eric Fontana
d56017e3f5 Updated version number to correct 1.2 2014-09-11 07:06:12 -04:00
Eric Fontana
19f8a496f8 Added type to GrokFilter 2014-09-08 09:07:18 -04:00
Eric Fontana
e1f87678d0 Included Version number in Package Name 2014-09-08 07:01:16 -04:00
Eric Fontana
b7340fecdb Fixed uninstaller to use Guid with braces. 2014-09-08 06:32:12 -04:00
Eric Fontana
9279046d63 Use Guid for Uninstall. 2014-09-05 09:45:17 -04:00
Eric Fontana
91857b9003 Added Chocolatey Uninstall 2014-09-05 08:55:30 -04:00
Eric Fontana
a23354bc0a Handle empty configuration directory. 2014-09-03 10:50:05 -04:00
Eric Fontana
b174511fad Added Google Group reference. 2014-09-02 07:48:53 -04:00
Eric Fontana
f5c6001865 Fixed problem with WindowsEvent Files and wildcards. 2014-08-29 16:41:28 -04:00
Eric Fontana
ab96ad3ea8 Run at a lower priority 2014-08-29 10:16:34 -04:00
Eric Fontana
472903f5ae Changed IIS parser to be smarter and only read the records required. 2014-08-29 09:29:32 -04:00
Eric Fontana
b607bedcaa Changed to use 64 bit value for recordnumbers 2014-08-29 08:19:49 -04:00
Eric Fontana
1fb9d8ff51 Fixed problem with large memory consumption and leak in the COM object LogParser with respect to WindowsEvents 2014-08-29 07:52:59 -04:00
Eric Fontana
162cff9d97 Link to the raw file. 2014-08-28 14:28:52 -04:00
Eric Fontana
7f5927050e Updated doc change. 2014-08-28 13:53:21 -04:00
Eric Fontana
ebd11ad3bd Updated doc 2014-08-28 08:27:54 -04:00
Eric Fontana
be3447307c Added dependency on LogParser 2014-08-28 08:06:34 -04:00
Eric Fontana
2ae633a4a6 Updated template 2014-08-28 07:01:27 -04:00
Eric Fontana
313d499b80 Removed template file from tools 2014-08-28 06:54:40 -04:00
Eric Fontana
03ccfc5d2c Added doc 2014-08-27 13:29:22 -04:00
219 changed files with 9696 additions and 156239 deletions

1
.gitignore vendored
View File

@@ -154,3 +154,4 @@ $RECYCLE.BIN/
# Mac desktop service store files
.DS_Store
packages

6
.nuget/NuGet.Config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>

BIN
.nuget/NuGet.exe Normal file

Binary file not shown.

144
.nuget/NuGet.targets Normal file
View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
<!-- Enable the restore command to run before builds -->
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
<!-- Property that enables building a package from a project -->
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
<!-- Determines if package restore consent is required to restore packages -->
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
<!-- Download NuGet.exe if it does not already exist -->
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
</PropertyGroup>
<ItemGroup Condition=" '$(PackageSources)' == '' ">
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
<!--
<PackageSource Include="https://www.nuget.org/api/v2/" />
<PackageSource Include="https://my-nuget-source/nuget/" />
-->
</ItemGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
<!-- Windows specific commands -->
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
</PropertyGroup>
<PropertyGroup>
<PackagesProjectConfig Condition=" '$(OS)' == 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config</PackagesProjectConfig>
<PackagesProjectConfig Condition=" '$(OS)' != 'Windows_NT'">$(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config</PackagesProjectConfig>
</PropertyGroup>
<PropertyGroup>
<PackagesConfig Condition="Exists('$(MSBuildProjectDirectory)\packages.config')">$(MSBuildProjectDirectory)\packages.config</PackagesConfig>
<PackagesConfig Condition="Exists('$(PackagesProjectConfig)')">$(PackagesProjectConfig)</PackagesConfig>
</PropertyGroup>
<PropertyGroup>
<!-- NuGet command -->
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath>
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 "$(NuGetExePath)"</NuGetCommand>
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch>
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir>
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir>
<!-- Commands -->
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand>
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand>
<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">
RestorePackages;
$(BuildDependsOn);
</BuildDependsOn>
<!-- Make the build depend on restore packages -->
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
$(BuildDependsOn);
BuildPackage;
</BuildDependsOn>
</PropertyGroup>
<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate nuget.exe -->
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
<!--
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
This effectively acts as a lock that makes sure that the download operation will only happen once and all
parallel builds will have to wait for it to complete.
-->
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" />
</Target>
<Target Name="_DownloadNuGet">
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
</Target>
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(RestoreCommand)"
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
<Exec Command="$(RestoreCommand)"
LogStandardErrorAsError="true"
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
</Target>
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(BuildCommand)"
Condition=" '$(OS)' != 'Windows_NT' " />
<Exec Command="$(BuildCommand)"
LogStandardErrorAsError="true"
Condition=" '$(OS)' == 'Windows_NT' " />
</Target>
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputFilename ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.IO" />
<Using Namespace="System.Net" />
<Using Namespace="Microsoft.Build.Framework" />
<Using Namespace="Microsoft.Build.Utilities" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
OutputFilename = Path.GetFullPath(OutputFilename);
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
</Project>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit.Runners" version="2.6.3" />
<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="System.Linq.Dynamic" version="1.0.4" targetFramework="net40" />
</packages>

View File

@@ -1,4 +1,4 @@
Copyright 2014 Vistaprint
Copyright 2014 Cimpress
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.

21
Performance1.psess Normal file
View File

@@ -0,0 +1,21 @@
<VSPerformanceSession Version="1.00">
<Options>
<CollectionMethod>Sampling</CollectionMethod>
<AllocationMethod>None</AllocationMethod>
<AddReport>true</AddReport>
<UniqueReport>Timestamp</UniqueReport>
<SamplingMethod>Cycles</SamplingMethod>
<CycleCount>10000000</CycleCount>
<PageFaultCount>10</PageFaultCount>
<SysCallCount>10</SysCallCount>
<SamplingCounter PlatformID="00000000" CounterID="0000000000000000" ReloadValue="000000000000000a" />
<RelocateBinaries>false</RelocateBinaries>
<HardwareCounters EnableHWCounters="false" />
</Options>
<PreinstrumentEvent>
<InstrEventExclude>false</InstrEventExclude>
</PreinstrumentEvent>
<PostinstrumentEvent>
<InstrEventExclude>false</InstrEventExclude>
</PostinstrumentEvent>
</VSPerformanceSession>

148
README.md
View File

@@ -7,35 +7,104 @@ TimberWinR is a native .NET implementation utilizing Microsoft's [LogParser](htt
no JVM/JRuby is required, and LogParser does all the heavy lifting. TimberWinR collects
the data from LogParser and ships it to Logstash via Redis (or can ship direcly to Elasticsearch)
## Release Notes
[View Version History](https://github.com/Cimpress-MCP/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 (Currently ships only to Redis)
3. Outputs (Redis, Elasticsearch or Stdout)
## Input Formats
### Support ###
Please use the TimberWinR Google Group for discussion and support:
https://groups.google.com/forum/#!forum/timberwinr
Latest Build:
![alt tag](https://ci.appveyor.com/api/projects/status/github/Cimpress-MCP/TimberWinR)
## Inputs
The current supported Input format sources are:
1. [Logs](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/Logs.md) (Files, a.k.a Tailing a file)
2. [Tcp](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/TcpInput.md) (listens on a port for JSON messages)
3. [IISW3C](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/IISW3CInput.md)(Internet Information Services W3C Format)
4. [WindowsEvents](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/WindowsEvents.md) (Windows Event Viewer)
5. [Stdin](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/StdinInput.md) (Standard Input for Debugging)
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 *New*)
## Codecs
The current list of supported codecs are:
1. [Multiline](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md)
## Filters
The current list of supported filters are:
1. [Grok](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/GrokFilter.md)
2. [Mutate](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/MutateFilter.md)
3. [Date](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/DateFilter.md)
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)
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:
A single Json filter using the single tag (this is only provided as a convienience, the array syntax is preferred)
```json
"Filters": [
{
"json": {
"type": "Win32-FileLog",
"condition": "\"[logSource]\" == \"dev\"",
"source": "Text",
"add_field": [
"_index",
"dev-%{yyyy.MM.dd}"
]
}
}
]
```
Multiple Json filters must use the jsonFilters and array syntax, also mutateFilters, grokFilters, dateFilters, geoipFilters.
```json
"Filters": [
{
"jsonFilters": [
{
"type": "Win32-FileLog",
"condition": "\"[logSource]\" == \"dev\"",
"source": "Text",
"add_field": [
"_index",
"dev-%{yyyy.MM.dd}"
]
},
{
"type": "Win32-FileLog",
"condition": "\"[logSource]\" == \"sta\"",
"source": "Text",
"add_field": [
"_index",
"sta-%{yyyy.MM.dd}"
]
}
]
}
]
```
## JSON
Since TimberWinR only ships to Redis, the format generated by TimberWinR is JSON. All fields referenced by TimberWinR can be
Since TimberWinR only ships to Redis and Elasticsearch, the format generated by TimberWinR is JSON. All fields referenced by TimberWinR can be
represented as a JSON Property or Array.
## Supported Output Formats
1. [Redis](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/RedisOutput.md)
2. [Elasticsearch](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/ElasticsearchOutput.md)
3. [Stdout](https://github.com/efontana/TimberWinR/blob/master/TimberWinR/mdocs/StdoutOutput.md)
## 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)
## Sample Configuration
TimberWinR reads a JSON configuration file, an example file is shown here:
@@ -54,7 +123,7 @@ TimberWinR reads a JSON configuration file, an example file is shown here:
"Filters": [
{
"grok": {
"condition": "[type] == \"Win32-Eventlog\"",
"condition": "\"[type]\" == \"Win32-Eventlog\"",
"match": [
"Message",
""
@@ -83,12 +152,14 @@ This configuration:
2. Filters: Removes the ComputerName field
3. Sends the event to Redis services (server1.host.com, server2.host.com) in a shuffling manner (balanced).
## Installation
You must first install LogParser, then install TimberWinR. Install LogParser from here:
## Installation from Source
If installing from source, you must first install LogParser, then install TimberWinR. Install LogParser from here:
[Install LogParser](http://www.microsoft.com/en-us/download/details.aspx?id=24659) from Microsoft.
After installing, follow the remaining directions here.
After installing, follow the remaining directions here. If you install from [Chocolatey](https://chocolatey.org/packages/TimberWinR) then LogParser will automatically
be installed first.
## Running Interactively
You can run TimberWinR interactively when you are developing your JSON config file, to do so use the
following options:
@@ -96,7 +167,13 @@ following options:
TimberWinR.ServiceHost.exe -configFile:myconfig.json -logLevel:Debug
```
## Installation as a Windows Service
## Automatic Installation via Chocolatey
[TimbeWinR Chocolatey](https://chocolatey.org/packages/TimberWinR)
![alt tag](https://raw.github.com/Cimpress-MCP/TimberWinR/master/chocolatey.png)
## Manual Installation as a Windows Service
TimberWinR uses [TopShelf](http://topshelf-project.com/) to install as a service, so all the documentation
for installing and configuring the service is show here [TopShelf Doc](http://docs.topshelf-project.com/en/latest/)
@@ -130,8 +207,8 @@ Options:
-configFile: Specifies the path to the JSON config file, or directory which contains .json file(s).
Default is -configFile:default.json
-diagnosticPort: Specifies the diagnostic port which can be used to get a health check of the service.
Default Port is 5142, A value of 0 will disable it. Open a browser
http://localhost:5142
Default Port is 5141, A value of 0 will disable it. Open a browser
http://localhost:5141
```
#### -configFile
This may be a single .json file or a directory containing .json file(s). If it is a directory, all
@@ -142,8 +219,8 @@ order on disk.
If you really just want to try it out, grab the binary distribution, extract the .zip file
into a directory, e.g. C:\TimberWinR
Grab the [JSON example file](https://github.com/efontana/TimberWinR/blob/master/TimberWinR.ServiceHost/default.json) and place it into C:\TimberWinR\default.json.
Edit the default.json file and change the Redis instance to match yours, replace 'tstlexiceapp006.vistaprint.svc' with the IP or DNS name
Grab the [JSON example file](https://raw.githubusercontent.com/Cimpress-MCP/TimberWinR/master/TimberWinR.ServiceHost/default.json) and place it into C:\TimberWinR\default.json.
Edit the default.json file and change the Redis instance to match yours, replace 'tstlexiceapp006.mycompany.svc' with the IP or DNS name
of the machine running redis. Fire up the collector, enable the verbose debugging to see some Windows Events.
```
@@ -160,6 +237,25 @@ TimberWinR.ServiceHost.exe start
```
### Builds ###
Builds are here.
Soon
TimberWinR is distributed as an installable package via Chocolatey, and it is dependent on
[TimbeWinR Chocolatey](https://chocolatey.org/packages/TimberWinR)
![alt tag](https://raw.github.com/Cimpress-MCP/TimberWinR/master/chocolatey.png)
#### Notes ####
After you install it via Chocolatey, you will have a folder:
```
C:\Program Files (x86)\TimberWinR
```
TimberWinR will be configured to read the file default.json located in the folder above. You can edit this file
to customize your installation.
Use these commands to Stop/Start the service.
```
sc stop TimberWinR ; stop the service
sc start TimberWinR; start the service
```

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
namespace TimberWinR.ExtractID
{
class MsiHandle : SafeHandleMinusOneIsInvalid
{
public MsiHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{
return NativeMethods.MsiCloseHandle(handle) == 0;
}
}
class NativeMethods
{
const string MsiDll = "Msi.dll";
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
public extern static uint MsiOpenPackageW(string szPackagePath, out MsiHandle product);
[DllImport(MsiDll, ExactSpelling = true)]
public extern static uint MsiCloseHandle(IntPtr hAny);
[DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern uint MsiGetProductPropertyW(MsiHandle hProduct, string szProperty, StringBuilder value, ref int length);
[DllImport(MsiDll, ExactSpelling = true)]
public static extern int MsiSetInternalUI(int value, IntPtr hwnd);
public static uint MsiGetProductProperty(MsiHandle hProduct, string szProperty, out string value)
{
StringBuilder sb = new StringBuilder(1024);
int length = sb.Capacity;
uint err;
value = null;
if (0 == (err = MsiGetProductPropertyW(hProduct, szProperty, sb, ref length)))
{
sb.Length = length;
value = sb.ToString();
return 0;
}
return err;
}
}
class Program
{
[STAThread]
static int Main(string[] args)
{
if (args.Length < 2)
{
Console.Error.WriteLine("Expecting MSI and Tempolate file arguments");
return 1;
}
string msiDirectory = args[0];
string updateFile = args[1];
string newFile = args[2];
string msiFile = Directory.GetFiles(msiDirectory, "TimberWinR*.msi").FirstOrDefault();
NativeMethods.MsiSetInternalUI(2, IntPtr.Zero); // Hide all UI. Without this you get a MSI dialog
MsiHandle msi;
uint err;
if (0 != (err = NativeMethods.MsiOpenPackageW(msiFile, out msi)))
{
Console.Error.WriteLine("Can't open MSI, error {0}", err);
return 1;
}
// Strings available in all MSIs
string productCode;
using (msi)
{
if (0 != NativeMethods.MsiGetProductProperty(msi, "ProductCode", out productCode))
throw new InvalidOperationException("Can't obtain product code");
string contents = File.ReadAllText(args[1]);
contents = contents.Replace("${PROJECTGUID}", productCode);
File.WriteAllText(args[2], contents);
Console.WriteLine("Updated {0} ProductID: {1}", args[2], productCode);
return 0;
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TimberWinR.ExtractID")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TimberWinR.ExtractID")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cfb670ee-743d-49c7-b2bf-456bac3a88ef")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{99096939-E9DD-4499-883D-4726745A5843}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TimberWinR.ExtractID</RootNamespace>
<AssemblyName>TimberWinR.ExtractID</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -7,7 +7,9 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Win32;
using TimberWinR.Outputs;
using TimberWinR.ServiceHost;
using TimberWinR.Inputs;
@@ -26,6 +28,9 @@ namespace TimberWinR.ServiceHost
private static void Main(string[] args)
{
Arguments arguments = new Arguments();
HostFactory.Run(hostConfigurator =>
@@ -38,7 +43,8 @@ namespace TimberWinR.ServiceHost
serviceConfigurator.WhenStarted(myService => myService.Start());
serviceConfigurator.WhenStopped(myService => myService.Stop());
});
hostConfigurator.AddCommandLineDefinition("liveMonitor", c => arguments.LiveMonitor = bool.Parse(c.ToString()));
hostConfigurator.AddCommandLineDefinition("configFile", c => arguments.ConfigFile = c);
hostConfigurator.AddCommandLineDefinition("logLevel", c => arguments.LogLevel = c);
hostConfigurator.AddCommandLineDefinition("logDir", c => arguments.LogfileDir = c);
@@ -60,6 +66,7 @@ namespace TimberWinR.ServiceHost
AddServiceParameter("-configFile", arguments.ConfigFile);
AddServiceParameter("-logLevel", arguments.LogLevel);
AddServiceParameter("-logDir", arguments.LogfileDir);
AddServiceParameter("-liveMonitor", arguments.LiveMonitor);
if (arguments.DiagnosticPort > 0)
AddServiceParameter("-diagnosticPort", arguments.DiagnosticPort);
}
@@ -68,8 +75,7 @@ namespace TimberWinR.ServiceHost
}
private static void AddServiceParameter(string paramName, string value)
{
{
string currentValue = Registry.GetValue(KeyPath, KeyName, "").ToString();
if (!string.IsNullOrEmpty(paramName) && !currentValue.Contains(string.Format("{0} ", paramName)))
@@ -80,8 +86,7 @@ namespace TimberWinR.ServiceHost
}
private static void AddServiceParameter(string paramName, int value)
{
{
string currentValue = Registry.GetValue(KeyPath, KeyName, "").ToString();
if (!string.IsNullOrEmpty(paramName) && !currentValue.Contains(string.Format("{0}:", paramName)))
@@ -91,6 +96,16 @@ namespace TimberWinR.ServiceHost
}
}
private static void AddServiceParameter(string paramName, bool value)
{
string currentValue = Registry.GetValue(KeyPath, KeyName, "").ToString();
if (!string.IsNullOrEmpty(paramName) && !currentValue.Contains(string.Format("{0}:", paramName)))
{
currentValue += string.Format(" {0} \"{1}\"", paramName, value.ToString());
Registry.SetValue(KeyPath, KeyName, currentValue);
}
}
}
internal class Arguments
@@ -99,9 +114,10 @@ namespace TimberWinR.ServiceHost
public string LogLevel { get; set; }
public string LogfileDir { get; set; }
public int DiagnosticPort { get; set; }
public bool LiveMonitor { get; set; }
public Arguments()
{
LiveMonitor = false;
DiagnosticPort = 5141;
ConfigFile = "default.json";
LogLevel = "Info";
@@ -147,7 +163,7 @@ namespace TimberWinR.ServiceHost
/// </summary>
private void RunService()
{
_manager = new TimberWinR.Manager(_args.ConfigFile, _args.LogLevel, _args.LogfileDir, _cancellationToken);
_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);
}

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TimberWinR.ServiceHost")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyCopyright("Copyright © 2014-2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -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.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyVersion("1.3.20.0")]
[assembly: AssemblyFileVersion("1.3.20.0")]

View File

@@ -12,6 +12,8 @@
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -32,6 +34,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>timberwinr.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="RapidRegex.Core, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -46,9 +51,8 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Topshelf, Version=3.1.122.0, Culture=neutral, PublicKeyToken=b800c4cfcdeea87b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Topshelf.3.1.3\lib\net40-full\Topshelf.dll</HintPath>
<Reference Include="Topshelf">
<HintPath>..\packages\Topshelf.3.1.4\lib\net40-full\Topshelf.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -62,6 +66,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="timberwinr.ico" />
<None Include="App.config" />
<Content Include="config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -72,7 +77,9 @@
<None Include="loopback.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TimberWinR\TimberWinR.csproj">
@@ -81,6 +88,13 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- 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.
<Target Name="BeforeBuild">

View File

@@ -23,7 +23,7 @@
"Filters": [
{
"grok": {
"condition": "[type] == \"Win32-Eventlog\"",
"condition": "\"[type]\" == \"Win32-Eventlog\"",
"match": [
"Message",
""
@@ -63,7 +63,7 @@
},
{
"date": {
"condition": "[type] == \"Win32-FileLog\"",
"condition": "\"[type]\" == \"Win32-FileLog\"",
"match": [
"timestamp",
"MMM d HH:mm:sss",
@@ -90,7 +90,7 @@
"interval": 5000,
"batch_count": 500,
"host": [
"tstlexiceapp006.vistaprint.svc"
"tstlexiceapp006.mycompany.svc"
]
}
],
@@ -99,7 +99,7 @@
"threads": 1,
"interval": 5000,
"host": [
"tstlexiceapp003.vistaprint.svc"
"tstlexiceapp003.mycompany.svc"
]
}
]

View File

@@ -1,30 +1,42 @@
{
"TimberWinR": {
"Inputs": {
"Inputs": {
"WindowsEvents": [
{
"source": "System,Application",
"source": "Application,System",
"binaryFormat": "PRINT",
"resolveSIDS": true
}
],
"Tcp": [
"Tcp": [
{
"_comment": "Output from NLog",
"_comment": "Output from NLog",
"port": 5140
}
]
},
},
"Filters": [
{
"grok": {
"condition": "\"[EventTypeName]\" == \"Information Event\"",
"match": [
"Text",
""
],
"drop": "true"
}
}
],
"Outputs": {
"Redis": [
{
"_comment": "Change the host to your Redis instance",
"port": 6379,
{
"_comment": "Change the host to your Redis instance",
"port": 6379,
"host": [
"tstlexiceapp006.vistaprint.svc"
"logaggregator.mycompany.svc"
]
}
]
}
}
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,82 @@
using System.ComponentModel;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommandLine.Text;
namespace TimberWinR.TestGenerator
{
class CommandLineOptions
{
// [Option('r', "read", Required = true, HelpText = "Input file to be processed.")]
// public string InputFile { get; set; }
[Option("timberWinRConfig", DefaultValue = "default.json", HelpText = "Config file/directory to use")]
public string TimberWinRConfigFile { get; set; }
[Option("testDir", DefaultValue = ".", HelpText = "Test directory to use (created if necessary)")]
public string TestDir { get; set; }
[Option("testFile", DefaultValue = "", HelpText = "Config file/directory to use")]
public string TestFile { get; set; }
[Option("resultsFile", HelpText = "Expected results Results json file")]
public string ExpectedResultsFile { get; set; }
[Option('n', "numMessages", DefaultValue = 1000, HelpText = "The number of messages to send to the output(s)")]
public int NumMessages { get; set; }
[Option('l', "logLevel", DefaultValue = "debug", HelpText = "Logging Level Debug|Error|Fatal|Info|Off|Trace|Warn")]
public string LogLevel { get; set; }
[Option('v', "verbose", DefaultValue = true, HelpText = "Prints all messages to standard output.")]
public bool Verbose { get; set; }
[Option("jsonLogDir", DefaultValue = ".", HelpText = "Json LogGenerator Log directory")]
public string JsonLogDir { get; set; }
[OptionArray('j', "json", DefaultValue = new string[] {})]
public string[] JsonLogFiles { get; set; }
[OptionArray("jroll", DefaultValue = new string[] { })]
public string[] JsonRollingLogFiles { get; set; }
[Option("jsonRate", DefaultValue = 30, HelpText = "Json Rate in Milliseconds between generation of log lines")]
public int JsonRate { get; set; }
[Option('u', "udp", DefaultValue = 0, HelpText = "Enable UDP generator on this Port")]
public int Udp { get; set; }
[Option("udp-host", DefaultValue = "localhost", HelpText = "Host to send Udp data to")]
public string UdpHost { get; set; }
[Option("udp-rate", DefaultValue = 10, HelpText = "Udp Rate in Milliseconds between generation of log lines")]
public int UdpRate { get; set; }
[Option('t', "tcp", DefaultValue = 0, HelpText = "Enable Tcp generator on this Port")]
public int Tcp { get; set; }
[Option("tcp-host", DefaultValue = "localhost", HelpText = "Host to send Tcp data to")]
public string TcpHost { get; set; }
[Option("tcp-rate", DefaultValue = 10, HelpText = "Tcp Rate in Milliseconds between generation of log lines")]
public int TcpRate { get; set; }
[Option('r', "redis", DefaultValue = 0, HelpText = "Enable Redis generator on this Port")]
public int Redis { get; set; }
[Option("redis-host", DefaultValue = "", HelpText = "Host to send Redis data to")]
public string RedisHost { get; set; }
[ParserState]
public IParserState LastParserState { get; set; }
[HelpOption]
public string GetUsage()
{
return HelpText.AutoBuild(this,
(HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,252 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
namespace TimberWinR.TestGenerator
{
class JsonLogFileTestParameters
{
public int NumMessages { get; set; }
public string LogFileDir { get; set; }
public string LogFileName { get; set; }
public int SleepTimeMilliseconds { get; set; }
public JsonLogFileTestParameters()
{
SleepTimeMilliseconds = 30;
LogFileDir = ".";
NumMessages = 10;
}
}
class JsonLogFileGenerator
{
public static int Generate(JsonLogFileTestParameters parms)
{
LogManager.GetCurrentClassLogger().Info("Start JSON LogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
try
{
if (File.Exists(logFilePath))
{
LogManager.GetCurrentClassLogger().Info("Deleting file: {0}", logFilePath);
File.Delete(logFilePath);
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
var watch = Stopwatch.StartNew();
// This text is always added, making the file longer over time
// if it is not deleted.
using (StreamWriter sw = File.AppendText(logFilePath))
{
sw.AutoFlush = true;
for (int i = 0; i < parms.NumMessages; i++)
{
JObject o = new JObject
{
{"LineNumber", i+1},
{"Application", "jsonlogfile-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "jsonlog"},
{"Message", string.Format("{0}: Testgenerator jsonlogfile message {1}", i+1, DateTime.UtcNow.ToString("o"))},
{"Index", "logstash"}
};
sw.WriteLine(o.ToString(Formatting.None));
Thread.Sleep(parms.SleepTimeMilliseconds);
}
LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
watch.Reset();
}
LogManager.GetCurrentClassLogger().Info("Finished JSON Log File Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
return parms.NumMessages;
}
}
class JsonRollingLogFileGenerator
{
public static int Generate(JsonLogFileTestParameters parms)
{
LogManager.GetCurrentClassLogger().Info("Start JSON RollingLogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
try
{
if (File.Exists(logFilePath))
File.Delete(logFilePath);
if (File.Exists(logFilePath + ".rolled"))
File.Delete(logFilePath + ".rolled");
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
int quarters = parms.NumMessages/4;
int[] segments = new int[] {quarters, quarters, quarters, quarters + parms.NumMessages%4};
var watch = Stopwatch.StartNew();
int recordNumber = 0;
int currentTotal = 0;
for (int segment = 0; segment < 4; segment++)
{
currentTotal += segments[segment];
// This text is always added, making the file longer over time
// if it is not deleted.
using (StreamWriter sw = File.AppendText(logFilePath))
{
sw.AutoFlush = true;
var lwatch = Stopwatch.StartNew();
// The Rolling Generator will roll 1/2 way through the log
for (int i = 0; i < segments[segment]; i++)
{
JObject o = new JObject
{
{"LineNumber", recordNumber + 1},
{"Application", "jsonrollinglogfile-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "jsonrollinglog"},
{
"Message",
string.Format("{0}: Testgenerator jsonrollinglogfile message {1}", recordNumber + 1,
DateTime.UtcNow.ToString("o"))
},
{"Index", "logstash"}
};
sw.WriteLine(o.ToString(Formatting.None));
recordNumber++;
Thread.Sleep(parms.SleepTimeMilliseconds);
}
LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds for {2} logs", Path.GetFullPath(parms.LogFileName), lwatch.Elapsed, segments[segment]);
}
//
// We might not have yet processed all the lines from the first file, so wait till
// we catch up before rolling the log file.
//
LogManager.GetCurrentClassLogger().Info("{0}: Waiting for output to catch up: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, currentTotal);
WaitOutputToCatchUp(logFilePath, currentTotal);
//
// Roll the log + wait for the reader to catch up.
//
LogManager.GetCurrentClassLogger().Info("{0}: Rolling Log File: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, File.GetCreationTimeUtc(logFilePath));
RollLogFile(logFilePath);
LogManager.GetCurrentClassLogger().Info("{0}: Finished Rolling Log File: {1}", Thread.CurrentThread.ManagedThreadId, logFilePath);
}
watch.Stop();
LogManager.GetCurrentClassLogger().Info("Finished JSON RollingLogFile File Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
return parms.NumMessages;
}
private static void WaitOutputToCatchUp(string logFilePath, int firstPart)
{
bool caughtUp = false;
do
{
var json = Program.Diagnostics.DiagnosticsOutput();
IList<JToken> inputs = json["timberwinr"]["inputs"].Children().ToList();
foreach (JToken t in inputs)
{
JProperty inputProp = t.First as JProperty;
if (inputProp.Name == "taillog" || inputProp.Name == "log")
{
var files = inputProp.Value["filedb"].Children().ToList();
foreach (var file in files)
{
var fileName = file["FileName"].ToString();
FileInfo fi1 = new FileInfo(fileName);
FileInfo fi2 = new FileInfo(logFilePath);
if (fi1.FullName == fi2.FullName)
{
var linesProcessed = file["LinesProcessed"].Value<int>();
if (linesProcessed >= firstPart)
{
caughtUp = true;
break;
}
}
}
}
}
Thread.Sleep(300);
} while (!caughtUp);
LogManager.GetCurrentClassLogger().Info("{0}: Finished Waiting for output to catch up: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, firstPart);
}
private static void RollLogFile(string logFilePath)
{
bool moved = false;
do
{
try
{
if (File.Exists(logFilePath + ".rolled"))
File.Delete(logFilePath + ".rolled");
File.Move(logFilePath, logFilePath + ".rolled");
moved = true;
}
catch (Exception)
{
Thread.Sleep(100);
}
} while (!moved);
Thread.Sleep(1000);
}
}
}

View File

@@ -0,0 +1,94 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO;
namespace TimberWinR.TestGenerator
{
class LogFileTestParameters
{
public int NumMessages { get; set; }
public string LogFileDir { get; set; }
public string LogFileName { get; set; }
public int SleepTimeMilliseconds { get; set; }
public LogFileTestParameters()
{
SleepTimeMilliseconds = 30;
LogFileDir = ".";
NumMessages = 10;
}
}
class LogFileGenerator
{
public static int Generate(JsonLogFileTestParameters parms)
{
LogManager.GetCurrentClassLogger().Info("Start LogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
try
{
if (File.Exists(logFilePath))
{
LogManager.GetCurrentClassLogger().Info("Deleting file: {0}", logFilePath);
File.Delete(logFilePath);
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
var watch = Stopwatch.StartNew();
// This text is always added, making the file longer over time
// if it is not deleted.
using (StreamWriter sw = File.AppendText(logFilePath))
{
sw.AutoFlush = true;
for (int i = 0; i < parms.NumMessages; i++)
{
JObject o = new JObject
{
{"LineNumber", i+1},
{"Application", "logfile-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "log"},
{"Message", string.Format("{0}: Testgenerator logfile message {1}", i+1, DateTime.UtcNow.ToString("o"))},
{"Index", "logstash"}
};
sw.WriteLine(o.ToString(Formatting.None));
Thread.Sleep(parms.SleepTimeMilliseconds);
}
LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
watch.Reset();
}
LogManager.GetCurrentClassLogger().Info("Finished LogFile Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
return parms.NumMessages;
}
}
}

View File

@@ -0,0 +1,579 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.CodeDom.Compiler;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Config;
using NLog.Targets;
using ServiceStack.Text.Jsv;
using TimberWinR.Parser;
namespace TimberWinR.TestGenerator
{
public class Program
{
private static List<Task> _tasks = new List<Task>();
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private static Manager _timberWinR;
public static Diagnostics.Diagnostics Diagnostics { get; set; }
private static PerformanceCounter cpuCounter = new PerformanceCounter();
private static PerformanceCounter ramCounter = new PerformanceCounter();
private static Task _monitorTask;
private static int _totalMessagesToSend;
private static int _cpuSampleCount;
private static double _avgCpuUsage;
private static double _totalCpuUsage;
private static double _maxCpuUsage;
private static int _memSampleCount;
private static double _avgMemUsage;
private static double _totalMemUsage;
private static double _maxMemUsage;
private static CommandLineOptions Options;
static int Main(string[] args)
{
_totalMessagesToSend = 0;
cpuCounter.CategoryName = "Processor";
cpuCounter.CounterName = "% Processor Time";
cpuCounter.InstanceName = "_Total";
ramCounter.CategoryName = "Memory";
ramCounter.CounterName = "% Committed Bytes In Use";
Options = new CommandLineOptions();
if (CommandLine.Parser.Default.ParseArguments(args, Options))
{
var testFile = Options.TestFile;
if (!string.IsNullOrEmpty(testFile))
{
if (!File.Exists(Options.TestFile))
throw new Exception(string.Format("No such test file: {0} found", Options.TestFile));
var fargs = ParseTestArguments(testFile, ref Options);
if (!CommandLine.Parser.Default.ParseArguments(fargs, Options))
return 2;
}
SetupTestDirectory(Options);
var swOverall = Stopwatch.StartNew();
swOverall.Start();
InitializeLogging(Options.LogLevel);
LogManager.GetCurrentClassLogger().Info("Starting CPU Usage: {0}, RAM Usage: {1}", getCurrentCpuUsage(), getAvailableRAM());
// Reset the tests.
ResetTests(Options);
var sw = Stopwatch.StartNew();
// Startup TimberWinR
StartTimberWinR(Options.TimberWinRConfigFile, Options.LogLevel, ".", false);
// Run the Generators
var arrayOfTasks = RunGenerators(Options);
// Wait for all Generators to finish
try
{
Task.WaitAll(arrayOfTasks);
}
catch (AggregateException aex)
{
LogManager.GetCurrentClassLogger().Error(aex);
}
LogManager.GetCurrentClassLogger().Info("Generation Finished: " + sw.Elapsed);
sw.Reset();
sw.Start();
// All generators are finished, wait till senders are done.
WaitForOutputTransmission();
LogManager.GetCurrentClassLogger().Info("Finished Transmission: " + sw.Elapsed);
sw.Reset();
sw.Start();
// Get all the stats
var jsonTimberWinr = ShutdownTimberWinR();
LogManager.GetCurrentClassLogger().Info("Finished Shutdown: " + sw.Elapsed);
sw.Stop();
swOverall.Stop();
LogManager.GetCurrentClassLogger().Info("Total Elapsed Time: {0}", swOverall.Elapsed);
int results = VerifyResults(Options, jsonTimberWinr);
Console.ReadKey();
return results;
}
return 1;
}
private static void CopySourceFile(string fileName, string outputDir)
{
FileInfo fi = new FileInfo(fileName);
if (fi.Exists)
File.Copy(fileName, Path.Combine(outputDir, fi.Name));
}
private static void SetupTestDirectory(CommandLineOptions options)
{
if (options.TestDir != "." && Directory.Exists(options.TestDir))
Directory.Delete(options.TestDir, true);
if (!Directory.Exists(options.TestDir))
Directory.CreateDirectory(options.TestDir);
CopySourceFile(options.TestFile, options.TestDir);
CopySourceFile(options.TimberWinRConfigFile, options.TestDir);
CopySourceFile(options.ExpectedResultsFile, options.TestDir);
Directory.SetCurrentDirectory(options.TestDir);
}
private static string[] ParseTestArguments(string testFile, ref CommandLineOptions options)
{
options = new CommandLineOptions();
JObject jtest = JObject.Parse(File.ReadAllText(testFile));
IList<JToken> inputs = jtest["arguments"].Children().ToList();
List<string> testargs = new List<string>();
foreach (JProperty it in inputs)
{
testargs.Add(it.Name);
var cc = it.Value.Children().Count();
if (cc > 0)
{
for (int i = 0; i < cc; i++)
{
testargs.Add(it.Value[i].ToString());
}
}
else
{
testargs.Add(it.Value.ToString());
}
}
var fargs = testargs.ToArray();
return fargs;
}
private static int VerifyResults(CommandLineOptions options, JObject json)
{
var jresult = JObject.Parse(File.ReadAllText(options.ExpectedResultsFile));
json["maxCpuUsage"] = _maxCpuUsage;
json["avgCpuUsage"] = _avgCpuUsage;
json["maxMemUsage"] = _maxMemUsage;
json["avgMemUsage"] = _avgMemUsage;
// TailLogs
IList<JToken> inputs = json["timberwinr"]["inputs"].Children().ToList();
foreach (JToken t in inputs)
{
JProperty inputProp = t.First as JProperty;
switch (inputProp.Name)
{
case "udp":
return VerifyConditions(json, new string[] { "udp" }, inputProp, jresult);
case "log":
case "taillog":
return VerifyConditions(json, new string[] { "log", "taillog" }, inputProp, jresult);
}
}
return 0;
}
private static int VerifyConditions(JObject json, string[] logTypes, JProperty inputProp, JObject jresult)
{
var ttail = inputProp.Value as JObject;
foreach (var resultInput in jresult["Results"]["Inputs"].Children().ToList())
{
JProperty rinputProp = resultInput.First as JProperty;
if (logTypes.Contains(rinputProp.Name))
{
foreach (JProperty testProp in rinputProp.Value)
{
try
{
var cond1 = testProp.Value.ToString();
IList<string> tkeys = ttail.Properties().Select(pn => pn.Name).ToList();
foreach (string tkey in tkeys)
cond1 = cond1.Replace(string.Format("[{0}]", tkey), string.Format("{0}", ttail[tkey].ToString()));
// Add builtins
cond1 = cond1.Replace("[avgCpuUsage]", json["avgCpuUsage"].ToString());
cond1 = cond1.Replace("[maxCpuUsage]", json["maxCpuUsage"].ToString());
cond1 = cond1.Replace("[avgMemUsage]", json["avgMemUsage"].ToString());
cond1 = cond1.Replace("[maxMemUsage]", json["maxMemUsage"].ToString());
var p1 = Expression.Parameter(typeof(JObject), "json");
var e1 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p1 },
typeof(bool), cond1);
bool r1 = (bool)e1.Compile().DynamicInvoke(ttail);
if (!r1)
{
LogManager.GetCurrentClassLogger().Error("Test Failed: '{0}: ({1})'", testProp.Name, cond1);
return 1;
}
else
{
LogManager.GetCurrentClassLogger()
.Info("PASSED({0}): '{1}: ({2})'", inputProp.Name, testProp.Name, cond1);
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger()
.Error("Error parsing expression '{0}': {1}", testProp.Value.ToString(),
ex.Message);
return 2;
}
}
}
}
return 0;
}
// Wait till all output has been transmitted.
private static void WaitForOutputTransmission()
{
bool completed = false;
do
{
var json = Diagnostics.DiagnosticsOutput();
//Console.WriteLine(json.ToString(Formatting.Indented));
IList<JToken> inputs = json["timberwinr"]["inputs"].Children().ToList();
foreach (var so in inputs.Children())
{
var token = so.First;
var messages = token["messages"].Value<int>();
// Console.WriteLine("{0} messages", messages);
}
IList<JToken> outputs = json["timberwinr"]["outputs"].Children().ToList();
foreach (var so in outputs.Children())
{
var outputToken = so.First;
var mbc = outputToken["queuedMessageCount"].Value<int>();
var smc = outputToken["sentMessageCount"].Value<int>();
// LogManager.GetCurrentClassLogger().Info("Queued: {0}, Sent: {1}", mbc, smc);
completed = mbc == 0 && smc >= _totalMessagesToSend;
}
Thread.Sleep(250);
} while (!completed);
}
private static void sampleUsages()
{
getCurrentCpuUsage();
getAvailableRAM();
}
private static string getCurrentCpuUsage()
{
_cpuSampleCount++;
var v = cpuCounter.NextValue();
if (v > _maxCpuUsage)
_maxCpuUsage = v;
_totalCpuUsage += v;
_avgCpuUsage = _totalCpuUsage / _cpuSampleCount;
return v + "%";
}
private static string getAvailableRAM()
{
_memSampleCount++;
var v = ramCounter.NextValue();
if (v > _maxMemUsage)
_maxMemUsage = v;
_totalMemUsage += v;
_avgMemUsage = _totalMemUsage / _memSampleCount;
return v + "MB";
}
private static JObject ShutdownTimberWinR()
{
_timberWinR.Shutdown();
// Cancel any/all other threads
_cancellationTokenSource.Cancel();
var json = Diagnostics.DiagnosticsOutput();
LogManager.GetCurrentClassLogger()
.Info("Average CPU Usage: {0}%, Average RAM Usage: {1}MB, Max CPU: {2}%, Max Mem: {3}MB", _avgCpuUsage, _avgMemUsage, _maxCpuUsage, _maxMemUsage);
LogManager.GetCurrentClassLogger().Info(json.ToString());
Diagnostics.Shutdown();
return json;
}
static void StartTimberWinR(string configFile, string logLevel, string logFileDir, bool enableLiveMonitor)
{
_timberWinR = new TimberWinR.Manager(configFile, logLevel, logFileDir, enableLiveMonitor, _cancellationTokenSource.Token, false);
_timberWinR.OnConfigurationProcessed += TimberWinROnOnConfigurationProcessed;
_timberWinR.Start(_cancellationTokenSource.Token);
Diagnostics = new Diagnostics.Diagnostics(_timberWinR, _cancellationTokenSource.Token, 5141);
}
private static void TimberWinROnOnConfigurationProcessed(Configuration configuration)
{
Console.WriteLine("Processed Config: {0}", configuration.GetHashCode());
if (!string.IsNullOrEmpty(Options.RedisHost) && configuration.RedisOutputs != null && configuration.RedisOutputs.Count() > 0)
{
foreach (var ro in configuration.RedisOutputs)
{
ro.Host = new string[] { Options.RedisHost };
}
}
}
static void InitializeLogging(string logLevel)
{
var loggingConfiguration = new LoggingConfiguration();
// Create our default targets
var coloredConsoleTarget = new ColoredConsoleTarget();
var logFileDir = ".";
Target fileTarget = CreateDefaultFileTarget(logFileDir);
loggingConfiguration.AddTarget("Console", coloredConsoleTarget);
loggingConfiguration.AddTarget("DailyFile", fileTarget);
// The LogLevel.Trace means has to be at least Trace to show up on console
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, coloredConsoleTarget));
// LogLevel.Debug means has to be at least Debug to show up in logfile
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
LogManager.Configuration = loggingConfiguration;
LogManager.EnableLogging();
LogManager.GlobalThreshold = LogLevel.FromString(logLevel);
}
static FileTarget CreateDefaultFileTarget(string logPath)
{
return new FileTarget
{
ArchiveEvery = FileArchivePeriod.None,
ArchiveAboveSize = 5 * 1024 * 1024,
MaxArchiveFiles = 5,
BufferSize = 10,
FileName = Path.Combine(logPath, "TimberWinR.TestGenerator", "TimberWinRTestGen.log"),
ArchiveFileName = Path.Combine(logPath, "TimberWinR-TestGenerator_log-{#######}.log"),
};
}
static void ResetTests(CommandLineOptions options)
{
if (File.Exists(".timberwinrdb"))
File.Delete(".timberwinrdb");
if (File.Exists("TimberWinR.TestGenerator\\TimberWinRTestGen.log"))
File.Delete("TimberWinR.TestGenerator\\TimberWinRTestGen.log");
if (File.Exists("TimberWinR\\TimberWinR.log"))
File.Delete("TimberWinR\\TimberWinR.log");
if (options.JsonLogFiles.Length > 0)
{
foreach (var logFile in options.JsonLogFiles)
{
if (File.Exists(logFile))
File.Delete(logFile);
}
}
if (options.JsonRollingLogFiles.Length > 0)
{
foreach (var logFile in options.JsonRollingLogFiles)
{
if (File.Exists(logFile))
File.Delete(logFile);
}
}
}
static Task[] RunGenerators(CommandLineOptions options)
{
_monitorTask = Task.Factory.StartNew(() =>
{
using (var syncHandle = new ManualResetEventSlim())
{
try
{
// Execute the query
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
sampleUsages();
// LogManager.GetCurrentClassLogger().Info("Starting CPU Usage: {0}, RAM Usage: {1}", getCurrentCpuUsage(), getAvailableRAM());
syncHandle.Wait(TimeSpan.FromMilliseconds(options.JsonRate), _cancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
}, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
StartJson(options);
StartJsonRolling(options);
StartUdp(options);
StartTcp(options);
return _tasks.ToArray();
}
static void StartJson(CommandLineOptions options)
{
if (options.JsonLogFiles.Length > 0)
{
foreach (var logFile in options.JsonLogFiles)
{
_totalMessagesToSend += options.NumMessages;
if (options.Verbose)
LogManager.GetCurrentClassLogger()
.Info("Starting LogFile Generator for {0}",
Path.GetFullPath(Path.Combine(options.JsonLogDir, logFile)));
_tasks.Add(Task.Factory.StartNew(() =>
{
var p = new JsonLogFileTestParameters()
{
NumMessages = options.NumMessages,
LogFileDir = options.JsonLogDir,
LogFileName = logFile,
SleepTimeMilliseconds = options.JsonRate
};
JsonLogFileGenerator.Generate(p);
Thread.Sleep(250);
}));
}
}
}
private static void StartJsonRolling(CommandLineOptions options)
{
if (options.JsonRollingLogFiles.Length > 0)
{
foreach (var logFile in options.JsonRollingLogFiles)
{
_totalMessagesToSend += options.NumMessages;
if (options.Verbose)
LogManager.GetCurrentClassLogger()
.Info("Starting RollingLogFile Generator for {0}",
Path.GetFullPath(Path.Combine(options.JsonLogDir, logFile)));
_tasks.Add(Task.Factory.StartNew(() =>
{
var p = new JsonLogFileTestParameters()
{
NumMessages = options.NumMessages,
LogFileDir = options.JsonLogDir,
LogFileName = logFile,
SleepTimeMilliseconds = options.JsonRate
};
JsonRollingLogFileGenerator.Generate(p);
Thread.Sleep(250);
}));
}
}
}
static void StartUdp(CommandLineOptions options)
{
if (options.Udp > 0)
{
if (options.Verbose)
LogManager.GetCurrentClassLogger()
.Info("Starting UDP Generator for {0}:{1}", options.UdpHost, options.Udp);
_tasks.Add(Task.Factory.StartNew(() =>
{
var p = new UdpTestParameters()
{
Port = options.Udp,
Host = options.UdpHost,
NumMessages = options.NumMessages,
SleepTimeMilliseconds = options.UdpRate
};
_totalMessagesToSend += UdpTestGenerator.Generate(p);
}));
}
}
static void StartTcp(CommandLineOptions options)
{
if (options.Tcp > 0)
{
if (options.Verbose)
LogManager.GetCurrentClassLogger()
.Info("Starting Tcp Generator for {0}:{1}", options.TcpHost, options.Tcp);
_totalMessagesToSend += options.NumMessages;
_tasks.Add(Task.Factory.StartNew(() =>
{
var p = new TcpTestParameters()
{
Port = options.Tcp,
Host = options.TcpHost,
NumMessages = options.NumMessages,
SleepTimeMilliseconds = options.TcpRate
};
TcpTestGenerator.Generate(p);
}));
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TimberWinR.TestGenerator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TimberWinR.TestGenerator")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a56bf91c-c5f8-4771-8ef8-ab9ad28179c4")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using ServiceStack.Redis;
namespace TimberWinR.TestGenerator
{
class RedisTestParameters
{
public int Port { get; set; }
public string Host { get; set; }
public int NumMessages { get; set; }
public RedisTestParameters()
{
NumMessages = 100;
Port = 6379;
Host = "localhost";
}
}
class RedisTestGenerator
{
public static void Generate(RedisTestParameters parms)
{
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
var rc = new RedisClient(parms.Host, parms.Port);
for (int i = 0; i < parms.NumMessages; i++)
{
JObject o = new JObject
{
{"Application", "redis-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "redis"},
{"Message", "redis message " + DateTime.UtcNow.ToString("o")},
{"Index", "logstash"}
};
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(o.ToString());
var restult = rc.RPush("logstash", bytes);
}
}
}
}

View File

@@ -0,0 +1,62 @@
using System.Threading;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using ServiceStack.Text;
namespace TimberWinR.TestGenerator
{
class TcpTestParameters
{
public int Port { get; set; }
public string Host { get; set; }
public int NumMessages { get; set; }
public int SleepTimeMilliseconds { get; set; }
public TcpTestParameters()
{
NumMessages = 100;
Port = 5140;
Host = "localhost";
SleepTimeMilliseconds = 10;
}
}
class TcpTestGenerator
{
public static int Generate(TcpTestParameters parms)
{
TcpClient server = new TcpClient(parms.Host, parms.Port);
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
using (NetworkStream stream = server.GetStream())
{
for (int i = 0; i < parms.NumMessages; i++)
{
JObject o = new JObject
{
{"Application", "tcp-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "tcp"},
{"Message", "tcp message " + DateTime.UtcNow.ToString("o")},
{"Index", "logstash"}
};
byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}\n", o.ToString()));
stream.Write(data, 0, data.Length);
Thread.Sleep(parms.SleepTimeMilliseconds);
}
}
return parms.NumMessages;
}
}
}

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TimberWinR.TestGenerator</RootNamespace>
<AssemblyName>TimberWinR.TestGenerator</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLine">
<HintPath>..\packages\CommandLineParser.1.9.71\lib\net40\CommandLine.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=3.2.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.3.2.0.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Common">
<HintPath>..\packages\ServiceStack.Common.Signed.4.0.38\lib\net40\ServiceStack.Common.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\packages\ServiceStack.Interfaces.4.0.38\lib\portable-wp80+sl5+net40+win8+monotouch+monoandroid\ServiceStack.Interfaces.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Redis">
<HintPath>..\packages\ServiceStack.Redis.Signed.4.0.38\lib\net40\ServiceStack.Redis.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Text">
<HintPath>..\packages\ServiceStack.Text.Signed.4.0.38\lib\net40\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CommandLineOptions.cs" />
<Compile Include="Dynamic.cs" />
<Compile Include="LogFileGenerator.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RedisTestGenerator.cs" />
<Compile Include="TcpTestGenerator.cs" />
<Compile Include="JsonLogFileGenerator.cs" />
<Compile Include="UdpTestGenerator.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="default.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="results1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="test1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="test1-twconfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="packages.config" />
<Content Include="test2-tw.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="test2.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="results2.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TimberWinR\TimberWinR.csproj">
<Project>{4ef96a08-21db-4178-be44-70dae594632c}</Project>
<Name>TimberWinR</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,70 @@
using System.Threading;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace TimberWinR.TestGenerator
{
class UdpTestParameters
{
public int Port { get; set; }
public string Host { get; set; }
public int NumMessages { get; set; }
public int SleepTimeMilliseconds { get; set; }
public UdpTestParameters()
{
NumMessages = 100;
Port = 6379;
Host = "localhost";
SleepTimeMilliseconds = 10;
}
}
class UdpTestGenerator
{
public static int Generate(UdpTestParameters parms)
{
var hostName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
IPAddress broadcast;
if (!IPAddress.TryParse(parms.Host, out broadcast))
broadcast = Dns.GetHostEntry(parms.Host).AddressList[0];
Socket s = new Socket(broadcast.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
LogManager.GetCurrentClassLogger().Info("Start UDP Generation");
for (int i = 0; i < parms.NumMessages; i++)
{
JObject o = new JObject
{
{"Application", "udp-generator"},
{"Host", hostName},
{"UtcTimestamp", DateTime.UtcNow.ToString("o")},
{"Type", "udp"},
{"Message", "Testgenerator udp message " + DateTime.UtcNow.ToString("o")},
{"Index", "logstash"}
};
byte[] sendbuf = Encoding.UTF8.GetBytes(o.ToString());
IPEndPoint ep = new IPEndPoint(broadcast, parms.Port);
s.SendTo(sendbuf, ep);
Thread.Sleep(parms.SleepTimeMilliseconds);
}
LogManager.GetCurrentClassLogger().Info("Finished UDP Generation");
return parms.NumMessages;
}
}
}

View File

@@ -0,0 +1,45 @@
{
"TimberWinR": {
"Inputs": {
"Udp": [
{
"_comment": "Output from NLog",
"port": 5140
}
],
"TailFiles": [
{
"interval": 5,
"logSource": "log files",
"location": "*.jlog",
"recurse": -1
}
]
},
"Filters": [
{
"grok": {
"condition": "\"[EventTypeName]\" == \"Information Event\"",
"match": [
"Text",
""
],
"drop": "true"
}
}
],
"Outputs": {
"Redis": [
{
"_comment": "Change the host to your Redis instance",
"port": 6379,
"batch_count": 500,
"threads": 2,
"host": [
"tstlexiceapp006.vistaprint.svc"
]
}
]
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommandLineParser" version="1.9.71" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net40" />
<package id="NLog" version="3.2.0.0" targetFramework="net40" />
<package id="ServiceStack.Common.Signed" version="4.0.38" targetFramework="net40" />
<package id="ServiceStack.Interfaces" version="4.0.38" targetFramework="net40" />
<package id="ServiceStack.Redis.Signed" version="4.0.38" targetFramework="net40" />
<package id="ServiceStack.Text.Signed" version="4.0.38" targetFramework="net40" />
</packages>

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
{
"TimberWinR": {
"Inputs": {
"Udp": [
{
"_comment": "Output from NLog",
"port": 5140
}
],
"TailFiles": [
{
"interval": 5,
"logSource": "log files",
"location": "*.jlog",
"recurse": -1
}
]
},
"Filters": [
{
"grok": {
"condition": "\"[EventTypeName]\" == \"Information Event\"",
"match": [
"Text",
""
],
"drop": "true"
}
}
],
"Outputs": {
"Redis": [
{
"_comment": "Change the host to your Redis instance",
"port": 6379,
"batch_count": 500,
"threads": 2,
"host": [
"tstlexiceapp006.vistaprint.svc"
]
}
]
}
}
}

View File

@@ -0,0 +1,15 @@
{
"test": "Test 1",
"arguments": {
"--testFile": "test1.json",
"--testDir": "test1",
"--timberWinRConfig": "test1-twconfig.json",
"--numMessages": 1234,
"--logLevel": "debug",
"--udp-host": "::1",
"--udp": "5140",
"--jroll": ["r1.jlog", "r2.jlog"],
"--json": ["1.jlog", "2.jlog", "3.jlog", "4.jlog"],
"--resultsFile": "results1.json"
}
}

View File

@@ -0,0 +1,45 @@
{
"TimberWinR": {
"Inputs": {
"Udp": [
{
"_comment": "Output from NLog",
"port": 5140
}
],
"Logs": [
{
"interval": 5,
"logSource": "log files",
"location": "*.jlog",
"recurse": -1
}
]
},
"Filters": [
{
"grok": {
"condition": "\"[EventTypeName]\" == \"Information Event\"",
"match": [
"Text",
""
],
"drop": "true"
}
}
],
"Outputs": {
"Redis": [
{
"_comment": "Change the host to your Redis instance",
"port": 6379,
"batch_count": 500,
"threads": 2,
"host": [
"tstlexiceapp006.vistaprint.svc"
]
}
]
}
}
}

View File

@@ -0,0 +1,14 @@
{
"test": "Test 2",
"arguments": {
"--testFile": "test2.json",
"--testDir": "test2",
"--timberWinRConfig": "test2-tw.json",
"--numMessages": 1234,
"--logLevel": "debug",
"--udp": "5140",
"--jroll": ["r1.jlog", "r2.jlog"],
"--json": ["1.jlog", "2.jlog", "3.jlog", "4.jlog"],
"--resultsFile": "results2.json"
}
}

View File

@@ -85,7 +85,7 @@ namespace TimberWinR.UnitTests
[{
""host"":
[
""logaggregator.vistaprint.svc""
""logaggregator.mycompany.svc""
]
}]
}
@@ -94,7 +94,7 @@ namespace TimberWinR.UnitTests
Configuration c = Configuration.FromString(redisJson);
RedisOutput redis = c.RedisOutputs.First() as RedisOutput;
RedisOutputParameters redis = c.RedisOutputs.First() as RedisOutputParameters;
Assert.IsTrue(redis.Host.Length >= 1);
}

View File

@@ -0,0 +1,126 @@
using System;
using System.IO;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using TimberWinR.Parser;
using System.Text;
using System.Collections.Generic;
namespace TimberWinR.UnitTests
{
// Class which implements a Fake redis server for test purposes.
class FakeRediServer
{
private readonly System.Net.Sockets.TcpListener _tcpListenerV4;
private readonly System.Net.Sockets.TcpListener _tcpListenerV6;
private Thread _listenThreadV4;
private Thread _listenThreadV6;
private readonly int _port;
private CancellationToken _cancelToken;
private bool _shutdown;
public FakeRediServer(CancellationToken cancelToken, int port = 6379)
{
_port = port;
_cancelToken = cancelToken;
_shutdown = false;
_tcpListenerV6 = new System.Net.Sockets.TcpListener(IPAddress.IPv6Any, port);
_tcpListenerV4 = new System.Net.Sockets.TcpListener(IPAddress.Any, port);
_listenThreadV4 = new Thread(new ParameterizedThreadStart(ListenForClients));
_listenThreadV4.Start(_tcpListenerV4);
_listenThreadV6 = new Thread(new ParameterizedThreadStart(ListenForClients));
_listenThreadV6.Start(_tcpListenerV6);
}
public void Shutdown()
{
_shutdown = true;
this._tcpListenerV4.Stop();
this._tcpListenerV6.Stop();
}
private void ListenForClients(object olistener)
{
System.Net.Sockets.TcpListener listener = olistener as System.Net.Sockets.TcpListener;
listener.Start();
while (!_cancelToken.IsCancellationRequested && !_shutdown)
{
try
{
//blocks until a client has connected to the server
TcpClient client = listener.AcceptTcpClient();
// Wait for a client, spin up a thread.
var clientThread = new Thread(new ParameterizedThreadStart(HandleNewClient));
clientThread.Start(client);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.Interrupted)
break;
}
}
}
private void HandleNewClient(object client)
{
var tcpClient = (TcpClient)client;
try
{
NetworkStream clientStream = tcpClient.GetStream();
int i;
Byte[] bytes = new Byte[16535];
String data = null;
do
{
try
{
// Loop to receive all the data sent by the client.
while ((i = clientStream.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
//System.Diagnostics.Debug.WriteLine(String.Format("Received: {0}", data));
// Process the data sent by the client.
data = ":1000\r\n";
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
// Send back a response.
clientStream.Write(msg, 0, msg.Length);
// System.Diagnostics.Debug.WriteLine(String.Format("Sent: {0}", data));
}
}
catch (IOException)
{
}
} while (true);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
tcpClient.Close();
}
private void ProcessJson(JObject json)
{
Console.WriteLine(json.ToString());
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NUnit.Framework;
using TimberWinR.Parser;
using Newtonsoft.Json.Linq;
namespace TimberWinR.UnitTests
{
[TestFixture]
public class GeoIPFilterTests
{
[Test]
public void TestDropConditions()
{
JObject jsonInputLine1 = new JObject
{
{"type", "Win32-FileLog"},
{"IP", "8.8.8.8"}
};
string jsonFilter = @"{
""TimberWinR"":{
""Filters"":[
{
""geoip"":{
""type"": ""Win32-FileLog"",
""target"": ""mygeoip"",
""source"": ""IP""
}
}]
}
}";
// Positive Tests
Configuration c = Configuration.FromString(jsonFilter);
GeoIP jf = c.Filters.First() as GeoIP;
Assert.IsTrue(jf.Apply(jsonInputLine1));
JObject stuff = jsonInputLine1["mygeoip"] as JObject;
Assert.IsNotNull(stuff);
Assert.AreEqual("8.8.8.8", stuff["ip"].ToString());
Assert.AreEqual("US", stuff["country_code2"].ToString());
Assert.AreEqual("United States", stuff["country_name"].ToString());
Assert.AreEqual("CA", stuff["region_name"].ToString());
Assert.AreEqual("Mountain View", stuff["city_name"].ToString());
Assert.AreEqual("California", stuff["real_region_name"].ToString());
Assert.AreEqual(37.386f, (float)stuff["latitude"]);
Assert.AreEqual(-122.0838f, (float) stuff["longitude"]);
}
}
}

View File

@@ -22,7 +22,7 @@ namespace TimberWinR.UnitTests
{"Index", 7},
{"Text", null},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.vistaprint.net"}
{"ComputerName", "dev.mycompany.net"}
};
string grokJson = @"{
@@ -30,7 +30,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""type"": ""Win32-FileLog"",
""match"":[
""Text"",
""""
@@ -55,7 +55,7 @@ namespace TimberWinR.UnitTests
Assert.IsTrue(grok.Apply(json));
// Verify host field added
Assert.AreEqual(json["host"].ToString(), "dev.vistaprint.net");
Assert.AreEqual(json["host"].ToString(), "dev.mycompany.net");
// Verify two tags added
Assert.AreEqual(json["tags"][0].ToString(), "rn_7");
@@ -69,9 +69,9 @@ namespace TimberWinR.UnitTests
{
{"LogFilename", @"C:\\Logs1\\test1.log"},
{"Index", 7},
{"Text", null},
{"Text", "crap"},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.vistaprint.net"}
{"ComputerName", "dev.mycompany.net"}
};
string grokJson = @"{
@@ -79,10 +79,10 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
""crap""
],
""remove_field"":[
""Index"",
@@ -106,6 +106,108 @@ namespace TimberWinR.UnitTests
Assert.IsNull(json["LogFilename"]);
}
[Test]
public void TestDropConditions()
{
JObject json1 = new JObject
{
{"LogFilename", @"C:\\Logs1\\test1.log"},
{"EventType", 1},
{"Index", 7},
{"Text", null},
{
"tags", new JArray
{
"tag1",
"tag2"
}
},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.mycompany.net"}
};
JObject json2 = new JObject
{
{"LogFilename", @"C:\\Logs1\\test1.log"},
{"EventType", 2},
{"Index", 7},
{"Text", null},
{
"tags", new JArray
{
"tag1",
"tag2"
}
},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.mycompany.net"}
};
string grokJsonDropIf1 = @"{
""TimberWinR"":{
""Filters"":[
{
""grok"":{
""condition"": ""[EventType] == 1"",
""drop"": ""true"",
""match"":[
""Text"",
""""
],
""remove_tag"":[
""tag1""
]
}
}]
}
}";
string grokJsonDropIfNot1 = @"{
""TimberWinR"":{
""Filters"":[
{
""grok"":{
""condition"": ""[EventType] != 1"",
""drop"": ""true"",
""match"":[
""Text"",
""""
],
""remove_tag"":[
// ""tag1""
]
}
}]
}
}";
// Positive Tests
Configuration c = Configuration.FromString(grokJsonDropIf1);
Grok grok = c.Filters.First() as Grok;
Assert.IsFalse(grok.Apply(json1));
c = Configuration.FromString(grokJsonDropIf1);
grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json2));
// Negative Tests
c = Configuration.FromString(grokJsonDropIfNot1);
grok = c.Filters.First() as Grok;
Assert.IsFalse(grok.Apply(json2));
c = Configuration.FromString(grokJsonDropIfNot1);
grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json1));
}
[Test]
public void TestConditions()
{
@@ -121,7 +223,8 @@ namespace TimberWinR.UnitTests
}
},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.vistaprint.net"}
{"Type", "Win32-MyType"},
{"ComputerName", "dev.mycompany.net"}
};
string grokJson1 = @"{
@@ -129,7 +232,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
@@ -147,7 +250,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type].Contains(\""Win32-FileLog\"")"",
""condition"": ""\""[type]\"".Contains(\""Win32-FileLog\"")"",
""match"":[
""Text"",
""""
@@ -166,7 +269,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type].Contains(\""Win32-Filelog\"")"",
""condition"": ""\""[type]\"".Contains(\""Win32-Filelog\"")"",
""match"":[
""Text"",
""""
@@ -179,11 +282,35 @@ namespace TimberWinR.UnitTests
}
}";
// Positive Tests
Configuration c = Configuration.FromString(grokJson1);
string grokJson4 = @"{
""TimberWinR"":{
""Filters"":[
{
""grok"":{
""condition"": ""!\""[Type]\"".StartsWith(\""[\"") && !\""[Type]\"".EndsWith(\""]\"") && (\""[type]\"" == \""Win32-FileLog\"")"",
""match"":[
""Text"",
""""
],
""remove_tag"":[
""tag1""
]
}
}]
}
}";
Configuration c = Configuration.FromString(grokJson4);
Grok grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json));
// Positive Tests
c = Configuration.FromString(grokJson1);
grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json));
c = Configuration.FromString(grokJson2);
grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json));
@@ -191,7 +318,7 @@ namespace TimberWinR.UnitTests
// Negative Test
c = Configuration.FromString(grokJson3);
grok = c.Filters.First() as Grok;
Assert.IsFalse(grok.Apply(json));
Assert.IsTrue(grok.Apply(json));
}
[Test]
@@ -209,7 +336,7 @@ namespace TimberWinR.UnitTests
}
},
{"type", "Win32-FileLog"},
{"ComputerName", "dev.vistaprint.net"}
{"ComputerName", "dev.mycompany.net"}
};
string grokJson = @"{
@@ -217,7 +344,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
@@ -248,7 +375,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
@@ -274,7 +401,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
@@ -300,7 +427,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""
@@ -330,7 +457,7 @@ namespace TimberWinR.UnitTests
""Filters"":[
{
""grok"":{
""condition"": ""[type] == \""Win32-FileLog\"""",
""condition"": ""\""[type]\"" == \""Win32-FileLog\"""",
""match"":[
""Text"",
""""

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NUnit.Framework;
using TimberWinR.Parser;
using Newtonsoft.Json.Linq;
namespace TimberWinR.UnitTests
{
[TestFixture]
public class JsonFilterTests
{
[Test]
public void TestDropConditions()
{
JObject jsonInputLine1 = new JObject
{
{"type", "Win32-FileLog"},
{"ComputerName", "dev.mycompany.net"},
{"Text", "{\"Email\":\"james@example.com\",\"Active\":true,\"CreatedDate\":\"2013-01-20T00:00:00Z\",\"Roles\":[\"User\",\"Admin\"]}"}
};
JObject jsonInputLine2 = new JObject
{
{"type", "Win32-FileLog"},
{"ComputerName", "dev.mycompany.net"},
{"Text", "{\"Email\":\"james@example.com\",\"Active\":true,\"CreatedDate\":\"2013-01-20T00:00:00Z\",\"Roles\":[\"User\",\"Admin\"]}"}
};
string jsonFilter = @"{
""TimberWinR"":{
""Filters"":[
{
""json"":{
""type"": ""Win32-FileLog"",
""target"": ""stuff"",
""source"": ""Text""
}
}]
}
}";
string jsonFilterNoTarget = @"{
""TimberWinR"":{
""Filters"":[
{
""json"":{
""type"": ""Win32-FileLog"",
""source"": ""Text""
}
}]
}
}";
// Positive Tests
Configuration c = Configuration.FromString(jsonFilter);
Json jf = c.Filters.First() as Json;
Assert.IsTrue(jf.Apply(jsonInputLine1));
JObject stuff = jsonInputLine1["stuff"] as JObject;
Assert.IsNotNull(stuff);
// 4 fields, Email, Active, CreatedDate, Roles
Assert.AreEqual(4, stuff.Count);
// Now, merge it into the root (starts as 3 fields, ends up with 7 fields)
Assert.AreEqual(3, jsonInputLine2.Count);
c = Configuration.FromString(jsonFilterNoTarget);
jf = c.Filters.First() as Json;
Assert.IsTrue(jf.Apply(jsonInputLine2));
JObject nostuff = jsonInputLine2["stuff"] as JObject;
Assert.IsNull(nostuff);
Assert.AreEqual(6, jsonInputLine2.Count);
}
}
}

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,118 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
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.CodecArguments = new CodecArguments();
sin.CodecArguments.Pattern = "\\\\$";
sin.CodecArguments.What = CodecArguments.WhatType.next;
sin.CodecArguments.Type = CodecArguments.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)
{
}
}
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.CodecArguments = new CodecArguments();
sin.CodecArguments.Pattern = "^(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2},\\d{3})(.*)$";
sin.CodecArguments.What = CodecArguments.WhatType.previous;
sin.CodecArguments.Type = CodecArguments.CodecType.multiline;
sin.CodecArguments.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)
{
}
}
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

@@ -0,0 +1,55 @@
namespace TimberWinR.UnitTests.Parser
{
using System;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using TimberWinR.Parser;
public class ElasticsearchOutputTests
{
private ElasticsearchOutputParameters parser;
[SetUp]
public void Setup()
{
this.parser = new ElasticsearchOutputParameters();
}
[Test]
public void Given_no_index_returns_default_index_name()
{
this.parser.Index = "someindex";
var json = new JObject();
var result = this.parser.GetIndexName(json);
Assert.AreEqual("someindex", result);
}
[Test]
public void Given_index_with_date_format_and_timestamp_returns_name_by_timestamp()
{
this.parser.Index = "someindex-%{yyyy.MM.dd}";
var json = new JObject();
json.Add(new JProperty("@timestamp", "2011-11-30T18:45:32.450Z"));
var result = this.parser.GetIndexName(json);
Assert.AreEqual("someindex-2011.11.30", result);
}
[Test]
public void Given_index_with_date_format_and_no_timestamp_returns_name_by_current_date()
{
this.parser.Index = "someindex-%{yyyy.MM.dd}";
var json = new JObject();
var result = this.parser.GetIndexName(json);
Assert.AreEqual("someindex-" + DateTime.UtcNow.ToString("yyyy.MM.dd"), result);
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
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 TailFileTests
{
[Test]
public void TestTailFile()
{
List<JObject> events = new List<JObject>();
if (File.Exists(".timberwinrdb"))
File.Delete(".timberwinrdb");
var mgr = new Manager();
mgr.LogfileDir = ".";
var tf = new TailFileArguments();
var cancelTokenSource = new CancellationTokenSource();
tf.Location = "TestTailFile1.log";
if (File.Exists(tf.Location))
File.Delete(tf.Location);
try
{
var listener = new TailFileListener(tf, cancelTokenSource.Token);
listener.OnMessageRecieved += o =>
{
events.Add(o);
if (events.Count >= 100)
cancelTokenSource.Cancel();
};
GenerateLogFile(tf.Location);
bool createdFile = false;
while (!listener.Stop && !cancelTokenSource.IsCancellationRequested)
{
Thread.Sleep(100);
if (!createdFile)
{
GenerateLogFile(tf.Location);
createdFile = true;
}
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Done!");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
Assert.AreEqual(100, events.Count);
}
}
private static void GenerateLogFile(string fileName)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(fileName))
{
for (int i = 0; i < 100; i++)
{
file.WriteLine("Log Line Number {0}", i);
}
}
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NUnit.Framework;
using TimberWinR.Parser;
using Newtonsoft.Json.Linq;
using System.Threading;
namespace TimberWinR.UnitTests
{
[TestFixture]
public class TestDynamicBatchCount
{
// [Test]
public void TestDynamicBatch()
{
var mgr = new Manager();
mgr.LogfileDir = ".";
mgr.Config = new Configuration();
CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
var cancelToken = cancelTokenSource.Token;
FakeRediServer fr = new FakeRediServer(cancelToken);
var redisParams = new RedisOutputParameters();
redisParams.BatchCount = 10;
redisParams.MaxBatchCount = 40;
redisParams.Interval = 100;
var redisOutput = new Outputs.RedisOutput(mgr, redisParams, cancelToken);
// Message is irrelavant
JObject jsonMessage = new JObject
{
{"type", "Win32-FileLog"},
{"ComputerName", "dev.vistaprint.net"},
{"Text", "{\"Email\":\"james@example.com\",\"Active\":true,\"CreatedDate\":\"2013-01-20T00:00:00Z\",\"Roles\":[\"User\",\"Admin\"]}"}
};
// Send 1000 messages at max throttle
for (int i = 0; i < 1000; i++)
{
Thread.Sleep(10);
redisOutput.Startup(jsonMessage);
}
while (redisOutput.SentMessages < 1000)
{
System.Diagnostics.Debug.WriteLine(redisOutput.SentMessages);
Thread.Sleep(1000);
}
fr.Shutdown();
cancelTokenSource.Cancel();
System.Diagnostics.Debug.WriteLine(redisOutput.ToJson());
System.Diagnostics.Debug.WriteLine(redisOutput.QueueDepth);
JObject json = redisOutput.ToJson();
var mbc = json["redis"]["reachedMaxBatchCountTimes"].Value<int>();
var sm = json["redis"]["sentMessageCount"].Value<int>();
var errs = json["redis"]["errors"].Value<int>();
var cbc = json["redis"]["currentBatchCount"].Value<int>();
// No errors
Assert.AreEqual(0, errs);
// Should have reached max at least 1 time
Assert.GreaterOrEqual(mbc, 1);
// Should have sent 1000 messages
Assert.AreEqual(1000, sm);
// Should reset back down to original
Assert.AreEqual(cbc, 10);
}
}
}

View File

@@ -12,6 +12,8 @@
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -33,13 +35,18 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Interop.MSUtil, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>False</EmbedInteropTypes>
<HintPath>..\TimberWinR\lib\com-logparser\Interop.MSUtil.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.4\lib\net40\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
<Reference Include="nunit.framework">
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -52,8 +59,15 @@
<ItemGroup>
<Compile Include="Configuration.cs" />
<Compile Include="DateFilterTests.cs" />
<Compile Include="FakeRediServer.cs" />
<Compile Include="GeoIPFilterTests.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="TailFileTests.cs" />
<Compile Include="TestDynamicBatchCount.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TimberWinR\TimberWinR.csproj">
@@ -65,11 +79,30 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Multiline2.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Multiline1.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Inputs\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- 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.
<Target Name="BeforeBuild">

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
<packages>
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net40" />
<package id="NUnit" version="2.6.4" targetFramework="net40" />
</packages>

View File

@@ -7,6 +7,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimberWinR", "TimberWinR\Ti
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{2C4AC7DB-018D-4CCA-9579-06AC5AD2C9D9}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.Config = .nuget\NuGet.Config
.nuget\NuGet.exe = .nuget\NuGet.exe
.nuget\NuGet.targets = .nuget\NuGet.targets
.nuget\packages.config = .nuget\packages.config
EndProjectSection
EndProject
@@ -17,13 +20,25 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1B2F600B-2400-45B9-A28E-CFC391D9EFA9}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
tools\chocolateyInstall.ps1.template = tools\chocolateyInstall.ps1.template
chocolatey.png = chocolatey.png
chocolateyInstall.ps1.template = chocolateyInstall.ps1.template
chocolateyUninstall.ps1.guid = chocolateyUninstall.ps1.guid
chocolateyUninstall.ps1.template = chocolateyUninstall.ps1.template
chocolateyUninstall.ps1.template.orig = chocolateyUninstall.ps1.template.orig
LICENSE.txt = LICENSE.txt
Performance1.psess = Performance1.psess
README.md = README.md
timberwinr.nuspec.template = timberwinr.nuspec.template
EndProjectSection
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "TimberWinR.Wix", "TimberWix\TimberWinR.Wix.wixproj", "{82A39B31-61EC-468D-AA71-0D949AC6528F}"
ProjectSection(ProjectDependencies) = postProject
{99096939-E9DD-4499-883D-4726745A5843} = {99096939-E9DD-4499-883D-4726745A5843}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimberWinR.ExtractID", "TimberWinR.ExtractID\TimberWinR.ExtractID.csproj", "{99096939-E9DD-4499-883D-4726745A5843}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimberWinR.TestGenerator", "TimberWinR.TestGenerator\TimberWinR.TestGenerator.csproj", "{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -77,6 +92,26 @@ Global
{82A39B31-61EC-468D-AA71-0D949AC6528F}.Release|Mixed Platforms.Build.0 = Release|x86
{82A39B31-61EC-468D-AA71-0D949AC6528F}.Release|x86.ActiveCfg = Release|x86
{82A39B31-61EC-468D-AA71-0D949AC6528F}.Release|x86.Build.0 = Release|x86
{99096939-E9DD-4499-883D-4726745A5843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Debug|x86.ActiveCfg = Debug|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|Any CPU.Build.0 = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|x86.ActiveCfg = Release|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|x86.ActiveCfg = Debug|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Any CPU.Build.0 = Release|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using TimberWinR.Inputs;
using TimberWinR.Parser;
namespace TimberWinR.Codecs
{
public class Multiline : ICodec
{
private CodecArguments _codecArguments;
private List<string> _multiline { get; set; }
// return true to cancel codec
public Multiline(CodecArguments args)
{
_codecArguments = args;
}
public void Apply(string msg, InputListener listener)
{
if (_codecArguments.Re == null)
_codecArguments.Re = new Regex(_codecArguments.Pattern);
Match match = _codecArguments.Re.Match(msg);
bool isMatch = (match.Success && !_codecArguments.Negate) || (!match.Success && _codecArguments.Negate);
switch (_codecArguments.What)
{
case CodecArguments.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(_codecArguments.MultilineTag));
listener.AddDefaultFields(jo);
listener.ProcessJson(jo);
}
_multiline = new List<string>();
_multiline.Add(msg);
}
break;
case CodecArguments.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(_codecArguments.MultilineTag));
listener.AddDefaultFields(jo);
listener.ProcessJson(jo);
}
else
{
JObject jo = new JObject();
jo["message"] = msg;
listener.AddDefaultFields(jo);
listener.ProcessJson(jo);
}
}
break;
}
}
}
}

View File

@@ -4,6 +4,8 @@ using System.Data.Odbc;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Xml;
using System.Xml.Linq;
@@ -16,60 +18,83 @@ using Newtonsoft.Json.Linq;
using TimberWinR.Inputs;
using TimberWinR.Filters;
using NLog;
using TimberWinR.Parser;
using Topshelf.Configurators;
using IISW3CLog = TimberWinR.Parser.IISW3CLog;
using WindowsEvent = TimberWinR.Parser.WindowsEvent;
namespace TimberWinR
{
public class Configuration
{
{
private CancellationToken _cancelToken;
private FileSystemWatcher _dirWatcher;
private Manager _manager;
private List<WindowsEvent> _events = new List<WindowsEvent>();
public IEnumerable<WindowsEvent> Events
{
get { return _events; }
}
private List<RedisOutput> _redisOutputs = new List<RedisOutput>();
public IEnumerable<RedisOutput> RedisOutputs
private List<RedisOutputParameters> _redisOutputs = new List<RedisOutputParameters>();
public IEnumerable<RedisOutputParameters> RedisOutputs
{
get { return _redisOutputs; }
}
private List<ElasticsearchOutput> _elasticsearchOutputs = new List<ElasticsearchOutput>();
public IEnumerable<ElasticsearchOutput> ElasticsearchOutputs
private List<ElasticsearchOutputParameters> _elasticsearchOutputs = new List<ElasticsearchOutputParameters>();
public IEnumerable<ElasticsearchOutputParameters> ElasticsearchOutputs
{
get { return _elasticsearchOutputs; }
}
private List<StdoutOutput> _stdoutOutputs = new List<StdoutOutput>();
public IEnumerable<StdoutOutput> StdoutOutputs
private List<StdoutOutputParameters> _stdoutOutputs = new List<StdoutOutputParameters>();
public IEnumerable<StdoutOutputParameters> StdoutOutputs
{
get { return _stdoutOutputs; }
}
private List<Tcp> _tcps = new List<Tcp>();
public IEnumerable<Tcp> Tcps
private List<TcpParameters> _tcps = new List<TcpParameters>();
public IEnumerable<TcpParameters> Tcps
{
get { return _tcps; }
}
}
private List<Log> _logs = new List<Log>();
public IEnumerable<Log> Logs
private List<UdpParameters> _udps = new List<UdpParameters>();
public IEnumerable<UdpParameters> Udps
{
get { return _udps; }
}
private List<LogParameters> _logs = new List<LogParameters>();
public IEnumerable<LogParameters> Logs
{
get { return _logs; }
}
}
private List<IISW3CLog> _iisw3clogs = new List<IISW3CLog>();
private List<TailFileArguments> _tails = new List<TailFileArguments>();
public IEnumerable<TailFileArguments> TailFiles
{
get { return _tails; }
}
public IEnumerable<IISW3CLog> IISW3C
private List<IISW3CLogParameters> _iisw3clogs = new List<IISW3CLogParameters>();
public IEnumerable<IISW3CLogParameters> IISW3C
{
get { return _iisw3clogs; }
}
private List<W3CLogParameters> _w3clogs = new List<W3CLogParameters>();
public IEnumerable<W3CLogParameters> W3C
{
get { return _w3clogs; }
}
private List<Stdin> _stdins = new List<Stdin>();
public IEnumerable<Stdin> Stdins
@@ -84,11 +109,87 @@ namespace TimberWinR
get { return _filters; }
}
private void MonitorDirectory(string directoryToWatch, CancellationToken cancelToken, Manager manager)
{
_manager = manager;
_cancelToken = cancelToken;
if (_dirWatcher == null)
{
_dirWatcher = new FileSystemWatcher();
_dirWatcher.Path = directoryToWatch;
_dirWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch json files.
_dirWatcher.Filter = "*.json";
_dirWatcher.Created += DirWatcherOnCreated;
_dirWatcher.Changed += DirWatcherOnChanged;
_dirWatcher.Renamed += DirWatcherOnRenamed;
_dirWatcher.EnableRaisingEvents = true;
}
}
public static Configuration FromDirectory(string jsonDirectory)
private void DirWatcherOnRenamed(object sender, RenamedEventArgs e)
{
// The Renamed file could be a different name from .json
FileInfo fi = new FileInfo(e.FullPath);
if (fi.Extension == ".json")
{
LogManager.GetCurrentClassLogger().Info("File: OnRenamed " + e.FullPath + " " + e.ChangeType);
ProcessNewJson(e.FullPath);
}
}
private void DirWatcherOnCreated(object sender, FileSystemEventArgs e)
{
FileInfo fi = new FileInfo(e.FullPath);
if (fi.Extension == ".json")
{
LogManager.GetCurrentClassLogger().Info("File: OnCreated " + e.FullPath + " " + e.ChangeType);
ProcessNewJson(e.FullPath);
}
}
private void DirWatcherOnChanged(object sender, FileSystemEventArgs e)
{
FileInfo fi = new FileInfo(e.FullPath);
if (fi.Extension == ".json")
{
// Specify what is done when a file is changed, created, or deleted.
LogManager.GetCurrentClassLogger()
.Info("File: OnChanged " + e.ChangeType.ToString() + " " + e.FullPath + " " + e.ChangeType);
ProcessNewJson(e.FullPath);
}
}
private void ProcessNewJson(string fileName)
{
try
{
Configuration c = new Configuration();
var config = Configuration.FromFile(fileName, c);
_manager.ProcessConfiguration(_cancelToken, config);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
private void ShutdownDirectoryMonitor()
{
_dirWatcher.EnableRaisingEvents = false;
LogManager.GetCurrentClassLogger().Info("Stopping Directory Monitor");
}
private void DirectoryWatcher(string directoryToWatch)
{
LogManager.GetCurrentClassLogger().Info("Starting Directory Monitor {0}", directoryToWatch);
}
public static Configuration FromDirectory(string jsonDirectory, CancellationToken cancelToken, Manager manager)
{
Configuration c = null;
foreach (string jsonConfFile in Directory.GetFiles(jsonDirectory, "*.json"))
{
if (!string.IsNullOrEmpty(jsonConfFile))
@@ -97,6 +198,10 @@ namespace TimberWinR
}
}
// Startup Directory Monitor
if (manager.LiveMonitor)
c.MonitorDirectory(jsonDirectory, cancelToken, manager);
return c;
}
@@ -127,29 +232,35 @@ namespace TimberWinR
if (x.TimberWinR.Inputs != null)
{
if (x.TimberWinR.Inputs.WindowsEvents != null)
c._events = x.TimberWinR.Inputs.WindowsEvents.ToList();
c._events.AddRange(x.TimberWinR.Inputs.WindowsEvents.ToList());
if (x.TimberWinR.Inputs.W3CLogs != null)
c._w3clogs.AddRange(x.TimberWinR.Inputs.W3CLogs.ToList());
if (x.TimberWinR.Inputs.IISW3CLogs != null)
c._iisw3clogs = x.TimberWinR.Inputs.IISW3CLogs.ToList();
c._iisw3clogs.AddRange(x.TimberWinR.Inputs.IISW3CLogs.ToList());
if (x.TimberWinR.Inputs.Stdins != null)
c._stdins = x.TimberWinR.Inputs.Stdins.ToList();
c._stdins.AddRange(x.TimberWinR.Inputs.Stdins.ToList());
if (x.TimberWinR.Inputs.Logs != null)
c._logs = x.TimberWinR.Inputs.Logs.ToList();
c._logs.AddRange(x.TimberWinR.Inputs.Logs.ToList());
if (x.TimberWinR.Inputs.TailFilesArguments != null)
c._tails.AddRange(x.TimberWinR.Inputs.TailFilesArguments.ToList());
if (x.TimberWinR.Inputs.Tcps != null)
c._tcps = x.TimberWinR.Inputs.Tcps.ToList();
c._tcps.AddRange(x.TimberWinR.Inputs.Tcps.ToList());
if (x.TimberWinR.Inputs.Udps != null)
c._udps.AddRange(x.TimberWinR.Inputs.Udps.ToList());
}
if (x.TimberWinR.Outputs != null)
{
if (x.TimberWinR.Outputs.Redis != null)
c._redisOutputs = x.TimberWinR.Outputs.Redis.ToList();
c._redisOutputs.AddRange(x.TimberWinR.Outputs.Redis.ToList());
if (x.TimberWinR.Outputs.Elasticsearch != null)
c._elasticsearchOutputs = x.TimberWinR.Outputs.Elasticsearch.ToList();
c._elasticsearchOutputs.AddRange(x.TimberWinR.Outputs.Elasticsearch.ToList());
if (x.TimberWinR.Outputs.Stdout != null)
c._stdoutOutputs = x.TimberWinR.Outputs.Stdout.ToList();
}
c._stdoutOutputs.AddRange(x.TimberWinR.Outputs.Stdout.ToList());
}
if (x.TimberWinR.Filters != null)
c._filters = x.TimberWinR.AllFilters.ToList();
c._filters.AddRange(x.TimberWinR.AllFilters.ToList());
c.Validate(c);
@@ -178,12 +289,13 @@ namespace TimberWinR
{
_filters = new List<LogstashFilter>();
_events = new List<WindowsEvent>();
_iisw3clogs = new List<IISW3CLog>();
_logs = new List<Log>();
_redisOutputs = new List<RedisOutput>();
_elasticsearchOutputs = new List<ElasticsearchOutput>();
_stdoutOutputs = new List<StdoutOutput>();
_tcps = new List<Tcp>();
_iisw3clogs = new List<IISW3CLogParameters>();
_logs = new List<LogParameters>();
_redisOutputs = new List<RedisOutputParameters>();
_elasticsearchOutputs = new List<ElasticsearchOutputParameters>();
_stdoutOutputs = new List<StdoutOutputParameters>();
_tcps = new List<TcpParameters>();
_udps = new List<UdpParameters>();
}
public static Object GetPropValue(String name, Object obj)

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Net;
@@ -19,11 +21,8 @@ namespace TimberWinR.Diagnostics
private CancellationToken CancelToken { get; set; }
public int Port { get; set; }
public Manager Manager { get; set; }
private readonly System.Net.Sockets.TcpListener _tcpListenerV4;
private readonly System.Net.Sockets.TcpListener _tcpListenerV6;
private Thread _listenThreadV4;
private Thread _listenThreadV6;
public bool Stop { get; set; }
private HttpListener web;
public Diagnostics(Manager manager, CancellationToken cancelToken, int port = 5141)
{
@@ -33,15 +32,100 @@ namespace TimberWinR.Diagnostics
LogManager.GetCurrentClassLogger().Info("Diagnostic(v4/v6) on Port {0} Ready", Port);
_tcpListenerV6 = new System.Net.Sockets.TcpListener(IPAddress.IPv6Any, Port);
_tcpListenerV4 = new System.Net.Sockets.TcpListener(IPAddress.Any, Port);
var hl = new Thread(new ParameterizedThreadStart(HttpListen));
hl.Start(null);
}
_listenThreadV4 = new Thread(new ParameterizedThreadStart(ListenForClients));
_listenThreadV4.Start(_tcpListenerV4);
void processRequest()
{
var result = web.BeginGetContext(DiagnosticCallback, web);
result.AsyncWaitHandle.WaitOne();
}
_listenThreadV6 = new Thread(new ParameterizedThreadStart(ListenForClients));
_listenThreadV6.Start(_tcpListenerV6);
private Assembly GetAssemblyByName(string name)
{
return AppDomain.CurrentDomain.GetAssemblies().
SingleOrDefault(assembly => assembly.GetName().Name == name);
}
public JObject DiagnosticsOutput()
{
JObject json = new JObject(
new JProperty("timberwinr",
new JObject(
new JProperty("version", Assembly.GetEntryAssembly().GetName().Version.ToString()),
new JProperty("messages", Manager.NumMessages),
new JProperty("startedon", Manager.StartedOn),
new JProperty("configfile", Manager.JsonConfig),
new JProperty("logdir", Manager.LogfileDir),
new JProperty("logginglevel", LogManager.GlobalThreshold.ToString()),
new JProperty("inputs",
new JArray(
from i in Manager.Listeners
select new JObject(i.ToJson()))),
new JProperty("filters",
new JArray(
from f in Manager.Config.Filters
select new JObject(f.ToJson()))),
new JProperty("outputs",
new JArray(
from o in Manager.Outputs
select new JObject(o.ToJson()))))));
return json;
}
private void DiagnosticCallback(IAsyncResult result)
{
if (web == null)
return;
try
{
var context = web.EndGetContext(result);
var response = context.Response;
var json = DiagnosticsOutput();
response.StatusCode = (int) HttpStatusCode.OK;
response.StatusDescription = HttpStatusCode.OK.ToString();
byte[] buffer = Encoding.UTF8.GetBytes(json.ToString());
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.OutputStream.Close();
}
catch (SocketException)
{
// Shutdown
}
catch (Exception ex)
{
if (!Stop)
LogManager.GetCurrentClassLogger().Error(ex);
}
}
private void HttpListen(object o)
{
web = new HttpListener();
try
{
web.Prefixes.Add(string.Format("http://*:{0}/", Port));
web.Start();
while (web != null && web.IsListening)
{
processRequest();
}
}
catch (SocketException)
{
// Shutdown
}
catch (Exception ex)
{
if (!Stop)
LogManager.GetCurrentClassLogger().Error("Diagnostic Listener Error: {0}", ex.ToString());
}
}
private void ListenForClients(object olistener)
@@ -49,7 +133,7 @@ namespace TimberWinR.Diagnostics
var listener = olistener as System.Net.Sockets.TcpListener;
listener.Start();
while (!CancelToken.IsCancellationRequested)
{
try
@@ -71,11 +155,13 @@ namespace TimberWinR.Diagnostics
}
}
private void HandleNewClient(object client)
{
var tcpClient = (TcpClient)client;
NetworkStream clientStream = null;
// Console.WriteLine("Handle new diag client: {0}, {1}", tcpClient.Connected, tcpClient.Client.RemoteEndPoint.ToString());
try
{
using (clientStream = tcpClient.GetStream())
@@ -87,17 +173,22 @@ namespace TimberWinR.Diagnostics
new JProperty("messages", Manager.NumMessages),
new JProperty("startedon", Manager.StartedOn),
new JProperty("configfile", Manager.JsonConfig),
new JProperty("logdir", Manager.LogfileDir),
new JProperty("logdir", Manager.LogfileDir),
new JProperty("logginglevel", LogManager.GlobalThreshold.ToString()),
new JProperty("inputs",
new JArray(
from i in Manager.Listeners
select new JObject(i.ToJson()))),
from i in Manager.Listeners
select new JObject(i.ToJson()))),
new JProperty("filters",
new JArray(
from f in Manager.Config.Filters
select new JObject(f.ToJson()))),
new JProperty("outputs",
new JArray(
from o in Manager.Outputs
select new JObject(o.ToJson()))))));
sw.WriteLine(json.ToString());
sw.Flush();
@@ -113,9 +204,19 @@ namespace TimberWinR.Diagnostics
public void Shutdown()
{
_tcpListenerV4.Stop();
_tcpListenerV6.Stop();
Stop = true;
try
{
if (web != null && web.IsListening)
{
LogManager.GetCurrentClassLogger().Info("Shutting down diagnostics listener");
web.Close();
web = null;
}
}
catch (Exception)
{
}
}
}
}

View File

@@ -16,8 +16,15 @@ namespace TimberWinR.Parser
{
public override bool Apply(JObject json)
{
if (!string.IsNullOrEmpty(Type))
{
JToken json_type = json["type"];
if (json_type != null && json_type.ToString() != Type)
return true; // Filter does not apply.
}
if (Condition != null && !EvaluateCondition(json, Condition))
return false;
return true;
if (Matches(json))
{
@@ -25,7 +32,19 @@ namespace TimberWinR.Parser
}
return true;
}
}
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("date",
new JObject(
new JProperty("condition", Condition),
new JProperty("type", Type),
new JProperty("addfields", AddField)
)));
return json;
}
// copy_field "field1" -> "field2"
private void AddFields(Newtonsoft.Json.Linq.JObject json)

View File

@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using MaxMind.GeoIP2;
using MaxMind.Db;
using MaxMind.GeoIP2.Exceptions;
using NLog;
namespace TimberWinR.Parser
{
public partial class GeoIP : LogstashFilter
{
private string DatabaseFileName { get; set; }
private DatabaseReader dr;
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("geoip",
new JObject(
new JProperty("source", Source),
new JProperty("type", Type),
new JProperty("condition", Condition),
new JProperty("target", Target)
)));
return json;
}
public GeoIP()
{
Target = "geoip";
DatabaseFileName = Path.Combine(AssemblyDirectory, "GeoLite2City.mmdb");
dr = new DatabaseReader(DatabaseFileName);
}
private static string AssemblyDirectory
{
get
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
public override bool Apply(JObject json)
{
if (!string.IsNullOrEmpty(Type))
{
JToken json_type = json["type"];
if (json_type != null && json_type.ToString() != Type)
return true; // Filter does not apply.
}
if (Condition != null)
{
var expr = EvaluateCondition(json, Condition);
if (!expr)
return true;
}
var source = json[Source];
if (source != null && !string.IsNullOrEmpty(source.ToString()))
{
try
{
var l = dr.City(source.ToString());
if (l != null)
{
JObject geo_json = new JObject(
new JProperty(Target,
new JObject(
new JProperty("ip", source.ToString()),
new JProperty("country_code2", l.Country.IsoCode),
new JProperty("country_name", l.Country.Name),
new JProperty("continent_code", l.Continent.Code),
new JProperty("region_name", l.MostSpecificSubdivision.IsoCode),
new JProperty("city_name", l.City.Name),
new JProperty("postal_code", l.Postal.Code),
new JProperty("latitude", l.Location.Latitude),
new JProperty("longitude", l.Location.Longitude),
new JProperty("dma_code", l.Location.MetroCode),
new JProperty("timezone", l.Location.TimeZone),
new JProperty("real_region_name", l.MostSpecificSubdivision.Name),
new JProperty("location",
new JArray(l.Location.Longitude, l.Location.Latitude)
))));
json.Merge(geo_json, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
});
}
else
{
json["_geoiperror"] = string.Format("IP Address not found: {0}", source.ToString());
}
}
catch (Exception ex)
{
json["_geoiperror"] = string.Format("IP Address not found: {0} ({1})", source.ToString(), ex.ToString());
return true;
}
}
AddFields(json);
AddTags(json);
RemoveFields(json);
RemoveTags(json);
return true;
}
private void AddFields(Newtonsoft.Json.Linq.JObject json)
{
if (AddField != null && AddField.Length > 0)
{
for (int i = 0; i < AddField.Length; i += 2)
{
string fieldName = ExpandField(AddField[i], json);
string fieldValue = ExpandField(AddField[i + 1], json);
AddOrModify(json, fieldName, fieldValue);
}
}
}
private void RemoveFields(Newtonsoft.Json.Linq.JObject json)
{
if (RemoveField != null && RemoveField.Length > 0)
{
for (int i = 0; i < RemoveField.Length; i++)
{
string fieldName = ExpandField(RemoveField[i], json);
RemoveProperties(json, new string[] { fieldName });
}
}
}
private void AddTags(Newtonsoft.Json.Linq.JObject json)
{
if (AddTag != null && AddTag.Length > 0)
{
for (int i = 0; i < AddTag.Length; i++)
{
string value = ExpandField(AddTag[i], json);
JToken tags = json["tags"];
if (tags == null)
json.Add("tags", new JArray(value));
else
{
JArray a = tags as JArray;
a.Add(value);
}
}
}
}
private void RemoveTags(Newtonsoft.Json.Linq.JObject json)
{
if (RemoveTag != null && RemoveTag.Length > 0)
{
JToken tags = json["tags"];
if (tags != null)
{
List<JToken> children = tags.Children().ToList();
for (int i = 0; i < RemoveTag.Length; i++)
{
string tagName = ExpandField(RemoveTag[i], json);
foreach (JToken token in children)
{
if (token.ToString() == tagName)
token.Remove();
}
}
}
}
}
}
}

View File

@@ -30,22 +30,66 @@ namespace TimberWinR.Parser
}
}
public partial class Grok : LogstashFilter
{
public override JObject ToJson()
{
string field = Match[0];
string expr = Match[1];
JObject json = new JObject(
new JProperty("grok",
new JObject(
new JProperty("match", field),
new JProperty("expr", expr),
new JProperty("condition", Condition),
new JProperty("addfields", AddField),
new JProperty("addtags", AddTag),
new JProperty("drop", DropIfMatch),
new JProperty("type", Type),
new JProperty("removefields", RemoveField),
new JProperty("removetag", RemoveTag)
)));
return json;
}
// Returns: true - Filter does not apply or has been applied successfully
// Returns: false - Drop this object
public override bool Apply(JObject json)
{
if (Condition != null && !EvaluateCondition(json, Condition))
return false;
if (!string.IsNullOrEmpty(Type))
{
JToken json_type = json["type"];
if (json_type != null && json_type.ToString() != Type)
return true; // Filter does not apply.
}
if (Matches(json))
{
if (Condition != null)
{
var expr = EvaluateCondition(json, Condition);
if (expr)
{
if (DropIfMatch)
return false; // drop this one
}
else
return true;
}
if (DropIfMatch)
return false;
AddFields(json);
AddTags(json);
AddTags(json);
RemoveFields(json);
RemoveTags(json);
return true;
}
return false;
return true;
}
private bool Matches(Newtonsoft.Json.Linq.JObject json)
@@ -73,7 +117,10 @@ namespace TimberWinR.Parser
return true; // Yes!
}
}
return true; // Empty field is no match
if (string.IsNullOrEmpty(expr))
return true; // Empty field is no match
else
return false;
}
return false; // Not specified is failure
}
@@ -91,8 +138,6 @@ namespace TimberWinR.Parser
}
}
private void RemoveFields(Newtonsoft.Json.Linq.JObject json)
{
if (RemoveField != null && RemoveField.Length > 0)
@@ -132,17 +177,17 @@ namespace TimberWinR.Parser
JToken tags = json["tags"];
if (tags != null)
{
List<JToken> children = tags.Children().ToList();
List<JToken> children = tags.Children().ToList();
for (int i = 0; i < RemoveTag.Length; i++)
{
string tagName = ExpandField(RemoveTag[i], json);
foreach(JToken token in children)
string tagName = ExpandField(RemoveTag[i], json);
foreach (JToken token in children)
{
if (token.ToString() == tagName)
token.Remove();
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace TimberWinR.Parser
{
public partial class Json : LogstashFilter
{
public Json()
{
RemoveSource = true;
}
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("json",
new JObject(
new JProperty("condition", Condition),
new JProperty("source", Source),
new JProperty("promote", Source),
new JProperty("target", Target),
new JProperty("type", Type),
new JProperty("addfields", AddField),
new JProperty("addtags", AddTag),
new JProperty("removefields", RemoveField),
new JProperty("removetag", RemoveTag)
)));
return json;
}
public override bool Apply(JObject json)
{
if (!string.IsNullOrEmpty(Type))
{
JToken json_type = json["type"];
if (json_type != null && json_type.ToString() != Type)
return true; // Filter does not apply.
}
if (Condition != null)
{
var expr = EvaluateCondition(json, Condition);
if (!expr)
return true;
}
var source = json[Source];
if (source == null)
return true;
var jsonOrig = source.ToString();
if (!string.IsNullOrEmpty(source.ToString()))
{
try
{
JObject subJson;
if (Target != null && !string.IsNullOrEmpty(Target))
{
subJson = new JObject();
subJson[Target] = JObject.Parse(source.ToString());
}
else
subJson = JObject.Parse(source.ToString());
if (!string.IsNullOrEmpty(Promote))
{
var promotedJson = subJson[Promote];
RemoveProperties(subJson, new string[] { Promote });
subJson.Merge(promotedJson, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Replace
});
}
json.Merge(subJson, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Replace
});
if (Rename != null && Rename.Length > 0)
{
string oldName = ExpandField(Rename[0], json);
string newName = ExpandField(Rename[1], json);
RenameProperty(json, oldName, newName);
}
if (RemoveSource)
{
RemoveProperties(json, new string[] { Source });
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
return true;
}
}
AddFields(json);
AddTags(json);
RemoveFields(json);
RemoveTags(json);
return true;
}
private void AddFields(Newtonsoft.Json.Linq.JObject json)
{
if (AddField != null && AddField.Length > 0)
{
for (int i = 0; i < AddField.Length; i += 2)
{
string fieldName = ExpandField(AddField[i], json);
string fieldValue = ExpandField(AddField[i + 1], json);
AddOrModify(json, fieldName, fieldValue);
}
}
}
private void RemoveFields(Newtonsoft.Json.Linq.JObject json)
{
if (RemoveField != null && RemoveField.Length > 0)
{
for (int i = 0; i < RemoveField.Length; i++)
{
string fieldName = ExpandField(RemoveField[i], json);
RemoveProperties(json, new string[] { fieldName });
}
}
}
private void AddTags(Newtonsoft.Json.Linq.JObject json)
{
if (AddTag != null && AddTag.Length > 0)
{
for (int i = 0; i < AddTag.Length; i++)
{
string value = ExpandField(AddTag[i], json);
JToken tags = json["tags"];
if (tags == null)
json.Add("tags", new JArray(value));
else
{
JArray a = tags as JArray;
a.Add(value);
}
}
}
}
private void RemoveTags(Newtonsoft.Json.Linq.JObject json)
{
if (RemoveTag != null && RemoveTag.Length > 0)
{
JToken tags = json["tags"];
if (tags != null)
{
List<JToken> children = tags.Children().ToList();
for (int i = 0; i < RemoveTag.Length; i++)
{
string tagName = ExpandField(RemoveTag[i], json);
foreach (JToken token in children)
{
if (token.ToString() == tagName)
token.Remove();
}
}
}
}
}
}
}

View File

@@ -11,17 +11,56 @@ namespace TimberWinR.Parser
{
public partial class Mutate : LogstashFilter
{
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("mutate",
new JObject(
new JProperty("condition", Condition),
new JProperty("splits", Split),
new JProperty("type", Type),
new JProperty("remove", Remove),
new JProperty("rename", Rename),
new JProperty("replace", Replace)
)));
return json;
}
public override bool Apply(JObject json)
{
if (Condition != null && !EvaluateCondition(json, Condition))
return false;
if (!string.IsNullOrEmpty(Type))
{
JToken json_type = json["type"];
if (json_type != null && json_type.ToString() != Type)
return true; // Filter does not apply.
}
if (Condition != null)
{
var expr = EvaluateCondition(json, Condition);
if (!expr)
return true;
}
ApplySplits(json);
ApplyRemoves(json);
ApplyRenames(json);
ApplyReplace(json);
return true;
}
private void ApplyRemoves(JObject json)
{
if (Remove != null && Remove.Length > 0)
{
for (int i = 0; i < Remove.Length; i += 1)
{
string name = ExpandField(Remove[i], json);
RemoveProperty(json, name);
}
}
}
private void ApplyRenames(JObject json)
{
if (Rename != null && Rename.Length > 0)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 MiB

13
TimberWinR/ICodec.cs Normal file
View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TimberWinR.Inputs;
namespace TimberWinR
{
public interface ICodec
{
void Apply(string msg, InputListener listener);
}
}

View File

@@ -1,15 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
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 IISW3CLogInputFormat = Interop.MSUtil.COMIISW3CInputContextClassClass;
@@ -20,22 +15,31 @@ namespace TimberWinR.Inputs
{
public class IISW3CInputListener : InputListener
{
private int _pollingIntervalInSeconds = 1;
private TimberWinR.Parser.IISW3CLog _arguments;
private readonly int _pollingIntervalInSeconds;
private readonly Parser.IISW3CLogParameters _arguments;
private long _receivedMessages;
public bool Stop { get; set; }
private IisW3CRowReader rowReader;
public IISW3CInputListener(TimberWinR.Parser.IISW3CLog arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 1)
public IISW3CInputListener(Parser.IISW3CLogParameters arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 5)
: base(cancelToken, "Win32-IISLog")
{
{
_arguments = arguments;
_receivedMessages = 0;
_pollingIntervalInSeconds = pollingIntervalInSeconds;
var task = new Task(IISW3CWatcher, cancelToken);
task.Start();
this.rowReader = new IisW3CRowReader(_arguments.Fields);
foreach (string loc in _arguments.Location.Split(','))
{
string hive = loc.Trim();
Task.Factory.StartNew(() => IISW3CWatcher(loc));
}
}
public override void Shutdown()
{
Stop = true;
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
base.Shutdown();
}
@@ -56,18 +60,18 @@ namespace TimberWinR.Inputs
return json;
}
private void IISW3CWatcher()
private void IISW3CWatcher(string location)
{
LogManager.GetCurrentClassLogger().Info("IISW3Listener Ready For {0}", location);
var oLogQuery = new LogQuery();
var iFmt = new IISW3CLogInputFormat()
{
codepage = _arguments.CodePage,
consolidateLogs = _arguments.ConsolidateLogs,
consolidateLogs = true,
dirTime = _arguments.DirTime,
dQuotes = _arguments.DoubleQuotes,
iCheckpoint = CheckpointFileName,
recurse = _arguments.Recurse,
useDoubleQuotes = _arguments.DoubleQuotes
};
@@ -75,58 +79,75 @@ namespace TimberWinR.Inputs
if (_arguments.MinDateMod.HasValue)
iFmt.minDateMod = _arguments.MinDateMod.Value.ToString("yyyy-MM-dd hh:mm:ss");
// Create the query
var query = string.Format("SELECT * FROM {0}", _arguments.Location);
Dictionary<string, Int64> logFileMaxRecords = new Dictionary<string, Int64>();
var firstQuery = true;
// Execute the query
while (!CancelToken.IsCancellationRequested)
using (var syncHandle = new ManualResetEventSlim())
{
try
// Execute the query
while (!Stop)
{
var rs = oLogQuery.Execute(query, iFmt);
Dictionary<string, int> colMap = new Dictionary<string, int>();
for (int col = 0; col < rs.getColumnCount(); col++)
// Execute the query
if (!CancelToken.IsCancellationRequested)
{
string colName = rs.getColumnName(col);
colMap[colName] = col;
}
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
// We want to "tail" the log, so skip the first query results.
if (!firstQuery)
try
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in _arguments.Fields)
{
if (!colMap.ContainsKey(field.Name))
continue;
oLogQuery = new LogQuery();
object v = record.getValue(field.Name);
if (field.DataType == typeof(DateTime))
var qfiles = string.Format("SELECT Distinct [LogFilename] FROM {0}", location);
var rsfiles = oLogQuery.Execute(qfiles, iFmt);
for (; !rsfiles.atEnd(); rsfiles.moveNext())
{
var record = rsfiles.getRecord();
string fileName = record.getValue("LogFilename") as string;
if (!logFileMaxRecords.ContainsKey(fileName))
{
DateTime dt = DateTime.Parse(v.ToString());
json.Add(new JProperty(field.Name, dt));
var qcount = string.Format("SELECT max(LogRow) as MaxRecordNumber FROM {0}",
fileName);
var rcount = oLogQuery.Execute(qcount, iFmt);
var qr = rcount.getRecord();
var lrn = (Int64) qr.getValueEx("MaxRecordNumber");
logFileMaxRecords[fileName] = lrn;
}
else
json.Add(new JProperty(field.Name, v));
}
ProcessJson(json);
_receivedMessages++;
foreach (string fileName in logFileMaxRecords.Keys.ToList())
{
var lastRecordNumber = logFileMaxRecords[fileName];
var query = string.Format("SELECT * FROM '{0}' Where LogRow > {1}", fileName,
lastRecordNumber);
var rs = oLogQuery.Execute(query, iFmt);
rowReader.ReadColumnMap(rs);
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
var record = rs.getRecord();
var json = rowReader.ReadToJson(record);
ProcessJson(json);
_receivedMessages++;
var lrn = (Int64) record.getValueEx("LogRow");
logFileMaxRecords[fileName] = lrn;
record = null;
json = null;
}
// Close the recordset
rs.close();
GC.Collect();
}
if (!Stop)
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
// Close the recordset
rs.close();
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
firstQuery = false;
System.Threading.Thread.Sleep(_pollingIntervalInSeconds * 1000);
}
Finished();

View File

@@ -0,0 +1,69 @@
namespace TimberWinR.Inputs
{
using System;
using System.Collections.Generic;
using Interop.MSUtil;
using Newtonsoft.Json.Linq;
using TimberWinR.Parser;
public class IisW3CRowReader
{
private readonly List<Field> fields;
private IDictionary<string, int> columnMap;
public IisW3CRowReader(List<Field> fields)
{
this.fields = fields;
}
public JObject ReadToJson(ILogRecord row)
{
var json = new JObject();
foreach (var field in this.fields)
{
if (this.columnMap.ContainsKey(field.Name))
{
object v = row.getValue(field.Name);
if (field.DataType == typeof(DateTime))
{
DateTime dt = DateTime.Parse(v.ToString());
json.Add(new JProperty(field.Name, dt));
}
else
{
json.Add(new JProperty(field.Name, v));
}
}
}
AddTimestamp(json);
return json;
}
public void ReadColumnMap(ILogRecordset rs)
{
this.columnMap = new Dictionary<string, int>();
for (int col = 0; col < rs.getColumnCount(); col++)
{
string colName = rs.getColumnName(col);
this.columnMap[colName] = col;
}
}
private static void AddTimestamp(JObject json)
{
if (json["date"] != null && json["time"] != null)
{
var date = DateTime.Parse(json["date"].ToString());
var time = DateTime.Parse(json["time"].ToString());
date = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond);
json.Add(new JProperty("@timestamp", date.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")));
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -18,6 +19,8 @@ namespace TimberWinR.Inputs
private string _typeName;
public AutoResetEvent FinishedEvent { get; set; }
public string CheckpointFileName { get; set; }
private object _locker = new object();
public List<string> Files { get; set; }
public string InputType
{
@@ -28,6 +31,7 @@ namespace TimberWinR.Inputs
public InputListener(CancellationToken token, string typeName)
{
Files = new List<string>();
CheckpointFileName = Path.Combine(System.IO.Path.GetTempPath(), string.Format("{0}.lpc", Guid.NewGuid().ToString()));
this.FinishedEvent = new AutoResetEvent(false);
@@ -40,6 +44,19 @@ namespace TimberWinR.Inputs
.ToString();
}
public bool HaveSeenFile(string fileName)
{
return Files.Contains(fileName);
}
protected void SaveVisitedFileName(string fileName)
{
lock (_locker)
{
if (!HaveSeenFile(fileName))
Files.Add(fileName);
}
}
protected string ToPrintable(string inputString)
{
string asAscii = Encoding.ASCII.GetString(
@@ -58,11 +75,17 @@ namespace TimberWinR.Inputs
public void Finished()
{
LogManager.GetCurrentClassLogger().Info("{0}: Signalling Event Shutdown {1}", Thread.CurrentThread.ManagedThreadId, InputType);
FinishedEvent.Set();
LogManager.GetCurrentClassLogger().Info("{0}: Finished signalling Shutdown {1}", Thread.CurrentThread.ManagedThreadId, InputType);
}
public virtual void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1}", Thread.CurrentThread.ManagedThreadId, InputType);
FinishedEvent.WaitOne();
try
{
if (File.Exists(CheckpointFileName))
@@ -74,7 +97,33 @@ namespace TimberWinR.Inputs
}
}
protected virtual void AddDefaultFields(JObject json)
protected void EnsureRollingCaught()
{
try
{
const string mteKey = @"SYSTEM\CurrentControlSet\Control\FileSystem";
var mte = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(mteKey).GetValue("MaximumTunnelEntries");
if (mte == null || (int)mte != 0)
{
LogManager.GetCurrentClassLogger()
.Error(
"HKLM\\{0}\\MaximumTunnelEntries is not set to accurately detect log rolling, a DWORD value of 0 is required.",
mteKey);
Microsoft.Win32.Registry.LocalMachine.CreateSubKey(mteKey).SetValue("MaximumTunnelEntries", 0, RegistryValueKind.DWord);
LogManager.GetCurrentClassLogger()
.Error(
"HKLM\\{0}\\MaximumTunnelEntries is now set to 0, A reboot is now required to fix this issue. See http://support.microsoft.com/en-us/kb/172190 for details",
mteKey);
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
public virtual void AddDefaultFields(JObject json)
{
if (json["type"] == null)
json.Add(new JProperty("type", _typeName));
@@ -94,7 +143,7 @@ namespace TimberWinR.Inputs
json.Add(new JProperty("UtcTimestamp", utc.ToString("o")));
}
protected void ProcessJson(JObject json)
public void ProcessJson(JObject json)
{
if (OnMessageRecieved != null)
{

View File

@@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
//
// Maintain persistent state for Log files (to be used across restarts)
//
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);
}
}
//
// Lookup the database entry for this log file, returns null if there isnt one.
//
private LogsFileDatabaseEntry FindFile(string logName)
{
lock (_locker)
{
var existingEntry = (from e in Entries where e.FileName == logName select e).FirstOrDefault();
return existingEntry;
}
}
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)
{
var de = new LogsFileDatabaseEntry();
lock (_locker)
{
var fi = new FileInfo(logName);
de.FileName = logName;
de.LogFileExists = fi.Exists;
de.NewFile = true;
de.ProcessedFile = false;
de.LastPosition = fi.Length;
de.SampleTime = DateTime.UtcNow;
de.CreationTimeUtc = fi.CreationTimeUtc;
Entries.Add(de);
WriteDatabaseFileNoLock();
}
return de;
}
public static LogsFileDatabaseEntry LookupLogFile(string logName)
{
LogsFileDatabaseEntry dbe = Instance.FindFile(logName);
if (dbe == null)
dbe = Instance.AddFileEntry(logName);
FileInfo fi = new FileInfo(logName);
dbe.LogFileExists = fi.Exists;
var creationTime = fi.CreationTimeUtc;
if (dbe.LogFileExists && creationTime != dbe.CreationTimeUtc)
dbe.NewFile = true;
dbe.CreationTimeUtc = creationTime;
return dbe;
}
// Find all the non-existent entries and remove them.
private void PruneFiles()
{
lock (_locker)
{
foreach(var entry in Entries.ToList())
{
FileInfo fi = new FileInfo(entry.FileName);
if (!fi.Exists)
Entries.Remove(entry);
}
WriteDatabaseFileNoLock();
}
}
public static void Update(LogsFileDatabaseEntry dbe, bool processedFile, long lastOffset)
{
dbe.ProcessedFile = processedFile;
dbe.LogFileExists = File.Exists(dbe.FileName);
Instance.UpdateEntry(dbe, lastOffset);
}
public static void Roll(LogsFileDatabaseEntry dbe)
{
dbe.ProcessedFile = false;
dbe.LastPosition = 0;
Instance.UpdateEntry(dbe, 0);
dbe.NewFile = true;
}
private void UpdateEntry(LogsFileDatabaseEntry dbe, long lastOffset)
{
lock (_locker)
{
var fi = new FileInfo(dbe.FileName);
dbe.NewFile = !fi.Exists;
dbe.CreationTimeUtc = fi.CreationTimeUtc;
dbe.SampleTime = DateTime.UtcNow;
dbe.LastPosition = lastOffset;
WriteDatabaseFileNoLock();
}
}
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 it exists, read the current state, otherwise create an empty database.
if (File.Exists(instance.DatabaseFileName))
instance.ReadDatabaseNoLock();
else
instance.WriteDatabaseFileNoLock();
if (instance.Entries == null)
instance.Entries = new List<LogsFileDatabaseEntry>();
instance.PruneFiles();
}
}
return instance;
}
}
// Serialize in the Database
private void ReadDatabaseNoLock()
{
try
{
var serializer = new JsonSerializer();
if (File.Exists(DatabaseFileName))
Entries =
JsonConvert.DeserializeObject<List<LogsFileDatabaseEntry>>(File.ReadAllText(DatabaseFileName));
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger()
.Error("Error reading database '{0}': {1}", DatabaseFileName, ex.ToString());
try
{
if (File.Exists(DatabaseFileName))
File.Delete(DatabaseFileName);
LogManager.GetCurrentClassLogger().Info("Creating New Database '{0}'", DatabaseFileName);
WriteDatabaseLock();
}
catch (Exception ex2)
{
LogManager.GetCurrentClassLogger().Info("Error Creating New Database '{0}': {1}", DatabaseFileName, ex2.ToString());
}
}
}
private void WriteDatabaseFileNoLock()
{
try
{
File.WriteAllText(DatabaseFileName, JsonConvert.SerializeObject(instance.Entries), Encoding.UTF8);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger()
.Error("Error saving database '{0}': {1}", DatabaseFileName, ex.ToString());
}
}
private void ReadDatabaseLock()
{
lock (_locker)
{
ReadDatabaseNoLock();
}
}
private void WriteDatabaseLock()
{
lock (_locker)
{
WriteDatabaseFileNoLock();
}
}
private LogsFileDatabase(string databaseDirectory)
{
DatabaseDirectory = databaseDirectory;
Entries = new List<LogsFileDatabaseEntry>();
}
}
//
// Represents a log file to be tailed
//
public class LogsFileDatabaseEntry
{
[JsonIgnore]
public bool NewFile { get; set; }
public bool ProcessedFile { get; set; }
public bool LogFileExists { get; set; }
public string FileName { get; set; }
public DateTime CreationTimeUtc { get; set; }
public DateTime SampleTime { get; set; }
public long LastPosition { get; set; }
public long LinesProcessed
{
get { return _linesProcessed; }
}
private int _linesProcessed;
public void IncrementLineCount()
{
Interlocked.Increment(ref _linesProcessed);
}
}
}

View File

@@ -1,19 +1,26 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;
using Interop.MSUtil;
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using NLog;
using NLog;
using TimberWinR.Codecs;
using LogQuery = Interop.MSUtil.LogQueryClassClass;
using TextLineInputFormat = Interop.MSUtil.COMTextLineInputContextClass;
using LogRecordSet = Interop.MSUtil.ILogRecordset;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
@@ -22,114 +29,258 @@ namespace TimberWinR.Inputs
/// </summary>
public class LogsListener : InputListener
{
private object _locker = new object();
private int _pollingIntervalInSeconds;
private TimberWinR.Parser.Log _arguments;
private TimberWinR.Parser.LogParameters _arguments;
private long _receivedMessages;
public LogsListener(TimberWinR.Parser.Log arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 3)
private CodecArguments _codecArguments;
private ICodec _codec;
public bool Stop { get; set; }
public bool IsWildcardFilePattern { get; set; }
public LogsListener(TimberWinR.Parser.LogParameters arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-FileLog")
{
Stop = false;
EnsureRollingCaught();
_codecArguments = arguments.CodecArguments;
_codecArguments = arguments.CodecArguments;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec = new Multiline(_codecArguments);
_receivedMessages = 0;
_arguments = arguments;
_pollingIntervalInSeconds = pollingIntervalInSeconds;
var task = new Task(FileWatcher, cancelToken);
task.Start();
_pollingIntervalInSeconds = arguments.Interval;
IsWildcardFilePattern = arguments.Location.Contains('*');
foreach (string srcFile in _arguments.Location.Split(','))
{
string file = srcFile.Trim();
string dir = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(dir))
dir = Environment.CurrentDirectory;
string fileSpec = Path.Combine(dir, file);
Task.Factory.StartNew(() => FileWatcher(fileSpec));
}
}
public override void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
Stop = true;
base.Shutdown();
}
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("splitLongLines", _arguments.SplitLongLines),
new JProperty("recurse", _arguments.Recurse),
new JProperty("filedb",
new JArray(from f in Files.ToList()
select JObject.FromObject(LogsFileDatabase.LookupLogFile(f)))),
new JProperty("files",
new JArray(from f in Files.ToList()
select new JValue(f)))
)));
if (_codecArguments != null)
{
var cp = new JProperty("codec",
new JArray(
new JObject(
new JProperty("type", _codecArguments.Type.ToString()),
new JProperty("what", _codecArguments.What.ToString()),
new JProperty("negate", _codecArguments.Negate),
new JProperty("multilineTag", _codecArguments.MultilineTag),
new JProperty("pattern", _codecArguments.Pattern))));
json.Add(cp);
}
return json;
}
private void FileWatcher()
{
private void FileWatcher(string fileToWatch)
{
var iFmt = new TextLineInputFormat()
{
iCodepage = _arguments.CodePage,
splitLongLines = _arguments.SplitLongLines,
iCheckpoint = CheckpointFileName,
recurse = _arguments.Recurse
};
// Create the query
var query = string.Format("SELECT * FROM {0}", _arguments.Location);
Dictionary<string, string> _fnfmap = new Dictionary<string, string>();
var firstQuery = true;
// Execute the query
while (!CancelToken.IsCancellationRequested)
using (var syncHandle = new ManualResetEventSlim())
{
var oLogQuery = new LogQuery();
try
{
var rs = oLogQuery.Execute(query, iFmt);
Dictionary<string, int> colMap = new Dictionary<string, int>();
for (int col=0; col<rs.getColumnCount(); col++)
// Execute the query
while (!Stop)
{
var oLogQuery = new LogQuery();
if (!CancelToken.IsCancellationRequested)
{
string colName = rs.getColumnName(col);
colMap[colName] = col;
}
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
// We want to "tail" the log, so skip the first query results.
if (!firstQuery)
try
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in _arguments.Fields)
var qfiles = string.Format("SELECT Distinct [LogFilename] FROM {0}", fileToWatch);
var rsfiles = oLogQuery.Execute(qfiles, iFmt);
for (; !rsfiles.atEnd(); rsfiles.moveNext())
{
if (!colMap.ContainsKey(field.Name))
continue;
var record = rsfiles.getRecord();
string logName = record.getValue("LogFilename") as string;
FileInfo fi = new FileInfo(logName);
object v = record.getValue(field.Name);
if (field.DataType == typeof (DateTime))
var dbe = LogsFileDatabase.LookupLogFile(logName);
SaveVisitedFileName(dbe.FileName);
DateTime creationTime = fi.CreationTimeUtc;
bool logHasRolled = dbe.NewFile || (creationTime != dbe.CreationTimeUtc || fi.Length < dbe.LastPosition);
if (logHasRolled)
{
DateTime dt = DateTime.Parse(v.ToString());
json.Add(new JProperty(field.Name, dt));
LogManager.GetCurrentClassLogger().Info("Log {0} has rolled", logName);
LogsFileDatabase.Roll(dbe);
}
else
json.Add(new JProperty(field.Name, v));
// Log has rolled or this is a new file, or we haven't processed yet.
bool processWholeFile = logHasRolled || !dbe.ProcessedFile;
if (processWholeFile)
LogsFileDatabase.Update(dbe, true, 0);
}
string msg = json["Text"].ToString();
if (!string.IsNullOrEmpty(msg))
rsfiles.close();
foreach (string fileName in Files.ToList())
{
ProcessJson(json);
_receivedMessages++;
var dbe = LogsFileDatabase.LookupLogFile(fileName);
var lastRecordNumber = dbe.LastPosition;
var query = string.Format("SELECT * FROM {0} where Index > {1}", fileName,
lastRecordNumber);
var rs = oLogQuery.Execute(query, iFmt);
Dictionary<string, int> colMap = new Dictionary<string, int>();
for (int col = 0; col < rs.getColumnCount(); col++)
{
string colName = rs.getColumnName(col);
colMap[colName] = col;
}
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in _arguments.Fields)
{
if (!colMap.ContainsKey(field.Name))
continue;
if (json["logSource"] == null)
{
if (string.IsNullOrEmpty(_arguments.LogSource))
json.Add(new JProperty("logSource", fileName));
else
json.Add(new JProperty("logSource", _arguments.LogSource));
}
object v = record.getValue(field.Name);
if (field.DataType == typeof(DateTime))
{
DateTime dt = DateTime.Parse(v.ToString());
json.Add(new JProperty(field.Name, dt));
}
else
json.Add(new JProperty(field.Name, v));
}
string msg = json["Text"].ToString();
if (!string.IsNullOrEmpty(msg))
{
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
{
_codec.Apply(msg, this);
_receivedMessages++;
dbe.IncrementLineCount();
}
else
{
ProcessJson(json);
dbe.IncrementLineCount();
_receivedMessages++;
}
}
var lrn = (Int64)record.getValueEx("Index");
LogsFileDatabase.Update(dbe, true, lrn);
GC.Collect();
}
colMap.Clear();
// Close the recordset
rs.close();
rs = null;
GC.Collect();
}
}
catch (FileNotFoundException fnfex)
{
string fn = fnfex.FileName;
if (!string.IsNullOrEmpty(fn) && !_fnfmap.ContainsKey(fn))
{
LogManager.GetCurrentClassLogger().Warn(fnfex.Message);
_fnfmap[fn] = fn;
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
finally
{
try
{
oLogQuery = null;
// Sleep
if (!Stop)
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
{
}
catch (Exception ex1)
{
LogManager.GetCurrentClassLogger().Warn(ex1);
}
}
}
// Close the recordset
rs.close();
rs = null;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
finally
{
oLogQuery = null;
}
firstQuery = false;
System.Threading.Thread.Sleep(_pollingIntervalInSeconds * 1000);
Finished();
}
Finished();
}
}
}
}

View File

@@ -1,21 +1,37 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using TimberWinR.Codecs;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
public class StdinListener : InputListener
{
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private Thread _listenThread;
private CodecArguments _codecArguments;
private ICodec _codec;
const int VK_RETURN = 0x0D;
const int WM_KEYDOWN = 0x100;
public StdinListener(CancellationToken cancelToken)
public StdinListener(TimberWinR.Parser.Stdin arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-Console")
{
_codecArguments = arguments.CodecArguments;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec = new Multiline(_codecArguments);
_listenThread = new Thread(new ThreadStart(ListenToStdin));
_listenThread.Start();
}
@@ -23,12 +39,35 @@ namespace TimberWinR.Inputs
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("stdin", "enabled"));
new JProperty("stdin", "enabled"));
if (_codecArguments != null)
{
var cp = new JProperty("codec",
new JArray(
new JObject(
new JProperty("type", _codecArguments.Type.ToString()),
new JProperty("what", _codecArguments.What.ToString()),
new JProperty("negate", _codecArguments.Negate),
new JProperty("multilineTag", _codecArguments.MultilineTag),
new JProperty("pattern", _codecArguments.Pattern))));
json.Add(cp);
}
return json;
}
public override void Shutdown()
{
{
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
// This must come from another thread.
ThreadPool.QueueUserWorkItem((o) =>
{
Thread.Sleep(100);
var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0);
});
base.Shutdown();
}
@@ -37,18 +76,22 @@ namespace TimberWinR.Inputs
LogManager.GetCurrentClassLogger().Info("StdIn Ready");
while (!CancelToken.IsCancellationRequested)
{
{
string line = Console.ReadLine();
if (line != null)
{
string msg = ToPrintable(line);
JObject jo = new JObject();
jo["message"] = msg;
AddDefaultFields(jo);
ProcessJson(jo);
}
else
break;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec.Apply(msg, this);
else
{
JObject jo = new JObject();
jo["message"] = msg;
AddDefaultFields(jo);
ProcessJson(jo);
}
}
}
Finished();
}

View File

@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;
using Interop.MSUtil;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using NLog;
using NLog.LayoutRenderers;
using TimberWinR.Codecs;
using TimberWinR.Parser;
namespace TimberWinR.Inputs
{
/// <summary>
/// Tail a file.
/// </summary>
public class TailFileListener : InputListener
{
private object _locker = new object();
private int _pollingIntervalInSeconds;
private TimberWinR.Parser.TailFileArguments _arguments;
private long _receivedMessages;
private CodecArguments _codecArguments;
private ICodec _codec;
public bool Stop { get; set; }
public TailFileListener(TimberWinR.Parser.TailFileArguments arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-TailLog")
{
Stop = false;
EnsureRollingCaught();
_codecArguments = arguments.CodecArguments;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec = new Multiline(_codecArguments);
_receivedMessages = 0;
_arguments = arguments;
_pollingIntervalInSeconds = arguments.Interval;
foreach (string srcFile in _arguments.Location.Split(','))
{
string file = srcFile.Trim();
Task.Factory.StartNew(() => TailFileWatcher(file));
}
}
public override void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1} for {2}", Thread.CurrentThread.ManagedThreadId, InputType, _arguments.Location);
Stop = true;
base.Shutdown();
}
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("taillog",
new JObject(
new JProperty("messages", _receivedMessages),
new JProperty("type", InputType),
new JProperty("location", _arguments.Location),
new JProperty("logSource", _arguments.LogSource),
new JProperty("recurse", _arguments.Recurse),
new JProperty("files",
new JArray(from f in Files
select new JValue(f))),
new JProperty("filedb",
new JArray(from f in Files
select JObject.FromObject(LogsFileDatabase.LookupLogFile(f))))
)));
if (_codecArguments != null)
{
var cp = new JProperty("codec",
new JArray(
new JObject(
new JProperty("type", _codecArguments.Type.ToString()),
new JProperty("what", _codecArguments.What.ToString()),
new JProperty("negate", _codecArguments.Negate),
new JProperty("multilineTag", _codecArguments.MultilineTag),
new JProperty("pattern", _codecArguments.Pattern))));
json.Add(cp);
}
return json;
}
private void TailFileContents(string fileName, long offset, LogsFileDatabaseEntry dbe)
{
using (StreamReader reader = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
//start at the end of the file
long lastMaxOffset = offset;
//if the file size has not changed, idle
if (reader.BaseStream.Length == lastMaxOffset)
return;
//seek to the last max offset
LogManager.GetCurrentClassLogger().Trace("{0}: File: {1} Seek to: {2}", Thread.CurrentThread.ManagedThreadId, fileName, lastMaxOffset);
reader.BaseStream.Seek(lastMaxOffset, SeekOrigin.Begin);
//read out of the file until the EOF
string line = "";
long lineOffset = 0;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrEmpty(line))
continue;
long index = lastMaxOffset + lineOffset;
string text = line;
string logFileName = fileName;
var json = new JObject();
if (json["logSource"] == null)
{
if (string.IsNullOrEmpty(_arguments.LogSource))
json.Add(new JProperty("logSource", fileName));
else
json.Add(new JProperty("logSource", _arguments.LogSource));
}
json["Text"] = line;
json["Index"] = index;
json["LogFileName"] = fileName;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
{
_codec.Apply(line, this);
Interlocked.Increment(ref _receivedMessages);
dbe.IncrementLineCount();
}
else
{
ProcessJson(json);
Interlocked.Increment(ref _receivedMessages);
dbe.IncrementLineCount();
//LogManager.GetCurrentClassLogger().Info("{0}: File: {1} {2} {3}", Thread.CurrentThread.ManagedThreadId, fileName, dbe.LinesProcessed, line);
}
lineOffset += line.Length;
}
//update the last max offset
lastMaxOffset = reader.BaseStream.Position;
LogsFileDatabase.Update(dbe, true, lastMaxOffset);
}
}
// One thread for each kind of file to watch, i.e. "*.log,*.txt" would be two separate
// threads.
private void TailFileWatcher(string fileToWatch)
{
Dictionary<string, string> _fnfmap = new Dictionary<string, string>();
using (var syncHandle = new ManualResetEventSlim())
{
// Execute the query
while (!Stop && !CancelToken.IsCancellationRequested)
{
try
{
if (!CancelToken.IsCancellationRequested)
{
var isWildcardPattern = fileToWatch.Contains('*');
string path = Path.GetDirectoryName(fileToWatch);
string name = Path.GetFileName(fileToWatch);
if (string.IsNullOrEmpty(path))
path = ".";
LogManager.GetCurrentClassLogger().Trace(":{0} Tailing File: {1}", Thread.CurrentThread.ManagedThreadId, Path.Combine(path, name));
// Ok, we have a potential file filter here as 'fileToWatch' could be foo.log or *.log
SearchOption so = SearchOption.TopDirectoryOnly;
if (_arguments.Recurse == -1)
so = SearchOption.AllDirectories;
foreach (string fileName in Directory.GetFiles(path, name, so))
{
var dbe = LogsFileDatabase.LookupLogFile(fileName);
// We only spin up 1 thread for a file we haven't yet seen.
if (isWildcardPattern && !HaveSeenFile(fileName) && dbe.NewFile)
{
LogManager.GetCurrentClassLogger().Debug(":{0} Starting Thread Tailing File: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
LogsFileDatabase.Update(dbe, false, dbe.LastPosition);
SaveVisitedFileName(fileName);
Task.Factory.StartNew(() => TailFileWatcher(fileName));
}
else if (!isWildcardPattern)
{
FileInfo fi = new FileInfo(dbe.FileName);
//LogManager.GetCurrentClassLogger().Info("Located File: {0}, New: {1}", dbe.FileName, dbe.NewFile);
long length = fi.Length;
bool logHasRolled = false;
if (fi.Length < dbe.LastPosition || fi.CreationTimeUtc != dbe.CreationTimeUtc)
{
LogManager.GetCurrentClassLogger().Info("{0}: Log has Rolled: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
logHasRolled = true;
LogsFileDatabase.Roll(dbe);
}
// Log has rolled or this is a file we are seeing for the first time.
bool processWholeFile = logHasRolled || !dbe.ProcessedFile;
if (processWholeFile)
{
LogsFileDatabase.Update(dbe, true, 0);
LogManager.GetCurrentClassLogger().Debug("{0}: Process Whole File: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
TailFileContents(dbe.FileName, 0, dbe);
}
else
{
TailFileContents(dbe.FileName, dbe.LastPosition, dbe);
}
}
}
}
}
catch (FileNotFoundException fnfex)
{
string fn = fnfex.FileName;
if (!_fnfmap.ContainsKey(fn))
LogManager.GetCurrentClassLogger().Warn(fnfex.Message);
_fnfmap[fn] = fn;
}
catch (IOException ioex)
{
LogManager.GetCurrentClassLogger().Debug("Log has rolled: {0}", ioex.Message);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
finally
{
try
{
if (!Stop)
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
{
Stop = true;
}
catch (Exception ex1)
{
LogManager.GetCurrentClassLogger().Warn(ex1);
}
}
}
}
Finished();
}
}
}

View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
@@ -36,9 +34,10 @@ namespace TimberWinR.Inputs
: base(cancelToken, "Win32-Tcp")
{
_port = port;
LogManager.GetCurrentClassLogger().Info("Tcp Input(v4/v6) on Port {0} Ready", _port);
_receivedMessages = 0;
_tcpListenerV6 = new System.Net.Sockets.TcpListener(IPAddress.IPv6Any, port);
_tcpListenerV4 = new System.Net.Sockets.TcpListener(IPAddress.Any, port);
@@ -53,6 +52,8 @@ namespace TimberWinR.Inputs
public override void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1}", Thread.CurrentThread.ManagedThreadId, InputType);
this._tcpListenerV4.Stop();
this._tcpListenerV6.Stop();
@@ -67,7 +68,7 @@ namespace TimberWinR.Inputs
listener.Start();
while (!CancelToken.IsCancellationRequested)
{
try
@@ -92,27 +93,35 @@ namespace TimberWinR.Inputs
private void HandleNewClient(object client)
{
var tcpClient = (TcpClient)client;
NetworkStream clientStream = null;
try
{
clientStream = tcpClient.GetStream();
var stream = new StreamReader(clientStream);
string line;
while ((line = stream.ReadLine()) != null)
NetworkStream clientStream = tcpClient.GetStream();
using (var stream = new StreamReader(clientStream))
{
try
//assume a continuous stream of JSON objects
using (var reader = new JsonTextReader(stream) { SupportMultipleContent = true })
{
JObject json = JObject.Parse(line);
ProcessJson(json);
_receivedMessages++;
while (reader.Read())
{
if (CancelToken.IsCancellationRequested) break;
try
{
JObject json = JObject.Load(reader);
ProcessJson(json);
_receivedMessages++;
}
catch (Exception ex)
{
var jex1 = LogErrors.LogException("Bad Json", ex);
if (jex1 != null)
ProcessJson(jex1);
LogManager.GetCurrentClassLogger().Warn(ex);
}
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
if (CancelToken.IsCancellationRequested)
break;
}
}
catch (Exception ex)
@@ -120,9 +129,6 @@ namespace TimberWinR.Inputs
LogManager.GetCurrentClassLogger().Error(ex);
}
if (clientStream != null)
clientStream.Close();
tcpClient.Close();
Finished();
}

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace TimberWinR.Inputs
{
public class UdpInputListener : InputListener
{
private UdpClient _udpListenerV6;
private readonly Thread _listenThreadV6;
private readonly int _port;
private long _receivedMessages;
private long _parsedErrors;
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("udp",
new JObject(
new JProperty("port", _port),
new JProperty("errors", _parsedErrors),
new JProperty("messages", _receivedMessages)
)));
return json;
}
public UdpInputListener(CancellationToken cancelToken, int port = 5140)
: base(cancelToken, "Win32-Udp")
{
_port = port;
_receivedMessages = 0;
_listenThreadV6 = new Thread(StartListener);
_listenThreadV6.Start();
}
public override void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
// close UDP listeners, which will end the listener threads
_udpListenerV6.Close();
// wait for completion of the threads
_listenThreadV6.Join();
base.Shutdown();
}
private void StartListener()
{
var groupV6 = new IPEndPoint(IPAddress.IPv6Any, _port);
// Create the socket as IPv6
var dualModeSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
//
// Now, disable the IPV6only flag to make it compatable with both ipv4 and ipv6
// See: http://blogs.msdn.com/b/malarch/archive/2005/11/18/494769.aspx
//
dualModeSocket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
dualModeSocket.Bind(groupV6);
_udpListenerV6 = new UdpClient();
_udpListenerV6.Client = dualModeSocket;
LogManager.GetCurrentClassLogger().Info("Udp Input on Port {0} Ready", groupV6);
string lastMessage = "";
try
{
while (!CancelToken.IsCancellationRequested)
{
try
{
byte[] bytes = _udpListenerV6.Receive(ref groupV6);
var data = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
lastMessage = data;
var json = JObject.Parse(data);
ProcessJson(json);
Interlocked.Increment(ref _receivedMessages);
}
catch(SocketException)
{
break;
}
catch (Exception ex)
{
var jex1 = LogErrors.LogException(string.Format("Invalid JSON: {0}", lastMessage), ex);
if (jex1 != null)
ProcessJson(jex1);
LogManager.GetCurrentClassLogger().Warn("Bad JSON: {0}", lastMessage);
LogManager.GetCurrentClassLogger().Warn(ex);
Interlocked.Increment(ref _parsedErrors);
}
}
_udpListenerV6.Close();
}
catch (Exception ex)
{
if (!CancelToken.IsCancellationRequested)
LogManager.GetCurrentClassLogger().Error(ex);
}
Finished();
}
}
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.AccessControl;
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 TimberWinR.Parser;
using LogQuery = Interop.MSUtil.LogQueryClassClass;
using W3CLogInputFormat = Interop.MSUtil.COMW3CInputContextClassClass;
using LogRecordSet = Interop.MSUtil.ILogRecordset;
namespace TimberWinR.Inputs
{
public class W3CInputListener : InputListener
{
private readonly int _pollingIntervalInSeconds;
private readonly TimberWinR.Parser.W3CLogParameters _arguments;
private long _receivedMessages;
public bool Stop { get; set; }
public W3CInputListener(TimberWinR.Parser.W3CLogParameters arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 5)
: base(cancelToken, "Win32-W3CLog")
{
_arguments = arguments;
_receivedMessages = 0;
_pollingIntervalInSeconds = pollingIntervalInSeconds;
foreach (string loc in _arguments.Location.Split(','))
{
string hive = loc.Trim();
Task.Factory.StartNew(() => IISW3CWatcher(loc));
}
}
public override void Shutdown()
{
Stop = true;
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
base.Shutdown();
}
public override JObject ToJson()
{
JObject json = new JObject(
new JProperty("iisw3c",
new JObject(
new JProperty("messages", _receivedMessages),
new JProperty("location", _arguments.Location),
new JProperty("codepage", _arguments.CodePage),
new JProperty("separator", _arguments.Separator),
new JProperty("dQuotes", _arguments.DoubleQuotes),
new JProperty("dtLines", _arguments.DtLines)
)));
return json;
}
private void IISW3CWatcher(string location)
{
LogManager.GetCurrentClassLogger().Info("IISW3Listener Ready For {0}", location);
var oLogQuery = new LogQuery();
var iFmt = new W3CLogInputFormat()
{
codepage = _arguments.CodePage,
iCodepage = _arguments.CodePage,
doubleQuotedStrings = _arguments.DoubleQuotes,
detectTypesLines = _arguments.DtLines,
dQuotes = _arguments.DoubleQuotes,
separator = _arguments.Separator
};
Dictionary<string, Int64> logFileMaxRecords = new Dictionary<string, Int64>();
using (var syncHandle = new ManualResetEventSlim())
{
// Execute the query
while (!Stop)
{
// Execute the query
if (!CancelToken.IsCancellationRequested)
{
try
{
oLogQuery = new LogQuery();
var qfiles = string.Format("SELECT Distinct [LogFilename] FROM {0}", location);
var rsfiles = oLogQuery.Execute(qfiles, iFmt);
for (; !rsfiles.atEnd(); rsfiles.moveNext())
{
var record = rsfiles.getRecord();
string fileName = record.getValue("LogFilename") as string;
if (!logFileMaxRecords.ContainsKey(fileName))
{
var qcount = string.Format("SELECT max(RowNumber) as MaxRecordNumber FROM {0}",
fileName);
var rcount = oLogQuery.Execute(qcount, iFmt);
var qr = rcount.getRecord();
var lrn = (Int64)qr.getValueEx("MaxRecordNumber");
logFileMaxRecords[fileName] = lrn;
}
}
foreach (string fileName in logFileMaxRecords.Keys.ToList())
{
var lastRecordNumber = logFileMaxRecords[fileName];
var query = string.Format(
"SELECT * FROM '{0}' Where RowNumber > {1} order by RowNumber", fileName,
lastRecordNumber);
var rs = oLogQuery.Execute(query, iFmt);
var colMap = new Dictionary<string, int>();
for (int col = 0; col < rs.getColumnCount(); col++)
{
string colName = rs.getColumnName(col);
colMap[colName] = col;
}
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in colMap.Keys)
{
object v = record.getValue(field);
if (field == "date" || field == "time")
{
DateTime dt = DateTime.Parse(v.ToString());
json.Add(new JProperty(field, dt));
}
else
json.Add(new JProperty(field, v));
}
ProcessJson(json);
_receivedMessages++;
var lrn = (Int64)record.getValueEx("RowNumber");
logFileMaxRecords[fileName] = lrn;
record = null;
json = null;
}
// Close the recordset
rs.close();
}
if (!Stop)
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
}
}
Finished();
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
using System.Threading;
@@ -25,18 +26,29 @@ namespace TimberWinR.Inputs
private int _pollingIntervalInSeconds = 1;
private TimberWinR.Parser.WindowsEvent _arguments;
private long _receivedMessages;
private List<Thread> _tasks { get; set; }
public bool Stop { get; set; }
public WindowsEvtInputListener(TimberWinR.Parser.WindowsEvent arguments, CancellationToken cancelToken, int pollingIntervalInSeconds = 1)
public WindowsEvtInputListener(TimberWinR.Parser.WindowsEvent arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-Eventlog")
{
_arguments = arguments;
_pollingIntervalInSeconds = pollingIntervalInSeconds;
var task = new Task(EventWatcher, cancelToken);
task.Start();
_pollingIntervalInSeconds = arguments.Interval;
_tasks = new List<Thread>();
foreach (string eventHive in _arguments.Source.Split(','))
{
string hive = eventHive.Trim();
var thread = new Thread(new ParameterizedThreadStart(EventWatcher));
_tasks.Add(thread);
thread.Start(eventHive);
}
}
public override void Shutdown()
{
Stop = true;
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
base.Shutdown();
}
@@ -48,6 +60,7 @@ namespace TimberWinR.Inputs
new JProperty("messages", _receivedMessages),
new JProperty("binaryFormat", _arguments.BinaryFormat.ToString()),
new JProperty("direction", _arguments.Direction.ToString()),
new JProperty("interval", _arguments.Interval),
new JProperty("formatMsg", _arguments.FormatMsg),
new JProperty("fullEventCode", _arguments.FullEventCode),
new JProperty("fullText", _arguments.FullText),
@@ -59,9 +72,11 @@ namespace TimberWinR.Inputs
return json;
}
private void EventWatcher()
private void EventWatcher(object ploc)
{
var oLogQuery = new LogQuery();
string location = ploc.ToString();
LogQuery oLogQuery = new LogQuery();
LogManager.GetCurrentClassLogger().Info("WindowsEvent Input Listener Ready");
@@ -75,55 +90,93 @@ namespace TimberWinR.Inputs
fullText = _arguments.FullText,
msgErrorMode = _arguments.MsgErrorMode.ToString(),
stringsSep = _arguments.StringsSep,
resolveSIDs = _arguments.ResolveSIDS,
iCheckpoint = CheckpointFileName,
resolveSIDs = _arguments.ResolveSIDS
};
oLogQuery = null;
// Create the query
var query = string.Format("SELECT * FROM {0}", _arguments.Source);
Dictionary<string, Int64> logFileMaxRecords = new Dictionary<string, Int64>();
var firstQuery = true;
// Execute the query
while (!CancelToken.IsCancellationRequested)
using (var syncHandle = new ManualResetEventSlim())
{
try
// Execute the query
while (!Stop)
{
var rs = oLogQuery.Execute(query, iFmt);
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
// Execute the query
if (!CancelToken.IsCancellationRequested)
{
// We want to "tail" the log, so skip the first query results.
if (!firstQuery)
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in _arguments.Fields)
try
{
oLogQuery = new LogQuery();
var qfiles = string.Format("SELECT Distinct [EventLog] FROM {0}", location);
var rsfiles = oLogQuery.Execute(qfiles, iFmt);
for (; !rsfiles.atEnd(); rsfiles.moveNext())
{
object v = record.getValue(field.Name);
if (field.Name == "Data")
v = ToPrintable(v.ToString());
json.Add(new JProperty(field.Name, v));
var record = rsfiles.getRecord();
string logName = record.getValue("EventLog") as string;
if (!logFileMaxRecords.ContainsKey(logName))
{
var qcount = string.Format("SELECT max(RecordNumber) as MaxRecordNumber FROM {0}",
logName);
var rcount = oLogQuery.Execute(qcount, iFmt);
var qr = rcount.getRecord();
var lrn = (Int64)qr.getValueEx("MaxRecordNumber");
logFileMaxRecords[logName] = lrn;
}
}
ProcessJson(json);
_receivedMessages++;
foreach (string fileName in logFileMaxRecords.Keys.ToList())
{
var lastRecordNumber = logFileMaxRecords[fileName];
var query = string.Format("SELECT * FROM {0} where RecordNumber > {1}", location,
lastRecordNumber);
var rs = oLogQuery.Execute(query, iFmt);
// Browse the recordset
for (; !rs.atEnd(); rs.moveNext())
{
var record = rs.getRecord();
var json = new JObject();
foreach (var field in _arguments.Fields)
{
object v = record.getValue(field.Name);
if (field.Name == "Data")
v = ToPrintable(v.ToString());
json.Add(new JProperty(field.Name, v));
}
var lrn = (Int64)record.getValueEx("RecordNumber");
logFileMaxRecords[fileName] = lrn;
record = null;
ProcessJson(json);
_receivedMessages++;
json = null;
}
// Close the recordset
rs.close();
rs = null;
GC.Collect();
}
if (!Stop)
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
// Close the recordset
rs.close();
firstQuery = false;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
firstQuery = true;
oLogQuery = new LogQuery();
}
System.Threading.Thread.Sleep(_pollingIntervalInSeconds * 1000);
Finished();
}
Finished();
}
}
}

48
TimberWinR/LogErrors.cs Normal file
View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using TimberWinR.Parser;
namespace TimberWinR
{
public class LogErrors
{
public static JObject LogException(Exception ex)
{
return LogException("Exception", ex);
}
public static JObject LogException(string errorMessage, Exception ex)
{
JObject result = new JObject();
result["type"] = "TimberWinR-Error";
result["ErrorMessage"] = errorMessage;
try
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
var exJson = JObject.Parse(JsonConvert.SerializeObject(ex));
result.Merge(exJson, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Replace
});
return result;
}
catch (Exception ex1)
{
result["ErrorMessage"] = ex1.ToString();
return result;
}
}
}
}

View File

@@ -1,5 +1,7 @@
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Targets;
@@ -10,6 +12,7 @@ using System.Text;
using TimberWinR.Inputs;
using TimberWinR.Outputs;
using System.Threading;
using Newtonsoft.Json.Linq;
namespace TimberWinR
{
@@ -21,12 +24,18 @@ namespace TimberWinR
public Configuration Config { get; set; }
public List<OutputSender> Outputs { get; set; }
public List<TcpInputListener> Tcps { get; set; }
public List<InputListener> Listeners { get; set; }
public List<TcpInputListener> Udps { get; set; }
public List<InputListener> Listeners { get; set; }
public bool LiveMonitor { get; set; }
public event Action<Configuration> OnConfigurationProcessed;
public DateTime StartedOn { get; set; }
public string JsonConfig { get; set; }
public string LogfileDir { get; set; }
public int NumConnections {
public int NumConnections
{
get { return numConnections; }
}
@@ -38,34 +47,47 @@ namespace TimberWinR
private static int numConnections;
private static int numMessages;
public void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("Shutting Down");
foreach (InputListener listener in Listeners)
listener.Shutdown();
LogManager.GetCurrentClassLogger().Info("Completed ShutDown");
}
public void IncrementMessageCount(int count = 1)
{
{
Interlocked.Add(ref numMessages, count);
}
public Manager(string jsonConfigFile, string logLevel, string logfileDir, CancellationToken cancelToken)
{
StartedOn = DateTime.UtcNow;
JsonConfig = jsonConfigFile;
public Manager()
{
LogsFileDatabase.Manager = this;
}
public Manager(string jsonConfigFile, string logLevel, string logfileDir, bool liveMonitor, CancellationToken cancelToken, bool processConfiguration = true)
{
LogsFileDatabase.Manager = this;
StartedOn = DateTime.UtcNow;
LiveMonitor = liveMonitor;
var vfi = new FileInfo(jsonConfigFile);
JsonConfig = vfi.FullName;
LogfileDir = logfileDir;
numMessages = 0;
numConnections = 0;
Outputs = new List<OutputSender>();
Outputs = new List<OutputSender>();
Listeners = new List<InputListener>();
var loggingConfiguration = new LoggingConfiguration();
// Create our default targets
@@ -80,107 +102,196 @@ namespace TimberWinR
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, coloredConsoleTarget));
// LogLevel.Debug means has to be at least Debug to show up in logfile
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
LogManager.Configuration = loggingConfiguration;
LogManager.EnableLogging();
LogManager.GlobalThreshold = LogLevel.FromString(logLevel);
//LogManager.GetCurrentClassLogger()
// .Info("TimberWinR Version {0}", GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString());
// Is it a directory?
if (Directory.Exists(jsonConfigFile))
LogManager.GetCurrentClassLogger()
.Info("TimberWinR Version {0}", Assembly.GetEntryAssembly().GetName().Version.ToString());
LogManager.GetCurrentClassLogger()
.Info("Database Filename: {0}", LogsFileDatabase.Instance.DatabaseFileName);
try
{
DirectoryInfo di = new DirectoryInfo(jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Configurations From {0}", di.FullName);
Config = Configuration.FromDirectory(jsonConfigFile);
// Is it a directory?
if (Directory.Exists(jsonConfigFile))
{
DirectoryInfo di = new DirectoryInfo(jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Configurations From {0}", di.FullName);
Config = Configuration.FromDirectory(jsonConfigFile, cancelToken, this);
}
else
{
var fi = new FileInfo(jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Configurations From File: {0}", fi.FullName);
if (!fi.Exists)
throw new FileNotFoundException("Missing config file", jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Config: {0}", fi.FullName);
Config = Configuration.FromFile(jsonConfigFile);
}
}
else
catch (JsonSerializationException jse)
{
var fi = new FileInfo(jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Configurations From File: {0}", fi.FullName);
if (!fi.Exists)
throw new FileNotFoundException("Missing config file", jsonConfigFile);
LogManager.GetCurrentClassLogger().Info("Initialized, Reading Config: {0}", fi.FullName);
Config = Configuration.FromFile(jsonConfigFile);
LogManager.GetCurrentClassLogger().Error(jse);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
LogManager.GetCurrentClassLogger().Info("Log Directory {0}", logfileDir);
LogManager.GetCurrentClassLogger().Info("Logging Level: {0}", LogManager.GlobalThreshold);
// Read the Configuration file
if (Config.RedisOutputs != null)
{
foreach (var ro in Config.RedisOutputs)
{
var redis = new RedisOutput(this, ro, cancelToken);
Outputs.Add(redis);
}
}
if (Config.ElasticsearchOutputs != null)
{
foreach (var ro in Config.ElasticsearchOutputs)
{
var els = new ElasticsearchOutput(this, ro, cancelToken);
Outputs.Add(els);
}
}
if (Config.StdoutOutputs != null)
{
foreach (var ro in Config.StdoutOutputs)
{
var stdout = new StdoutOutput(this, ro, cancelToken);
Outputs.Add(stdout);
}
}
LogManager.GetCurrentClassLogger().Info("Logging Level: {0}", LogManager.GlobalThreshold);
foreach (Parser.IISW3CLog iisw3cConfig in Config.IISW3C)
if (processConfiguration)
{
var elistner = new IISW3CInputListener(iisw3cConfig, cancelToken);
Listeners.Add(elistner);
foreach(var output in Outputs)
output.Connect(elistner);
ProcessConfiguration(cancelToken, Config);
}
foreach (Parser.WindowsEvent eventConfig in Config.Events)
{
var elistner = new WindowsEvtInputListener(eventConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var logConfig in Config.Logs)
{
var elistner = new LogsListener(logConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var tcp in Config.Tcps)
{
var elistner = new TcpInputListener(cancelToken, tcp.Port);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var tcp in Config.Stdins)
{
var elistner = new StdinListener(cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
}
public void Start(CancellationToken cancelToken)
{
ProcessConfiguration(cancelToken, Config);
}
public void ProcessConfiguration(CancellationToken cancelToken, Configuration config)
{
// Read the Configuration file
if (config != null)
{
if (OnConfigurationProcessed != null)
OnConfigurationProcessed(config);
if (config.RedisOutputs != null)
{
foreach (var ro in config.RedisOutputs)
{
var redis = new RedisOutput(this, ro, cancelToken);
Outputs.Add(redis);
}
}
if (config.ElasticsearchOutputs != null)
{
foreach (var ro in config.ElasticsearchOutputs)
{
var els = new ElasticsearchOutput(this, ro, cancelToken);
Outputs.Add(els);
}
}
if (config.StdoutOutputs != null)
{
foreach (var ro in config.StdoutOutputs)
{
var stdout = new StdoutOutput(this, ro, cancelToken);
Outputs.Add(stdout);
}
}
foreach (Parser.IISW3CLogParameters iisw3cConfig in config.IISW3C)
{
var elistner = new IISW3CInputListener(iisw3cConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (Parser.W3CLogParameters iisw3cConfig in config.W3C)
{
var elistner = new W3CInputListener(iisw3cConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (Parser.WindowsEvent eventConfig in config.Events)
{
var elistner = new WindowsEvtInputListener(eventConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var logConfig in config.Logs)
{
var elistner = new LogsListener(logConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var logConfig in config.TailFiles)
{
var elistner = new TailFileListener(logConfig, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var tcp in config.Tcps)
{
var elistner = new TcpInputListener(cancelToken, tcp.Port);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var udp in config.Udps)
{
var elistner = new UdpInputListener(cancelToken, udp.Port);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
foreach (var stdin in config.Stdins)
{
var elistner = new StdinListener(stdin, cancelToken);
Listeners.Add(elistner);
foreach (var output in Outputs)
output.Connect(elistner);
}
var computerName = System.Environment.MachineName + "." +
Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
@"SYSTEM\CurrentControlSet\services\Tcpip\Parameters")
.GetValue("Domain", "")
.ToString();
foreach (var output in Outputs)
{
var name = Assembly.GetExecutingAssembly().GetName();
JObject json = new JObject(
new JProperty("TimberWinR",
new JObject(
new JProperty("version",
Assembly.GetEntryAssembly().GetName().Version.ToString()),
//GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString()),
new JProperty("host", computerName),
new JProperty("output", output.Name),
new JProperty("initialized", DateTime.UtcNow)
)));
json.Add(new JProperty("type", "Win32-TimberWinR"));
json.Add(new JProperty("host", computerName));
output.Startup(json);
}
}
}
private Assembly GetAssemblyByName(string name)
{
return AppDomain.CurrentDomain.GetAssemblies().
SingleOrDefault(assembly => assembly.GetName().Name == name);
}
/// <summary>
/// Creates the default <see cref="FileTarget"/>.
/// </summary>

View File

@@ -2,52 +2,93 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Elasticsearch.Net;
using Elasticsearch.Net.ConnectionPool;
using Nest;
using Newtonsoft.Json.Linq;
using NLog;
using RapidRegex.Core;
using RestSharp;
using System.Text.RegularExpressions;
using Elasticsearch.Net.Serialization;
using Newtonsoft.Json;
namespace TimberWinR.Outputs
{
public class Person
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
public partial class ElasticsearchOutput : OutputSender
{
private TimberWinR.Manager _manager;
private readonly int _port;
private readonly int _interval;
private readonly string[] _host;
private readonly string _protocol;
private readonly string _index;
private int _hostIndex;
private readonly int _flushSize;
private readonly int _idleFlushTimeSeconds;
private readonly string[] _hosts;
private readonly string _protocol;
private readonly int _timeout;
private readonly object _locker = new object();
private readonly List<JObject> _jsonQueue;
private readonly int _numThreads;
private long _sentMessages;
private long _errorCount;
private readonly int _maxQueueSize;
private readonly bool _queueOverflowDiscardOldest;
private Parser.ElasticsearchOutputParameters _parameters;
public bool Stop { get; set; }
/// <summary>
/// Get the bulk connection pool of hosts
/// </summary>
/// <returns></returns>
private ElasticClient getClient()
{
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 pool = new StaticConnectionPool(nodes.ToArray());
var settings = new ConnectionSettings(pool)
.ExposeRawResponse();
public ElasticsearchOutput(TimberWinR.Manager manager, Parser.ElasticsearchOutput eo, CancellationToken cancelToken)
: base(cancelToken)
var client = new ElasticClient(settings);
return client;
}
public ElasticsearchOutput(TimberWinR.Manager manager, Parser.ElasticsearchOutputParameters parameters, CancellationToken cancelToken)
: base(cancelToken, "Elasticsearch")
{
_sentMessages = 0;
_errorCount = 0;
_protocol = eo.Protocol;
_timeout = eo.Timeout;
_parameters = parameters;
_flushSize = parameters.FlushSize;
_idleFlushTimeSeconds = parameters.IdleFlushTimeInSeconds;
_protocol = parameters.Protocol;
_timeout = parameters.Timeout;
_manager = manager;
_port = eo.Port;
_interval = eo.Interval;
_host = eo.Host;
_index = eo.Index;
_hostIndex = 0;
_port = parameters.Port;
_interval = parameters.Interval;
_hosts = parameters.Host;
_jsonQueue = new List<JObject>();
_numThreads = eo.NumThreads;
_numThreads = parameters.NumThreads;
_maxQueueSize = parameters.MaxQueueSize;
_queueOverflowDiscardOldest = parameters.QueueOverflowDiscardOldest;
for (int i = 0; i < eo.NumThreads; i++)
for (int i = 0; i < parameters.NumThreads; i++)
{
var elsThread = new Task(ElasticsearchSender, cancelToken);
elsThread.Start();
Task.Factory.StartNew(ElasticsearchSender, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
}
@@ -56,16 +97,20 @@ namespace TimberWinR.Outputs
JObject json = new JObject(
new JProperty("elasticsearch",
new JObject(
new JProperty("host", string.Join(",", _host)),
new JProperty("errors", _errorCount),
new JProperty("sent_messages", _sentMessages),
new JProperty("queued_messages", _jsonQueue.Count),
new JProperty("host", string.Join(",", _hosts)),
new JProperty("errors", _errorCount),
new JProperty("sentMmessageCount", _sentMessages),
new JProperty("queuedMessageCount", _jsonQueue.Count),
new JProperty("port", _port),
new JProperty("flushSize", _flushSize),
new JProperty("idleFlushTime", _idleFlushTimeSeconds),
new JProperty("interval", _interval),
new JProperty("threads", _numThreads),
new JProperty("threads", _numThreads),
new JProperty("maxQueueSize", _maxQueueSize),
new JProperty("overflowDiscardOldest", _queueOverflowDiscardOldest),
new JProperty("hosts",
new JArray(
from h in _host
from h in _hosts
select new JObject(
new JProperty("host", h)))))));
return json;
@@ -75,118 +120,145 @@ namespace TimberWinR.Outputs
//
private void ElasticsearchSender()
{
while (!CancelToken.IsCancellationRequested)
{
JObject[] messages;
lock (_locker)
{
messages = _jsonQueue.Take(1).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
if (messages.Length > 0)
_manager.IncrementMessageCount(messages.Length);
}
// Force an inital flush
DateTime lastFlushTime = DateTime.MinValue;
if (messages.Length > 0)
{
int numHosts = _host.Length;
while (numHosts-- > 0)
using (var syncHandle = new ManualResetEventSlim())
{
// Execute the query
while (!Stop)
{
if (!CancelToken.IsCancellationRequested)
{
try
{
// Get the next client
RestClient client = getClient();
if (client != null)
{
LogManager.GetCurrentClassLogger()
.Debug("Sending {0} Messages to {1}", messages.Length, client.BaseUrl);
foreach (JObject json in messages)
{
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)
{
string typeName = "Win32-Elasticsearch";
if (json["type"] != null)
typeName = json["type"].ToString();
string indexName = _index;
if (string.IsNullOrEmpty(indexName))
{
DateTime now = DateTime.UtcNow;
indexName = string.Format("logstash-{0}", DateTime.UtcNow.ToString("yyyy.MM.dd"));
}
var req = new RestRequest(string.Format("/{0}/{1}/", indexName, typeName), Method.POST);
req.AddParameter("text/json", json.ToString(), ParameterType.RequestBody);
req.RequestFormat = DataFormat.Json;
try
{
client.ExecuteAsync(req, response =>
{
if (response.StatusCode != HttpStatusCode.Created)
{
LogManager.GetCurrentClassLogger()
.Error("Failed to send: {0}", response.ErrorMessage);
Interlocked.Increment(ref _errorCount);
}
else
{
_sentMessages++;
}
});
}
catch (Exception error)
{
LogManager.GetCurrentClassLogger().Error(error);
Interlocked.Increment(ref _errorCount);
}
messages = _jsonQueue.Take(messageCount).ToList();
_jsonQueue.RemoveRange(0, messageCount);
if (messages.Count > 0)
_manager.IncrementMessageCount(messages.Count);
}
}
else
// We have some messages to work with
if (messages.Count > 0)
{
var client = getClient();
LogManager.GetCurrentClassLogger()
.Fatal("Unable to connect with any Elasticsearch hosts, {0}",
String.Join(",", _host));
Interlocked.Increment(ref _errorCount);
.Debug("Sending {0} Messages to {1}", messages.Count, string.Join(",", _hosts));
// This loop will process all messages we've taken from the queue
// that have the same index and type (an elasticsearch requirement)
do
{
try
{
// Grab all messages with same index and type (this is the whole point, group the same ones)
var bulkTypeName = this._parameters.GetTypeName(messages[0]);
var bulkIndexName = this._parameters.GetIndexName(messages[0]);
IEnumerable<JObject> bulkItems =
messages.TakeWhile(
message =>
String.Compare(bulkTypeName, _parameters.GetTypeName(message), false) == 0 &&
String.Compare(bulkIndexName, _parameters.GetIndexName(message), false) == 0);
// Send the message(s), if the are successfully sent, they
// are removed from the queue
lastFlushTime = transmitBulkData(bulkItems, bulkIndexName, bulkTypeName, client, lastFlushTime, messages);
GC.Collect();
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
break;
}
} while (messages.Count > 0);
}
GC.Collect();
if (!Stop)
{
syncHandle.Wait(TimeSpan.FromMilliseconds(_interval), CancelToken);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
Interlocked.Increment(ref _errorCount);
}
}
}
System.Threading.Thread.Sleep(_interval);
}
}
private RestClient getClient()
{
if (_hostIndex >= _host.Length)
_hostIndex = 0;
int numTries = 0;
while (numTries < _host.Length)
//
// Send the messages to Elasticsearch (bulk)
//
private DateTime transmitBulkData(IEnumerable<JObject> bulkItems, string bulkIndexName, string bulkTypeName,
ElasticClient client, DateTime lastFlushTime, List<JObject> messages)
{
var bulkRequest = new BulkRequest() {Refresh = true};
bulkRequest.Operations = new List<IBulkOperation>();
foreach (var json in bulkItems)
{
try
{
string url = string.Format("{0}://{1}:{2}", _protocol.Replace(":",""), _host[_hostIndex], _port);
var client = new RestClient(url);
client.Timeout = _timeout;
_hostIndex++;
if (_hostIndex >= _host.Length)
_hostIndex = 0;
return client;
}
catch (Exception)
{
}
numTries++;
// ES requires a timestamp, add one if not present
var ts = json["@timestamp"];
if (ts == null)
json["@timestamp"] = DateTime.UtcNow;
var bi = new BulkIndexOperation<JObject>(json);
bi.Index = bulkIndexName;
bi.Type = bulkTypeName;
bulkRequest.Operations.Add(bi);
}
return null;
// The total messages processed for this operation.
int numMessages = bulkItems.Count();
var response = client.Bulk(bulkRequest);
if (!response.IsValid)
{
LogManager.GetCurrentClassLogger().Error("Failed to send: {0}", response);
Interlocked.Increment(ref _errorCount);
interlockedInsert(messages); // Put the messages back into the queue
}
else // Success!
{
lastFlushTime = DateTime.UtcNow;
LogManager.GetCurrentClassLogger()
.Info("Successfully sent {0} messages in a single bulk request", numMessages);
Interlocked.Add(ref _sentMessages, numMessages);
}
// Remove them from the working list
messages.RemoveRange(0, numMessages);
return lastFlushTime;
}
// Places messages back into the queue (for a future attempt)
private void interlockedInsert(List<JObject> messages)
{
lock (_locker)
{
_jsonQueue.InsertRange(0, messages);
if (_jsonQueue.Count > _maxQueueSize)
{
LogManager.GetCurrentClassLogger().Warn("Exceeded maximum queue depth");
}
}
}
@@ -196,10 +268,30 @@ namespace TimberWinR.Outputs
ApplyFilters(jsonMessage);
var message = jsonMessage.ToString();
LogManager.GetCurrentClassLogger().Debug(message);
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);
}
}

View File

@@ -12,10 +12,12 @@ namespace TimberWinR.Outputs
{
public CancellationToken CancelToken { get; private set; }
private List<InputListener> _inputs;
public OutputSender(CancellationToken cancelToken)
public string Name { get; set; }
public OutputSender(CancellationToken cancelToken, string name)
{
CancelToken = cancelToken;
Name = name;
_inputs = new List<InputListener>();
}
@@ -24,6 +26,11 @@ namespace TimberWinR.Outputs
listener.OnMessageRecieved += MessageReceivedHandler;
}
public void Startup(JObject json)
{
MessageReceivedHandler(json);
}
public abstract JObject ToJson();
protected abstract void MessageReceivedHandler(JObject jsonMessage);
}

View File

@@ -1,11 +1,12 @@
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 ctstone.Redis;
using CSRedis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
@@ -13,30 +14,130 @@ using System.Threading.Tasks;
using RapidRegex.Core;
using System.Text.RegularExpressions;
using System.Globalization;
using TimberWinR.Parser;
namespace TimberWinR.Outputs
{
internal class BatchCounter
{
// Total number of times reached max batch count (indicates we are under pressure)
public int ReachedMaxBatchCountTimes { get; set; }
private readonly int[] _sampleQueueDepths;
private int _sampleCountIndex;
private const int QUEUE_SAMPLE_SIZE = 30; // 30 samples over 2.5 minutes (default)
private object _locker = new object();
private bool _warnedReachedMax;
private readonly int _maxBatchCount;
private readonly int _batchCount;
private int _totalSamples;
public int[] Samples()
{
return _sampleQueueDepths;
}
public BatchCounter(int batchCount, int maxBatchCount)
{
_batchCount = batchCount;
_maxBatchCount = maxBatchCount;
_sampleQueueDepths = new int[QUEUE_SAMPLE_SIZE];
_sampleCountIndex = 0;
_totalSamples = 0;
ReachedMaxBatchCountTimes = 0;
}
public void SampleQueueDepth(int queueDepth)
{
lock (_locker)
{
if (_totalSamples < QUEUE_SAMPLE_SIZE)
_totalSamples++;
// Take a sample of the queue depth
if (_sampleCountIndex >= QUEUE_SAMPLE_SIZE)
_sampleCountIndex = 0;
_sampleQueueDepths[_sampleCountIndex++] = queueDepth;
}
}
public int AverageQueueDepth()
{
lock (_locker)
{
if (_totalSamples > 0)
{
var samples = _sampleQueueDepths.Take(_totalSamples);
int avg = (int)samples.Average();
return avg;
}
return 0;
}
}
// Sample the queue and adjust the batch count if needed (ramp up slowly)
public int UpdateCurrentBatchCount(int queueSize, int currentBatchCount)
{
if (currentBatchCount < _maxBatchCount && currentBatchCount < queueSize && AverageQueueDepth() > currentBatchCount)
{
currentBatchCount += Math.Max(_maxBatchCount / _batchCount, 1);
if (currentBatchCount >= _maxBatchCount && !_warnedReachedMax)
{
LogManager.GetCurrentClassLogger().Warn("Maximum Batch Count of {0} reached.", currentBatchCount);
_warnedReachedMax = true; // Only complain when it's reached (1 time, unless reset)
ReachedMaxBatchCountTimes++;
currentBatchCount = _maxBatchCount;
}
}
else // Reset to default
{
currentBatchCount = _batchCount;
_warnedReachedMax = false;
}
return currentBatchCount;
}
}
public class RedisOutput : OutputSender
{
public int QueueDepth
{
get { return _jsonQueue.Count; }
}
public long SentMessages
{
get { return _sentMessages; }
}
private readonly string _logstashIndexName;
private readonly int _port;
private readonly int _timeout;
private readonly object _locker = new object();
private readonly List<string> _jsonQueue;
// readonly Task _consumerTask;
private readonly string[] _redisHosts;
private int _redisHostIndex;
private TimberWinR.Manager _manager;
private readonly int _batchCount;
private int _currentBatchCount;
private readonly int _maxBatchCount;
private readonly int _interval;
private readonly int _numThreads;
private long _sentMessages;
private long _errorCount;
private long _redisDepth;
private DateTime? _lastErrorTimeUTC;
private readonly int _maxQueueSize;
private readonly bool _queueOverflowDiscardOldest;
private BatchCounter _batchCounter;
public bool Stop { get; set; }
/// <summary>
/// Get the next client
/// Get the next client from the list of hosts.
/// </summary>
/// <returns></returns>
private RedisClient getClient()
@@ -49,17 +150,19 @@ namespace TimberWinR.Outputs
{
try
{
RedisClient client = new RedisClient(_redisHosts[_redisHostIndex], _port, _timeout);
_redisHostIndex++;
if (_redisHostIndex >= _redisHosts.Length)
_redisHostIndex = 0;
RedisClient client = new RedisClient(_redisHosts[_redisHostIndex], _port);
client.SendTimeout = _timeout;
return client;
}
catch (Exception)
{
}
finally
{
_redisHostIndex++;
if (_redisHostIndex >= _redisHosts.Length)
_redisHostIndex = 0;
}
numTries++;
}
@@ -73,13 +176,21 @@ namespace TimberWinR.Outputs
new JObject(
new JProperty("host", string.Join(",", _redisHosts)),
new JProperty("errors", _errorCount),
new JProperty("redis_depth", _redisDepth),
new JProperty("sent_messages", _sentMessages),
new JProperty("queued_messages", _jsonQueue.Count),
new JProperty("lastErrorTimeUTC", _lastErrorTimeUTC),
new JProperty("redisQueueDepth", _redisDepth),
new JProperty("sentMessageCount", _sentMessages),
new JProperty("queuedMessageCount", _jsonQueue.Count),
new JProperty("port", _port),
new JProperty("maxQueueSize", _maxQueueSize),
new JProperty("overflowDiscardOldest", _queueOverflowDiscardOldest),
new JProperty("interval", _interval),
new JProperty("threads", _numThreads),
new JProperty("batchcount", _batchCount),
new JProperty("currentBatchCount", _currentBatchCount),
new JProperty("reachedMaxBatchCountTimes", _batchCounter.ReachedMaxBatchCountTimes),
new JProperty("maxBatchCount", _maxBatchCount),
new JProperty("averageQueueDepth", _batchCounter.AverageQueueDepth()),
new JProperty("queueSamples", new JArray(_batchCounter.Samples())),
new JProperty("index", _logstashIndexName),
new JProperty("hosts",
new JArray(
@@ -89,23 +200,33 @@ namespace TimberWinR.Outputs
return json;
}
public RedisOutput(TimberWinR.Manager manager, Parser.RedisOutput ro, CancellationToken cancelToken)
: base(cancelToken)
public RedisOutput(TimberWinR.Manager manager, Parser.RedisOutputParameters parameters, CancellationToken cancelToken)
: base(cancelToken, "Redis")
{
_redisDepth = 0;
_batchCount = ro.BatchCount;
_batchCount = parameters.BatchCount;
_maxBatchCount = parameters.MaxBatchCount;
// Make sure maxBatchCount is larger than batchCount
if (_maxBatchCount <= _batchCount)
_maxBatchCount = _batchCount * 10;
_manager = manager;
_redisHostIndex = 0;
_redisHosts = ro.Host;
_redisHosts = parameters.Host;
_jsonQueue = new List<string>();
_port = ro.Port;
_timeout = ro.Timeout;
_logstashIndexName = ro.Index;
_interval = ro.Interval;
_numThreads = ro.NumThreads;
_port = parameters.Port;
_timeout = parameters.Timeout;
_logstashIndexName = parameters.Index;
_interval = parameters.Interval;
_numThreads = parameters.NumThreads;
_errorCount = 0;
_lastErrorTimeUTC = null;
_maxQueueSize = parameters.MaxQueueSize;
_queueOverflowDiscardOldest = parameters.QueueOverflowDiscardOldest;
_batchCounter = new BatchCounter(_batchCount, _maxBatchCount);
_currentBatchCount = _batchCount;
for (int i = 0; i < ro.NumThreads; i++)
for (int i = 0; i < parameters.NumThreads; i++)
{
var redisThread = new Task(RedisSender, cancelToken);
redisThread.Start();
@@ -114,7 +235,7 @@ namespace TimberWinR.Outputs
public override string ToString()
{
return string.Format("Redis Host: {0} Port: {1}, Threads: {2}, Interval: {3}, BatchCount: {4}", string.Join(",", _redisHosts) , _port, _numThreads, _interval, _batchCount);
return string.Format("Redis Host: {0} Port: {1}, Threads: {2}, Interval: {3}, BatchCount: {4}", string.Join(",", _redisHosts), _port, _numThreads, _interval, _batchCount);
}
/// <summary>
@@ -124,90 +245,166 @@ namespace TimberWinR.Outputs
protected override void MessageReceivedHandler(JObject jsonMessage)
{
if (_manager.Config.Filters != null)
ApplyFilters(jsonMessage);
{
if (ApplyFilters(jsonMessage))
return;
}
var message = jsonMessage.ToString();
LogManager.GetCurrentClassLogger().Debug(message);
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(message);
}
}
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))
{
LogManager.GetCurrentClassLogger().Debug("{0}: Dropping: {1}", Thread.CurrentThread.ManagedThreadId, json.ToString());
drop = true;
}
}
return drop;
}
//
// Pull off messages from the Queue, batch them up and send them all across
//
private void RedisSender()
{
while (!CancelToken.IsCancellationRequested)
using (var syncHandle = new ManualResetEventSlim())
{
string[] messages;
lock (_locker)
// Execute the query
while (!Stop)
{
messages = _jsonQueue.Take(_batchCount).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
if (messages.Length > 0)
_manager.IncrementMessageCount(messages.Length);
}
if (messages.Length > 0)
{
int numHosts = _redisHosts.Length;
while (numHosts-- > 0)
if (!CancelToken.IsCancellationRequested)
{
try
{
// Get the next client
using (RedisClient client = getClient())
string[] messages;
// Exclusively
lock (_locker)
{
if (client != null)
{
client.StartPipe();
LogManager.GetCurrentClassLogger()
.Debug("Sending {0} Messages to {1}", messages.Length, client.Host);
_batchCounter.SampleQueueDepth(_jsonQueue.Count);
// Re-compute current batch size
_currentBatchCount = _batchCounter.UpdateCurrentBatchCount(_jsonQueue.Count, _currentBatchCount);
foreach (string jsonMessage in messages)
messages = _jsonQueue.Take(_currentBatchCount).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
}
if (messages.Length > 0)
{
int numHosts = _redisHosts.Length;
bool sentSuccessfully = false;
while (numHosts-- > 0)
{
try
{
try
// Get the next client
using (RedisClient client = getClient())
{
_redisDepth = client.RPush(_logstashIndexName, jsonMessage);
_sentMessages++;
}
catch (SocketException ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
Interlocked.Increment(ref _errorCount);
if (client != null)
{
client.StartPipe();
LogManager.GetCurrentClassLogger()
.Debug("{0}: Sending {1} Messages to {2}", Thread.CurrentThread.ManagedThreadId, messages.Length, client.Host);
try
{
_redisDepth = client.RPush(_logstashIndexName, messages);
Interlocked.Add(ref _sentMessages, messages.Length);
client.EndPipe();
sentSuccessfully = true;
if (messages.Length > 0)
_manager.IncrementMessageCount(messages.Length);
}
catch (SocketException ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
Interlocked.Increment(ref _errorCount);
_lastErrorTimeUTC = DateTime.UtcNow;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
Interlocked.Increment(ref _errorCount);
_lastErrorTimeUTC = DateTime.UtcNow;
}
break;
}
else
{
Interlocked.Increment(ref _errorCount);
LogManager.GetCurrentClassLogger()
.Fatal("Unable to connect with any Redis hosts, {0}",
String.Join(",", _redisHosts));
_lastErrorTimeUTC = DateTime.UtcNow;
}
}
}
client.EndPipe();
break;
}
else
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
Interlocked.Increment(ref _errorCount);
_lastErrorTimeUTC = DateTime.UtcNow;
}
} // No more hosts to try.
// Couldn't send, put it back into the queue.
if (!sentSuccessfully)
{
Interlocked.Increment(ref _errorCount);
LogManager.GetCurrentClassLogger()
.Fatal("Unable to connect with any Redis hosts, {0}",
String.Join(",", _redisHosts));
lock (_locker)
{
_jsonQueue.InsertRange(0, messages);
}
}
}
}
if (!Stop)
syncHandle.Wait(TimeSpan.FromMilliseconds(_interval), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (ThreadAbortException)
{
break;
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
_lastErrorTimeUTC = DateTime.UtcNow;
Interlocked.Increment(ref _errorCount);
LogManager.GetCurrentClassLogger().Error(ex);
}
}
}
System.Threading.Thread.Sleep(_interval);
}
}
}

View File

@@ -15,9 +15,10 @@ namespace TimberWinR.Outputs
private readonly object _locker = new object();
private readonly List<JObject> _jsonQueue;
private long _sentMessages;
public bool Stop { get; set; }
public StdoutOutput(TimberWinR.Manager manager, Parser.StdoutOutput eo, CancellationToken cancelToken)
: base(cancelToken)
public StdoutOutput(TimberWinR.Manager manager, Parser.StdoutOutputParameters eo, CancellationToken cancelToken)
: base(cancelToken, "Stdout")
{
_sentMessages = 0;
_manager = manager;
@@ -33,8 +34,8 @@ namespace TimberWinR.Outputs
JObject json = new JObject(
new JProperty("stdout",
new JObject(
new JProperty("sent_messages", _sentMessages))));
new JProperty("sentMessageCount", _sentMessages))));
return json;
}
@@ -43,38 +44,59 @@ namespace TimberWinR.Outputs
//
private void StdoutSender()
{
while (!CancelToken.IsCancellationRequested)
using (var syncHandle = new ManualResetEventSlim())
{
JObject[] messages;
lock (_locker)
// Execute the query
while (!Stop)
{
messages = _jsonQueue.Take(1).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
}
if (messages.Length > 0)
{
try
if (!CancelToken.IsCancellationRequested)
{
foreach (JObject obj in messages)
try
{
JObject[] messages;
lock (_locker)
{
messages = _jsonQueue.Take(_jsonQueue.Count).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
}
if (messages.Length > 0)
{
try
{
foreach (JObject obj in messages)
{
Console.WriteLine(obj.ToString());
_sentMessages++;
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
if (!Stop)
syncHandle.Wait(TimeSpan.FromMilliseconds(_interval), CancelToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception)
{
Console.WriteLine(obj.ToString());
_sentMessages++;
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Error(ex);
}
}
System.Threading.Thread.Sleep(_interval);
}
}
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);
@@ -85,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

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;
using System.Linq.Dynamic;
using System.Reflection;
using System.Runtime.Remoting.Channels;
using System.Text;
@@ -11,89 +13,59 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using TimberWinR.Outputs;
using System.CodeDom.Compiler;
namespace TimberWinR.Parser
{
using System.Text.RegularExpressions;
interface IValidateSchema
{
void Validate();
}
public abstract class LogstashFilter : IValidateSchema
{
public abstract bool Apply(JObject json);
protected void RemoveProperty(JObject json, string name)
{
JToken token = json[name];
if (token != null)
{
json.Remove(name);
}
}
protected void RenameProperty(JObject json, string oldName, string newName)
{
JToken token = json[oldName];
if (token != null)
{
json.Remove(oldName);
json.Add(newName, token);
JToken newToken = json[newName];
if (newToken == null)
json.Add(newName, token);
}
}
public abstract JObject ToJson();
protected bool EvaluateCondition(JObject json, string condition)
{
// Create a new instance of the C# compiler
var cond = condition;
IList<string> keys = json.Properties().Select(p => p.Name).ToList();
IList<string> keys = json.Properties().Select(pn => pn.Name).ToList();
foreach (string key in keys)
cond = cond.Replace(string.Format("[{0}]", key), string.Format("\"{0}\"", json[key].ToString()));
cond = cond.Replace(string.Format("[{0}]", key), string.Format("{0}", json[key].ToString()));
var compiler = new CSharpCodeProvider();
var p = Expression.Parameter(typeof(JObject), "");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, cond);
// Create some parameters for the compiler
var parms = new System.CodeDom.Compiler.CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true
};
parms.ReferencedAssemblies.Add("System.dll");
parms.ReferencedAssemblies.Add("System.Core.dll");
parms.ReferencedAssemblies.Add("Newtonsoft.Json.dll");
var result = e.Compile().DynamicInvoke(json);
var code = string.Format(@" using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
class EvaluatorClass
{{
public JObject json {{ get; set; }}
public bool Evaluate()
{{
return {0};
}}
}}", cond);
// Try to compile the string into an assembly
var results = compiler.CompileAssemblyFromSource(parms, new string[] { code });
// If there weren't any errors get an instance of "MyClass" and invoke
// the "Message" method on it
if (results.Errors.Count == 0)
{
var evClass = results.CompiledAssembly.CreateInstance("EvaluatorClass");
evClass.GetType().GetProperty("json").SetValue(evClass, json, null);
var result = evClass.GetType().
GetMethod("Evaluate").
Invoke(evClass, null);
return bool.Parse(result.ToString());
}
else
{
foreach (var e in results.Errors)
{
LogManager.GetCurrentClassLogger().Error(e);
LogManager.GetCurrentClassLogger().Error("Bad Code: {0}", code);
}
}
return false;
return (bool)result;
}
protected void RemoveProperties(JToken token, string[] fields)
{
@@ -101,6 +73,7 @@ namespace TimberWinR.Parser
if (container == null) return;
List<JToken> removeList = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
@@ -115,6 +88,7 @@ namespace TimberWinR.Parser
{
el.Remove();
}
}
protected void ReplaceProperty(JObject json, string propertyName, string propertyValue)
@@ -151,7 +125,7 @@ namespace TimberWinR.Parser
}
public abstract void Validate();
}
[JsonObject(MemberSerialization.OptIn)]
@@ -200,7 +174,7 @@ namespace TimberWinR.Parser
To = to;
}
}
public class WindowsEvent : IValidateSchema
{
public enum FormatKinds
@@ -241,16 +215,19 @@ namespace TimberWinR.Parser
public List<Field> Fields { get; set; }
[JsonProperty(PropertyName = "formatMsg")]
public bool FormatMsg { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
public WindowsEvent()
{
Interval = 60; // Every minute
Source = "System";
StringsSep = "|";
FormatMsg = true;
FullText = true;
BinaryFormat = FormatKinds.ASC;
FullEventCode = false;
Fields = new List<Field>();
Fields.Add(new Field("EventLog", "string"));
Fields.Add(new Field("RecordNumber", "int"));
@@ -266,7 +243,7 @@ namespace TimberWinR.Parser
Fields.Add(new Field("ComputerName", "string"));
Fields.Add(new Field("SID", "string"));
Fields.Add(new Field("Message", "string"));
Fields.Add(new Field("Data", "string"));
Fields.Add(new Field("Data", "string"));
}
public void Validate()
@@ -274,16 +251,83 @@ namespace TimberWinR.Parser
}
}
public class Stdin : IValidateSchema
{
[JsonProperty(PropertyName = "codec")]
public CodecArguments CodecArguments { get; set; }
public void Validate()
{
}
}
public class Log : IValidateSchema
public class CodecArguments
{
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 CodecArguments()
{
Negate = false;
MultilineTag = "multiline";
}
}
public class TailFileArguments : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
[JsonProperty(PropertyName = "recurse")]
public int Recurse { get; set; }
[JsonProperty(PropertyName = "fields")]
public List<Field> Fields { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
[JsonProperty(PropertyName = "logSource")]
public string LogSource { get; set; }
[JsonProperty(PropertyName = "codec")]
public CodecArguments CodecArguments { get; set; }
public TailFileArguments()
{
Fields = new List<Field>();
Fields.Add(new Field("LogFilename", "string"));
Fields.Add(new Field("Index", "integer"));
Fields.Add(new Field("Text", "string"));
Interval = 30;
}
public void Validate()
{
}
}
public class LogParameters : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
@@ -295,39 +339,97 @@ namespace TimberWinR.Parser
public bool SplitLongLines { get; set; }
[JsonProperty(PropertyName = "fields")]
public List<Field> Fields { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
[JsonProperty(PropertyName = "logSource")]
public string LogSource { get; set; }
[JsonProperty(PropertyName = "codec")]
public CodecArguments CodecArguments { get; set; }
public Log()
public LogParameters()
{
Fields = new List<Field>();
Fields.Add(new Field("LogFilename", "string"));
Fields.Add(new Field("Index", "integer"));
Fields.Add(new Field("Text", "string"));
Interval = 30;
}
public void Validate()
{
}
}
public class Tcp : IValidateSchema
public class TcpParameters : IValidateSchema
{
[JsonProperty(PropertyName = "port")]
public int Port { get; set; }
public Tcp()
public TcpParameters()
{
Port = 5140;
}
public void Validate()
{
}
}
public class IISW3CLog : IValidateSchema
{
public class UdpParameters : IValidateSchema
{
[JsonProperty(PropertyName = "port")]
public int Port { get; set; }
public UdpParameters()
{
Port = 5142;
}
public void Validate()
{
}
}
public class W3CLogParameters : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
[JsonProperty(PropertyName = "separator")]
public string Separator { get; set; }
[JsonProperty(PropertyName = "iCodepage")]
public int CodePage { get; set; }
[JsonProperty(PropertyName = "dtLines")]
public int DtLines { get; set; }
[JsonProperty(PropertyName = "dQuotes")]
public bool DoubleQuotes { get; set; }
[JsonProperty(PropertyName = "fields")]
public List<Field> Fields { get; set; }
public W3CLogParameters()
{
CodePage = 0;
DtLines = 10;
Fields = new List<Field>();
Separator = "auto";
Fields.Add(new Field("LogFilename", "string"));
Fields.Add(new Field("RowNumber", "integer"));
}
public void Validate()
{
}
}
public class IISW3CLogParameters : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
[JsonProperty(PropertyName = "iCodepage")]
@@ -346,54 +448,56 @@ namespace TimberWinR.Parser
[JsonProperty(PropertyName = "fields")]
public List<Field> Fields { get; set; }
public IISW3CLog()
public IISW3CLogParameters()
{
CodePage = -2;
Recurse = 0;
Fields = new List<Field>();
Fields.Add(new Field("LogFilename", "string"));
Fields.Add(new Field("LogRow", "integer" ));
Fields.Add(new Field("date", "DateTime" ));
Fields.Add(new Field("time", "DateTime" ));
Fields.Add(new Field("c-ip", "string" ));
Fields.Add(new Field("cs-username", "string" ));
Fields.Add(new Field("s-sitename", "string" ));
Fields.Add(new Field("s-computername", "integer" ));
Fields.Add(new Field("s-ip", "string" ));
Fields.Add(new Field("s-port", "integer" ));
Fields.Add(new Field("cs-method", "string" ));
Fields.Add(new Field("cs-uri-stem", "string" ));
Fields.Add(new Field("cs-uri-query", "string" ));
Fields.Add(new Field("sc-status", "integer" ));
Fields.Add(new Field("sc-substatus", "integer" ));
Fields.Add(new Field("sc-win32-status", "integer" ));
Fields.Add(new Field("sc-bytes", "integer" ));
Fields.Add(new Field("cs-bytes", "integer" ));
Fields.Add(new Field("time-taken", "integer" ));
Fields.Add(new Field("cs-version", "string" ));
Fields.Add(new Field("cs-host", "string" ));
Fields.Add(new Field("cs(User-Agent)", "string" ));
Fields.Add(new Field("cs(Cookie)", "string" ));
Fields.Add(new Field("cs(Referer)", "string" ));
Fields.Add(new Field("s-event", "string" ));
Fields.Add(new Field("s-process-type", "string" ));
Fields.Add(new Field("s-user-time", "double" ));
Fields.Add(new Field("s-kernel-time", "double" ));
Fields.Add(new Field("s-page-faults", "integer" ));
Fields.Add(new Field("s-total-procs", "integer" ));
Fields.Add(new Field("s-active-procs", "integer" ));
Fields.Add(new Field("LogRow", "integer"));
Fields.Add(new Field("date", "DateTime"));
Fields.Add(new Field("time", "DateTime"));
Fields.Add(new Field("c-ip", "string"));
Fields.Add(new Field("cs-username", "string"));
Fields.Add(new Field("s-sitename", "string"));
Fields.Add(new Field("s-computername", "integer"));
Fields.Add(new Field("s-ip", "string"));
Fields.Add(new Field("s-port", "integer"));
Fields.Add(new Field("cs-method", "string"));
Fields.Add(new Field("cs-uri-stem", "string"));
Fields.Add(new Field("cs-uri-query", "string"));
Fields.Add(new Field("sc-status", "integer"));
Fields.Add(new Field("sc-substatus", "integer"));
Fields.Add(new Field("sc-win32-status", "integer"));
Fields.Add(new Field("sc-bytes", "integer"));
Fields.Add(new Field("cs-bytes", "integer"));
Fields.Add(new Field("time-taken", "integer"));
Fields.Add(new Field("cs-version", "string"));
Fields.Add(new Field("cs-host", "string"));
Fields.Add(new Field("cs(User-Agent)", "string"));
Fields.Add(new Field("cs(Cookie)", "string"));
Fields.Add(new Field("cs(Referer)", "string"));
Fields.Add(new Field("s-event", "string"));
Fields.Add(new Field("s-process-type", "string"));
Fields.Add(new Field("s-user-time", "double"));
Fields.Add(new Field("s-kernel-time", "double"));
Fields.Add(new Field("s-page-faults", "integer"));
Fields.Add(new Field("s-total-procs", "integer"));
Fields.Add(new Field("s-active-procs", "integer"));
Fields.Add(new Field("s-stopped-procs", "integer"));
}
public void Validate()
{
}
}
public class ElasticsearchOutput
public class ElasticsearchOutputParameters
{
const string IndexDatePattern = "(%\\{(?<format>[^\\}]+)\\})";
[JsonProperty(PropertyName = "host")]
public string[] Host { get; set; }
[JsonProperty(PropertyName = "index")]
@@ -408,21 +512,72 @@ namespace TimberWinR.Parser
public string Protocol { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
public ElasticsearchOutput()
[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; }
public ElasticsearchOutputParameters()
{
FlushSize = 5000;
IdleFlushTimeInSeconds = 10;
Protocol = "http";
Port = 9200;
Index = "";
Host = new string[] { "localhost" };
Timeout = 10000;
Timeout = 10000;
NumThreads = 1;
Interval = 1000;
QueueOverflowDiscardOldest = true;
MaxQueueSize = 50000;
}
public string GetIndexName(JObject json)
{
////check if the submitted JSON object provides a custom index. If yes, use this one
var token = json["_index"];
var indexName = token == null ? this.Index : token.Value<string>();
if (string.IsNullOrEmpty(indexName))
{
indexName = string.Format("logstash-{0}", DateTime.UtcNow.ToString("yyyy.MM.dd"));
}
else
{
var date = DateTime.UtcNow;
if (json["@timestamp"] != null)
{
date = DateTime.Parse(json["@timestamp"].ToString());
}
var match = Regex.Match(indexName, IndexDatePattern);
if (match.Success)
{
indexName = Regex.Replace(indexName, IndexDatePattern, date.ToString(match.Groups["format"].Value));
}
}
return indexName;
}
public string GetTypeName(JObject json)
{
string typeName = "Win32-Elasticsearch";
if (json["type"] != null)
{
typeName = json["type"].ToString();
}
return typeName;
}
}
public class RedisOutput
{
public class RedisOutputParameters
{
[JsonProperty(PropertyName = "host")]
public string[] Host { get; set; }
[JsonProperty(PropertyName = "index")]
@@ -433,29 +588,38 @@ namespace TimberWinR.Parser
public int Timeout { get; set; }
[JsonProperty(PropertyName = "batch_count")]
public int BatchCount { get; set; }
[JsonProperty(PropertyName = "max_batch_count")]
public int MaxBatchCount { get; set; }
[JsonProperty(PropertyName = "threads")]
public int NumThreads { get; set; }
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
[JsonProperty(PropertyName = "max_queue_size")]
public int MaxQueueSize { get; set; }
[JsonProperty(PropertyName = "queue_overflow_discard_oldest")]
public bool QueueOverflowDiscardOldest { get; set; }
public RedisOutput()
public RedisOutputParameters()
{
Port = 6379;
Index = "logstash";
Host = new string[] {"localhost"};
Host = new string[] { "localhost" };
Timeout = 10000;
BatchCount = 10;
BatchCount = 200;
MaxBatchCount = BatchCount*10;
NumThreads = 1;
Interval = 5000;
QueueOverflowDiscardOldest = true;
MaxQueueSize = 50000;
}
}
public class StdoutOutput
public class StdoutOutputParameters
{
[JsonProperty(PropertyName = "interval")]
public int Interval { get; set; }
public StdoutOutput()
public StdoutOutputParameters()
{
Interval = 1000;
}
@@ -464,13 +628,13 @@ namespace TimberWinR.Parser
public class OutputTargets
{
[JsonProperty("Redis")]
public RedisOutput[] Redis { get; set; }
public RedisOutputParameters[] Redis { get; set; }
[JsonProperty("Elasticsearch")]
public ElasticsearchOutput[] Elasticsearch { get; set; }
public ElasticsearchOutputParameters[] Elasticsearch { get; set; }
[JsonProperty("Stdout")]
public StdoutOutput[] Stdout { get; set; }
public StdoutOutputParameters[] Stdout { get; set; }
}
public class InputSources
@@ -479,24 +643,33 @@ namespace TimberWinR.Parser
public WindowsEvent[] WindowsEvents { get; set; }
[JsonProperty("Logs")]
public Log[] Logs { get; set; }
public LogParameters[] Logs { get; set; }
[JsonProperty("TailFiles")]
public TailFileArguments[] TailFilesArguments { get; set; }
[JsonProperty("Tcp")]
public Tcp[] Tcps { get; set; }
public TcpParameters[] Tcps { get; set; }
[JsonProperty("Udp")]
public UdpParameters[] Udps { get; set; }
[JsonProperty("IISW3CLogs")]
public IISW3CLog[] IISW3CLogs { get; set; }
public IISW3CLogParameters[] IISW3CLogs { get; set; }
[JsonProperty("W3CLogs")]
public W3CLogParameters[] W3CLogs { get; set; }
[JsonProperty("Stdin")]
public Stdin[] Stdins { get; set; }
}
public partial class Grok : LogstashFilter, IValidateSchema
{
public class GrokFilterException : Exception
{
public GrokFilterException()
: base("Grok filter missing required match, must be 2 array entries.")
: base("Grok filter missing required match, must be 2 array entries.")
{
}
}
@@ -508,10 +681,14 @@ namespace TimberWinR.Parser
{
}
}
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("condition")]
public string Condition { get; set; }
[JsonProperty("drop_if_match")]
[JsonProperty("drop")]
public bool DropIfMatch { get; set; }
[JsonProperty("match")]
@@ -521,10 +698,10 @@ namespace TimberWinR.Parser
public string[] AddTag { get; set; }
[JsonProperty("add_field")]
public string[] AddField { get; set; }
public string[] AddField { get; set; }
[JsonProperty("remove_field")]
public string[] RemoveField { get; set; }
public string[] RemoveField { get; set; }
[JsonProperty("remove_tag")]
public string[] RemoveTag { get; set; }
@@ -534,7 +711,7 @@ namespace TimberWinR.Parser
if (Match == null || Match.Length != 2)
throw new GrokFilterException();
if (AddTag != null && AddTag.Length%2 != 0)
if (AddTag != null && AddTag.Length % 2 != 0)
throw new GrokAddTagException();
}
}
@@ -557,6 +734,9 @@ namespace TimberWinR.Parser
}
}
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("condition")]
public string Condition { get; set; }
@@ -573,7 +753,7 @@ namespace TimberWinR.Parser
public bool ConvertToUTC { get; set; }
[JsonProperty("add_field")]
public string[] AddField { get; set; }
public string[] AddField { get; set; }
public override void Validate()
{
@@ -593,9 +773,15 @@ namespace TimberWinR.Parser
public partial class Mutate : LogstashFilter
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("condition")]
public string Condition { get; set; }
[JsonProperty("remove")]
public string[] Remove { get; set; }
[JsonProperty("rename")]
public string[] Rename { get; set; }
@@ -607,10 +793,127 @@ namespace TimberWinR.Parser
public override void Validate()
{
}
}
public partial class GeoIP : LogstashFilter
{
public class GeoIPMissingSourceException : Exception
{
public GeoIPMissingSourceException()
: base("GeoIP filter source is required")
{
}
}
public class GeoIPAddFieldException : Exception
{
public GeoIPAddFieldException()
: base("GeoIP filter add_field requires tuples")
{
}
}
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("condition")]
public string Condition { get; set; }
[JsonProperty("source")]
public string Source { get; set; }
[JsonProperty("target")]
public string Target { get; set; }
[JsonProperty("add_tag")]
public string[] AddTag { get; set; }
[JsonProperty("add_field")]
public string[] AddField { get; set; }
[JsonProperty("remove_field")]
public string[] RemoveField { get; set; }
[JsonProperty("remove_tag")]
public string[] RemoveTag { get; set; }
public override void Validate()
{
if (string.IsNullOrEmpty(Source))
throw new GeoIPMissingSourceException();
if (AddField != null && AddField.Length % 2 != 0)
throw new GeoIPAddFieldException();
}
}
public partial class Json : LogstashFilter
{
public class JsonMissingSourceException : Exception
{
public JsonMissingSourceException()
: base("JSON filter source is required")
{
}
}
public class JsonAddFieldException : Exception
{
public JsonAddFieldException()
: base("JSON filter add_field requires tuples")
{
}
}
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("condition")]
public string Condition { get; set; }
[JsonProperty("source")]
public string Source { get; set; }
[JsonProperty("remove_source")]
public bool RemoveSource { get; set; }
[JsonProperty("target")]
public string Target { get; set; }
[JsonProperty("add_tag")]
public string[] AddTag { get; set; }
[JsonProperty("add_field")]
public string[] AddField { get; set; }
[JsonProperty("remove_field")]
public string[] RemoveField { get; set; }
[JsonProperty("remove_tag")]
public string[] RemoveTag { get; set; }
[JsonProperty("rename")]
public string[] Rename { get; set; }
[JsonProperty("promote")]
public string Promote { get; set; }
public override void Validate()
{
if (string.IsNullOrEmpty(Source))
throw new JsonMissingSourceException();
if (AddField != null && AddField.Length % 2 != 0)
throw new JsonAddFieldException();
}
}
public class Filter
{
[JsonProperty("grok")]
@@ -621,8 +924,29 @@ namespace TimberWinR.Parser
[JsonProperty("date")]
public DateFilter Date { get; set; }
[JsonProperty("json")]
public Json Json { get; set; }
[JsonProperty("geoip")]
public GeoIP GeoIP { get; set; }
[JsonProperty("grokFilters")]
public Grok[] Groks { get; set; }
[JsonProperty("mutateFilters")]
public Mutate[] Mutates { get; set; }
[JsonProperty("dateFilters")]
public DateFilter[] Dates { get; set; }
[JsonProperty("jsonFilters")]
public Json[] Jsons { get; set; }
[JsonProperty("geoipFilters")]
public GeoIP[] GeoIPs { get; set; }
}
public class TimberWinR
{
[JsonProperty("Inputs")]
@@ -630,13 +954,14 @@ namespace TimberWinR.Parser
[JsonProperty("Filters")]
public List<Filter> Filters { get; set; }
[JsonProperty("Outputs")]
public OutputTargets Outputs { get; set; }
public OutputTargets Outputs { get; set; }
public LogstashFilter[] AllFilters
{
get
{
var list = new List<LogstashFilter>();
foreach (var filter in Filters)
{
foreach (var prop in filter.GetType().GetProperties())
@@ -646,13 +971,18 @@ namespace TimberWinR.Parser
{
list.Add(typedFilter as LogstashFilter);
}
else if (typedFilter != null && typedFilter.GetType().IsArray && typeof(LogstashFilter).IsAssignableFrom(typedFilter.GetType().GetElementType()))
{
IEnumerable<LogstashFilter> lf = typedFilter as IEnumerable<LogstashFilter>;
list.AddRange(lf);
}
}
}
return list.ToArray();
}
}
}
public class RootObject
{
public TimberWinR TimberWinR { get; set; }

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.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]

View File

@@ -0,0 +1,85 @@
TimberWinR Release Notes
==================================
A Native Windows to Redis/Elasticsearch Logstash Agent which runs as a service.
Version / Date
### 1.4.0.0 - 04/03/2015
1. A re-factoring of Logs and TailLogs to be more efficient and detect log rolling correctly,
this requires http://support.microsoft.com/en-us/kb/172190 which will be detected and
set by TimberWinR, however, requires a reboot.
2. Fixed issue [#38](https://github.com/Cimpress-MCP/TimberWinR/issues/38) diagnostic output not showing drop flag for Grok filter.
3. Created TimberWinR.TestGenerator for complete testing of TimberWinR
4. Fixed ipv4/ipv6 thread-safe issue with UdpInputListener which might lead to corrupted input data.
### 1.3.19.1 - 03/03/2015
1. Added new Redis parameter _max\_batch\_count_ which increases the _batch\_count_ dynamically over time
to handle input flooding. Default is _batch\_count_ * 10
### 1.3.19.0 - 02/26/2015
1. Added support for Multiline codecs for Stdin and Logs listeners, closes issue [#23](https://github.com/Cimpress-MCP/TimberWinR/issues/23)
2. Added new TailFiles input type which uses a native implementation (more-efficient) than using LogParser's Log
3. Updated Udp input listner to use UTF8 Encoding rather than ASCII
4. Reduced noisy complaint about missing log files for Logs listener
5. Fixed bug when tailing non-existent log files which resulted in high cpu-usage.
6. Added feature to watch the configuration directory
### 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
intervals to be interpreted as seconds instead of milliseconds. 1.3.17.0 should not be used.
2. Removed ability for installer to downgrade which was leading to leaving previous versions laying around (i.e. reverts 1.3.13.0 change)
### 1.3.17.0 - 12/19/2014
1. Continued work improving shutdown time by using syncHandle.Wait instead of Thread.Sleep
### 1.3.16.0 - 12/19/2014
1. Added logSource property to the Log input to facility the steering of log messages to different indices.
### 1.3.15.0 - 12/12/2014
1. Fixed bug whereby if the Udp or Tcp inputs receive an impropery formatted Json it caused the thread to terminate, and ignore
future messages.
### 1.3.14.0 - 12/11/2014
1. Fixed bug with the Grok filter to match properly the value of the Text field against non-blank entries.
### 1.3.13.0 - 12/02/2014
1. Fixed MSI installer to allow downgrades.
### 1.3.12.0 - 11/25/2014
1. Fixed all remaining memory leaks due to the COM Weak Surrogate which requires an explicit GC.Collect
### 1.3.11.0 - 11/21/2014
1. Re-worked WindowsEvent listener to enable shutting down in a quicker fashion.
### 1.3.10.0 - 11/18/2014
1. Refactored Conditions handler to use non-leaking evaluator.
### 1.3.9.0 - 11/11/2014
1. Merged in pull request #9
2. Updated chocolately uninstall to preserve GUID
### 1.3.8.0 - 11/06/2014
1. Added interval parameter to WindowsEvent input listener
2. Increased default value for interval to 60 seconds for polling WindowsEvents
### 1.3.7.0 - 10/21/2014
1. Added additional information for diagnostics port
2. Completed minor handling of Log rolling detection
### 1.3.6.0 - 10/16/2014
1. Handle rolling of logs whereby the logfile remains the same, but the content resets back to 0 bytes.

View File

@@ -12,6 +12,8 @@
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -32,47 +34,71 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="csredis">
<HintPath>..\packages\csredis.1.4.7.1\lib\net40\csredis.dll</HintPath>
<HintPath>..\packages\csredis.3.2.1\lib\net40\csredis.dll</HintPath>
</Reference>
<Reference Include="Elasticsearch.Net">
<HintPath>..\packages\Elasticsearch.Net.1.3.1\lib\Elasticsearch.Net.dll</HintPath>
</Reference>
<Reference Include="Interop.MSUtil, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>False</EmbedInteropTypes>
<HintPath>lib\com-logparser\Interop.MSUtil.dll</HintPath>
</Reference>
<Reference Include="MaxMind.Db">
<HintPath>..\packages\MaxMind.Db.1.0.0.0\lib\net40\MaxMind.Db.dll</HintPath>
</Reference>
<Reference Include="MaxMind.GeoIP2">
<HintPath>..\packages\MaxMind.GeoIP2.2.1.0.0\lib\net40\MaxMind.GeoIP2.dll</HintPath>
</Reference>
<Reference Include="Nest">
<HintPath>..\packages\NEST.1.3.1\lib\Nest.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.4\lib\net40\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.6.0.5\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.3.1.0.0\lib\net40\NLog.dll</HintPath>
<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>
<Reference Include="RestSharp">
<HintPath>..\packages\RestSharp.104.4.0\lib\net4\RestSharp.dll</HintPath>
<HintPath>..\packages\RestSharp.105.0.0\lib\net4\RestSharp.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Linq.Dynamic">
<HintPath>..\packages\System.Linq.Dynamic.1.0.4\lib\net40\System.Linq.Dynamic.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Topshelf, Version=3.1.122.0, Culture=neutral, PublicKeyToken=b800c4cfcdeea87b, processorArchitecture=MSIL">
<Reference Include="Topshelf, Version=3.1.135.0, Culture=neutral, PublicKeyToken=b800c4cfcdeea87b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Topshelf.3.1.3\lib\net40-full\Topshelf.dll</HintPath>
<HintPath>..\packages\Topshelf.3.1.4\lib\net40-full\Topshelf.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Codecs\Multiline.cs" />
<Compile Include="Configuration.cs" />
<Compile Include="ConfigurationErrors.cs" />
<Compile Include="Diagnostics\Diagnostics.cs" />
<Compile Include="Filters\DateFilter.cs" />
<Compile Include="Filters\FilterBase.cs" />
<Compile Include="Filters\GrokFilter.cs" />
<Compile Include="Filters\GeoIPFilter.cs" />
<Compile Include="Filters\JsonFilter.cs" />
<Compile Include="Filters\MutateFilter.cs" />
<Compile Include="ICodec.cs" />
<Compile Include="Inputs\FieldDefinitions.cs" />
<Compile Include="Inputs\IISW3CRowReader.cs" />
<Compile Include="Inputs\LogsFileDatabase.cs" />
<Compile Include="Inputs\TailFileListener.cs" />
<Compile Include="Inputs\UdpInputListener.cs" />
<Compile Include="Inputs\W3CInputListener.cs" />
<Compile Include="Inputs\IISW3CInputListener.cs" />
<Compile Include="Inputs\InputBase.cs" />
<Compile Include="Inputs\InputListener.cs" />
@@ -81,6 +107,7 @@
<Compile Include="Inputs\TcpInputListener.cs" />
<Compile Include="Inputs\LogsListener.cs" />
<Compile Include="Inputs\WindowsEvtInputListener.cs" />
<Compile Include="LogErrors.cs" />
<Compile Include="Manager.cs" />
<Compile Include="Outputs\Elasticsearch.cs" />
<Compile Include="Outputs\OutputSender.cs" />
@@ -103,8 +130,17 @@
<None Include="configSchema.xsd">
<SubType>Designer</SubType>
</None>
<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" />
<None Include="mdocs\TailFiles.md" />
<None Include="mdocs\UdpInput.md" />
<None Include="mdocs\W3CInput.md" />
<None Include="mdocs\JsonFilter.md" />
<None Include="mdocs\GrokFilter.md" />
<None Include="mdocs\ElasticsearchOutput.md" />
<None Include="mdocs\RedisOutput.md" />
@@ -114,7 +150,10 @@
<None Include="mdocs\Logs.md" />
<None Include="mdocs\IISW3CInput.md" />
<None Include="mdocs\WindowsEvents.md" />
<None Include="packages.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<None Include="ReleaseNotes.md" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
@@ -124,6 +163,13 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- 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.
<Target Name="BeforeBuild">

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

@@ -0,0 +1,36 @@
# 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? | |
| *negate* | bool |Inverts the pattern sense | If true, a message not matching the pattern will constitute a match of the multiline filter and the what will be applied. (vice-versa is also true) | false |
| *multiline_tag* | string |Tag to be added when multiline conversion is applied | | multiline |
This codec applies to [Logs](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Logs.md) and [Stdin](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/StdinInput.md) only.
Example Input: Mutliline input log file
```json
{
"TimberWinR": {
"Inputs": {
"Logs": [
{
"location": "C:\\Logs1\\multiline.log",
"recurse": -1,
"codec": {
"negate": false,
"type": "multiline",
"pattern": "(^.+Exception: .+)|(^\\s+at .+)|(^\\s+... \\d+ more)|(^\\s*Caused by:.+)",
"what": "previous"
}
}
}
}
}
```

View File

@@ -34,7 +34,7 @@ Given this configuration
"Filters": [
{
"date": {
"condition": "[type] == \"Win32-FileLog\"",
"condition": "\"[type]\" == \"Win32-FileLog\"",
"match": [
"timestamp",
"MMM d HH:mm:sss",
@@ -56,7 +56,7 @@ then the operation(s) will be executed in order.
"Filters": [
{
"grok": {
"condition": "[type] == \"Win32-EventLog\""
"condition": "\"[type]\" == \"Win32-EventLog\""
"add_field": [
"ComputerName", "%{Host}"
]
@@ -72,7 +72,7 @@ The fields must be in pairs with fieldName first and value second.
"Filters": [
{
"date": {
"condition": "[type] == \"Win32-FileLog\"",
"condition": "\"[type]\" == \"Win32-FileLog\"",
"match": [
"timestamp",
"MMM d HH:mm:sss",
@@ -92,7 +92,7 @@ If true and the filter matches, the time parsed will be converted to UTC
"Filters": [
{
"date": {
"condition": "[type] == \"Win32-FileLog\"",
"condition": "\"[type]\" == \"Win32-FileLog\"",
"match": [
"timestamp",
"MMM d HH:mm:sss",

View File

@@ -7,11 +7,18 @@ The following parameters are allowed when configuring the Redis output.
| Parameter | Type | Description | Details | Default |
| :-------------|:---------|:------------------------------------------------------------| :--------------------------- | :-- |
| *threads* | string | Location of log files(s) to monitor | Number of worker theads to send messages | 1 |
| *interval* | integer | Interval in milliseconds to sleep during batch sends | Interval | 5000 |
| *index* | string | The index name to use | index used/created | logstash-yyyy.dd.mm |
| *host* | [string] | The hostname(s) of your Elasticsearch server(s) | IP or DNS name | |
| *port* | integer | Redis port number | This port must be open | 9200 |
| *flush_size* | integer | Maximum number of messages before flushing | | 50000 |
| *host* | [string] | Array of hostname(s) of your Elasticsearch server(s) | IP or DNS name | |
| *idle_flush_time* | integer | Maximum number of seconds elapsed before triggering a flush | | 10 |
| *index* | [string] | The index name to use | index used/created | logstash-yyyy.dd.mm |
| *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 |
| *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 |
### Index parameter
If you want to output your data everyday to a new index, use following index format: "index-%{yyyy.MM.dd}". Here date format could be any forwat which you need.
Example Input:
```json
@@ -23,7 +30,7 @@ Example Input:
"threads": 1,
"interval": 5000,
"host": [
"tstlexiceapp006.vistaprint.svc"
"tstlexiceapp006.mycompany.svc"
]
}
]

View File

@@ -12,7 +12,7 @@ Example Input:
"Filters": [
{
"grok": {
"condition": "[type] == \"Win32-Eventlog\"",
"condition": "\"[type]\" == \"Win32-Eventlog\"",
"match": [
"Message",
""
@@ -35,7 +35,7 @@ Example Input:
},
{
"date": {
"condition": "[type] == \"Win32-FileLog\"",
"condition": "\"[type]\" == \"Win32-FileLog\"",
"match": [
"timestamp",
"MMM d HH:mm:sss",
@@ -64,6 +64,6 @@ Example Input:
[1]: http://logstash.net/docs/1.4.2/filters/grok
[2]: http://logstash.net/docs/1.4.2/filters/date
[3]: http://logstash.net/docs/1.4.2/filters/mutate
[4]: https://github.com/efontana/TimberWinR/blob/master/mdocs/GrokFilter.md
[5]: https://github.com/efontana/TimberWinR/blob/master/mdocs/DateFilter.md
[6]: https://github.com/efontana/TimberWinR/blob/master/mdocs/MutateFilter.md
[4]: https://github.com/Cimpress-MCP/TimberWinR/blob/master/mdocs/GrokFilter.md
[5]: https://github.com/Cimpress-MCP/TimberWinR/blob/master/mdocs/DateFilter.md
[6]: https://github.com/Cimpress-MCP/TimberWinR/blob/master/mdocs/MutateFilter.md

View File

@@ -0,0 +1,138 @@
# GeoIP Filter
The GeoIP filter adds information about the geographical location of IP addresses, based on data from the Maxmind database.
TimberWinR releases ship with the GeoLiteCity database made available from Maxmind with a CCA-ShareAlike 3.0 license.
For more details on GeoLite, see http://www.maxmind.com/en/geolite.
## GeoIP Operations
The following operations are allowed when mutating a field.
| Operation | Type | Description
| :---------------|:----------------|:-----------------------------------------------------------------------|
| *type* | property:string |Type to which this filter applies, if empty, applies to all types.
| *condition* | property:string |C# expression, if the expression is true, continue, otherwise, ignore
| *source* | property:string |Required field indicates which field contains the IP address to be parsed
| *target* | property:string |If suppled, the parsed json will be contained underneath a propery named *target*, default=geoip
| *add_field* | property:array |If the filter is successful, add an arbitrary field to this event. Field names can be dynamic and include parts of the event using the %{field} syntax. This property must be specified in pairs.
| *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.
| *add_tag* | property:array |If the filter is successful, add an arbitrary tag to this event. Tag 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.
## Operation Details
### source
The match field is required, the first argument is the field to inspect, and compare to the expression specified by the second
argument. In the below example, the message is spected to be something like this from a fictional sample log:
Given this input configuration:
Lets assume that a newline such as the following is appended to foo.jlog:
```
{"type": "Win32-FileLog", "IP": "8.8.8.8" }
```
```json
"Inputs": {
"Logs": [
{
"location": "C:\\Logs1\\foo.jlog",
"recurse": -1
}
]
},
"Filters":[
{
"geoip":{
"type": "Win32-FileLog",
"source": "IP"
}
}]
}
```
In the above example, the file foo.jlog is being tailed, and when a newline is appended, it is assumed
to be Json and is parsed from the Text field, the parsed Json is then inserted underneath a property *stuff*
The resulting output would be:
```
{
"type": "Win32-FileLog",
"IP": "8.8.8.8",
"mygeoip": {
"ip": "8.8.8.8",
"country_code2": "US",
"country_name": "United States",
"continent_code": "NA",
"region_name": "CA",
"city_name": "Mountain View",
"postal_code": null,
"latitude": 37.386,
"longitude": -122.0838,
"dma_code": 807,
"timezone": "America/Los_Angeles",
"real_region_name": "California",
"location": [
-122.0838,
37.386
]
}
```
### add_field ["fieldName", "fieldValue", ...]
The fields must be in pairs with fieldName first and value second.
```json
"Filters": [
{
"json": {
"add_field": [
"ComputerName", "Host",
"Username", "%{SID}"
]
}
}
]
```
### remove_field ["tag1", "tag2", ...]
Remove the fields. More than one field can be specified at a time.
```json
"Filters": [
{
"json": {
"remove_tag": [
"static_tag1",
"Computer_%{Host}"
]
}
}
]
```
### add_tag ["tag1", "tag2", ...]
Adds the tag(s) to the tag array.
```json
"Filters": [
{
"json": {
"add_tag": [
"foo_%{Host}",
"static_tag1"
]
}
}
]
```
### remove_tag ["tag1", "tag2", ...]
Remove the tag(s) to the tag array. More than one tag can be specified at a time.
```json
"Filters": [
{
"json": {
"remove_tag": [
"static_tag1",
"Username"
]
}
}
]
```

View File

@@ -26,7 +26,9 @@ The following operations are allowed when mutating a field.
| Operation | Type | Description
| :---------------|:----------------|:-----------------------------------------------------------------------|
| *type* | property:string |Type to which this filter applyes, if empty, applies to all types.
| *condition* | property:string |C# expression
| *rename* | property:array |Rename one or more fields
| *match* | property:string |Required field must match before any subsequent grok operations are executed.
| *add_field* | property:array |If the filter is successful, add an arbitrary field to this event. Field names can be dynamic and include parts of the event using the %{field} syntax. This property must be specified in pairs.
| *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.
@@ -82,8 +84,8 @@ then the operation(s) will be executed in order.
```json
"Filters": [
{
"grok": {
"condition": "[type] == \"Win32-EventLog\""
"grok": {
"type": "Win32-EventLog",
"add_field": [
"ComputerName", "%{Host}"
]

View File

@@ -0,0 +1,153 @@
# Json Filter
The Json filter allows you to parse a single line of Json into its corresponding fields. This is
particularly useful when parsing log files.
## Json Operations
The following operations are allowed when mutating a field.
| Operation | Type | Description
| :---------------|:----------------|:-----------------------------------------------------------------------|
| *type* | property:string |Type to which this filter applies, if empty, applies to all types.
| *condition* | property:string |C# expression, if the expression is true, continue, otherwise, ignore
| *remove_source* | property:bool |If true, the source property is removed, default: true
| *source* | property:string |Required field indicates which field contains the Json to be parsed
| *promote* | property:string |If supplied any properties named *promote* will be promoted to top-level
| *target* | property:string |If suppled, the parsed json will be contained underneath a propery named *target*
| *add_field* | property:array |If the filter is successful, add an arbitrary field to this event. Field names can be dynamic and include parts of the event using the %{field} syntax. This property must be specified in pairs.
| *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.
| *add_tag* | property:array |If the filter is successful, add an arbitrary tag to this event. Tag 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.
## Operation Details
### source
The match field is required, the first argument is the field to inspect, and compare to the expression specified by the second
argument. In the below example, the message is spected to be something like this from a fictional sample log:
Given this input configuration:
Lets assume that a newline such as the following is appended to foo.jlog:
```
{"Email":"james@example.com","Active":true,"CreatedDate":"2013-01-20T00:00:00Z","Roles":["User","Admin"]}
```
```json
"Inputs": {
"Logs": [
{
"location": "C:\\Logs1\\foo.jlog",
"recurse": -1
}
]
},
"Filters":[
{
"json":{
"type": "Win32-FileLog",
"target": "stuff",
"source": "Text",
"rename": [
"Text",
"Data"
]
}
}]
}
```
In the above example, the file foo.jlog is being tailed, and when a newline is appended, it is assumed
to be Json and is parsed from the Text field, the parsed Json is then inserted underneath a property *stuff*
The resulting output would be:
```
{
"type": "Win32-FileLog",
"ComputerName": "dev.mycompany.net",
"Text": "{\"Email\":\"james@example.com\",\"Active\":true,\"CreatedDate\":\"2013-01-20T00:00:00Z\",\"Roles\":[\"User\",\"Admin\"]}",
"stuff": {
"Email": "james@example.com",
"Active": true,
"CreatedDate": "2013-01-20T00:00:00Z",
"Roles": [
"User",
"Admin"
]
}
}
```
### rename ["oldname", "newname", ...]
The fields must be in pairs with oldname first and newname second.
```json
"Filters": [
{
"json":{
"type": "Win32-FileLog",
"target": "stuff",
"source": "Text",
"rename": [
"Text",
"Data"
]
}
}
]
```
### add_field ["fieldName", "fieldValue", ...]
The fields must be in pairs with fieldName first and value second.
```json
"Filters": [
{
"json": {
"add_field": [
"ComputerName", "Host",
"Username", "%{SID}"
]
}
}
]
```
### remove_field ["tag1", "tag2", ...]
Remove the fields. More than one field can be specified at a time.
```json
"Filters": [
{
"json": {
"remove_tag": [
"static_tag1",
"Computer_%{Host}"
]
}
}
]
```
### add_tag ["tag1", "tag2", ...]
Adds the tag(s) to the tag array.
```json
"Filters": [
{
"json": {
"add_tag": [
"foo_%{Host}",
"static_tag1"
]
}
}
]
```
### remove_tag ["tag1", "tag2", ...]
Remove the tag(s) to the tag array. More than one tag can be specified at a time.
```json
"Filters": [
{
"json": {
"remove_tag": [
"static_tag1",
"Username"
]
}
}
]
```

View File

@@ -8,9 +8,11 @@ The following parameters are allowed when configuring WindowsEvents.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *location* | string |Location of file(s) to monitor | Path to text file(s) including wildcards. | |
| *logSource* | string |Source name | Used for conditions | |
| *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.
@@ -20,6 +22,7 @@ Example Input: Monitors all files (recursively) located at C:\Logs1\ matching *.
"Inputs": {
"Logs": [
{
"logSource": "log files",
"location": "C:\\Logs1\\*.log",
"recurse": -1
}

View File

@@ -8,6 +8,7 @@ The following operations are allowed when mutating a field.
| Operation | Type | Description
| :-----------|:----------------|:-----------------------------------------------------------------------|
| *condition* | property:string |C# Expression
| *remove* | property:array |Remove one or more fields
| *rename* | property:array |Rename one or more fields
| *replace* | property:array |Replace a field with a new value. The new value can include %{foo} strings to help you build a new value from other parts of the event.
| *split* | property:array |Separator between values of the "Strings" field.
@@ -18,18 +19,31 @@ If present, the condition must evaluate to true in order for the remaining opera
then the operation(s) will be executed in order.
```json
"Filters": [
{
{
"mutate": {
"condition": "[type] == \"Win32-EventLog\""
"condition": "\"[type]\" == \"Win32-EventLog\"",
"rename": [
"ComputerName", "Host"
]
}
}
]
```
The above example will rename ComputerName to Host only for Win32-EventLog types.
### remove ["name", ...]
Removes field.
```json
"Filters": [
{
"mutate": {
"remove": [
"ComputerName", "Username"
]
}
}
]
```
The above example will rename ComputerName to Host only for Win32-EventLog types.
### rename ["oldname", "newname", ...]
The fields must be in pairs with oldname first and newname second.
```json

View File

@@ -8,11 +8,14 @@ The following parameters are allowed when configuring the Redis output.
| Parameter | Type | Description | Details | Default |
| :-------------|:---------|:------------------------------------------------------------| :--------------------------- | :-- |
| *threads* | string | Location of log files(s) to monitor | Number of worker theads to send messages | 1 |
| *batch_count* | integer | Sent as a single message | Number of messages to aggregate | 200 |
| *max_batch_count* | integer | Dynamically adjusted count maximum | Increases over time | batch_count*10 |
| *interval* | integer | Interval in milliseconds to sleep during batch sends | Interval | 5000 |
| *batch_count* | integer | The number of events to send in a single transaction | | 10 |
| *index* | string | The name of the redis list | logstash index name | logstash |
| *host* | [string] | The hostname(s) of your Redis server(s) | IP or DNS name | |
| *port* | integer | Redis port number | This port must be open | 6379 |
| *max_queue_size* | integer | Maximum redis queue depth | | 50000 |
| *queue_overflow_discard_oldest* | bool | If true, discard oldest messages when max_queue_size reached otherwise discard newest | | true |
Example Input:
```json
@@ -25,7 +28,7 @@ Example Input:
"interval": 5000,
"batch_count": 500,
"host": [
"tstlexiceapp006.vistaprint.svc"
"tstlexiceapp006.mycompany.svc"
]
}
]

View File

@@ -3,7 +3,12 @@
The Stdin Input will read from the console (Console.ReadLine) and build a simple message for testing.
## Parameters
There are no Parameters at this time.
The following parameters are allowed when configuring WindowsEvents.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| [codec](https://github.com/Cimpress-MCP/TimberWinR/blob/master/TimberWinR/mdocs/Codec.md) | object | Codec to use |
```json
{
@@ -26,4 +31,3 @@ A field: "type": "Win32-Stdin" is automatically appended, and the entire JSON is
| ---- |:-----| :-----------------------------------------------------------------------|
| type | STRING |Win32-Stdin |
| message | STRING | The message typed in |

View File

@@ -0,0 +1,41 @@
# Input: TailFiles
The TailFiles input will monitor a log (text) file similar to how a Linux "tail -f" command works. This uses
a native implementation rather than uses LogParser
## Parameters
The following parameters are allowed when configuring WindowsEvents.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *location* | string |Location of file(s) to monitor | Path to text file(s) including wildcards. | |
| *logSource* | string |Source name | Used for conditions | |
| *recurse* | integer |Max subdirectory recursion level. | 0 disables subdirectory recursion; -1 enables unlimited recursion. | 0 |
| *interval* | integer |Polling interval in seconds | Defaults every 60 seconds | 60 |
| [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.
```json
{
"TimberWinR": {
"Inputs": {
"TailFiles": [
{
"logSource": "log files",
"location": "C:\\Logs1\\*.log",
"recurse": -1
}
]
}
}
}
```
## Fields
After a successful parse of an event, the following fields are added:
| Name | Type | Description |
| ---- |:-----| :-----------|
| LogFilename | STRING |Full path of the file containing this line |
| Index | INTEGER | Line number |
| Text | STRING | Text line content |

View File

@@ -9,7 +9,7 @@ The following parameters are allowed when configuring the Tcp input.
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *port* | integer |Port number to open | Must be an available port | |
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.
Example Input: Listen on Port 5140
```json
{

View File

@@ -0,0 +1,28 @@
# Input: Udp
The Udp input will open a port and listen for properly formatted UDP datagrams to be broadcast.
## Parameters
The following parameters are allowed when configuring the Udp input.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *port* | integer |Port number to open | Must be an available port | |
Example Input: Listen on Port 5142
```json
{
"TimberWinR": {
"Inputs": {
"Udp": [
{
"port": 5142
}
]
}
}
}
```
## Fields
A field: "type": "Win32-Udp" is automatically appended, and the entire JSON is passed on vertabim.

View File

@@ -0,0 +1,51 @@
# Input: W3CLogs
The W3C input format parses IIS log files in the W3C Extended Log File Format, and handles custom fields unlike the IISW3C input.
IIS web sites logging in the W3C Extended format can be configured to log only a specific subset of the available fields.
Log files in this format begin with some informative headers ("directives"), the most important of which is the "#Fields" directive,
describing which fields are logged at which position in a log row. After the directives, the log entries follow.
Each log entry is a space-separated list of field values.
If the logging configuration of an IIS virtual site is updated, the structure of the fields in the file that is
currently logged to might change according to the new configuration. In this case, a new "#Fields" directive is
logged describing the new fields structure, and the W3C input format keeps track of the structure change and
parses the new log entries accordingly.
## Parameters
The following parameters are allowed when configuring W3CLogs input.
| Parameter | Type | Description | Details | Default |
| :---------------- |:---------------| :----------------------------------------------------------------------- | :--------------------------- | :-- |
| *location* | string |Location of log files(s) to monitor | Path to text file(s) including wildcards, may be separated by commas | |
| *iCodepage* | integer |Codepage of the text file. | 0 is the system codepage, -1 is UNICODE. | 0 |
| *dtLines* | integer |Number of lines examined to determine field types at run time. | This parameter specifies the number of initial log lines that the W3C input format examines to determine the data type of the input record fields. If the value is zero, all fields will be assumed to be of the STRING data type. | false |
| *dQuotes* | boolean |Specifies that string values in the log are double-quoted. | Log processors might generate W3C logs whose string values are enclosed in double-quotes. | false |
| *separator* | enum |Separator character between fields. | Different W3C log files can use different separator characters between the fields; for example, Exchange Tracking log files use tab characters, while Personal Firewall log files use space characters. The "auto" value instructs the W3C input format to detect automatically the separator character used in the input log(s). | auto/space/tab/character |
Example Input:
```json
{
"TimberWinR": {
"Inputs": {
"W3CLogs": [
{
"location": "C:\\inetpub\\logs\\LogFiles\\W3SVC1\\*"
}
]
}
}
}
```
## Fields
After a successful parse of an event, the following fields are added [(if configured to be logged)](http://www.iis.net/learn/extensions/advanced-logging-module/advanced-logging-readme)
| Name | Type | Description |
| ---- |:-----| :-----------------------------------------------------------------------|
|LogFilename| STRING | Full path of the log file containing this entry |
|LogRow | INTEGER | Line in the log file containing this entry |
After the above fields, all other fields selected to be logged will be appended.

View File

@@ -2,7 +2,7 @@
The WindowsEvents input will collect events from the Windows Event Viewer. The source parameter indicates which event
logs to collect data from. You can specify more than one log by using the comma, i.e. "Application,System" will collect
logs from the Application and System event logs.
logs from the Application and System event logs. The default interval for scanning for new Events is 60 seconds.
## Parameters
The following parameters are allowed when configuring WindowsEvents.
@@ -18,6 +18,7 @@ The following parameters are allowed when configuring WindowsEvents.
| *fullText* | bool |Retrieve the full text message | true,false | **true** |
| *resolveSIDS* | bool |Resolve SID values into full account names | true,false | **true** |
| *formatMsg* | bool |Format the text message as a single line. | true,false | **true** |
| *interval* | integer | Interval in seconds to sleep during checks | Interval | 60 |
### source format
The source indicates where to collect the event(s) from, it can be of these form(s):

View File

@@ -1,8 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="csredis" version="1.4.7.1" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net40" />
<package id="NLog" version="3.1.0.0" targetFramework="net40" />
<package id="csredis" version="3.2.1" targetFramework="net40" />
<package id="Elasticsearch.Net" version="1.3.1" targetFramework="net40" />
<package id="MaxMind.Db" version="1.0.0.0" targetFramework="net40" />
<package id="MaxMind.GeoIP2" version="2.1.0.0" targetFramework="net40" />
<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="RestSharp" version="104.4.0" targetFramework="net40" />
<package id="RestSharp" version="105.0.0" 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

@@ -6,8 +6,14 @@
<Property Id="LOGDIR">c:\logs</Property>
<Property Id="LOGLEVEL">Info</Property>
<Property Id="DIAGPORT">5141</Property>
<UIRef Id="WixUI_InstallDir" />
<PropertyRef Id="NETFRAMEWORK40FULL"/>
<Condition Message="This application requires Microsoft .NET Framework 4.0 Runtime in order to run. Please install the .NET Framework and then run this installer again.">
<![CDATA[Installed OR NETFRAMEWORK40FULL]]>
</Condition>
<UIRef Id="WixUI_InstallDir" />
<!--
We need to be able to uninstall a newer version from an older version.
The default reinstallmode is "omus", of which the 'o' means "reinstall if missing or older"
@@ -16,8 +22,7 @@
-->
<Property Id="REINSTALLMODE" Value="dmus" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes" />
@@ -40,7 +45,7 @@
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent" Guid="1BDEC5F3-5E9F-4E2E-8B1B-30E7968A99E1">
<File Id="TimberWinR.ServiceHost.exe" Name="$(var.TimberWinR.ServiceHost.TargetFileName)" Source="$(var.TimberWinR.ServiceHost.TargetPath)" />
<File Id="TimberWinR.ServiceHost.exe" Name="$(var.TimberWinR.ServiceHost.TargetFileName)" Source="$(var.TimberWinR.ServiceHost.TargetPath)" />
<File Id="TimberWinR.ServiceHost.exe.config" Source="$(var.TimberWinR.ServiceHost.TargetDir)\TimberWinR.ServiceHost.exe.config" />
<File Id="Interop.MSUtil.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\Interop.MSUtil.dll" />
<File Id="csredis.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\csredis.dll" />
@@ -50,11 +55,16 @@
<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" />
<File Id="System.Linq.Dynamic.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\System.Linq.Dynamic.dll" />
<File Id="Topshelf.dll" Source="$(var.TimberWinR.ServiceHost.TargetDir)\Topshelf.dll" />
<File Id="MaxMind.Db.dll" Source="$(var.TimberWinR.TargetDir)\MaxMind.db.dll" />
<File Id="MaxMind.GeoIP2.dll" Source="$(var.TimberWinR.TargetDir)\MaxMind.GeoIP2.dll" />
<File Id="GeoLite2City.mmdb" Source="$(var.TimberWinR.TargetDir)\GeoLite2City.mmdb" />
</Component>
</ComponentGroup>
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallFinalize" Overridable="yes" />
<Custom Action='ManagedInstall' After="InstallFiles">NOT Installed</Custom>
<Custom Action='ManagedInstall2' After="ManagedInstall">NOT Installed</Custom>
<Custom Action='ManagedUnInstall' Before="RemoveFiles">Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>

Some files were not shown because too many files have changed in this diff Show More