diff --git a/World Possible Dev.pbix b/World Possible Dev.pbix
new file mode 100644
index 0000000..6d35665
Binary files /dev/null and b/World Possible Dev.pbix differ
diff --git a/World Possible Master.pbix b/World Possible Master.pbix
new file mode 100644
index 0000000..c8dc169
Binary files /dev/null and b/World Possible Master.pbix differ
diff --git a/parserapp/access4.log b/parserapp/access4.log
index 58ca7b5..c97b67a 100644
--- a/parserapp/access4.log
+++ b/parserapp/access4.log
@@ -1,8 +1,30 @@
-192.168.1.10 - - [24/Jul/2019:18:51:16 +0000] "GET /modules/en-teachertraining/index.html HTTP/1.1" 200 13480 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 RACHEL 80 [24/Jul/2019:18:51:16 +0000] "GET /modules/en-teachertraining/teachertraining-logo.jpg HTTP/1.1" 200 13480 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 - - [24/Jul/2019:18:52:16 +0000] "GET /modules/en-kolibri-index/kolibri-logo.svg HTTP/1.1" 200 10220 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 RACHEL 80 [24/Jul/2019:18:52:16 +0000] "GET /modules/en-kolibri-index/kolibri-logo.svg HTTP/1.1" 200 10220 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 - - [24/Jul/2019:18:53:16 +0000] "GET /modules/en-wikipedia/enwiki.png HTTP/1.1" 200 20616 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 RACHEL 80 [24/Jul/2019:18:53:16 +0000] "GET /modules/en-wikipedia/enwiki.png HTTP/1.1" 200 20616 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 - - [24/Jul/2019:18:53:16 +0000] "GET /modules/en-wikipedia_for_schools/wfs_logo_smooth.jpg HTTP/1.1" 200 31908 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
-192.168.1.10 RACHEL 80 [24/Jul/2019:18:53:16 +0000] "GET /modules/en-wikipedia_for_schools/wfs_logo_smooth.jpg HTTP/1.1" 200 31908 "http://192.168.1.150/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET / HTTP/1.1","status": "200","body_bytes_sent":"4821","request_time":"0.394","http_referrer":"-","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET / HTTP/1.1" 200 4821 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-asst_medical/Logo-small.png HTTP/1.1","status": "200","body_bytes_sent":"9043","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-asst_medical/Logo-small.png HTTP/1.1" 200 9043 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-educate/logo.png HTTP/1.1","status": "200","body_bytes_sent":"28560","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-educate/logo.png HTTP/1.1" 200 28560 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-catdogbooks/logo.png HTTP/1.1","status": "200","body_bytes_sent":"72051","request_time":"0.028","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-catdogbooks/logo.png HTTP/1.1" 200 72051 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /js/jquery-1.10.2.min.js HTTP/1.1","status": "200","body_bytes_sent":"93107","request_time":"0.015","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /js/jquery-1.10.2.min.js HTTP/1.1" 200 93107 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /css/normalize-1.1.3.css HTTP/1.1","status": "200","body_bytes_sent":"9559","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /css/normalize-1.1.3.css HTTP/1.1" 200 9559 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-edison/Edison-Logo.jpg HTTP/1.1","status": "200","body_bytes_sent":"10914","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-edison/Edison-Logo.jpg HTTP/1.1" 200 10914 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-afristory/af.png HTTP/1.1","status": "200","body_bytes_sent":"17787","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-afristory/af.png HTTP/1.1" 200 17787 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /css/ui-lightness/jquery-ui-1.10.4.custom.min.css HTTP/1.1","status": "200","body_bytes_sent":"17114","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /css/ui-lightness/jquery-ui-1.10.4.custom.min.css HTTP/1.1" 200 17114 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-blockly-games/logo.png HTTP/1.1","status": "200","body_bytes_sent":"8954","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-blockly-games/logo.png HTTP/1.1" 200 8954 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /js/jquery-ui-1.10.4.custom.min.js HTTP/1.1","status": "200","body_bytes_sent":"60845","request_time":"0.011","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /js/jquery-ui-1.10.4.custom.min.js HTTP/1.1" 200 60845 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-bookdash/logo.png HTTP/1.1","status": "200","body_bytes_sent":"12730","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /modules/en-bookdash/logo.png HTTP/1.1" 200 12730 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:25 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /css/style.css HTTP/1.1","status": "200","body_bytes_sent":"4171","request_time":"0.000","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:25 +0000] "GET /css/style.css HTTP/1.1" 200 4171 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:26 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-causebooks/osu-logo.png HTTP/1.1","status": "200","body_bytes_sent":"65596","request_time":"0.062","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:26 +0000] "GET /modules/en-causebooks/osu-logo.png HTTP/1.1" 200 65596 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
+{"time_local":"23/Sep/2019:22:23:26 +0000","remote_addr":"192.168.88.238","remote_user":"-","request":"GET /modules/en-binofino/B&NFiles/Bino&Fino5.png HTTP/1.1","status": "200","body_bytes_sent":"136122","request_time":"0.062","http_referrer":"http://my.content/","http_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
+192.168.88.238 RACHEL 80 [23/Sep/2019:22:23:26 +0000] "GET /modules/en-binofino/B&NFiles/Bino&Fino5.png HTTP/1.1" 200 136122 "http://my.content/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
\ No newline at end of file
diff --git a/parserapp/parserapp.csproj b/parserapp/parserapp.csproj
index 22294a0..1ee5384 100644
--- a/parserapp/parserapp.csproj
+++ b/parserapp/parserapp.csproj
@@ -32,7 +32,7 @@
-
+
diff --git a/parserfn/AccessLogSummary.cs b/parserfn/AccessLogSummary.cs
new file mode 100644
index 0000000..7776fcd
--- /dev/null
+++ b/parserfn/AccessLogSummary.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserFn
+{
+ class AccessLogSummary
+ {
+ public string DeviceId { get; set; }
+ public string AccessDate { get; set; }
+ public long Bandwidth { get; set; }
+ }
+}
diff --git a/parserfn/AccessSitesLog.cs b/parserfn/AccessSitesLog.cs
new file mode 100644
index 0000000..be9a10e
--- /dev/null
+++ b/parserfn/AccessSitesLog.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserFn
+{
+ public class AccessedSitesLog
+ {
+ public string id { get; set; }
+ public string PartitionKey { get; set; }
+ public string Date { get; set; }
+ public string Url { get; set; }
+ public long Bandwidth { get; set; }
+ public long MainModuleCount { get; set; }
+ public long SubModuleCount { get; set; }
+ }
+}
diff --git a/parserfn/JsonToDB.cs b/parserfn/JsonToDB.cs
index c0ad8b3..bb457e8 100644
--- a/parserfn/JsonToDB.cs
+++ b/parserfn/JsonToDB.cs
@@ -8,18 +8,10 @@
using Microsoft.Azure.Documents;
using System.Collections.Generic;
-namespace parserfn
+namespace ParserFn
{
// Cosmos DB Entity
- public class AccessedSitesLog
- {
- public string id { get; set; }
- public string PartitionKey { get; set; }
- public string Date { get; set; }
- public string Url { get; set; }
- public long MainModuleCount { get; set; }
- public long SubModuleCount { get; set; }
- }
+
public static class JsonToDB
{
@@ -42,7 +34,7 @@ public static void Run(
result = serializer.Deserialize(jsonTextReader);
}
}
-
+
//For each AccessData Object in the list - create a record in Cosmos DB
foreach (AccessData accessedsite in result.AccessDetails)
{
@@ -54,13 +46,27 @@ public static void Run(
Url = accessedsite.ModuleName,
Date = accessedsite.UpLoadTime.ToString("MM/dd/yyyy"),
MainModuleCount = accessedsite.MainModuleCount,
- SubModuleCount = accessedsite.SubModuleCount
+ SubModuleCount = accessedsite.SubModuleCount,
+ Bandwidth = accessedsite.Bandwidth
};
// upload accessedSiteRecord to cosmos db
cosmosOutput.Add(accessedSiteRecord);
log.LogInformation($"Added all to Cosmos DB");
}
+
+ // Add general information
+ var summary = new AccessedSitesLog
+ {
+ PartitionKey = result.DeviceId + " " + result.AccessDate,
+ id = result.DeviceId + " TotalBandwidth",
+ Url = "Total Bandwidth",
+ MainModuleCount = -1,
+ SubModuleCount = -1,
+ Date = result.AccessDetails[0].UpLoadTime.ToString("MM/dd/yyy"), // should be same date anyways and forces using the correct format. Assumes at least one module was counted
+ Bandwidth = result.Bandwidth
+ };
+ cosmosOutput.Add(summary);
}
}
}
diff --git a/parserfn/LogToJson.cs b/parserfn/LogToJson.cs
index dd91f98..7ea4e74 100644
--- a/parserfn/LogToJson.cs
+++ b/parserfn/LogToJson.cs
@@ -5,7 +5,7 @@
using Microsoft.Extensions.Logging;
using ParserLib;
-namespace parserfn
+namespace ParserFn
{
public static class LogToJson
{
diff --git a/parserfn/parserfn.csproj b/parserfn/parserfn.csproj
index 331f46c..6256dc2 100644
--- a/parserfn/parserfn.csproj
+++ b/parserfn/parserfn.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp2.2
v2
@@ -10,7 +10,7 @@
-
+
diff --git a/parserlib/AccessData.cs b/parserlib/AccessData.cs
new file mode 100644
index 0000000..18498c3
--- /dev/null
+++ b/parserlib/AccessData.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserLib
+{
+ //Represents a record in Access Log
+ public class AccessData
+ {
+ public string ModuleName { get; set; }
+ public long MainModuleCount { get; set; }
+ public long SubModuleCount { get; set; }
+ public DateTime UpLoadTime { get; set; }
+ public long Bandwidth { get; set; }
+ }
+}
diff --git a/parserlib/AccessDataDetail.cs b/parserlib/AccessDataDetail.cs
new file mode 100644
index 0000000..d714d24
--- /dev/null
+++ b/parserlib/AccessDataDetail.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserLib
+{
+ //AccessDataDetail Object to be serialized into JSON - One per day
+ public class AccessDataDetail
+ {
+ public string DeviceId { get; set; }
+ public string AccessDate { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public long Bandwidth { get; set; }
+ public List AccessDetails { get; set; } = new List();
+ }
+}
diff --git a/parserlib/AzureBlobStorage.cs b/parserlib/AzureBlobStorage.cs
new file mode 100644
index 0000000..4742569
--- /dev/null
+++ b/parserlib/AzureBlobStorage.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Azure.Storage;
+using Microsoft.Azure.Storage.Blob;
+using Newtonsoft.Json;
+using System.Text.RegularExpressions;
+
+namespace ParserLib
+{
+ //Utility Class to Upload to Blob
+ public class AzureBlobStorage : IStorage
+ {
+ public async Task Create(Stream stream, string path, string containerName, string storageConnectionString)
+ {
+ CloudStorageAccount account = CloudStorageAccount.Parse(storageConnectionString);
+
+ var blobClient = account.CreateCloudBlobClient();
+
+ var blobContainer = blobClient.GetContainerReference(containerName);
+ await blobContainer.CreateIfNotExistsAsync();
+
+ CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(path);
+ await blockBlob.UploadFromStreamAsync(stream);
+
+ }
+ }
+}
diff --git a/parserlib/DateRange.cs b/parserlib/DateRange.cs
new file mode 100644
index 0000000..193fa35
--- /dev/null
+++ b/parserlib/DateRange.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserLib
+{
+ public class DateRange
+ {
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ }
+}
diff --git a/parserlib/IStorage.cs b/parserlib/IStorage.cs
new file mode 100644
index 0000000..c6ce179
--- /dev/null
+++ b/parserlib/IStorage.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Azure.Storage;
+using Microsoft.Azure.Storage.Blob;
+using Newtonsoft.Json;
+using System.Text.RegularExpressions;
+
+namespace ParserLib
+{
+ public interface IStorage
+ {
+ Task Create(Stream stream, string path, string containerName, string storageConnectionString);
+ }
+}
diff --git a/parserlib/LineDataDetail.cs b/parserlib/LineDataDetail.cs
new file mode 100644
index 0000000..8b2f941
--- /dev/null
+++ b/parserlib/LineDataDetail.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ParserLib
+{
+ //contains information taken out of each line of the log
+ public class LineDataDetail
+ {
+ public string ClientIP { get; set; }
+ public string Date { get; set; }
+ public string Module { get; set; }
+ public long Bandwidth { get; set; }
+
+ //note whether this particular line is of JSON type or of "tokenized" type
+ //these are the only types of log lines that we know of, for now
+ public string LineType { get; set; }
+
+ //to account for some specific condition in "tokenized" lines that the lines must be skipped
+ public bool Skip { get; set; }
+ }
+}
diff --git a/parserlib/parserlib.cs b/parserlib/LogParser.cs
similarity index 69%
rename from parserlib/parserlib.cs
rename to parserlib/LogParser.cs
index cb012fc..390fa6d 100644
--- a/parserlib/parserlib.cs
+++ b/parserlib/LogParser.cs
@@ -8,58 +8,10 @@
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Newtonsoft.Json;
-using System.Text.RegularExpressions;
-
+using System.Text.RegularExpressions;
+
namespace ParserLib
{
- public interface IStorage
- {
- Task Create(Stream stream, string path, string containerName, string storageConnectionString);
- }
-
- //Utility Class to Upload to Blob
- public class AzureBlobStorage : IStorage
- {
- public async Task Create(Stream stream, string path, string containerName, string storageConnectionString)
- {
- CloudStorageAccount account = CloudStorageAccount.Parse(storageConnectionString);
-
- var blobClient = account.CreateCloudBlobClient();
-
- var blobContainer = blobClient.GetContainerReference(containerName);
- await blobContainer.CreateIfNotExistsAsync();
-
- CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(path);
- await blockBlob.UploadFromStreamAsync(stream);
-
- }
- }
-
- //Represents a record in Access Log
- public class AccessData
- {
- public string ModuleName { get; set; }
- public long MainModuleCount { get; set; }
- public long SubModuleCount { get; set; }
- public DateTime UpLoadTime { get; set; }
- }
-
- //AccessDataDetail Object to be serialized into JSON - One per day
- public class AccessDataDetail
- {
- public string DeviceId { get; set; }
- public string AccessDate { get; set; }
- public DateTime StartTime { get; set; }
- public DateTime EndTime { get; set; }
- public List AccessDetails { get; set; } = new List();
- }
-
- public class DateRange
- {
- public DateTime StartTime { get; set; }
- public DateTime EndTime { get; set; }
- }
-
public class LogParser
{
private IDictionary> accessDataList = new Dictionary>();
@@ -82,195 +34,299 @@ public LogParser(string connectionString, string containerName, bool inCloud, st
//Blob Name is of the following format
//abc@def.ghi.com_AccessDate
//Device Id is "def" - that is anything between @ and the first "."
- private string GetDeviceId(string blobName)
- {
- string[] blobSubStrings = blobName.Split('@', '.');
- if (blobSubStrings.Length >= 2)
- return blobSubStrings[1];
- return "unknown";
+ private string GetDeviceId(string blobName)
+ {
+ string[] blobSubStrings = blobName.Split('@', '.');
+ if (blobSubStrings.Length >= 2)
+ return blobSubStrings[1];
+ return "unknown";
}
- private string ComputeJsonName(string deviceId, string accessDate)
- {
- string result = deviceId + "_" + accessDate + ".json";
- return result;
+ private string ComputeJsonName(string deviceId, string accessDate)
+ {
+ string result = deviceId + "_" + accessDate + ".json";
+ return result;
}
- public AccessDataDetail DeserializeJsonDataInCloud(string deviceId, string accessDate)
- {
- string jsonName = ComputeJsonName(deviceId, accessDate);
-
- // connect to our storage account and create a blob client
- var storageAccount = CloudStorageAccount.Parse(connectionString);
- var blobClient = storageAccount.CreateCloudBlobClient();
-
- // get a reference to the container
- var blobcontainer = blobClient.GetContainerReference(containerName);
- blobcontainer.CreateIfNotExists();
-
- foreach(var blob in blobcontainer.ListBlobs())
- {
- string flName = blob.Uri.Segments.Last().ToString();
- Console.WriteLine(flName);
-
- if(jsonName == flName)
- {
- CloudBlob cblob = blobcontainer.GetBlobReference(flName);
- using (StreamReader reader = new StreamReader(cblob.OpenRead()))
- {
- string content = "";
- StringBuilder strjson=new StringBuilder();
- while ((content = reader.ReadLine())!=null)
- {
- strjson.Append(content);
- }
- AccessDataDetail overlapAddObj = JsonConvert.DeserializeObject(strjson.ToString());
- return overlapAddObj;
- }
- }
- }
-
- return null;
+ public AccessDataDetail DeserializeJsonDataInCloud(string deviceId, string accessDate)
+ {
+ string jsonName = ComputeJsonName(deviceId, accessDate);
+
+ // connect to our storage account and create a blob client
+ var storageAccount = CloudStorageAccount.Parse(connectionString);
+ var blobClient = storageAccount.CreateCloudBlobClient();
+
+ // get a reference to the container
+ var blobcontainer = blobClient.GetContainerReference(containerName);
+ blobcontainer.CreateIfNotExists();
+
+ foreach (var blob in blobcontainer.ListBlobs())
+ {
+ string flName = blob.Uri.Segments.Last().ToString();
+ Console.WriteLine(flName);
+
+ if (jsonName == flName)
+ {
+ CloudBlob cblob = blobcontainer.GetBlobReference(flName);
+ using (StreamReader reader = new StreamReader(cblob.OpenRead()))
+ {
+ string content = "";
+ StringBuilder strjson = new StringBuilder();
+ while ((content = reader.ReadLine()) != null)
+ {
+ strjson.Append(content);
+ }
+ AccessDataDetail overlapAddObj = JsonConvert.DeserializeObject(strjson.ToString());
+ return overlapAddObj;
+ }
+ }
+ }
+
+ return null;
}
- private AccessDataDetail DeserializeJsonDataInLocal(string deviceId, string accessDate)
- {
- string jsonName = ComputeJsonName(deviceId, accessDate);
-
- if (!Directory.Exists(localPath))
- return null;
-
- string fullPath = Path.Combine(localPath, jsonName);
- if (File.Exists(fullPath))
- {
- var content = File.ReadAllText(fullPath);
- AccessDataDetail deserializedObj = JsonConvert.DeserializeObject(content);
- return deserializedObj;
- }
-
- return null;
+ private AccessDataDetail DeserializeJsonDataInLocal(string deviceId, string accessDate)
+ {
+ string jsonName = ComputeJsonName(deviceId, accessDate);
+
+ if (!Directory.Exists(localPath))
+ return null;
+
+ string fullPath = Path.Combine(localPath, jsonName);
+ if (File.Exists(fullPath))
+ {
+ var content = File.ReadAllText(fullPath);
+ AccessDataDetail deserializedObj = JsonConvert.DeserializeObject(content);
+ return deserializedObj;
+ }
+
+ return null;
+ }
+
+ private AccessDataDetail DeserializeJsonData(string deviceId, string accessDate)
+ {
+ try
+ {
+ if (inCloud)
+ return DeserializeJsonDataInCloud(deviceId, accessDate);
+
+ return DeserializeJsonDataInLocal(deviceId, accessDate);
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ // Updates a bandwidth record for the given module
+ // Returns true if this module was already in the dictionary,
+ // Returns false otherwise
+ private void IncrementModuleBandwidth(IDictionary bandwidths, string module, long bandwidth)
+ {
+ if (!bandwidths.TryGetValue(module, out long current)) {
+ bandwidths.Add(module, bandwidth);
+ } else
+ {
+ bandwidths[module] = current + bandwidth;
+ }
}
- private AccessDataDetail DeserializeJsonData(string deviceId, string accessDate)
- {
- try
- {
- if (inCloud)
- return DeserializeJsonDataInCloud(deviceId, accessDate);
-
- return DeserializeJsonDataInLocal(deviceId, accessDate);
- }
- catch(Exception)
- {
- return null;
- }
+ private void IncrementModuleBandwidth(IDictionary> bandwidths, string date, string module, long bandwidth)
+ {
+ if (!bandwidths.TryGetValue(date, out IDictionary dateBandwidths))
+ {
+ dateBandwidths = new Dictionary();
+ bandwidths.Add(date, dateBandwidths);
+ }
+ IncrementModuleBandwidth(dateBandwidths, module, bandwidth);
+ }
+
+ private LineDataDetail ExtractLineData(string lineEntry)
+ {
+ LineDataDetail info = new LineDataDetail();
+
+ try
+ {
+ dynamic line = JsonConvert.DeserializeObject(lineEntry);
+ info.ClientIP = line.remote_addr;
+ //info.Date = line.time_local.Substring(0, line.time_local.Length - 6);
+ info.Date = ((string)line.time_local).Substring(0, ((string)line.time_local).Length - 6);
+ info.Module = line.request;
+ info.Bandwidth = Convert.ToInt64(line.body_bytes_sent);
+ info.LineType = "json";
+ info.Skip = false;
+ }
+ catch (JsonReaderException)
+ {
+ string[] tokens = lineEntry.Split(' ');
+
+ info.ClientIP = string.Empty;
+ info.Date = string.Empty;
+ info.Module = string.Empty;
+ info.LineType = "tokenized";
+ info.Bandwidth = 0;
+
+ //If the first token (token[0]) is not of type IP address - skip processing - evaluate via Regex
+ Regex ipRegex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b");
+ MatchCollection regexResult = ipRegex.Matches(tokens[0]);
+
+ if (regexResult.Count == 0 || tokens.Length <= 6)
+ {
+ //this line would be skipped anyway so no data is recorded because the line is probably broken
+ info.Skip = true;
+ }
+ else
+ {
+ //token[0] - ip address
+ info.ClientIP = tokens[0];
+ //Ignore token[1] & token[2] - Irrelevant
+ //token[3] - Date information
+ info.Date = tokens[3].Substring(1, 20);
+ //Ignore token[4] & token[5] - Irrelevant
+ //token[6] - Contains Module Information.
+ info.Module = tokens[6];
+ //token[7] and token[8] are not used for now
+ //token[9] - Contains bandwidth info
+ info.Bandwidth = Convert.ToInt64(tokens[9]);
+ info.Skip = false;
+ }
+ }
+
+ return info;
}
public IList Parse(string blobName, Stream accessLogBlob)
{
string deviceId = GetDeviceId(blobName);
+ // Using another object that does its work before the skip logic. This should be refactored at some point.
+ IDictionary> bandwidths = new Dictionary>(); // any way to approximate final size to reduce rehashes?
+
+ long totalBandwidth = 0L;
+
IList result = new List();
string lineEntry = string.Empty;
//Initialize the prev record to some random date
DateTime prevRecord = DateTime.Parse("01/01/1900");
+ string prevLineType = string.Empty;
+
var file = new StreamReader(accessLogBlob);
while ((lineEntry = file.ReadLine()) != null)
{
- string[] tokens = lineEntry.Split(' ');
- bool isMainModule = false;
+ LineDataDetail lineInfo = ExtractLineData(lineEntry);
- //TODO: We skip this for now - need to check if this is an error and log appropriately
- if (tokens.Length <= 6)
- continue;
+ //skip the next line if the next line is not of the same type as the previous type
+ //deciding type is the type of the very first line
+ //this assumes that the group of the lines of different type contain the same data so they can just be skipped without losing data
+ if (!prevLineType.Equals(string.Empty))
+ {
+ if (!lineInfo.LineType.Equals(prevLineType))
+ {
+ continue;
+ }
+ }
+ else
+ {
+ prevLineType = lineInfo.LineType;
+ }
- //If the first token (token[0]) is not of type IP address - skip processing - evaluate via Regex
- string clientIP = tokens[0];
- Regex ipRegex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b");
- MatchCollection regexResult = ipRegex.Matches(clientIP);
- if (regexResult.Count == 0)
+ if (lineInfo.Skip)
+ {
continue;
+ }
- //Ignore Token 2(token[1]) & Token 3(token[2]) - Irrelevant
-
- //Token 4(token[3]) - Date information
- string dateValue = tokens[3].Substring(1);
+ string dateValue = lineInfo.Date;
CultureInfo provider = CultureInfo.InvariantCulture;
DateTime dtObj = DateTime.ParseExact(dateValue, "dd/MMM/yyyy:HH:mm:ss", provider);
//TODO return if date is not valid
string formattedDateValue = dtObj.ToString("yyyyMMdd");
- //Ignore Token 5(token[4]) & Token 6(token[5]) - Irrelevant
-
- //Token 7(token[6]) Contains Module Information.
- string module = tokens[6];
+ string module = lineInfo.Module;
+
+ long bandwidth = lineInfo.Bandwidth;
string[] moduleTokens = module.Split('/', '?');
+
//TODO: We skip this for now - need to check if this is an error and log appropriately
- if (moduleTokens.Length <= 2)
- continue;
+ if (moduleTokens.Length <= 2)
+ {
+ //continue;
+ throw new Exception();
+ }
+
+ // Handle bandwidth before we skip
+ // Cleans hour:minute:second from the datetime string
+ IncrementModuleBandwidth(bandwidths, formattedDateValue, moduleTokens[2], bandwidth);
+ Console.WriteLine("{0} {1}", moduleTokens[2], bandwidth);
+ totalBandwidth += bandwidth;
//Specification says, the module url should start with "modules" - if that's not the case skip
- if (!moduleTokens.Contains("modules"))
+ if (!moduleTokens.Contains("modules"))
+ {
continue;
+ }
//Ignore all the Image based URLs
- if (moduleTokens[moduleTokens.Length - 1].Contains(".png") || moduleTokens[moduleTokens.Length - 1].Contains(".jpg") ||
+ if (moduleTokens[moduleTokens.Length - 1].Contains(".png") || moduleTokens[moduleTokens.Length - 1].Contains(".jpg") ||
moduleTokens[moduleTokens.Length - 1].Contains(".bmp") || moduleTokens[moduleTokens.Length - 1].Contains(".gif") ||
moduleTokens[moduleTokens.Length - 1].Contains(".js") || moduleTokens[moduleTokens.Length - 1].Contains(".css"))
+ {
continue;
-
- //Ignore all the request that came for the same time - An html page has css, js, and lot of references - they dont count towards an article that was read - round off to 1 event for 1 second.
- if (prevRecord == dtObj)
+ }
+
+ //Ignore all the request that came for the same time - An html page has css, js, and lot of references - they dont count towards an article that was read - round off to 1 event for 1 second.
+ if (prevRecord == dtObj)
continue;
else
prevRecord = dtObj;
//If we reached here - we are certainly processing the record - Save any metadata information about the record.
- if (!dateRangeList.ContainsKey(formattedDateValue))
- {
- //If there is no record for a date - create a record and register start and end time as that of current log entry
- dateRangeList.Add(formattedDateValue, new DateRange() { StartTime = DateTime.Parse(dtObj.ToString("HH:mm:ss")), EndTime = DateTime.Parse(dtObj.ToString("HH:mm:ss")) });
+ if (!dateRangeList.ContainsKey(formattedDateValue))
+ {
+ //If there is no record for a date - create a record and register start and end time as that of current log entry
+ dateRangeList.Add(formattedDateValue, new DateRange() { StartTime = DateTime.Parse(dtObj.ToString("HH:mm:ss")), EndTime = DateTime.Parse(dtObj.ToString("HH:mm:ss")) });
}
else
- {
- //If there exist a record, update only the end time.
- DateRange rangeObj = dateRangeList[formattedDateValue];
- rangeObj.EndTime = DateTime.Parse(dtObj.ToString("HH:mm:ss"));
+ {
+ //If there exist a record, update only the end time.
+ DateRange rangeObj = dateRangeList[formattedDateValue];
+ rangeObj.EndTime = DateTime.Parse(dtObj.ToString("HH:mm:ss"));
}
- if (!existingRecords.ContainsKey(formattedDateValue))
- {
- //Check if there is a JSON for this date - if exist load the JSON in memory
- AccessDataDetail existingObj = DeserializeJsonData(deviceId, formattedDateValue);
- if (existingObj != null)
- {
- existingRecords.Add(formattedDateValue, existingObj);
- }
- else
- {
- existingRecords.Add(formattedDateValue, null);
+ if (!existingRecords.ContainsKey(formattedDateValue))
+ {
+ //Check if there is a JSON for this date - if exist load the JSON in memory
+ AccessDataDetail existingObj = DeserializeJsonData(deviceId, formattedDateValue);
+ if (existingObj != null)
+ {
+ existingRecords.Add(formattedDateValue, existingObj);
+ }
+ else
+ {
+ existingRecords.Add(formattedDateValue, null);
}
}
- if (existingRecords[formattedDateValue] != null)
- {
- DateTime startTime = existingRecords[formattedDateValue].StartTime;
- DateTime endTime = existingRecords[formattedDateValue].EndTime;
-
- DateTime currrentTime = DateTime.Parse(dtObj.ToString("HH:mm:ss"));
-
- //If the current log entry is a overlapping with existing record - ignore
- if (currrentTime >= startTime && currrentTime <= endTime)
- continue;
+ if (existingRecords[formattedDateValue] != null)
+ {
+ DateTime startTime = existingRecords[formattedDateValue].StartTime;
+ DateTime endTime = existingRecords[formattedDateValue].EndTime;
+
+ DateTime currrentTime = DateTime.Parse(dtObj.ToString("HH:mm:ss"));
+
+ //TODO: need to ignore module count but still add to bandwidth count here
+ //If the current log entry is a overlapping with existing record - ignore
+ if (currrentTime >= startTime && currrentTime <= endTime)
+ {
+ //it seems that total bandwidth is still being added to totalBandwidth at this point
+ //because of continue, the module count below is not executed but the bandwidth count before the continue is still executed
+ //so far, it seems nothing needs to be done for this TODO
+ continue;
+ }
}
-
- //Check if the request is for Main Module
- if (moduleTokens[3].Contains(".htm") || moduleTokens[3].Contains("html"))
- isMainModule = true;
if (!accessDataList.ContainsKey(formattedDateValue))
{
@@ -290,10 +346,15 @@ public IList Parse(string blobName, Stream accessLogBlob)
}
AccessData accessDataObj = dictionaryObj[moduleName];
- if (isMainModule)
- accessDataObj.MainModuleCount = accessDataObj.MainModuleCount + 1;
+ //Check if the request is for Main Module
+ if (moduleTokens[3].Contains(".htm") || moduleTokens[3].Contains("html"))
+ {
+ accessDataObj.MainModuleCount += 1;
+ }
else
- accessDataObj.SubModuleCount = accessDataObj.SubModuleCount + 1;
+ {
+ accessDataObj.SubModuleCount += 1;
+ }
}
@@ -303,59 +364,63 @@ public IList Parse(string blobName, Stream accessLogBlob)
AccessDataDetail addObj = new AccessDataDetail();
addObj.AccessDate = dateValue;
addObj.DeviceId = deviceId;
+ addObj.Bandwidth = totalBandwidth;
- if (dateRangeList.ContainsKey(dateValue))
- {
- addObj.StartTime = dateRangeList[dateValue].StartTime;
- addObj.EndTime = dateRangeList[dateValue].EndTime;
+ if (dateRangeList.ContainsKey(dateValue))
+ {
+ addObj.StartTime = dateRangeList[dateValue].StartTime;
+ addObj.EndTime = dateRangeList[dateValue].EndTime;
}
IDictionary dictionaryObj = accessDataList[dateValue];
foreach (string moduleName in dictionaryObj.Keys)
{
- addObj.AccessDetails.Add(dictionaryObj[moduleName]);
+ var next = dictionaryObj[moduleName];
+ next.Bandwidth = (bandwidths[dateValue])[moduleName];
+ addObj.AccessDetails.Add(next);
+ Console.WriteLine(moduleName);
}
- //Merge existing record with current record
- if (existingRecords.ContainsKey(dateValue))
- {
- if (existingRecords[dateValue] != null)
- {
- if (existingRecords[dateValue].StartTime != null)
- {
- //Update Start Time with minimum of both
- if (existingRecords[dateValue].StartTime <= addObj.StartTime)
- addObj.StartTime = existingRecords[dateValue].StartTime;
- }
-
- if (existingRecords[dateValue].EndTime != null)
- {
- //Update End Time with maximum of both
- if (existingRecords[dateValue].EndTime >= addObj.EndTime)
- addObj.EndTime = existingRecords[dateValue].EndTime;
- }
-
- foreach (AccessData existingaddObj in existingRecords[dateValue].AccessDetails)
- {
- string moduleName = existingaddObj.ModuleName;
- //Check if record exist for this module
- bool matchFound = false;
- foreach (AccessData newaddObj in addObj.AccessDetails)
- {
- if (moduleName == newaddObj.ModuleName)
- {
- matchFound = true;
- newaddObj.MainModuleCount += existingaddObj.MainModuleCount;
- newaddObj.SubModuleCount += existingaddObj.SubModuleCount;
- break;
- }
- }
-
- if (matchFound == false)
- {
- addObj.AccessDetails.Add(existingaddObj);
- }
- }
+ // Merge existing record with current record
+ if (existingRecords.ContainsKey(dateValue))
+ {
+ if (existingRecords[dateValue] != null)
+ {
+ if (existingRecords[dateValue].StartTime != null)
+ {
+ //Update Start Time with minimum of both
+ if (existingRecords[dateValue].StartTime <= addObj.StartTime)
+ addObj.StartTime = existingRecords[dateValue].StartTime;
+ }
+
+ if (existingRecords[dateValue].EndTime != null)
+ {
+ //Update End Time with maximum of both
+ if (existingRecords[dateValue].EndTime >= addObj.EndTime)
+ addObj.EndTime = existingRecords[dateValue].EndTime;
+ }
+
+ foreach (AccessData existingaddObj in existingRecords[dateValue].AccessDetails)
+ {
+ string moduleName = existingaddObj.ModuleName;
+ //Check if record exist for this module
+ bool matchFound = false;
+ foreach (AccessData newaddObj in addObj.AccessDetails)
+ {
+ if (moduleName == newaddObj.ModuleName)
+ {
+ matchFound = true;
+ newaddObj.MainModuleCount += existingaddObj.MainModuleCount;
+ newaddObj.SubModuleCount += existingaddObj.SubModuleCount;
+ break;
+ }
+ }
+
+ if (matchFound == false)
+ {
+ addObj.AccessDetails.Add(existingaddObj);
+ }
+ }
}
}
@@ -379,26 +444,25 @@ public IList Parse(string blobName, Stream accessLogBlob)
return null;
}
}
-
return result;
}
//Upload JSON Files to Temp Path
private bool UploadFile(IList fileList)
- {
- if (!Directory.Exists(localPath))
- return false;
-
- foreach (string jsonPath in fileList)
- {
- FileInfo fInfo = new FileInfo(jsonPath);
- string destPath = Path.Combine(localPath, fInfo.Name);
- File.Copy(jsonPath, destPath, true);
- }
- return true;
- }
-
- //Upload JSON Files to blob container
+ {
+ if (!Directory.Exists(localPath))
+ return false;
+
+ foreach (string jsonPath in fileList)
+ {
+ FileInfo fInfo = new FileInfo(jsonPath);
+ string destPath = Path.Combine(localPath, fInfo.Name);
+ File.Copy(jsonPath, destPath, true);
+ }
+ return true;
+ }
+
+ //Upload JSON Files to blob container
private bool UploadBlob(IList fileList)
{
foreach (string filePath in fileList)
diff --git a/parserlib/parserlib.csproj b/parserlib/parserlib.csproj
index 7979c2b..4666274 100644
--- a/parserlib/parserlib.csproj
+++ b/parserlib/parserlib.csproj
@@ -5,9 +5,11 @@
-
-
+
+
+
+
diff --git a/parsertest/parsertest.csproj b/parsertest/parsertest.csproj
index 8c33a3a..06f40cb 100644
--- a/parsertest/parsertest.csproj
+++ b/parsertest/parsertest.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp2.2
@@ -40,7 +40,7 @@
-
+
diff --git a/websrvlogprocessor.sln b/websrvlogprocessor.sln
index faaf663..d893378 100644
--- a/websrvlogprocessor.sln
+++ b/websrvlogprocessor.sln
@@ -1,15 +1,15 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28307.779
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29324.140
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "parserfn", "parserfn\parserfn.csproj", "{B2A8AFA6-7B11-489E-BAF2-7947723755C2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParserFn", "parserfn\ParserFn.csproj", "{B2A8AFA6-7B11-489E-BAF2-7947723755C2}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "parserapp", "parserapp\parserapp.csproj", "{DC45E53E-4E10-4EEB-AC0C-45330F8D58CC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParserApp", "parserapp\ParserApp.csproj", "{DC45E53E-4E10-4EEB-AC0C-45330F8D58CC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "parsertest", "parsertest\parsertest.csproj", "{C7FA2820-8051-4819-BEF8-D4A11B79A22C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParserTest", "parsertest\ParserTest.csproj", "{C7FA2820-8051-4819-BEF8-D4A11B79A22C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "parserlib", "parserlib\parserlib.csproj", "{D4B553BC-AA2C-4966-A962-606CB2447471}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParserLib", "parserlib\ParserLib.csproj", "{D4B553BC-AA2C-4966-A962-606CB2447471}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +17,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D4B553BC-AA2C-4966-A962-606CB2447471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D4B553BC-AA2C-4966-A962-606CB2447471}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D4B553BC-AA2C-4966-A962-606CB2447471}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D4B553BC-AA2C-4966-A962-606CB2447471}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Release|Any CPU.Build.0 = Release|Any CPU
{DC45E53E-4E10-4EEB-AC0C-45330F8D58CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC45E53E-4E10-4EEB-AC0C-45330F8D58CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC45E53E-4E10-4EEB-AC0C-45330F8D58CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -29,10 +29,10 @@ Global
{C7FA2820-8051-4819-BEF8-D4A11B79A22C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7FA2820-8051-4819-BEF8-D4A11B79A22C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7FA2820-8051-4819-BEF8-D4A11B79A22C}.Release|Any CPU.Build.0 = Release|Any CPU
- {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B2A8AFA6-7B11-489E-BAF2-7947723755C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4B553BC-AA2C-4966-A962-606CB2447471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4B553BC-AA2C-4966-A962-606CB2447471}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4B553BC-AA2C-4966-A962-606CB2447471}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4B553BC-AA2C-4966-A962-606CB2447471}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE