initial commit
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Database;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
class ConsoleAppender : Appender
|
||||
{
|
||||
public ConsoleAppender(byte id, string name, LogLevel level, AppenderFlags flags) : base(id, name, level, flags)
|
||||
{
|
||||
_consoleColor = new[]
|
||||
{
|
||||
ConsoleColor.White,
|
||||
ConsoleColor.White,
|
||||
ConsoleColor.Gray,
|
||||
ConsoleColor.Green,
|
||||
ConsoleColor.Yellow,
|
||||
ConsoleColor.Red,
|
||||
ConsoleColor.Blue
|
||||
};
|
||||
}
|
||||
|
||||
public override void _write(LogMessage message)
|
||||
{
|
||||
Console.ForegroundColor = _consoleColor[(int)message.level];
|
||||
Console.WriteLine(message.prefix + message.text);
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public override AppenderType GetAppenderType()
|
||||
{
|
||||
return AppenderType.Console;
|
||||
}
|
||||
|
||||
ConsoleColor[] _consoleColor;
|
||||
}
|
||||
|
||||
class FileAppender : Appender, IDisposable
|
||||
{
|
||||
public FileAppender(byte id, string name, LogLevel level, string fileName, string logDir, AppenderFlags flags) : base(id, name, level, flags)
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
_fileName = fileName;
|
||||
_logDir = logDir;
|
||||
_dynamicName = _fileName.Contains("{0}");
|
||||
|
||||
if (_dynamicName)
|
||||
{
|
||||
Directory.CreateDirectory(logDir + "/" + _fileName.Substring(0, _fileName.IndexOf('/') + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
_logStream = OpenFile(_fileName, FileMode.Create);
|
||||
}
|
||||
|
||||
FileStream OpenFile(string filename, FileMode mode)
|
||||
{
|
||||
return new FileStream(_logDir + "/" + filename, mode, FileAccess.Write, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
public override void _write(LogMessage message)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
var logBytes = Encoding.Unicode.GetBytes(message.prefix + message.text + "\r\n");
|
||||
|
||||
if (_dynamicName)
|
||||
{
|
||||
var logStream = OpenFile(string.Format(_fileName, message.dynamicName), FileMode.Append);
|
||||
logStream.Write(logBytes, 0, logBytes.Length);
|
||||
logStream.Flush();
|
||||
logStream.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_logStream.Write(logBytes, 0, logBytes.Length);
|
||||
_logStream.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override AppenderType GetAppenderType()
|
||||
{
|
||||
return AppenderType.File;
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_logStream.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
string _fileName;
|
||||
string _logDir;
|
||||
bool _dynamicName;
|
||||
FileStream _logStream;
|
||||
object locker = new object();
|
||||
}
|
||||
|
||||
class DBAppender : Appender
|
||||
{
|
||||
public DBAppender(byte id, string name, LogLevel level) : base(id, name, level) { }
|
||||
|
||||
public override void _write(LogMessage message)
|
||||
{
|
||||
// Avoid infinite loop, PExecute triggers Logging with "sql.sql" type
|
||||
if (!enabled || message.type == LogFilter.Sql)
|
||||
return;
|
||||
|
||||
PreparedStatement stmt = DB.Login.GetPreparedStatement(LoginStatements.INS_LOG);
|
||||
stmt.AddValue(0, Time.DateTimeToUnixTime(message.mtime));
|
||||
stmt.AddValue(1, realmId);
|
||||
stmt.AddValue(2, message.type);
|
||||
stmt.AddValue(3, (byte)message.level);
|
||||
stmt.AddValue(4, message.text);
|
||||
DB.Login.Execute(stmt);
|
||||
}
|
||||
|
||||
public override AppenderType GetAppenderType()
|
||||
{
|
||||
return AppenderType.DB;
|
||||
}
|
||||
|
||||
public override void setRealmId(uint _realmId)
|
||||
{
|
||||
enabled = true;
|
||||
realmId = _realmId;
|
||||
}
|
||||
|
||||
uint realmId;
|
||||
bool enabled;
|
||||
}
|
||||
|
||||
abstract class Appender
|
||||
{
|
||||
public Appender(byte id, string name, LogLevel level = LogLevel.Disabled, AppenderFlags flags = AppenderFlags.None)
|
||||
{
|
||||
_id = id;
|
||||
_name = name;
|
||||
_level = level;
|
||||
_flags = flags;
|
||||
}
|
||||
|
||||
public void Write(LogMessage message)
|
||||
{
|
||||
if (_level == LogLevel.Disabled || _level > message.level)
|
||||
return;
|
||||
|
||||
StringBuilder ss = new StringBuilder();
|
||||
|
||||
if (_flags.HasAnyFlag(AppenderFlags.PrefixTimestamp))
|
||||
ss.AppendFormat("{0} ", message.mtime.ToString("MM/dd/yyyy HH:mm:ss"));
|
||||
|
||||
if (_flags.HasAnyFlag(AppenderFlags.PrefixLogLevel))
|
||||
ss.AppendFormat("{0}: ", message.level);
|
||||
|
||||
if (_flags.HasAnyFlag(AppenderFlags.PrefixLogFilterType))
|
||||
ss.AppendFormat("[{0}] ", message.type);
|
||||
|
||||
message.prefix = ss.ToString();
|
||||
_write(message);
|
||||
}
|
||||
|
||||
public abstract void _write(LogMessage message);
|
||||
|
||||
public byte getId()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
public string getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public abstract AppenderType GetAppenderType();
|
||||
|
||||
public virtual void setRealmId(uint realmId) { }
|
||||
|
||||
public void setLogLevel(LogLevel level)
|
||||
{
|
||||
_level = level;
|
||||
}
|
||||
|
||||
byte _id;
|
||||
string _name;
|
||||
LogLevel _level;
|
||||
AppenderFlags _flags;
|
||||
}
|
||||
|
||||
class LogMessage
|
||||
{
|
||||
public LogMessage(LogLevel _level, LogFilter _type, string _text)
|
||||
{
|
||||
level = _level;
|
||||
type = _type;
|
||||
text = _text;
|
||||
mtime = DateTime.Now;
|
||||
}
|
||||
|
||||
public LogLevel level;
|
||||
public LogFilter type;
|
||||
public string text;
|
||||
public string prefix;
|
||||
public string dynamicName;
|
||||
public DateTime mtime;
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Collections;
|
||||
using Framework.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class Log
|
||||
{
|
||||
static Log()
|
||||
{
|
||||
m_logsDir = ConfigMgr.GetDefaultValue("LogsDir", "");
|
||||
|
||||
foreach (var appenderName in ConfigMgr.GetKeysByString("Appender."))
|
||||
{
|
||||
CreateAppenderFromConfig(appenderName);
|
||||
}
|
||||
|
||||
foreach (var loggerName in ConfigMgr.GetKeysByString("Logger."))
|
||||
{
|
||||
CreateLoggerFromConfig(loggerName);
|
||||
}
|
||||
|
||||
// Bad config configuration, creating default config
|
||||
if (!loggers.ContainsKey(LogFilter.Server))
|
||||
{
|
||||
Console.WriteLine("Wrong Loggers configuration. Review your Logger config section.\nCreating default loggers [Server (Info)] to console\n");
|
||||
|
||||
loggers.Clear();
|
||||
appenders.Clear();
|
||||
|
||||
byte id = NextAppenderId();
|
||||
|
||||
var appender = new ConsoleAppender(id, "Console", LogLevel.Debug, AppenderFlags.None);
|
||||
appenders[id] = appender;
|
||||
|
||||
var serverLogger = new Logger("Server", LogLevel.Error);
|
||||
serverLogger.addAppender(id, appender);
|
||||
loggers[LogFilter.Server] = serverLogger;
|
||||
}
|
||||
|
||||
outInfo(LogFilter.Server, @" ____ __ ");
|
||||
outInfo(LogFilter.Server, @"/\ _`\ /\ \ ");
|
||||
outInfo(LogFilter.Server, @"\ \ \/\_\ __ __ _____\ \ \___ __ _ __ ");
|
||||
outInfo(LogFilter.Server, @" \ \ \/_/_/\ \/\ \/\ '__`\ \ _ `\ /'__`\/\`'__\");
|
||||
outInfo(LogFilter.Server, @" \ \ \L\ \ \ \_\ \ \ \L\ \ \ \ \ \/\ __/\ \ \/ ");
|
||||
outInfo(LogFilter.Server, @" \ \____/\/`____ \ \ ,__/\ \_\ \_\ \____\\ \_\ ");
|
||||
outInfo(LogFilter.Server, @" \/___/ `/___/> \ \ \/ \/_/\/_/\/____/ \/_/ ");
|
||||
outInfo(LogFilter.Server, @" /\___/\ \_\ ");
|
||||
outInfo(LogFilter.Server, @" \/__/ \/_/ Core");
|
||||
outInfo(LogFilter.Server, "\r");
|
||||
}
|
||||
|
||||
static bool ShouldLog(LogFilter type, LogLevel level)
|
||||
{
|
||||
Logger logger = GetLoggerByType(type);
|
||||
if (logger == null)
|
||||
return false;
|
||||
|
||||
LogLevel logLevel = logger.getLogLevel();
|
||||
return logLevel != LogLevel.Disabled && logLevel <= level;
|
||||
}
|
||||
|
||||
public static void outInfo(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Info))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Info, text, args);
|
||||
}
|
||||
|
||||
public static void outWarn(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Warn))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Warn, text, args);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void outDebug(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Debug))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Debug, text, args);
|
||||
}
|
||||
|
||||
public static void outError(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Error))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Error, text, args);
|
||||
}
|
||||
|
||||
public static void outException(Exception ex, [CallerMemberName]string memberName = "")
|
||||
{
|
||||
if (!ShouldLog(LogFilter.Server, LogLevel.Fatal))
|
||||
return;
|
||||
|
||||
outMessage(LogFilter.Server, LogLevel.Fatal, "MemberName: {0} ExceptionMessage: {1}", memberName, ex.Message);
|
||||
}
|
||||
|
||||
public static void outFatal(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Fatal))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Fatal, text, args);
|
||||
}
|
||||
|
||||
public static void outTrace(LogFilter type, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(type, LogLevel.Trace))
|
||||
return;
|
||||
|
||||
outMessage(type, LogLevel.Trace, text, args);
|
||||
}
|
||||
|
||||
public static void outCommand(uint accountId, string text, params object[] args)
|
||||
{
|
||||
if (!ShouldLog(LogFilter.Commands, LogLevel.Info))
|
||||
return;
|
||||
|
||||
var msg = new LogMessage(LogLevel.Info, LogFilter.Commands, string.Format(text, args));
|
||||
msg.dynamicName = accountId.ToString();
|
||||
|
||||
Logger logger = GetLoggerByType(LogFilter.Commands);
|
||||
logger.write(msg);
|
||||
}
|
||||
|
||||
static void outMessage(LogFilter type, LogLevel level, string text, params object[] args)
|
||||
{
|
||||
Logger logger = GetLoggerByType(type);
|
||||
logger.write(new LogMessage(level, type, string.Format(text, args)));
|
||||
}
|
||||
|
||||
static byte NextAppenderId()
|
||||
{
|
||||
return AppenderId++;
|
||||
}
|
||||
|
||||
static void CreateAppenderFromConfig(string appenderName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(appenderName))
|
||||
return;
|
||||
|
||||
string options = ConfigMgr.GetDefaultValue(appenderName, "");
|
||||
var tokens = new StringArray(options, ',');
|
||||
string name = appenderName.Substring(9);
|
||||
|
||||
if (tokens.Length < 2)
|
||||
{
|
||||
Console.WriteLine("Log.CreateAppenderFromConfig: Wrong configuration for appender {0}. Config line: {1}", name, options);
|
||||
return;
|
||||
}
|
||||
|
||||
AppenderFlags flags = AppenderFlags.None;
|
||||
AppenderType type = (AppenderType)uint.Parse(tokens[0]);
|
||||
LogLevel level = (LogLevel)uint.Parse(tokens[1]);
|
||||
|
||||
if (level > LogLevel.Fatal)
|
||||
{
|
||||
Console.WriteLine("Log.CreateAppenderFromConfig: Wrong Log Level {0} for appender {1}\n", level, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokens.Length > 2)
|
||||
flags = (AppenderFlags)uint.Parse(tokens[2]);
|
||||
|
||||
byte id = NextAppenderId();
|
||||
switch (type)
|
||||
{
|
||||
case AppenderType.Console:
|
||||
{
|
||||
var appender = new ConsoleAppender(id, name, level, flags);
|
||||
appenders[id] = appender;
|
||||
break;
|
||||
}
|
||||
case AppenderType.File:
|
||||
{
|
||||
string filename = "";
|
||||
if (tokens.Length < 4)
|
||||
{
|
||||
if (name != "Server")
|
||||
{
|
||||
Console.WriteLine("Log.CreateAppenderFromConfig: Missing file name for appender {0}", name);
|
||||
return;
|
||||
}
|
||||
|
||||
filename = Process.GetCurrentProcess().ProcessName + ".log";
|
||||
}
|
||||
else
|
||||
filename = tokens[3];
|
||||
|
||||
appenders[id] = new FileAppender(id, name, level, filename, m_logsDir, flags);
|
||||
break;
|
||||
}
|
||||
case AppenderType.DB:
|
||||
{
|
||||
appenders[id] = new DBAppender(id, name, level);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Console.WriteLine("Log.CreateAppenderFromConfig: Unknown type {0} for appender {1}", type, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void CreateLoggerFromConfig(string appenderName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(appenderName))
|
||||
return;
|
||||
|
||||
LogLevel level = LogLevel.Disabled;
|
||||
string name = appenderName.Substring(7);
|
||||
|
||||
string options = ConfigMgr.GetDefaultValue(appenderName, "");
|
||||
if (string.IsNullOrEmpty(options))
|
||||
{
|
||||
Console.WriteLine("Log.CreateLoggerFromConfig: Missing config option Logger.{0}", name);
|
||||
return;
|
||||
}
|
||||
var tokens = new StringArray(options, ',');
|
||||
|
||||
LogFilter type = name.ToEnum<LogFilter>();
|
||||
if (loggers.ContainsKey(type))
|
||||
{
|
||||
Console.WriteLine("Error while configuring Logger {0}. Already defined", name);
|
||||
return;
|
||||
}
|
||||
|
||||
level = (LogLevel)uint.Parse(tokens[0]);
|
||||
if (level > LogLevel.Fatal)
|
||||
{
|
||||
Console.WriteLine("Log.CreateLoggerFromConfig: Wrong Log Level {0} for logger {1}", type, name);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger logger = new Logger(name, level);
|
||||
|
||||
int i = 0;
|
||||
var ss = new StringArray(tokens[1], ' ');
|
||||
while (i < ss.Length)
|
||||
{
|
||||
var str = ss[i++];
|
||||
Appender appender = GetAppenderByName(str);
|
||||
if (appender == null)
|
||||
Console.WriteLine("Error while configuring Appender {0} in Logger {1}. Appender does not exist", str, name);
|
||||
else
|
||||
logger.addAppender(appender.getId(), appender);
|
||||
}
|
||||
|
||||
loggers[type] = logger;
|
||||
}
|
||||
|
||||
static Appender GetAppenderByName(string name)
|
||||
{
|
||||
return appenders.First(p => p.Value.getName() == name).Value;
|
||||
}
|
||||
|
||||
static Logger GetLoggerByType(LogFilter type)
|
||||
{
|
||||
if (loggers.ContainsKey(type))
|
||||
return loggers[type];
|
||||
|
||||
string typeString = type.ToString();
|
||||
|
||||
int index = 1;
|
||||
for (; index < typeString.Length - 1; ++index)
|
||||
{
|
||||
if (char.IsUpper(typeString[index]))
|
||||
break;
|
||||
}
|
||||
|
||||
typeString = typeString.Substring(0, index);
|
||||
if (typeString.IsEmpty())
|
||||
return null;
|
||||
|
||||
LogFilter parentLogger;
|
||||
if (!Enum.TryParse(typeString, out parentLogger))
|
||||
return null;
|
||||
|
||||
return GetLoggerByType(parentLogger);
|
||||
}
|
||||
|
||||
public static bool SetLogLevel(string name, string newLevelc, bool isLogger = true)
|
||||
{
|
||||
LogLevel newLevel = (LogLevel)uint.Parse(newLevelc);
|
||||
if (newLevel < 0)
|
||||
return false;
|
||||
|
||||
if (isLogger)
|
||||
{
|
||||
foreach (var logger in loggers.Values)
|
||||
{
|
||||
if (logger.getName() == name)
|
||||
{
|
||||
logger.setLogLevel(newLevel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Appender appender = GetAppenderByName(name);
|
||||
if (appender == null)
|
||||
return false;
|
||||
|
||||
appender.setLogLevel(newLevel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SetRealmId(uint id)
|
||||
{
|
||||
foreach (var appender in appenders.Values)
|
||||
appender.setRealmId(id);
|
||||
}
|
||||
|
||||
static string GetLogsDir() { return m_logsDir; }
|
||||
|
||||
static Dictionary<byte, Appender> appenders = new Dictionary<byte, Appender>();
|
||||
static Dictionary<LogFilter, Logger> loggers = new Dictionary<LogFilter, Logger>();
|
||||
static string m_logsDir;
|
||||
static byte AppenderId;
|
||||
}
|
||||
|
||||
enum AppenderType
|
||||
{
|
||||
None,
|
||||
Console,
|
||||
File,
|
||||
DB
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum AppenderFlags
|
||||
{
|
||||
None = 0x00,
|
||||
PrefixTimestamp = 0x01,
|
||||
PrefixLogLevel = 0x02,
|
||||
PrefixLogFilterType = 0x04,
|
||||
}
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
Disabled = 0,
|
||||
Trace = 1,
|
||||
Debug = 2,
|
||||
Info = 3,
|
||||
Warn = 4,
|
||||
Error = 5,
|
||||
Fatal = 6,
|
||||
Max = 6
|
||||
}
|
||||
|
||||
public enum LogFilter
|
||||
{
|
||||
Misc,
|
||||
Achievement,
|
||||
Addon,
|
||||
Ahbot,
|
||||
AreaTrigger,
|
||||
Arena,
|
||||
Auctionhouse,
|
||||
Battlefield,
|
||||
Battleground,
|
||||
BattlegroundReportPvpAfk,
|
||||
Calendar,
|
||||
ChatLog,
|
||||
ChatSystem,
|
||||
Cheat,
|
||||
Commands,
|
||||
Condition,
|
||||
Garrison,
|
||||
Gameevent,
|
||||
Guild,
|
||||
Lfg,
|
||||
Loot,
|
||||
MapsScript,
|
||||
Maps,
|
||||
Movement,
|
||||
Network,
|
||||
Outdoorpvp,
|
||||
Pet,
|
||||
Player,
|
||||
PlayerCharacter,
|
||||
PlayerDump,
|
||||
PlayerItems,
|
||||
PlayerLoading,
|
||||
PlayerSkills,
|
||||
Pool,
|
||||
Rbac,
|
||||
Realmlist,
|
||||
Scenario,
|
||||
Scenes,
|
||||
Scripts,
|
||||
ScriptsAi,
|
||||
Server,
|
||||
ServerLoading,
|
||||
ServiceProtobuf,
|
||||
Session,
|
||||
SessionRpc,
|
||||
Spells,
|
||||
SpellsPeriodic,
|
||||
Sql,
|
||||
SqlDev,
|
||||
SqlDriver,
|
||||
SqlUpdates,
|
||||
Transport,
|
||||
Unit,
|
||||
Vehicle,
|
||||
Warden,
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
class Logger
|
||||
{
|
||||
public Logger(string _name, LogLevel _level)
|
||||
{
|
||||
name = _name;
|
||||
level = _level;
|
||||
}
|
||||
|
||||
public void addAppender(byte id, Appender appender)
|
||||
{
|
||||
appenders[id] = appender;
|
||||
}
|
||||
|
||||
public void delAppender(byte id)
|
||||
{
|
||||
appenders.Remove(id);
|
||||
}
|
||||
|
||||
public void setLogLevel(LogLevel _level)
|
||||
{
|
||||
level = _level;
|
||||
}
|
||||
|
||||
public string getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public LogLevel getLogLevel()
|
||||
{
|
||||
return level;
|
||||
}
|
||||
|
||||
public void write(LogMessage message)
|
||||
{
|
||||
if (level == 0 || level > message.level || string.IsNullOrEmpty(message.text))
|
||||
return;
|
||||
|
||||
foreach (var appender in appenders.Values)
|
||||
appender.Write(message);
|
||||
}
|
||||
|
||||
string name;
|
||||
LogLevel level;
|
||||
Dictionary<byte, Appender> appenders = new Dictionary<byte, Appender>();
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
class Metric
|
||||
{
|
||||
void Initialize(string realmName, Action overallStatusLogger)
|
||||
{
|
||||
_realmName = realmName;
|
||||
_batchTimer = new Timer();
|
||||
_overallStatusTimer = new Timer();
|
||||
_overallStatusLogger = overallStatusLogger;
|
||||
LoadFromConfigs();
|
||||
}
|
||||
|
||||
bool Connect()
|
||||
{
|
||||
_dataStream.Connect(_hostname, _port);
|
||||
auto error = _dataStream.Client.error();
|
||||
if (error)
|
||||
{
|
||||
TC_LOG_ERROR("metric", "Error connecting to '%s:%s', disabling Metric. Error message : %s",
|
||||
_hostname.c_str(), _port.c_str(), error.message().c_str());
|
||||
_enabled = false;
|
||||
return false;
|
||||
}
|
||||
_dataStream.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadFromConfigs()
|
||||
{
|
||||
bool previousValue = _enabled;
|
||||
_enabled = sConfigMgr.GetBoolDefault("Metric.Enable", false);
|
||||
_updateInterval = sConfigMgr.GetIntDefault("Metric.Interval", 10);
|
||||
if (_updateInterval < 1)
|
||||
{
|
||||
TC_LOG_ERROR("metric", "'Metric.Interval' config set to %d, overriding to 1.", _updateInterval);
|
||||
_updateInterval = 1;
|
||||
}
|
||||
|
||||
_overallStatusTimerInterval = sConfigMgr.GetIntDefault("Metric.OverallStatusInterval", 1);
|
||||
if (_overallStatusTimerInterval < 1)
|
||||
{
|
||||
TC_LOG_ERROR("metric", "'Metric.OverallStatusInterval' config set to %d, overriding to 1.", _overallStatusTimerInterval);
|
||||
_overallStatusTimerInterval = 1;
|
||||
}
|
||||
|
||||
// Schedule a send at this point only if the config changed from Disabled to Enabled.
|
||||
// Cancel any scheduled operation if the config changed from Enabled to Disabled.
|
||||
if (_enabled && !previousValue)
|
||||
{
|
||||
std::string connectionInfo = sConfigMgr.GetStringDefault("Metric.ConnectionInfo", "");
|
||||
if (connectionInfo.empty())
|
||||
{
|
||||
TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
|
||||
return;
|
||||
}
|
||||
|
||||
Tokenizer tokens(connectionInfo, ';');
|
||||
if (tokens.size() != 3)
|
||||
{
|
||||
TC_LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file.");
|
||||
return;
|
||||
}
|
||||
|
||||
_hostname.assign(tokens[0]);
|
||||
_port.assign(tokens[1]);
|
||||
_databaseName.assign(tokens[2]);
|
||||
Connect();
|
||||
|
||||
ScheduleSend();
|
||||
ScheduleOverallStatusLog();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_overallStatusTimerTriggered)
|
||||
{
|
||||
_overallStatusTimerTriggered = false;
|
||||
_overallStatusLogger();
|
||||
}
|
||||
}
|
||||
|
||||
void LogEvent(string category, string title, string description)
|
||||
{
|
||||
MetricData data = new MetricData();
|
||||
data.Category = category;
|
||||
data.Timestamp = system_clock::now();
|
||||
data.Type = METRIC_DATA_EVENT;
|
||||
data.Title = title;
|
||||
data.Text = description;
|
||||
|
||||
_queuedData.Enqueue(data);
|
||||
}
|
||||
|
||||
void SendBatch()
|
||||
{
|
||||
std::stringstream batchedData;
|
||||
MetricData data;
|
||||
bool firstLoop = true;
|
||||
while (_queuedData.Dequeue(data))
|
||||
{
|
||||
if (!firstLoop)
|
||||
batchedData << "\n";
|
||||
|
||||
batchedData << data.Category;
|
||||
if (!_realmName.empty())
|
||||
batchedData << ",realm=" << _realmName;
|
||||
|
||||
batchedData << " ";
|
||||
|
||||
switch (data.Type)
|
||||
{
|
||||
case METRIC_DATA_VALUE:
|
||||
batchedData << "value=" << data.Value;
|
||||
break;
|
||||
case METRIC_DATA_EVENT:
|
||||
batchedData << "title=\"" << data.Title << "\",text=\"" << data.Text << "\"";
|
||||
break;
|
||||
}
|
||||
|
||||
batchedData << " ";
|
||||
|
||||
batchedData << std::to_string(duration_cast<nanoseconds>(data.Timestamp.time_since_epoch()).count());
|
||||
|
||||
firstLoop = false;
|
||||
delete data;
|
||||
}
|
||||
|
||||
// Check if there's any data to send
|
||||
if (batchedData.tellp() == std::streampos(0))
|
||||
{
|
||||
ScheduleSend();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_dataStream.good() && !Connect())
|
||||
return;
|
||||
|
||||
var blah = new InfluxDB.Net.InfluxDb(_hostname, "root", "root", InfluxDB.Net.Enums.InfluxVersion.v010x);
|
||||
InfluxDB.Net.Models.Serie b = new InfluxDB.Net.Models.Serie();
|
||||
b.Columns
|
||||
_dataStream.UploadString( << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
|
||||
_dataStream << "Host: " << _hostname << ":" << _port << "\r\n";
|
||||
_dataStream << "Accept: */*\r\n";
|
||||
_dataStream << "Content-Type: application/octet-stream\r\n";
|
||||
_dataStream << "Content-Transfer-Encoding: binary\r\n";
|
||||
|
||||
_dataStream << "Content-Length: " << std::to_string(batchedData.tellp()) << "\r\n\r\n";
|
||||
_dataStream << batchedData.rdbuf();
|
||||
|
||||
std::string http_version;
|
||||
_dataStream >> http_version;
|
||||
unsigned int status_code = 0;
|
||||
_dataStream >> status_code;
|
||||
if (status_code != 204)
|
||||
{
|
||||
TC_LOG_ERROR("metric", "Error sending data, returned HTTP code: %u", status_code);
|
||||
}
|
||||
|
||||
// Read and ignore the status description
|
||||
std::string status_description;
|
||||
std::getline(_dataStream, status_description);
|
||||
// Read headers
|
||||
std::string header;
|
||||
while (std::getline(_dataStream, header) && header != "\r")
|
||||
{
|
||||
if (header == "Connection: close\r")
|
||||
_dataStream.close();
|
||||
}
|
||||
|
||||
ScheduleSend();
|
||||
}
|
||||
|
||||
void ScheduleSend()
|
||||
{
|
||||
if (_enabled)
|
||||
{
|
||||
_batchTimer.expires_from_now(boost::posix_time::seconds(_updateInterval));
|
||||
_batchTimer.async_wait(std::bind(&SendBatch, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
_dataStream.close();
|
||||
MetricData* data;
|
||||
// Clear the queue
|
||||
while (_queuedData.Dequeue(data))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void ForceSend()
|
||||
{
|
||||
// Send what's queued only if io_service is stopped (so only on shutdown)
|
||||
if (_enabled && _batchTimer.get_io_service().stopped())
|
||||
SendBatch();
|
||||
}
|
||||
|
||||
void ScheduleOverallStatusLog()
|
||||
{
|
||||
if (_enabled)
|
||||
{
|
||||
_overallStatusTimer.expires_from_now(boost::posix_time::seconds(_overallStatusTimerInterval));
|
||||
_overallStatusTimer.async_wait([this](const boost::system::error_code&)
|
||||
{
|
||||
_overallStatusTimerTriggered = true;
|
||||
ScheduleOverallStatusLog();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static string FormatInfluxDBValue<T>(T value) { return Convert.ToString(value) + 'i'; }
|
||||
|
||||
static string FormatInfluxDBValue(string value)
|
||||
{
|
||||
return '"' + boost::replace_all_copy(value, "\"", "\\\"") + '"';
|
||||
}
|
||||
static string FormatInfluxDBValue(bool value) { return value ? "t" : "f"; }
|
||||
static string FormatInfluxDBValue(char[] value) { return FormatInfluxDBValue(new string(value)); }
|
||||
static string FormatInfluxDBValue(double value) { return Convert.ToString(value); }
|
||||
static string FormatInfluxDBValue(float value) { return FormatInfluxDBValue((double)value); }
|
||||
|
||||
void LogValue<T>(string category, T value)
|
||||
{
|
||||
MetricData data = new MetricData();
|
||||
data.Category = category;
|
||||
data.Timestamp = DateTime.Now;
|
||||
data.Type = MetricDataType.Value;
|
||||
data.Value = FormatInfluxDBValue(value);
|
||||
|
||||
_queuedData.Enqueue(data);
|
||||
}
|
||||
|
||||
System.Net.WebClient _dataStream;
|
||||
MPSCQueue<MetricData> _queuedData;
|
||||
Timer _batchTimer;
|
||||
Timer _overallStatusTimer;
|
||||
int _updateInterval = 0;
|
||||
int _overallStatusTimerInterval = 0;
|
||||
bool _enabled = false;
|
||||
bool _overallStatusTimerTriggered = false;
|
||||
string _hostname;
|
||||
string _port;
|
||||
string _databaseName;
|
||||
Action _overallStatusLogger;
|
||||
string _realmName;
|
||||
}
|
||||
|
||||
struct MetricData
|
||||
{
|
||||
public string Category;
|
||||
public DateTime Timestamp;
|
||||
public MetricDataType Type;
|
||||
|
||||
// LogValue-specific fields
|
||||
public string Value;
|
||||
|
||||
// LogEvent-specific fields
|
||||
public string Title;
|
||||
public string Text;
|
||||
}
|
||||
|
||||
enum MetricDataType
|
||||
{
|
||||
Value,
|
||||
Event
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user