TShock/TShockAPI/SqlLog.cs
2018-01-01 02:17:21 -07:00

315 lines
No EOL
8.8 KiB
C#

/*
TShock, a server mod for Terraria
Copyright (C) 2011-2018 Nyx Studios (fka. The TShock Team)
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;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using MySql.Data.MySqlClient;
using TShockAPI.DB;
namespace TShockAPI
{
struct LogInfo
{
public string timestamp;
public string message;
public string caller;
public TraceLevel logLevel;
public override string ToString()
{
return string.Format("Message: {0}: {1}: {2}",
caller, logLevel.ToString().ToUpper(), message);
}
}
/// <summary>
/// Class inheriting ILog for writing logs to TShock's SQL database
/// </summary>
public class SqlLog : ILog, IDisposable
{
private readonly IDbConnection _database;
private readonly TextLog _backupLog;
private readonly List<LogInfo> _failures = new List<LogInfo>(TShock.Config.RevertToTextLogsOnSqlFailures);
private bool _useTextLog;
public string FileName { get; set; }
/// <summary>
/// Sets the database connection and the initial log level.
/// </summary>
/// <param name="db">Database connection</param>
/// <param name="textlogFilepath">File path to a backup text log in case the SQL log fails</param>
/// <param name="clearTextLog"></param>
public SqlLog(IDbConnection db, string textlogFilepath, bool clearTextLog)
{
FileName = string.Format("{0}://database", db.GetSqlType());
_database = db;
_backupLog = new TextLog(textlogFilepath, clearTextLog);
var table = new SqlTable("Logs",
new SqlColumn("ID", MySqlDbType.Int32) {AutoIncrement = true, Primary = true},
new SqlColumn("TimeStamp", MySqlDbType.Text),
new SqlColumn("LogLevel", MySqlDbType.Int32),
new SqlColumn("Caller", MySqlDbType.Text),
new SqlColumn("Message", MySqlDbType.Text)
);
var creator = new SqlTableCreator(db,
db.GetSqlType() == SqlType.Sqlite
? (IQueryBuilder) new SqliteQueryCreator()
: new MysqlQueryCreator());
creator.EnsureTableStructure(table);
}
public bool MayWriteType(TraceLevel type)
{
return type != TraceLevel.Off;
}
/// <summary>
/// Writes data to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void Data(string message)
{
Write(message, TraceLevel.Verbose);
}
/// <summary>
/// Writes data to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void Data(string format, params object[] args)
{
Data(string.Format(format, args));
}
/// <summary>
/// Writes an error to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void Error(string message)
{
Write(message, TraceLevel.Error);
}
/// <summary>
/// Writes an error to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void Error(string format, params object[] args)
{
Error(string.Format(format, args));
}
/// <summary>
/// Writes an error to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void ConsoleError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.Gray;
Write(message, TraceLevel.Error);
}
/// <summary>
/// Writes an error to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void ConsoleError(string format, params object[] args)
{
ConsoleError(string.Format(format, args));
}
/// <summary>
/// Writes a warning to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void Warn(string message)
{
Write(message, TraceLevel.Warning);
}
/// <summary>
/// Writes a warning to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void Warn(string format, params object[] args)
{
Warn(string.Format(format, args));
}
/// <summary>
/// Writes an informative string to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void Info(string message)
{
Write(message, TraceLevel.Info);
}
/// <summary>
/// Writes an informative string to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void Info(string format, params object[] args)
{
Info(string.Format(format, args));
}
/// <summary>
/// Writes an informative string to the log file. Also outputs to the console.
/// </summary>
/// <param name="message">The message to be written.</param>
public void ConsoleInfo(string message)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(message);
Console.ForegroundColor = ConsoleColor.Gray;
Write(message, TraceLevel.Info);
}
/// <summary>
/// Writes an informative string to the log file. Also outputs to the console.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void ConsoleInfo(string format, params object[] args)
{
ConsoleInfo(string.Format(format, args));
}
/// <summary>
/// Writes a debug string to the log file.
/// </summary>
/// <param name="message">The message to be written.</param>
public void Debug(string message)
{
#if DEBUG
Write(message, TraceLevel.Verbose);
#endif
}
/// <summary>
/// Writes a debug string to the log file.
/// </summary>
/// <param name="format">The format of the message to be written.</param>
/// <param name="args">The format arguments.</param>
public void Debug(string format, params object[] args)
{
#if DEBUG
Debug(string.Format(format, args));
#endif
}
public void Write(string message, TraceLevel level)
{
if (!MayWriteType(level))
return;
var caller = "TShock";
var frame = new StackTrace().GetFrame(2);
if (frame != null)
{
var meth = frame.GetMethod();
if (meth != null && meth.DeclaringType != null)
caller = meth.DeclaringType.Name;
}
try
{
if (_useTextLog)
{
_backupLog.Write(message, level);
return;
}
_database.Query("INSERT INTO Logs (TimeStamp, Caller, LogLevel, Message) VALUES (@0, @1, @2, @3)",
DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), caller, (int)level, message);
var success = true;
while (_failures.Count > 0 && success)
{
var info = _failures.First();
try
{
_database.Query("INSERT INTO Logs (TimeStamp, Caller, LogLevel, Message) VALUES (@0, @1, @2, @3)",
info.timestamp, info.caller, (int)info.logLevel, info.message);
}
catch (Exception ex)
{
success = false;
_failures.Add(new LogInfo
{
caller = "TShock",
logLevel = TraceLevel.Error,
message = string.Format("SQL Log insert query failed: {0}", ex),
timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
});
}
if (success)
_failures.RemoveAt(0);
}
}
catch (Exception ex)
{
_backupLog.ConsoleError("SQL Log insert query failed: {0}", ex);
_failures.Add(new LogInfo
{
logLevel = level,
message = message,
caller = caller,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
});
}
if (_failures.Count >= TShock.Config.RevertToTextLogsOnSqlFailures)
{
_useTextLog = true;
_backupLog.ConsoleError("SQL Logging disabled due to errors. Reverting to text logging.");
foreach (var logInfo in _failures)
{
_backupLog.Write(string.Format("SQL log failed at: {0}. {1}", logInfo.timestamp, logInfo),
TraceLevel.Error);
}
_failures.Clear();
}
}
public void Dispose()
{
_backupLog.Dispose();
}
}
}