-
Notifications
You must be signed in to change notification settings - Fork 11
/
logger.inc
271 lines (226 loc) · 7.5 KB
/
logger.inc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// built-in include guard removal
// just in case the user has a local dependency with the same file name
#if defined _inc_logger
#undef _inc_logger
#endif
// custom include-guard to ensure we don't duplicate
#if defined _logger_included
#endinput
#endif
#define _logger_included
#include <a_samp>
#include <crashdetect>
// An event represents a single log event
// a log event contains one or more fields
// a field is composed of a name, an equals sign and a value
// if a value is a string, it is enclosed in quotes
// if a value is a string that contains quotes, they are escaped
#if !defined MAX_EVENT_FIELDS
#define MAX_EVENT_FIELDS (32)
#endif
#if !defined MAX_FIELD_NAME
#define MAX_FIELD_NAME (16)
#endif
#if !defined MAX_FIELD_VALUE
#define MAX_FIELD_VALUE (256)
#endif
#define MAX_FIELD_LENGTH (MAX_FIELD_NAME + 3 + MAX_FIELD_VALUE)
#define MAX_EVENT_LENGTH (MAX_EVENT_FIELDS * MAX_FIELD_LENGTH)
forward LOGGER_FIELD:Logger_I(const field[], value);
forward LOGGER_FIELD:Logger_B(const field[], value);
forward LOGGER_FIELD:Logger_X(const field[], value);
forward LOGGER_FIELD:Logger_F(const field[], Float:value);
forward LOGGER_FIELD:Logger_S(const field[], const value[]);
static
FieldBuffer[MAX_FIELD_LENGTH],
EventBuffer[MAX_EVENT_LENGTH];
// log is a structured log event printer, it takes a text parameter which describes the event
// followed by zero or more `LOGGER_FIELD` strings which are generated using `Logger_I`, `Logger_F` and `Logger_S`.
// example:
// Logger_Log("an event has happened with values",
// Logger_I("worldid", 4),
// Logger_F("health", 64.5),
// Logger_S("message", "tim said \"hello\".")
// );
// this would be formatted as:
// text="an event has happened with values" worldid=4 health=64.500000 message="tim said \"hello\"."
// quotes in messages are escaped in order to make parsing the logs easier.
stock Logger_Log(const text[], LOGGER_FIELD:...) {
new total = numargs();
format(EventBuffer, sizeof EventBuffer, "lvl=\"info\" %s",
_:Logger_S("msg", text)
);
if (total == 1) {
print(EventBuffer);
return;
}
for(new arg = 1; arg < total && arg < MAX_EVENT_FIELDS; ++arg) {
new field[MAX_FIELD_LENGTH];
for(new ch; ch < MAX_FIELD_LENGTH; ++ch) {
field[ch] = getarg(arg, ch);
if(field[ch] == EOS) {
strcat(EventBuffer, " ");
strcat(EventBuffer, field);
break;
}
}
}
print(EventBuffer);
}
// dbg is a conditional version of log, it behaves the same but with one extra parameter which is
// the name of the debug handler. Internally, this is just an SVar and simply checks if the value
// is non-zero before continuing the print the event.
stock Logger_Dbg(const handler[], const text[], LOGGER_FIELD:...) {
if(GetSVarInt(handler) == 0) {
return;
}
new total = numargs();
format(EventBuffer, sizeof EventBuffer, "lvl=\"debug\" %s",
_:Logger_S("msg", text)
);
if (total == 2) {
print(EventBuffer);
return;
}
for(new arg = 2; arg < total && arg < MAX_EVENT_FIELDS; ++arg) {
new field[MAX_FIELD_LENGTH];
for(new ch; ch < MAX_FIELD_LENGTH; ++ch) {
field[ch] = getarg(arg, ch);
if(field[ch] == EOS) {
strcat(EventBuffer, " ");
strcat(EventBuffer, field);
break;
}
}
}
// todo: process crashdetect backtrace to grab call site
print(EventBuffer);
}
stock Logger_Err(const text[], LOGGER_FIELD:...) {
new total = numargs();
format(EventBuffer, sizeof EventBuffer, "lvl=\"error\" %s",
_:Logger_S("msg", text)
);
if (total == 1) {
_PrintBufferAmxBacktrace();
return;
}
for(new arg = 1; arg < total && arg < MAX_EVENT_FIELDS; ++arg) {
new field[MAX_FIELD_LENGTH];
for(new ch; ch < MAX_FIELD_LENGTH; ++ch) {
field[ch] = getarg(arg, ch);
if(field[ch] == EOS) {
strcat(EventBuffer, " ");
strcat(EventBuffer, field);
break;
}
}
}
_PrintBufferAmxBacktrace();
}
stock Logger_Fatal(const text[], LOGGER_FIELD:...) {
new total = numargs();
format(EventBuffer, sizeof EventBuffer, "lvl=\"fatal\" %s",
_:Logger_S("msg", text)
);
if (total == 1) {
_PrintBufferAmxBacktrace();
return;
}
for(new arg = 1; arg < total && arg < MAX_EVENT_FIELDS; ++arg) {
new field[MAX_FIELD_LENGTH];
for(new ch; ch < MAX_FIELD_LENGTH; ++ch) {
field[ch] = getarg(arg, ch);
if(field[ch] == EOS) {
strcat(EventBuffer, " ");
strcat(EventBuffer, field);
break;
}
}
}
// trigger a crash to escape the gamemode
_PrintBufferAmxBacktrace(true);
}
static stock _PrintBufferAmxBacktrace(bool:crash = false) {
print(EventBuffer);
PrintBacktrace();
if(crash) {
new File:f = fopen("nonexistentfile", io_read), tmp[1];
fread(f, tmp);
fclose(f);
}
}
stock Logger_ToggleDebug(const handler[], bool:toggle) {
if(toggle) {
SetSVarInt(handler, 1);
} else {
DeleteSVar(handler);
}
return 1;
}
// Logger_I is a log field converter for integers, it takes a named integer and
// returns a log field representation of it.
stock LOGGER_FIELD:Logger_I(const field[], value) {
format(FieldBuffer, sizeof(FieldBuffer), "%s=%d", field, value);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_B is a log field converter for integers, it takes a named integer and
// returns a log field representation of it in binary.
stock LOGGER_FIELD:Logger_B(const field[], value) {
format(FieldBuffer, sizeof(FieldBuffer), "%s=%b", field, value);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_X is a log field converter for integers, it takes a named integer and
// returns a log field representation of it in hexadecimal.
stock LOGGER_FIELD:Logger_X(const field[], value) {
format(FieldBuffer, sizeof(FieldBuffer), "%s=%x", field, value);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_F is a log field converter for floats, it takes a named float and
// returns a log field representation of it.
stock LOGGER_FIELD:Logger_F(const field[], Float:value) {
format(FieldBuffer, sizeof(FieldBuffer), "%s=%f", field, value);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_S is a log field converter for strings, it takes a named string and
// returns a log field representation of it.
stock LOGGER_FIELD:Logger_S(const field[], const value[]) {
new quoted[MAX_FIELD_VALUE];
Logger_EscapeQuote(value, MAX_FIELD_VALUE, quoted, MAX_FIELD_VALUE);
format(FieldBuffer, sizeof(FieldBuffer), "%s=\"%s\"", field, quoted);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_A is a log field converter for arrays, it takes a named array and
// returns a log field representation of it where each cell is printed.
stock LOGGER_FIELD:Logger_A(const field[], const value[], len = sizeof value) {
new array[MAX_FIELD_VALUE];
format(array, sizeof array, "%d", array, value[0]);
for(new i = 1; i < len; i++) {
format(array, sizeof array, "%s, %d", array, value[i]);
}
format(FieldBuffer, sizeof(FieldBuffer), "%s=\"[%s]\"", field, array);
return LOGGER_FIELD:FieldBuffer;
}
// Logger_P is a log field converter for player names, it takes a player ID and
// returns a log field with the player's name.
stock LOGGER_FIELD:Logger_P(playerid) {
if(!IsPlayerConnected(playerid)) {
format(FieldBuffer, sizeof(FieldBuffer), "player=\"(disconnected)\"");
} else {
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, MAX_PLAYER_NAME);
format(FieldBuffer, sizeof(FieldBuffer), "player=\"%s\"", name);
}
return LOGGER_FIELD:FieldBuffer;
}
// quote_escape returns a copy of value with all quotes escaped with backslashes
stock Logger_EscapeQuote(const input[], inputLen, output[], outputLen) {
new j;
for(new i; input[i] != EOS && i < inputLen && j < (outputLen - 1); ++i) {
if(input[i] == '"') {
output[j++] = '\\';
}
output[j++] = input[i];
}
output[j] = EOS;
}