using System; using System.Collections.Generic; using System.Globalization; using System.Text; using System.Threading; using Arrowgene.Services.Buffers; using Arrowgene.Services.Logging; using Mhf.Cli.Argument; using Mhf.Server.Logging; namespace Mhf.Cli { public class LogWriter : ISwitchConsumer { private readonly object _consoleLock; private readonly HashSet _packetIdWhitelist; private readonly HashSet _packetIdBlacklist; private readonly ILogger _logger; private readonly Queue _logQueue; private bool _paused; private bool _continueing; public LogWriter() { _logger = LogProvider.Logger(this); _packetIdWhitelist = new HashSet(); _packetIdBlacklist = new HashSet(); _logQueue = new Queue(); _consoleLock = new object(); Switches = new List(); _paused = false; _continueing = false; Reset(); LoadSwitches(); LogProvider.GlobalLogWrite += LogProviderOnGlobalLogWrite; } public List Switches { get; } /// /// --max-packet-size=64 /// public int MaxPacketSize { get; set; } /// /// --no-data=true /// public bool NoData { get; set; } /// /// --log-level=2 /// public int MinLogLevel { get; set; } public void Reset() { MaxPacketSize = -1; NoData = false; MinLogLevel = (int) LogLevel.Debug; _packetIdWhitelist.Clear(); _packetIdBlacklist.Clear(); } public void WhitelistPacket(ushort packetId) { if (_packetIdWhitelist.Contains(packetId)) { _logger.Error($"PacketId:{packetId} is already whitelisted"); return; } _packetIdWhitelist.Add(packetId); } public void BlacklistPacket(ushort packetId) { if (_packetIdBlacklist.Contains(packetId)) { _logger.Error($"PacketId:{packetId} is already blacklisted"); return; } _packetIdBlacklist.Add(packetId); } public void Pause() { _paused = true; } public void Continue() { _continueing = true; while (_logQueue.TryDequeue(out Log log)) { WriteLog(log); } _paused = false; _continueing = false; } private void LoadSwitches() { Switches.Add( new SwitchProperty( "--no-data", "--no-data=true (true|false)", "Don't display packet data", bool.TryParse, (result => NoData = result) ) ); Switches.Add( new SwitchProperty( "--max-packet-size", "--max-packet-size=64 (integer)", "Don't display packet data", int.TryParse, (result => MaxPacketSize = result) ) ); Switches.Add( new SwitchProperty( "--log-level", "--log-level=20 (integer) [Debug=10, Info=20, Error=30]", "Only display logs of the same level or above", int.TryParse, (result => MinLogLevel = result) ) ); Switches.Add( new SwitchProperty( "--clear", "--clear", "Resets all switches to default", SwitchProperty.NoOp, result => Reset() ) ); Switches.Add( new SwitchProperty>( "--b-list", "--b-list=1000,2000,0xAA (PacketId[0xA|10])", "A blacklist that does not logs packets specified", TryParsePacketIdList, results => { AssinPacketIdList(results, BlacklistPacket); } ) ); Switches.Add( new SwitchProperty>( "--w-list", "--w-list=1000,2000,0xAA (PacketId[0xA|10])", "A whitelist that only logs packets specified", TryParsePacketIdList, results => { AssinPacketIdList(results, WhitelistPacket); } ) ); } /// /// parses strings like "1:1000,2000,3:4000" into ServerType and PacketId /// private bool TryParsePacketIdList(string value, out List result) { if (string.IsNullOrEmpty(value)) { result = null; return false; } string[] values = value.Split(","); result = new List(); foreach (string entry in values) { NumberStyles numberStyles; String entryStr; if (entry.StartsWith("0x")) { entryStr = entry.Substring(2); numberStyles = NumberStyles.HexNumber; } else { entryStr = entry; numberStyles = NumberStyles.Integer; } if (!ushort.TryParse(entryStr, numberStyles, null, out ushort val)) { return false; } result.Add(val); } return true; } private void AssinPacketIdList(List results, Action addToPacketList) { foreach (ushort entry in results) { addToPacketList(entry); } } private void LogProviderOnGlobalLogWrite(object sender, LogWriteEventArgs logWriteEventArgs) { while (_continueing) { Thread.Sleep(10000); } if (_paused) { _logQueue.Enqueue(logWriteEventArgs.Log); return; } WriteLog(logWriteEventArgs.Log); } private void WriteLog(Log log) { ConsoleColor consoleColor; string text; object tag = log.Tag; if (tag is MhfLogPacket logPacket) { switch (logPacket.LogType) { case MhfLogType.PacketIn: consoleColor = ConsoleColor.Green; break; case MhfLogType.PacketOut: consoleColor = ConsoleColor.Blue; break; case MhfLogType.PacketUnhandled: consoleColor = ConsoleColor.Red; break; default: consoleColor = ConsoleColor.Gray; break; } text = CreatePacketLog(logPacket); } else { LogLevel logLevel = log.LogLevel; if ((int) logLevel < MinLogLevel) { return; } switch (logLevel) { case LogLevel.Debug: consoleColor = ConsoleColor.DarkCyan; break; case LogLevel.Info: consoleColor = ConsoleColor.Cyan; break; case LogLevel.Error: consoleColor = ConsoleColor.Red; break; default: consoleColor = ConsoleColor.Gray; break; } text = log.ToString(); } if (text == null) { return; } lock (_consoleLock) { Console.ForegroundColor = consoleColor; Console.WriteLine(text); Console.ResetColor(); } } private bool ExcludeLog(ushort packetId) { bool useWhitelist = _packetIdWhitelist.Count > 0; bool whitelisted = _packetIdWhitelist.Contains(packetId); bool blacklisted = _packetIdBlacklist.Contains(packetId); if (useWhitelist && whitelisted) { return false; } if (useWhitelist) { return true; } if (blacklisted) { return true; } return false; } private string CreatePacketLog(MhfLogPacket logPacket) { ushort packetId = logPacket.Id; if (ExcludeLog(packetId)) { return null; } int dataSize = logPacket.Data.Size; StringBuilder sb = new StringBuilder(); sb.Append($"{logPacket.ClientIdentity} Packet Log"); sb.Append(Environment.NewLine); sb.Append("----------"); sb.Append(Environment.NewLine); sb.Append($"[{logPacket.TimeStamp:HH:mm:ss}][Typ:{logPacket.LogType}]"); sb.Append(Environment.NewLine); sb.Append( $"[Id:0x{logPacket.Id:X2}|{logPacket.Id}][BodyLen:{logPacket.Data.Size}][{logPacket.PacketIdName}]"); sb.Append(Environment.NewLine); sb.Append(logPacket.Header.ToLogText()); sb.Append(Environment.NewLine); if (!NoData) { IBuffer data = logPacket.Data; int maxPacketSize = MaxPacketSize; if (maxPacketSize > 0 && dataSize > maxPacketSize) { data = data.Clone(0, maxPacketSize); sb.Append($"- Truncated Data showing {maxPacketSize} of {dataSize} bytes"); sb.Append(Environment.NewLine); } sb.Append("ASCII:"); sb.Append(Environment.NewLine); sb.Append(data.ToAsciiString(true)); sb.Append(Environment.NewLine); sb.Append("HEX:"); sb.Append(Environment.NewLine); sb.Append(data.ToHexString(' ')); sb.Append(Environment.NewLine); } sb.Append("----------"); return sb.ToString(); } } }