-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Log.pm
458 lines (413 loc) · 13.7 KB
/
Log.pm
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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
#########################################################################
# OpenKore - Message Logging Framework
#
# This software is open source, licensed under the GNU General Public
# License, version 2.
# Basically, this means that you're allowed to modify and distribute
# this software. However, if you distribute modified versions, you MUST
# also distribute the source code.
# See http://www.gnu.org/licenses/gpl.html for the full license.
#
#########################################################################
##
# MODULE DESCRIPTION: Message Logging framework
#
# <h3>What is a logging framework and why is it needed?</h3>
#
# Kore used to print messages to the console using print(). There are several
# problems though:
# `l
# - Messages can only be printed to the console. If you want to print it
# to elsewhere you have to resort to all kinds of hacks (take a look
# at the code for sending console output to X-Kore, for example).
# - The messages have no classification. You have a message, but there's
# no easy way for the program to find out what kind of message it is;
# you don't know it's context. This means that the user can't really control
# what kind of messages he does and doesn't want to see.
# - Debug messages are all in the form of print "bla\n" if ($config{'verbose'});
# You can either enable all debug messages, or nothing at all. For developers,
# the huge amount of debug messages can make things look cluttered.
# `l`
#
# The logging framework provides a new way to print messages:
# `l
# - You can print messages (of course).
# - You can classify messages: attaching a context (domain) to a message you print.
# - You can intercept messages and decide what else to do with it. You can write to
# a file, send to X-Kore (based on the message's domain), or whatever you want.
# - You can attach certain colors to messages of a certain domains.
# - You can choose what kind of message you do and do not want to see.
# `l`
#
# The most important functions are:
# Log::message(), Log::warning(), Log::error(), Log::debug()
#
# You pass the following arguments to those functions:
# `l
# - message: The message you want to print.
# - domain: The message domain (context). This is used to classify a message.
# - level: The message's verbosity level. The message will only be printed if this number
# is lower than or equal to $config{'verbose'} (or $config{'debug'} if this is a
# debug message). Important messages should have a low verbosity level,
# unimportant/redundant messages should have a high verbosity level.
# `l`
# Known domains:
# attacked Monster attacks you
# attackedMiss Monster attacks you but miss
# attackMon You attack monster
# attackMonMiss You attack monster but miss
# connection Connection messages
# deal Deal messages
# drop Monster drop related
# emotion Emoticon
# equip Equipment Switching
# gmchat GM chat message
# guildchat Guild chat message
# info View info that's requested by the user (status, guild info, etc.)
# input Waiting for user input
# inventory Inventory related messages
# useItem You used item
# list List of information (monster list, player list, item list, etc.)
# load Loading config files
# menu Menu choices
# npc NPC messages
# party Party/follow related
# partychat Party chat messages
# plugins Messages about plugin handling
# pm Private chat message
# publicchat Public chat message
# route Routing/pathfinding messages
# sold Item sold while vending.
# skill Skill use unrelated to attack
# selfSkill Skills used by yourself
# startup Messages that are printed during startup.
# storage Storage item added/removed
# success An operation succeeded
# syntax Syntax check files
# system System messages
# teleport Teleporting
# xkore X-Kore system messages
# Debug domains:
# ai_attack
# ai_autoCart
# ai_move
# parseInput
# parseMsg
# parseMsg_damage
# parseMsg_presence
# portalRecord
# sendPacket
# ai
# npc
# route
# useTeleport
package Log;
use strict;
use Exporter;
use Time::HiRes;
use base qw(Exporter);
use Modules 'register';
use Globals qw(%config $interface %consoleColors $field);
use Utils::DataStructures qw(binAdd existsInList);
use Utils qw(binAdd existsInList getFormattedDate);
our @EXPORT_OK = qw(message warning error debug);
#################################
#################################
# VARIABLES
#################################
#################################
# The verbosity level for messages. Messages that have a higher verbosity than this will not be printed.
# Low level = important messages. High level = less important messages.
# If you set the current verbosity higher, you will see more messages.
our $warningVerbosity;
our $errorVerbosity;
# Enable/disable printing certain domains to console.
# Usage: $messageConsole{$domain} = $enabled
our %messageConsole;
our %warningConsole;
our %errorConsole;
our %debugConsole;
# Messages can also printed to files. These variables
# contain filenames of the files to print to.
# Usage: @{$messageFiles{$domain}} = (list of filenames)
our %messageFiles;
our %warningFiles;
our %errorFiles;
our %debugFiles;
# Message hooks are stored here
our @hooks;
# Enable/disable adding a timestamp to log files.
our $logTimestamp;
# Enable/disable adding a timestamp to chat logs.
our $chatTimestamp;
# use SelfLoader; 1;
# __DATA__
#################################
#################################
# PRIVATE FUNCTIONS
#################################
#################################
sub MODINIT {
$warningVerbosity = 1;
$errorVerbosity = 1;
$logTimestamp = 1;
$chatTimestamp = 1;
}
sub processMsg {
my $type = shift;
my $message = shift;
my $domain = (shift or "console");
my $level = (shift or 0);
my $currentVerbosity = shift;
my $consoleVar = shift;
my $files = shift;
my (undef, undef, undef, $near) = caller(2);
my (undef, undef, undef, $far) = caller(3);
$currentVerbosity = 1 if ($currentVerbosity eq "");
# Beep on certain domains
$interface->beep() if existsInList($config{beepDomains}, $domain) &&
!(existsInList($config{beepDomains_notInTown}, $domain) &&
$field->isCity);
# Add timestamp if domain was specified in config.txt/showTimeDomains
if (existsInList($config{showTimeDomains}, $domain)) {
my @tmpdate = localtime();
$tmpdate[5] += 1900;
$tmpdate[4]++;
for (my $i = 0; $i < @tmpdate; $i++) {
if ($tmpdate[$i] < 10) {$tmpdate[$i] = "0".$tmpdate[$i]};
}
if (defined (my $format = $config{showTimeDomainsFormat})) {
$format =~ s/H/$tmpdate[2]/g;
$format =~ s/M/$tmpdate[1]/g;
$format =~ s/S/$tmpdate[0]/g;
$format =~ s/y/$tmpdate[5]/g;
$format =~ s/m/$tmpdate[4]/g;
$format =~ s/d/$tmpdate[3]/g;
$message = "$format $message";
} else {
$message = "[$tmpdate[2]:$tmpdate[1]:$tmpdate[0]] $message";
}
};
# Print to console if the current verbosity is high enough
if ($level <= $currentVerbosity) {
$consoleVar->{$domain} = 1 if (!defined($consoleVar->{$domain}));
if ($consoleVar->{$domain}) {
if ($interface) {
$message = "[$domain] " . $message if ($config{showDomain});
my (undef, $microseconds) = Time::HiRes::gettimeofday;
$microseconds = substr($microseconds, 0, 2);
my $message2 = "[".getFormattedDate(int(time)).".$microseconds] ".$message;
if ($config{showTime}) {
$interface->writeOutput($type, $message2, $domain);
} else {
$interface->writeOutput($type, $message, $domain);
}
if ($config{logConsole} &&
open(F, ">>:utf8", $Settings::console_log_file)) {
print F $message2;
close(F);
}
} else {
print $message;
}
}
}
# Print to files
foreach my $file (@{$files->{$domain}}) {
if (open(F, ">>:utf8", "$Settings::logs_folder/$file")) {
print F '['. getFormattedDate(int(time)) .'] ' if ($logTimestamp);
print F $message;
close(F);
}
}
# Call hooks
foreach (@hooks) {
next if (!defined($_));
$_->{'func'}->($type, $domain, $level, $currentVerbosity, $message, $_->{'user_data'}, $near, $far);
}
}
#################################
#################################
# PUBLIC METHODS
#################################
#################################
##
# Log::message(message, [domain], [level])
# Requires: $message must be encoded in UTF-8.
#
# Prints a normal message. See the description for Log.pm for more details
# about the parameters.
sub message {
my ($message, $domain, $level) = @_;
$domain ||= "console";
$level = 5 if existsInList($config{squelchDomains}, $domain);
$level = 0 if existsInList($config{verboseDomains}, $domain);
return processMsg("message", # type
$message,
$domain,
$level,
$config{'verbose'}, # currentVerbosity
\%messageConsole,
\%messageFiles);
}
##
# Log::warning(message, [domain], [level])
#
# Prints a warning message. It warns the user that a possible non-fatal error has occured or will occur.
# See the description for Log.pm for more details about the parameters.
sub warning {
my ($message, $domain, $level) = @_;
$domain ||= "console";
$level = 5 if existsInList($config{squelchDomains}, $domain);
$level = 0 if existsInList($config{verboseDomains}, $domain);
return processMsg("warning",
$message,
$domain,
$level,
$warningVerbosity,
\%warningConsole,
\%warningFiles);
}
##
# Log::error(message, [domain], [level])
# Requires: $message must be encoded in UTF-8.
#
# Prints an error message. It tells the user that a non-recoverable error has
# occured. A "non-recoverable error" could either be a fatal error, or an
# error that prevents the program from performing an action the user requested.
#
# Examples of non-recoverable errors:
# `l
# - Kore receives the "You haven't paid for this account"-packet. The error is
# fatal, so the entire program must exit.
# - The user typed in an invalid/unrecognized command. Kore cannot perform the
# command the user requested, but will not exit because this error is not
# fatal.
# `l`
# See the description for Log.pm for more details about the parameters.
sub error {
my ($message, $domain, $level) = @_;
$domain ||= "console";
return processMsg("error",
$message,
$domain,
$level,
$errorVerbosity,
\%errorConsole,
\%errorFiles);
}
##
# Log::debug(message, [domain], [level])
# Requires: $message must be encoded in UTF-8.
#
# Prints a debugging message. See the description for Log.pm for more details about the parameters.
sub debug {
my ($message, $domain, $level) = @_;
$domain ||= "console";
$level = 1 if (!defined $level);
$level = 0 if (existsInList($config{debugDomains}, $_[1]));
$level = 5 if (existsInList($config{squelchDomains}, $_[1]));
return processMsg("debug",
$message,
$domain,
$level,
(defined $config{'debug'}) ? $config{'debug'} : 0,
\%debugConsole,
\%debugFiles);
}
##
# Log::addHook(r_func, [user_data])
# r_func: A reference to the function to call.
# user_data: Additional data to pass to r_func.
# Returns: An ID which you can use to remove this hook.
#
# Adds a hook. Every time Log::message(), Log::warning(), Log::error() or Log::debug() is called,
# r_func is also called, in the following way:
# <pre>
# r_func->($type, $domain, $level, $globalVerbosity, $message, $user_data);
# $type : One of the following: "message", "warning", "error", "debug".
# $domain : The message's domain.
# $level : The message's own verbosity level.
# $globalVerbosity : The global verbosity level.
# $message : The message itself.
# $user_data : The value of user_data, as passed to addHook.
# $near : The function that called "message", "warning", "error" or "debug"
# $far : The function that called $near
# </pre>
#
# See also: Log::delHook()
#
# Example:
# sub hook {
# my $type = shift; # "message"
# my $domain = shift; # "MyDomain"
# my $level = shift; # 2
# my $globalVerbosity = shift; # 1 (equal to $config{'verbose'})
# my $message = shift; # "Hello World"
# my $user_data = shift; # "my_user_data"
# my $near = shift; # "Commands::cmdWhere"
# my $far = shift; # "Commands::run"
# # Do whatever you want here
# }
# Log::addHook(\&hook, "my_user_data");
#
# $config{'verbose'} = 1;
# # Note that the following function will not print anything to screen,
# # because it's verbosity level is higher than the global verbosity
# # level ($config{'verbose'}).
# Log::message("Hello World", "MyDomain", 2); # hook() will now be called
sub addHook {
my ($r_func, $user_data) = @_;
my %hook;
$hook{func} = $r_func;
$hook{user_data} = $user_data;
return binAdd(\@hooks, \%hook);
}
##
# Log::delHook(ID)
# ID: A hook ID, as returned by addHook().
#
# Removes a hook. r_func will not be called anymore.
#
# Example:
# my $ID = Log::addHook(\&hook);
# Log::message("Hello World", "MyDomain"); # hook() is called
# Log::delHook($ID);
# Log::message("Hello World", "MyDomain"); # hook() is NOT called
sub delHook {
my $ID = shift;
delete $hooks[$ID];
}
##
# Log::parseLogToFile(args,hash)
#
# args has to look like domain=file
# but can look like domain1=file1.txt;domain2=file2.txt,file3.txt
#
# The hash has to be a reference for the output hash.
sub parseLogToFile {
my $args = shift;
my $list = shift;
$args =~ s/\s//g;
my @domains = split (';', $args);
my $files;
foreach my $domain (@domains) {
($domain,$files) = split ('=', $domain);
my @filesArray = split (',', $files);
$list->{$domain} = [];
foreach my $file (@filesArray) {
push(@{$list->{$domain}}, $file);
}
}
}
##
# initLogFiles()
#
# This function should be called everytime config.txt is (re)loaded.
sub initLogFiles {
parseLogToFile($config{logToFile_Messages}, \%messageFiles) if $config{logToFile_Messages};
parseLogToFile($config{logToFile_Warnings}, \%warningFiles) if $config{logToFile_Warnings};
parseLogToFile($config{logToFile_Errors}, \%errorFiles) if $config{logToFile_Errors};
parseLogToFile($config{logToFile_Debug}, \%debugFiles) if $config{logToFile_Debug};
}
return 1;