PageRenderTime 83ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/private/system/lib/Stuffed/System/Error.pm

http://github.com/eych/Stuffed-System
Perl | 538 lines | 287 code | 108 blank | 143 comment | 49 complexity | d6c92f5ce85fc0c8721e8b97e3316fbe MD5 | raw file
Possible License(s): AGPL-3.0
  1. # ============================================================================
  2. #
  3. # ___
  4. # ,yQ$SSS$Q', ,'yQQQL
  5. # j$$"` `?$' ,d$P"```'$$,
  6. # i_L I$; `$`` `$$,
  7. # ` I$$
  8. # .:yQ$$$$, ; _,d$$'
  9. # ,d$$P"^```?$b, _,' ; ,:d$$P"
  10. # ,d$P"` `"?$$Q#QPw` $d$$P"`
  11. # ,$$" ; `` ;$?'
  12. # $$; ,dI I$;
  13. # `$$, ,d$$$` j$I
  14. # ?$S#S$P'j$' $$; Copyright (c) Stuffed Guys
  15. # `"` j$' __....,,,.__ j$I www.stuffedguys.org
  16. # j$$'"`` ',$$
  17. # I$; ,$$'
  18. # `$$, _.:u$$:`
  19. # "?$$Q##Q$$SP$"^`
  20. # `````
  21. #
  22. # ============================================================================
  23. # Package: Stuffed::System::Error
  24. #
  25. # This class is used mainly to throw errors inside subs, that later are
  26. # displayed in the templates. Also, <__just_die> function is specified to
  27. # CGI::Carp when Stuffed::System initializes as a die handler.
  28. package Stuffed::System::Error;
  29. $VERSION = 1.00;
  30. use strict;
  31. use Stuffed::System;
  32. sub new {
  33. my $class = shift;
  34. my $self = bless({}, $class);
  35. return $self;
  36. }
  37. #use AutoLoader 'AUTOLOAD';
  38. #__END__
  39. # ============================================================================
  40. # Group: Methods for working with web error instance
  41. # Method: setup
  42. #
  43. # Creates and returns new web error instance
  44. #
  45. # Returns:
  46. #
  47. # <Stuffed::System::Error::Web> object
  48. sub setup {
  49. my $self = shift;
  50. my $in = {
  51. __act => undef, # optional if you don't want to throw an error
  52. __sub => undef, # optional if you don't want to throw an error
  53. msg_pkg => undef, # optional if you don't want to throw an error with i18n message id
  54. form => undef,
  55. @_
  56. };
  57. require Stuffed::System::Error::Web;
  58. return Stuffed::System::Error::Web->new(container => $self, %$in);
  59. }
  60. # Method: was_thrown
  61. #
  62. # Checks wether at least one error was already thrown or announced (note:
  63. # setting up an error and throwing it or announcing it are different things).
  64. #
  65. # Returns:
  66. #
  67. # 1 - if error was thrown
  68. # undef - if no error was thrown
  69. sub was_thrown {
  70. my $self = shift;
  71. return ($self->{__stack} and @{$self->{__stack}} ? 1 : undef);
  72. }
  73. # Method: clear
  74. #
  75. # Removes any logged errors from the state, after that was_thrown will return
  76. # undef as if no errors were thrown
  77. #
  78. # Returns:
  79. #
  80. # 1 - always
  81. sub clear {
  82. my $self = shift;
  83. delete $self->{__stack};
  84. return 1;
  85. }
  86. # Method: get_error
  87. #
  88. # Returns the specified <Stuffed::System::Error::Web> object from the stack.
  89. # Stack is used to save errors at the moment when they are thrown. So if no
  90. # errors were thrown yet, this method will return undef.
  91. #
  92. # Parameters:
  93. #
  94. # 1st - specifies what error to return, could be "first", "last" or number of
  95. # the error in the stack (starting from 0)
  96. #
  97. # Returns:
  98. #
  99. # <Stuffed::System::Error::Web> object
  100. sub get_error {
  101. my $self = shift;
  102. my $what = shift;
  103. # return undef if stack is not present or is empty
  104. return undef if not $self->{__stack} or not @{$self->{__stack}};
  105. # return the whole stack of errors if it was not specified what to return,
  106. # or the stack is empty (doesn't exist)
  107. return $self->{__stack} if false($what);
  108. if (lc($what) eq 'first') {
  109. return $self->{__stack}[0];
  110. } elsif (lc($what) eq 'last') {
  111. return $self->{__stack}[$#{$self->{__stack}}];
  112. } elsif ($what =~ /^\d+$/) {
  113. return $self->{__stack}[$what];
  114. }
  115. return undef;
  116. }
  117. # Method: __save_error
  118. #
  119. # Saves error in the stack at the moment when it is thrown. It is used
  120. # internally by <Stuffed::System::Error::Web> class.
  121. #
  122. # Parameters:
  123. #
  124. # 1st - specifies <Stuffed::System::Error::Web> object
  125. #
  126. # Returns:
  127. #
  128. # <Stuffed::System::Error> object
  129. sub __save_error {
  130. my $self = shift;
  131. my $error = shift;
  132. return undef if not $error;
  133. push @{$self->{__stack}}, $error;
  134. return $self;
  135. }
  136. sub __clean_file_for_log {
  137. my $file = shift;
  138. my $path = quotemeta($system->path);
  139. $file =~ s/\s+\(autosplit[^\)]+\)//;
  140. $file =~ s/$path//;
  141. $file =~ s/\.\//\//;
  142. # windows paths
  143. $file =~ s/\\/\//g;
  144. return $file;
  145. }
  146. # ============================================================================
  147. # Group: General error methods
  148. # Method: die
  149. #
  150. # Generally works the same as the core "die" function, if it was invoked
  151. # from inside the Stuffed System. When invoked it prints out headers,
  152. # using <Stuffed::System::Output::__print_header> and then calls <__just_die>.
  153. #
  154. # Parameters:
  155. #
  156. # 1st - specifies the die message
  157. sub die {
  158. my $self = shift;
  159. my $message = shift;
  160. my $in = {
  161. kind_of => undef, # optional, the message will be logged, but process will not die
  162. @_
  163. };
  164. return undef if false($message);
  165. if ($message !~ /\n$/) {
  166. my ($package, $filename, $line) = caller;
  167. $filename = __clean_file_for_log($filename);
  168. $message .= " at $filename line $line\n";
  169. }
  170. $system->out->__print_header if not $in->{kind_of};
  171. __just_die($message, @_);
  172. }
  173. # Method: log
  174. #
  175. # Is used to log an error message to the Stuffed System all errors log, if this type of
  176. # logging is turned on.
  177. sub log {
  178. my $self = shift;
  179. my $in = {
  180. msg => undef, # text of the error message to log
  181. fields => undef, # optional ARRAY ref of form fields related to the error message
  182. stack => undef, # optional call stack (could be slightly changed from the actual one in a die handler, so it is passed as a param)
  183. is_critical => undef, # critical flag for the error (such as coming from a die handler)
  184. @_
  185. };
  186. my ($msg, $fields, $stack, $is_critical) = @$in{qw(msg fields stack is_critical)};
  187. my $config = $system->config;
  188. # logging error in all errors log if log_all_errors option is on in config
  189. return undef if not $config or not $config->get('log_all_errors');
  190. # no new lines are allowed in the message, as all of the message and additional information should be on one line in the log
  191. $msg =~ s/[\r\n]+/ /g;
  192. # turning multiple spaces into one
  193. $msg =~ s/\s+/ /g;
  194. # kill spaces in the beginning and the end of the message
  195. $msg =~ s/^\s+|\s+$//g;
  196. my $stack_line = '';
  197. if (ref $stack ne 'ARRAY' or not @$stack) {
  198. my $counter = 0;
  199. while (my @frame = caller($counter)) {
  200. push @$stack, \@frame;
  201. $counter += 1;
  202. }
  203. }
  204. my $frame = $stack->[0];
  205. my $sub = ( $stack->[1] ? $stack->[1][3] . '()' : 'main()' );
  206. my $file = __clean_file_for_log( $frame->[1] );
  207. $stack_line = "$sub, $file line $frame->[2]";
  208. require Stuffed::System::Utils;
  209. my $ip = Stuffed::System::Utils::get_ip();
  210. my $url = '';
  211. if ($ENV{REQUEST_URI}) {
  212. $url = true($ENV{HTTP_HOST}) ? 'http'.($ENV{HTTPS} eq 'on' ? 's' : '').'://'.$ENV{HTTP_HOST} : '';
  213. $url .= $ENV{REQUEST_URI};
  214. }
  215. my $referrer = $ENV{HTTP_REFERER} || '';
  216. my $filename = $config->get('all_errors_file');
  217. # relative path, we add system path in front
  218. if (true($filename) and $filename !~ /^\//) {
  219. $filename = $system->path . '/' . $filename
  220. }
  221. # file not specified, using default file name and location
  222. elsif (false($filename)) {
  223. $filename = $system->path . '/private/.ht_errors.all.log';
  224. }
  225. my $user_agent = $ENV{HTTP_USER_AGENT};
  226. my $content = '[' . localtime() . '] ' . $ip;
  227. $content .= ' [C]' if $is_critical;
  228. $content .= ' "' . $msg . '"';
  229. $content .= ' "' . ( ref $fields eq 'ARRAY' and @$fields ? join(', ', @$fields) : '' ) . '"';
  230. $content .= ' "' . $stack_line . '"';
  231. $content .= ' "' . $url . '"';
  232. $content .= ' "' . $referrer . '"';
  233. $content .= ' "' . $user_agent . '"';
  234. __append_to_file(
  235. filename => $filename,
  236. content => $content,
  237. );
  238. }
  239. # ============================================================================
  240. # Group: Internal functions and methods, not meant to be used outside this package
  241. sub __append_to_file {
  242. my $in = {
  243. filename => undef,
  244. content => undef,
  245. @_
  246. };
  247. my ($filename, $content) = @$in{qw(filename content)};
  248. return undef if false($filename) or false($content);
  249. return undef if not open(LOG, '>> '.$filename);
  250. flock LOG, 2 | 4;
  251. print LOG $content . "\n";
  252. flock LOG, 8;
  253. close(LOG);
  254. }
  255. # Function: __log_error
  256. #
  257. # Tries to log a specified error to the Stuffed System error log (if logging
  258. # is switched on). The location of the log file is taken from "critical_errors_file"
  259. # parameters in system config. If it is not specified the default location
  260. # is used: "private/.ht_errors.log".
  261. #
  262. # Parameters:
  263. #
  264. # 1st - specifies the error message
  265. sub __log_error {
  266. my $message = shift;
  267. return undef if false($message);
  268. my $config = $system->config;
  269. # logging error in the system error log if log_errors option is on in config
  270. return undef if not $config or not $config->get('log_critical_errors');
  271. my $filename = $config->get('critical_errors_file');
  272. # relative path, we add system path in front
  273. if (true($filename) and $filename !~ /^\//) {
  274. $filename = $system->path . '/' . $filename
  275. }
  276. # file not specified, using default file name and location
  277. elsif (false($filename)) {
  278. $filename = $system->path . '/private/.ht_errors.critical.log';
  279. }
  280. require Stuffed::System::Utils;
  281. my $ip = Stuffed::System::Utils::get_ip();
  282. chomp(my $log_message = $message);
  283. __append_to_file(
  284. filename => $filename,
  285. content => '[' . scalar localtime() . " - $ip] $log_message\n",
  286. );
  287. }
  288. # Function: __just_die
  289. #
  290. # A die handler, it is specified as a reference to CGI::Carp when Stuffed System
  291. # initializes. It will try to log an error to the Stuffed System error
  292. # log (if logging is switched on). At the end, it always stops the system
  293. # (with <Stuffed::System::stop>).
  294. #
  295. # Parameters:
  296. #
  297. # 1st - specifies the die message
  298. sub __just_die {
  299. my $message = shift;
  300. my $in = {
  301. kind_of => undef, # optional, the message will be logged, but process will not die
  302. skip_last_in_stack => undef, # optional, skip last entry in stack
  303. @_
  304. };
  305. my $config = $system->config;
  306. my $trace;
  307. my @stack;
  308. my $counter = 0;
  309. while (my @frame = caller($counter)) {
  310. push @stack, \@frame;
  311. $counter += 1;
  312. }
  313. splice(@stack, 0, 1) if $in->{skip_last_in_stack};
  314. # cutting out everything before the main system eval from the stack
  315. if ($ENV{STUFFED_STACK_START}) {
  316. @stack = reverse @stack;
  317. splice(@stack, 0, $ENV{STUFFED_STACK_START});
  318. @stack = reverse @stack;
  319. }
  320. # cleaning up a bit first
  321. my @final_stack;
  322. for (my $i = 0; $i < scalar @stack; $i++) {
  323. my $sub = ($stack[$i+1] ? $stack[$i+1][3] : '');
  324. # skipping CGI::Carp routines as they always repeat since our die handler
  325. # works through the CGI::Carp's handler
  326. next if $sub eq 'CGI::Carp::die';
  327. if ($sub eq 'Stuffed::System::Error::__just_die') {
  328. # mod_perl 2.0
  329. if (exists $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} == 2) {
  330. ModPerl::Util::exit();
  331. }
  332. # mod_perl 1.0
  333. elsif ($ENV{MOD_PERL}) {
  334. Apache::exit();
  335. }
  336. else {
  337. CORE::exit();
  338. }
  339. }
  340. push @final_stack, $stack[$i];
  341. }
  342. @stack = @final_stack;
  343. my $message_line_info;
  344. for (my $i = 0; $i < scalar @stack; $i++) {
  345. my $frame = $stack[$i];
  346. my $sub = ($stack[$i+1] ? $stack[$i+1][3].'()' : 'main()');
  347. my $file = __clean_file_for_log($frame->[1]);
  348. $message_line_info = " at $file line $frame->[2]." if false $message_line_info;
  349. $trace .= "-- $sub, $file line $frame->[2];\n";
  350. }
  351. # ============================================================================
  352. if ( CGI::Carp::ineval() ) {
  353. CORE::die( $message . ($message !~ /\n$/ ? $message_line_info . "\n" : '' ) );
  354. }
  355. # ============================================================================
  356. # additional logic if this is a DBI error
  357. my $database_info = '';
  358. if ($message =~ /^DBD::mysql/ and $config) {
  359. $database_info = '<main@'.$config->get('db_host').':'.$config->get('db_name').'> ';
  360. if ($config->get('enable_read_db')) {
  361. $database_info .= '<read@'.$config->get('read_db_host').':'.$config->get('read_db_name').'> ';
  362. }
  363. }
  364. # ============================================================================
  365. Stuffed::System::Error->new->log(
  366. msg => $database_info . $message,
  367. stack => \@stack,
  368. is_critical => 1,
  369. );
  370. # ============================================================================
  371. my @add_message;
  372. if ($ENV{REQUEST_URI}) {
  373. my $url = true($ENV{HTTP_HOST}) ? 'http'.($ENV{HTTPS} eq 'on' ? 's' : '').'://'.$ENV{HTTP_HOST} : '';
  374. $url .= $ENV{REQUEST_URI};
  375. push @add_message, "URL: $url";
  376. }
  377. if ($ENV{HTTP_REFERER}) {
  378. push @add_message, "Referrer: $ENV{HTTP_REFERER}";
  379. }
  380. if (true($trace)) {
  381. push @add_message, "Stack trace:\n$trace";
  382. }
  383. # ============================================================================
  384. __log_error(
  385. $database_info . $message . ( @add_message ? ($message !~ /\n$/ ? "\n" : '') . join("\n", @add_message) : '' )
  386. );
  387. return if $in->{kind_of};
  388. $message .= $message_line_info if $message !~ /\n$/;
  389. if ( $system->out->context('web') ) {
  390. $message =~ s/[\r\n]+$//;
  391. $message =~ s/\n/<br>/sg;
  392. my $HTML;
  393. if ($config and $config->get('display_critical_errors')) {
  394. if ($system->out->context('ajax')) {
  395. $HTML = <<HTML;
  396. <div><strong>System error has just occured:</strong></div>
  397. <div>$message</div>
  398. HTML
  399. } else {
  400. $HTML = <<HTML;
  401. <div style="font-size: 11pt;">
  402. <strong style="font-size: 16pt;">System error has just occured:</strong>
  403. <p style="color: #555; padding: 10px; border: 1px dotted black;">$message</p>
  404. We are sorry for any inconvenience this error might have caused. Be assured that we
  405. are already working on solving the problem.
  406. </div>
  407. HTML
  408. }
  409. } else {
  410. if ($system->out->context('ajax')) {
  411. $HTML = <<HTML;
  412. <div><strong>A critical system error has just occured!</strong></div>
  413. <div>We are sorry for any inconvenience this error might have caused. Be assured that we
  414. are already working on solving the problem.</div>
  415. HTML
  416. } else {
  417. $HTML = <<HTML;
  418. <div style="font-size: 11pt;">
  419. <strong style="font-size: 16pt;">A critical system error has just occured!</strong><br><br>
  420. We are sorry for any inconvenience this error might have caused. Be assured that we
  421. are already working on solving the problem.
  422. </div>
  423. HTML
  424. }
  425. }
  426. $message = $HTML;
  427. }
  428. $system->out->error( $message, no_logging => 1 );
  429. }
  430. 1;