Logs every admin command (kick/ban/gag/slay/slap/map) with SteamID, target, map and time to MySQL via threaded SQLx, with a flat-file fallback and a csb_log_action native.
csb_admin_logger.sma v1.0.0
1
/*
2
* CSB Admin Logger
3
* Copyright (C) 2026 counter-strike-boost.com
4
*
5
* Logs every admin command (kick, ban, gag, slay, map change and any action
6
* pushed through the csb_log_action native) with the admin's SteamID, name, the
7
* target/args, the current map and a timestamp into a MySQL table via threaded
8
* SQLx. If the database is unreachable it falls back to a flat log file.
9
*
10
* This program is free software: you can redistribute it and/or modify it under
11
* the terms of the GNU General Public License as published by the Free Software
12
* Foundation, either version 3 of the License, or (at your option) any later
13
* version. It is distributed in the hope that it will be useful, but WITHOUT
14
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
* FOR A PARTICULAR PURPOSE. See <https://www.gnu.org/licenses/> for details.
16
*/
17
18
#include <amxmodx>
19
#include <amxmisc>
20
#include <sqlx>
21
22
new const PLUGIN[] = "CSB Admin Logger"
23
new const VERSION[] = "1.0.0"
24
new const AUTHOR[] = "counter-strike-boost.com"
25
26
#define FALLBACK_FILE "addons/amxmodx/logs/csb_admin_actions.log"
27
28
new const g_watched[][] = { "amx_kick", "amx_ban", "amx_banip", "amx_gag", "amx_slay", "amx_slap", "amx_map" }
29
30
new Handle:g_hTuple = Empty_Handle
31
new bool:g_bDbReady = false
32
new bool:g_bTableChecked = false
33
34
new g_pHost, g_pUser, g_pPass, g_pDb
35
36
public plugin_init()
37
{
38
register_plugin(PLUGIN, VERSION, AUTHOR)
39
40
g_pHost = register_cvar("csb_log_sql_host", "127.0.0.1")
41
g_pUser = register_cvar("csb_log_sql_user", "root")
42
g_pPass = register_cvar("csb_log_sql_pass", "")
43
g_pDb = register_cvar("csb_log_sql_db", "amxx")
44
45
for (new i = 0; i < sizeof(g_watched); i++)
46
register_clcmd(g_watched[i], "cmdWatch")
47
}
48
49
public plugin_natives()
50
{
51
register_native("csb_log_action", "native_log_action", 0)
52
}
53
54
public plugin_cfg()
55
{
56
new host[64], user[32], pass[32], db[32]
57
get_pcvar_string(g_pHost, host, charsmax(host))
58
get_pcvar_string(g_pUser, user, charsmax(user))
59
get_pcvar_string(g_pPass, pass, charsmax(pass))
60
get_pcvar_string(g_pDb, db, charsmax(db))
61
62
if (!host[0] || !user[0] || !db[0])
63
{
64
log_amx("[CSB Logger] SQL not configured; using flat-file fallback only.")
65
return
66
}
67
68
g_hTuple = SQL_MakeDbTuple(host, user, pass, db)
69
g_bDbReady = true
70
71
new query[256]
72
formatex(query, charsmax(query),
73
"CREATE TABLE IF NOT EXISTS csb_admin_log (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, \
74
stamp DATETIME, map VARCHAR(64), admin_name VARCHAR(64), admin_auth VARCHAR(48), \
75
target VARCHAR(64), action VARCHAR(255))")
76
77
SQL_ThreadQuery(g_hTuple, "queryCreate", query)
78
}
79
80
public plugin_end()
81
{
82
if (g_hTuple != Empty_Handle)
83
SQL_FreeHandle(g_hTuple)
84
}
85
86
public queryCreate(failstate, Handle:query, error[], errcode, data[], size, Float:queuetime)
87
{
88
if (failstate != TQUERY_SUCCESS)
89
{
90
log_amx("[CSB Logger] table check failed (%d): %s - falling back to file.", errcode, error)
91
g_bDbReady = false
92
return
93
}
94
95
g_bTableChecked = true
96
}
97
98
/* native: csb_log_action(const admin_auth[], const admin_name[], const target[], const action[]) */
99
public native_log_action(plugin, params)
100
{
101
new adminAuth[48], adminName[64], target[64], action[128]
102
get_string(1, adminAuth, charsmax(adminAuth))
103
get_string(2, adminName, charsmax(adminName))
104
get_string(3, target, charsmax(target))
105
get_string(4, action, charsmax(action))
106
107
storeAction(adminName, adminAuth, target, action)
108
return 1
109
}
110
111
public cmdWatch(id)
112
{
113
new cmd[32]
114
read_argv(0, cmd, charsmax(cmd))
115
116
new target[64]
117
read_argv(1, target, charsmax(target))
118
119
new args[128]
120
read_args(args, charsmax(args))
121
remove_quotes(args)
122
123
new adminName[64], adminAuth[48]
124
getName(id, adminName, charsmax(adminName))
125
if (id)
126
get_user_authid(id, adminAuth, charsmax(adminAuth))
127
else
128
copy(adminAuth, charsmax(adminAuth), "CONSOLE")
129
130
new action[160]
131
formatex(action, charsmax(action), "%s %s", cmd, args)
132
133
storeAction(adminName, adminAuth, target, action)
134
return PLUGIN_CONTINUE
135
}
136
137
storeAction(const adminName[], const adminAuth[], const target[], const action[])
138
{
139
new sName[64], sAuth[48], sTarget[64], sAction[160]
140
sanitize(sName, charsmax(sName), adminName)
141
sanitize(sAuth, charsmax(sAuth), adminAuth)
142
sanitize(sTarget, charsmax(sTarget), target)
143
sanitize(sAction, charsmax(sAction), action)
144
145
new map[64]
146
get_mapname(map, charsmax(map))
147
148
if (g_bDbReady && g_hTuple != Empty_Handle)
149
{
150
new query[512]
151
formatex(query, charsmax(query),
152
"INSERT INTO csb_admin_log (stamp, map, admin_name, admin_auth, target, action) \
153
VALUES (NOW(), '%s', '%s', '%s', '%s', '%s')",
154
map, sName, sAuth, sTarget, sAction)
155
156
SQL_ThreadQuery(g_hTuple, "queryInsert", query)
157
}
158
else
159
{
160
writeFallback(map, sName, sAuth, sTarget, sAction)
161
}
162
}
163
164
public queryInsert(failstate, Handle:query, error[], errcode, data[], size, Float:queuetime)
165
{
166
if (failstate != TQUERY_SUCCESS)
167
{
168
log_amx("[CSB Logger] insert failed (%d): %s", errcode, error)
169
g_bDbReady = false
170
}
171
}
172
173
writeFallback(const map[], const name[], const auth[], const target[], const action[])
174
{
175
new stamp[32]
176
get_time("%Y-%m-%d %H:%M:%S", stamp, charsmax(stamp))
177
178
new line[320]
179
formatex(line, charsmax(line), "[%s] map=%s admin=%s (%s) target=%s action=%s",
180
stamp, map, name, auth, target, action)
181
182
write_file(FALLBACK_FILE, line)
183
}
184
185
/* strip characters that would break the single-quoted SQL literal */
186
sanitize(out[], len, const in[])
187
{
188
copy(out, len, in)
189
replace_all(out, len, "'", " ")
190
replace_all(out, len, "\", " ")
191
replace_all(out, len, "^n", " ")
192
}
193
194
getName(id, name[], len)
195
{
196
if (id)
197
get_user_name(id, name, len)
198
else
199
copy(name, len, "CONSOLE")
200
}
201









