From 9e1c47899789c87f6c58fc01aed3461a3acd69fe Mon Sep 17 00:00:00 2001 From: Jay Long Date: Tue, 25 Nov 2025 13:44:15 -0600 Subject: [PATCH 1/4] Fluff out the project Readme --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index a44e4ed..783fd4d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ # TailGrab VRChat Log Parser and Automation tool to help moderators manage trouble makers in VRChat since VRChat will not take moderation seriously. + +# Usage +Open a Powershell or Command Line prompt in your windows host, change directory to where ```tailgrab``` has been extracted to and start it with: + +```.\tailgrab.exe``` + +Or if you have moved where the VR Chat ```output_log_*.txt``` are located; then: + +```.\tailgrab.exe {full path to VR Chat logs ending with a \}``` + + +# Capabilities + +The core concept of the TailGrab was to create a Windows friendly ```grep``` of VR Chat log events that would allow a group moderation team to review, get insights of bad actors and with the action framework to perform a scripted reaction to a log event. + +EG: +A ```Vote To Kick``` is received from a patreon, the action sequence could: +- Send a OSC Avatar Parameter(s) that change the avatar's ear position +- Delay for a quarter of a second +- Send a keystroke to your soundboard application +- Send a keystroke to OBS to start recording + + +## POC Version +- Parse VRChat log files +- World ```Furry Hideout``` will record User Pen Interaction +- Record User's avatar usage while in the instance +- Record User's moderation while in the instance (Warn and final Kick) +- Partial work with OSC Triggered events to send to your avatar +- Partial work with Keystroke events sent to a application of your choice \ No newline at end of file From 89153a91a7218264dac86c80b2a551925b753fd7 Mon Sep 17 00:00:00 2001 From: Jay Long Date: Tue, 25 Nov 2025 14:18:11 -0600 Subject: [PATCH 2/4] Rename the log output files to try to ensure a non-conflicting startups where a user may have multiple bots running --- NLog.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NLog.config b/NLog.config index e07d769..73acbab 100644 --- a/NLog.config +++ b/NLog.config @@ -5,7 +5,7 @@ throwConfigExceptions="true"> - From b0a59783f28fe7d2c58bc9c8dc4333148f74e45d Mon Sep 17 00:00:00 2001 From: Jay Long Date: Tue, 25 Nov 2025 18:36:55 -0600 Subject: [PATCH 3/4] Code and set base configuration managment for app load. --- Actions/Actions.cs | 6 +- LineHandlers/AbstractLineHandler.cs | 4 +- Program.cs | 53 +----- config.json | 108 ++++++++++- configuration/ConfigurationManager.cs | 250 +++++++++++++++++++++++--- tailgrab.csproj | 13 +- 6 files changed, 343 insertions(+), 91 deletions(-) diff --git a/Actions/Actions.cs b/Actions/Actions.cs index 234bff1..deabfda 100644 --- a/Actions/Actions.cs +++ b/Actions/Actions.cs @@ -147,14 +147,14 @@ public class OSCAction : IAction public string ParameterName { get; set; } - public OscType Type { get; set; } + public OscType OscTypeValue { get; set; } public string Value { get; set; } public OSCAction(string parameterName, OscType type, string value) { ParameterName = parameterName; - Type = type; + OscTypeValue = type; Value = value; } @@ -167,7 +167,7 @@ public void PerformAction() return; } - switch (Type) + switch (OscTypeValue) { case OscType.Bool: if (bool.TryParse(value, out bool boolValue)) diff --git a/LineHandlers/AbstractLineHandler.cs b/LineHandlers/AbstractLineHandler.cs index a131c8e..46ed1bf 100644 --- a/LineHandlers/AbstractLineHandler.cs +++ b/LineHandlers/AbstractLineHandler.cs @@ -7,7 +7,7 @@ namespace Tailgrab.LineHandler public interface ILineHandler { void AddAction(IAction action); - + bool HandleLine(string line); void LogColor( string color ); @@ -18,7 +18,7 @@ public abstract class AbstractLineHandler: ILineHandler { protected string Pattern { get; } protected Regex regex; - protected List Actions = new List(); + public List Actions = new List(); protected bool LogOutput { get; set; } = true; protected string LogColor = "37m"; // Default to white public string COLOR_PREFIX => $"\u001b[{LogColor}"; diff --git a/Program.cs b/Program.cs index ed83716..a1bacce 100644 --- a/Program.cs +++ b/Program.cs @@ -4,18 +4,19 @@ using Tailgrab.Actions; using Tailgrab.LineHandler; using NLog; +using Tailgrab.Configuration; public class FileTailer { /// /// A list of the Regex Line Matchers that are used to process log lines. /// - static List handlers = new List{}; + static List HandlerList = new List{}; /// /// A List of opened file paths to avoid opening the same file multiple times. /// - static List openedFiles = new List{}; + static List OpenedFiles = new List{}; /// /// The path to the user's profile directory. @@ -32,56 +33,16 @@ public class FileTailer public static Logger logger = LogManager.GetCurrentClassLogger(); - /// - /// Load and Initialize all the Line Handlers from the regular-expressions.txt file. - /// - public static void InitializeMatchsets() - { - using (FileStream fs = new FileStream("./regular-expressions.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (StreamReader sr = new StreamReader(fs, Encoding.UTF8)) - { - while (true && sr != null) - { - string? line = sr.ReadLine(); - if (line == null) - { - break; - } - - handlers.Add( new LoggingLineHandler(line)); - } - - handlers.Add( new OnPlayerJoinHandler(OnPlayerJoinHandler.LOG_PATTERN) ); - handlers.Add( new OnPlayerNetworkHandler(OnPlayerNetworkHandler.LOG_PATTERN) ); - handlers.Add( new StickerHandler(StickerHandler.LOG_PATTERN) ); - handlers.Add( new PrintHandler(PrintHandler.LOG_PATTERN) ); - handlers.Add( new AvatarChangeHandler(AvatarChangeHandler.LOG_PATTERN) ); - handlers.Add( new AvatarUnpackHandler(AvatarUnpackHandler.LOG_PATTERN) ); - handlers.Add( new WarnKickHandler(WarnKickHandler.LOG_PATTERN) ); - handlers.Add( new PenNetworkHandler(PenNetworkHandler.LOG_PATTERN) ); - handlers.Add( new QuitHandler(QuitHandler.LOG_PATTERN) ); - - //handlers.Add( new VTKHandler(VTKHandler.LOG_PATTERN) ); - ILineHandler handler = new VTKHandler(VTKHandler.LOG_PATTERN); - handler.AddAction( new OSCAction("/avatar/parameters/Ear/Right_Angle", OscType.Float, "20.0" )); - handler.AddAction( new DelayAction(500) ); - handler.AddAction( new OSCAction("/avatar/parameters/Ear/Right_Angle", OscType.Float, "0.0" )); - handler.LogColor("31;1m"); // Bright Red - handlers.Add( handler ); - - } - } - /// /// Threaded tailing of a file, reading new lines as they are added. /// public static async Task TailFileAsync(string filePath) { - if( openedFiles.Contains(filePath) ) + if( OpenedFiles.Contains(filePath) ) { return; } - openedFiles.Add(filePath); + OpenedFiles.Add(filePath); Console.WriteLine($"Tailing file: {filePath}. Press Ctrl+C to stop."); @@ -105,7 +66,7 @@ public static async Task TailFileAsync(string filePath) string? line; while ((line = await sr.ReadLineAsync()) != null) { - foreach (ILineHandler handler in handlers) + foreach (ILineHandler handler in HandlerList) { if (handler.HandleLine(line)) { @@ -171,7 +132,7 @@ public static async Task Main(string[] args) return; } - InitializeMatchsets(); + ConfigurationManager.LoadLineHandlersFromConfig(HandlerList); await WatchPath(filePath); } } diff --git a/config.json b/config.json index 90ffad2..dace6a9 100644 --- a/config.json +++ b/config.json @@ -1,30 +1,120 @@ { "lineHandlers": [ { - "type": "VTKHandler", + "handlerTypeValue": "AvatarChange", "enabled": true, - "logPatternType": "*default", - "logPattern": ".*\\[VTK\\].*Right_Ear_Angle.*", + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[Behaviour\\]\\WSwitching\\W([\\S\\W]+)\\Wto\\Wavatar\\W([\\S\\W]+)", "logOutput": false, - "logOutputColor": "*default", + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "AvatarUnpack", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[AssetBundleDownloadManager\\]\\W\\[\\d+\\] Unpacking Avatar \\(([\\S\\W]+) by ([\\S\\W]+)\\)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "OnPlayerJoin", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[Behaviour\\]\\WOnPlayer([\\d\\w]+)\\W([\\d\\w\\W]+)\\W\\((usr_[\\d\\w\\W]+)\\)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "OnPlayerNetwork", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[AP\\]\\WPlayer\\W\\W([\\d\\w\\W]+)\\W joined with ID ([\\d]+)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "PenNetwork", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[NetworkProcessing\\] Received ownership transfer of ([\\d]+) from ([\\d]+) to ([\\d]+)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "Print", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[API\\]\\WRequesting\\WGet\\Wprints/(prnt_[\\d\\w\\W]+)\\W\\{\\{\\}\\}", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "Sticker", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W(?:[\\[Always\\]\\W]*)\\[StickersManager\\]\\WUser\\W(usr_[\\d\\w\\W]+)\\W\\(([\\d\\w\\W]+)\\)\\Wspawned\\Wsticker\\W(file_[\\d\\w\\W]+)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "VTK", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[ModerationManager\\] A vote kick has been initiated against ([\\S\\W]+), do you agree", + "logOutput": false, + "logOutputColor": "Default", "actions": [ { - "type": "OSCAction", + "actionTypeValue": "OSCAction", "parameterName": "/avatar/parameters/Ear/Right_Angle", - "oscType": "Float", + "oscValueType": "Float", "value": "20.0" }, { - "type": "DelayAction", + "actionTypeValue": "DelayAction", "milliseconds": 500 }, { - "type": "OSCAction", + "actionTypeValue": "OSCAction", "parameterName": "/avatar/parameters/Ear/Right_Angle", - "oscType": "Float", + "oscValueType": "Float", "value": "0.0" } ] + }, + { + "handlerTypeValue": "WarnKick", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[ModerationManager\\]\\W([\\S\\W]+)\\Whas\\Wbeen\\W(warned|kicked)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] + }, + { + "handlerTypeValue": "Quit", + "enabled": true, + "patternTypeValue": "Default", + "pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\WVRCApplication: HandleApplicationQuit at ([\\d\\W]+)", + "logOutput": false, + "logOutputColor": "Default", + "actions": [ + ] } ] } \ No newline at end of file diff --git a/configuration/ConfigurationManager.cs b/configuration/ConfigurationManager.cs index c0920c4..3d5ffa3 100644 --- a/configuration/ConfigurationManager.cs +++ b/configuration/ConfigurationManager.cs @@ -1,14 +1,16 @@ -using System; -using System.IO; -using System.Text; -using System.Collections.Generic; -using System.Linq; +using Tailgrab.Actions; using Tailgrab.LineHandler; +using BuildSoft.VRChat.Osc; +using Microsoft.Extensions.Configuration; +using System.Collections.Generic; +using NLog; namespace Tailgrab.Configuration { public class ConfigurationManager { + public static Logger logger = LogManager.GetCurrentClassLogger(); + public static string GetConfigFilePath() { string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); @@ -16,58 +18,250 @@ public static string GetConfigFilePath() if (!Directory.Exists(configDirectory)) { Directory.CreateDirectory(configDirectory); - } + } return Path.Combine(configDirectory, "config.json"); } - public static ILineHandler loadLineHandlersFromConfig( LineHandlerConfig lineHandlerConfig ) + public static List LoadLineHandlersFromConfig( List handlers ) { - List handlers = new List(); + var configuration = new ConfigurationBuilder() + .AddJsonFile("config.json") + .Build(); + + var myConfigurations = new List(); + configuration.GetSection("lineHandlers").Bind(myConfigurations); + + foreach (var configItem in myConfigurations) + { + if (configItem == null) + { + continue; + } + + string? pattern = null; + if (configItem.PatternTypeValue == PatternType.Default || configItem.Pattern == null) + { + pattern = null; + } + else + { + pattern = configItem.Pattern; + } + + List actions = ParseActionsFromConfig(configItem.Actions); + + + // + // Build the appropriate line handler based on the type + AbstractLineHandler? handler = null; + switch (configItem.HandlerTypeValue) + { + case LineHandlerType.AvatarChange: + if (pattern == null) + { + pattern = AvatarChangeHandler.LOG_PATTERN; + } + handler = new AvatarChangeHandler(pattern); + break; + + case LineHandlerType.AvatarUnpack: + if (pattern == null) + { + pattern = AvatarUnpackHandler.LOG_PATTERN; + } + handler = new AvatarUnpackHandler(pattern); + break; + + case LineHandlerType.Logging: + if (pattern == null) + { + pattern = ""; + } + handler = new LoggingLineHandler(pattern); + break; + + case LineHandlerType.OnPlayerJoin: + if (pattern == null) + { + pattern = OnPlayerJoinHandler.LOG_PATTERN; + } + handler = new OnPlayerJoinHandler(pattern); + break; + + case LineHandlerType.OnPlayerNetwork: + if (pattern == null) + { + pattern = OnPlayerNetworkHandler.LOG_PATTERN; + } + handler = new OnPlayerNetworkHandler(pattern); + break; + + case LineHandlerType.PenNetwork: + if (pattern == null) + { + pattern = PenNetworkHandler.LOG_PATTERN; + } + handler = new PenNetworkHandler(pattern); + break; + + case LineHandlerType.Print: + if (pattern == null) + { + pattern = PrintHandler.LOG_PATTERN; + } + handler = new PrintHandler(pattern); + break; + + case LineHandlerType.Quit: + if (pattern == null) + { + pattern = QuitHandler.LOG_PATTERN; + } + handler = new QuitHandler(pattern); + break; + + case LineHandlerType.Sticker: + if (pattern == null) + { + pattern = StickerHandler.LOG_PATTERN; + } + handler = new StickerHandler(pattern); + break; + + case LineHandlerType.VTK: + if (pattern == null) + { + pattern = VTKHandler.LOG_PATTERN; + } + handler = new VTKHandler(pattern); + break; + + case LineHandlerType.WarnKick: + if (pattern == null) + { + pattern = WarnKickHandler.LOG_PATTERN; + } + handler = new WarnKickHandler(pattern); + break; + + default: + logger.Warn($"Unsupported line handler type in configuration: {configItem.HandlerTypeValue}, skipping this handler."); + continue; + } + handler.Actions = actions; + handlers.Add(handler); + } + + return handlers; + } + + private static List ParseActionsFromConfig(List actionConfigs) + { + List actions = new List(); + foreach (var actionConfig in actionConfigs) + { + if (actionConfig == null) + { + continue; + } + if (actionConfig.ActionTypeValue == ActionType.OSCAction) + { + var oscActionConfig = (OSCActionConfig)actionConfig; + if (oscActionConfig.ParameterName == null) + { + logger.Warn("OSC Action configuration is missing required field; 'parameterName', skipping this action."); + continue; + } + if (oscActionConfig.Value == null) + { + logger.Warn("OSC Action configuration is missing required field; 'value', skipping this action."); + continue; + } + + actions.Add(new OSCAction(oscActionConfig.ParameterName, oscActionConfig.OscValueType, oscActionConfig.Value)); + continue; + } - return handlers.FirstOrDefault() ?? throw new Exception("No line handlers found in configuration."); + if (actionConfig.ActionTypeValue == ActionType.DelayAction) + { + var delayActionConfig = (DelayActionConfig)actionConfig; + actions.Add(new DelayAction(delayActionConfig.Milliseconds)); + continue; + } + } + + return actions; } } + public enum LineHandlerType { + AvatarChange, + AvatarUnpack, Logging, OnPlayerJoin, OnPlayerNetwork, - Sticker, + PenNetwork, Print, - AvatarChange, - AvatarUnpack, + Sticker, + VTK, WarnKick, - PenNetwork, - VTK + Quit + } + + public enum PatternType + { + Default, + Override } public class LineHandlerConfig { - public LineHandlerType HandlerType { get; set; } - public string Pattern { get; set; } - public List Actions { get; set; } + public LineHandlerType HandlerTypeValue { get; set; } + public PatternType PatternTypeValue { get; set; } + public string? Pattern { get; set; } + public bool LogOutput { get; set; } = true; + public string LogOutputColor { get; set; } = "0m"; + public List Actions { get; set; } = new List(); + } + + + public enum ActionType + { + OSCAction, + DelayAction, + KeyPressAction + } + + public abstract class ActionBase + { + public ActionType ActionTypeValue { get; set; } + } + - public LineHandlerConfig(LineHandlerType handlerType, string pattern, List actions) + public class OSCActionConfig : ActionBase + { + public string? ParameterName { get; set; } + public OscType OscValueType { get; set; } = OscType.Float; + public string? Value { get; set; } + + public OSCActionConfig() { - HandlerType = handlerType; - Pattern = pattern; - Actions = actions; + ActionTypeValue = ActionType.OSCAction; } } - - public class ActionConfig + public class DelayActionConfig : ActionBase { - public string ActionType { get; set; } - public Dictionary Parameters { get; set; } + public int Milliseconds { get; set; } = 1000; + public int DelayMilliseconds { get; internal set; } - public ActionConfig(string actionType, Dictionary parameters) + public DelayActionConfig() { - ActionType = actionType; - Parameters = parameters; + ActionTypeValue = ActionType.DelayAction; } } } \ No newline at end of file diff --git a/tailgrab.csproj b/tailgrab.csproj index 4605a97..265ca94 100644 --- a/tailgrab.csproj +++ b/tailgrab.csproj @@ -5,15 +5,22 @@ net9.0 enable enable - 1.0.0.123 - 1.0.0.123 - 1.0.0.123-beta + 1.0.2.1005 + 1.0.2.1005 + 1.0.2.1005-beta + + + + + + + From 981a7761a539f315dd80830f953233dc927942bb Mon Sep 17 00:00:00 2001 From: Jay Long Date: Wed, 26 Nov 2025 04:46:28 -0600 Subject: [PATCH 4/4] Tweaks to the Logging naming as I didn't like the behaviour and code cleanups --- Actions/Actions.cs | 1 - LineHandlers/PenNetworkIdHandler.cs | 1 - NLog.config | 2 +- Program.cs | 14 ++++++-------- configuration/ConfigurationManager.cs | 1 - 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Actions/Actions.cs b/Actions/Actions.cs index deabfda..843c259 100644 --- a/Actions/Actions.cs +++ b/Actions/Actions.cs @@ -1,4 +1,3 @@ -using System.Threading; using System.Runtime.InteropServices; using BuildSoft.VRChat.Osc; using BuildSoft.VRChat.Osc.Avatar; diff --git a/LineHandlers/PenNetworkIdHandler.cs b/LineHandlers/PenNetworkIdHandler.cs index 1ca0681..8fe058f 100644 --- a/LineHandlers/PenNetworkIdHandler.cs +++ b/LineHandlers/PenNetworkIdHandler.cs @@ -3,7 +3,6 @@ namespace Tailgrab.LineHandler; using System.Text; using System.Text.RegularExpressions; using Tailgrab.PlayerManagement; -using NLog; public class PenNetworkHandler : AbstractLineHandler { diff --git a/NLog.config b/NLog.config index 73acbab..97c860e 100644 --- a/NLog.config +++ b/NLog.config @@ -5,7 +5,7 @@ throwConfigExceptions="true"> - diff --git a/Program.cs b/Program.cs index a1bacce..0488c6b 100644 --- a/Program.cs +++ b/Program.cs @@ -1,7 +1,5 @@ using System.Reflection; using System.Text; -using BuildSoft.VRChat.Osc; -using Tailgrab.Actions; using Tailgrab.LineHandler; using NLog; using Tailgrab.Configuration; @@ -115,24 +113,24 @@ public static async Task Main(string[] args) logger.Info($"Tailgrab Version: {BuildInfo.GetInformationalVersion()}"); - string filePath = VRChatAppDataPath + @"\\"; + string filePath = VRChatAppDataPath + Path.DirectorySeparatorChar; if (args.Length == 0) { - logger.Warn("No path argument provided, defaulting to VRChat log directory."); - Console.WriteLine("Usage: dotnet run "); - Console.WriteLine($"Running without arguments will watch the VRChat log directory at '{filePath}'"); - } else + logger.Warn("No path argument provided, defaulting to VRChat log directory: {filePath}"); + } + else { filePath = args[0]; } if (!Directory.Exists(filePath)) { - logger.Info($"WatchZing VRChat log directory at '{filePath}'"); + logger.Info($"Missing VRChat log directory at '{filePath}'"); return; } ConfigurationManager.LoadLineHandlersFromConfig(HandlerList); + logger.Info($"Watching for log changes from: '{filePath}'"); await WatchPath(filePath); } } diff --git a/configuration/ConfigurationManager.cs b/configuration/ConfigurationManager.cs index 3d5ffa3..9774add 100644 --- a/configuration/ConfigurationManager.cs +++ b/configuration/ConfigurationManager.cs @@ -2,7 +2,6 @@ using Tailgrab.LineHandler; using BuildSoft.VRChat.Osc; using Microsoft.Extensions.Configuration; -using System.Collections.Generic; using NLog; namespace Tailgrab.Configuration