1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2017 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #ifndef DP_SRV_SERVERLOG_H
21 #define DP_SRV_SERVERLOG_H
22 
23 #include <QDateTime>
24 #include <QHostAddress>
25 
26 #include "../libshared/util/ulid.h"
27 
28 class QJsonObject;
29 
30 namespace server {
31 
32 class ServerLog;
33 
34 /**
35  * @brief Server log entry
36  */
37 class Log {
38 	Q_GADGET
39 public:
40 	enum class Level {
41 		Error, // severe message (requires admin attention)
42 		Warn,  // acceptable errors
43 		Info,  // useful info for moderators
44 		Debug, // useful info for developers
45 	};
46 	Q_ENUM(Level)
47 
48 	enum class Topic {
49 		Join,      // user joined a session
50 		Leave,     // user left a session
51 		Kick,      // user was kicked
52 		Ban,       // user was banned
53 		Unban,     // a ban was lifted
54 		Op,        // user was granted OP
55 		Deop,      // OP status was removed
56 		Mute,      // User was muted
57 		Unmute,    // User was unmuted
58 		Trust,     // User was tagged as trusted
59 		Untrust,   // User's trusted tag was removed
60 		BadData,   // Received an invalid message from a client
61 		RuleBreak, // User tried to use a command they're not allowed to
62 		PubList,   // Session announcement
63 		Status     // General stuff
64 	};
65 	Q_ENUM(Topic)
66 
Log()67 	Log() : m_timestamp(QDateTime::currentDateTimeUtc()), m_level(Level::Warn), m_topic(Topic::Status) { }
Log(const QDateTime & ts,const QString & sessionId,const QString & user,Level level,Topic topic,const QString message)68 	Log(const QDateTime &ts, const QString &sessionId, const QString &user, Level level, Topic topic, const QString message)
69 		: m_timestamp(ts), m_session(sessionId), m_user(user), m_level(level), m_topic(topic), m_message(message)
70 		{ }
71 
72 	//! Get the entry timestamp
timestamp()73 	QDateTime timestamp() const { return m_timestamp; }
74 
75 	//! Get the session ID (blank if not pertinent to any session)
session()76 	QString session() const { return m_session; }
77 
78 	//! Get the user info triplet (ID;IP;name) or empty if not pertinent to any user
user()79 	QString user() const { return m_user; }
80 
81 	//! Get the log entry severity level
level()82 	Level level() const { return m_level; }
83 
84 	//! What's this log entry about?
topic()85 	Topic topic() const { return m_topic; }
86 
87 	//! Get the freeform message part
message()88 	QString message() const { return m_message; }
89 
about(Level l,Topic t)90 	Log &about(Level l, Topic t) { m_level=l; m_topic=t; return *this; }
user(uint8_t id,const QHostAddress & ip,const QString & name)91 	Log &user(uint8_t id, const QHostAddress &ip, const QString &name) { m_user = QStringLiteral("%1;%2;%3").arg(int(id)).arg(ip.toString()).arg(name); return *this; }
session(const QString & id)92 	Log &session(const QString &id) { m_session=id; return *this; }
message(const QString & msg)93 	Log &message(const QString &msg) { m_message=msg; return *this; }
94 
95 	inline void to(ServerLog *logger);
96 
97 	/**
98 	 * @brief Get the log message as a string
99 	 * @param abridged if true, the timestamp and log level are omitted
100 	 */
101 	QString toString(bool abridged=false) const;
102 
103 	enum JsonOption {
104 		NoOptions = 0,
105 		NoPrivateData = 0x01,
106 		NoSession = 0x02
107 	};
108 	Q_DECLARE_FLAGS(JsonOptions, JsonOption)
109 
110 	/**
111 	 * @brief Get the log message as a JSON object
112 	 *
113 	 * If noPrivateData is true, this may return a blank object if the whole
114 	 * log entry contains information only the server administrator should see.
115 	 *
116 	 * @param noPrivateData if true, private data (user IP address) is omitted
117 	 */
118 	QJsonObject toJson(JsonOptions options=NoOptions) const;
119 
120 private:
121 	QDateTime m_timestamp;
122 	QString m_session;
123 	QString m_user;
124 	Level m_level;
125 	Topic m_topic;
126 	QString m_message;
127 };
128 
129 Q_DECLARE_OPERATORS_FOR_FLAGS(Log::JsonOptions)
130 
131 class ServerLog;
132 
133 /**
134  * @brief Log query builder
135  */
136 class ServerLogQuery {
137 public:
ServerLogQuery(const ServerLog & log)138 	ServerLogQuery(const ServerLog &log) : m_log(log), m_offset(0), m_limit(0), m_atleast(Log::Level::Debug) { }
139 
session(const QString & id)140 	ServerLogQuery &session(const QString &id) { m_session = id; return *this; }
page(int page,int entriesPerPage)141 	ServerLogQuery &page(int page, int entriesPerPage) { m_offset = page*entriesPerPage; m_limit=entriesPerPage; return *this; }
after(const QDateTime & ts)142 	ServerLogQuery &after(const QDateTime &ts) { m_after = ts; return *this; }
atleast(Log::Level level)143 	ServerLogQuery &atleast(Log::Level level) { m_atleast = level; return *this; }
144 
isFiltered()145 	bool isFiltered() const { return !m_session.isNull() || m_offset>0 || m_limit>0; }
146 	QList<Log> get() const;
147 
148 private:
149 	const ServerLog &m_log;
150 	QString m_session;
151 	int m_offset;
152 	int m_limit;
153 	Log::Level m_atleast;
154 	QDateTime m_after;
155 };
156 
157 /**
158  * @brief Abstract base class for server logger implementations
159  */
160 class ServerLog
161 {
162 public:
ServerLog()163 	ServerLog() : m_silent(false) { }
164 	virtual ~ServerLog() = default;
165 
166 	//! Don't log messages to stderr
setSilent(bool silent)167 	void setSilent(bool silent) { m_silent = silent; }
168 
169 	/**
170 	 * @brief Log a message
171 	 *
172 	 * @param entry
173 	 */
174 	void logMessage(const Log &entry);
175 
176 	/**
177 	 * @brief Get all available log messages that match the given filters
178 	 *
179 	 * @param session get only log entries for this session
180 	 * @param after get messages whose timestamp is greater than this
181 	 * @param atleast minimum log level
182 	 * @param offset ignore first *offset* messages
183 	 * @param limit return at most this many messages
184 	 */
185 	virtual QList<Log> getLogEntries(const QString &session, const QDateTime &after, Log::Level atleast, int offset, int limit) const = 0;
186 
187 	/**
188 	 * @brief Return a query builder
189 	 * @return
190 	 */
query()191 	ServerLogQuery query() const { return ServerLogQuery(*this); }
192 
193 protected:
194 	virtual void storeMessage(const Log &entry) = 0;
195 
196 private:
197 	bool m_silent;
198 };
199 
get()200 inline QList<Log> ServerLogQuery::get() const {
201 	return m_log.getLogEntries(m_session, m_after, m_atleast, m_offset, m_limit);
202 }
203 
to(ServerLog * logger)204 void Log::to(ServerLog *logger)
205 {
206 	if(logger)
207 		logger->logMessage(*this);
208 	else
209 		qWarning("logger(null): %s", qPrintable(toString()));
210 }
211 
212 /**
213  * @brief A simple ServerLog implementation that keeps the latest messages in memory
214  */
215 class InMemoryLog : public ServerLog
216 {
217 public:
InMemoryLog()218 	InMemoryLog() : m_limit(1000) { }
219 	void setHistoryLimit(int limit);
220 
221 	QList<Log> getLogEntries(const QString &session, const QDateTime &after, Log::Level atleast, int offset, int limit) const override;
222 
223 protected:
224 	void storeMessage(const Log &entry) override;
225 
226 private:
227 	QList<Log> m_history;
228 	int m_limit;
229 };
230 
231 }
232 
233 #endif
234