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