-
Notifications
You must be signed in to change notification settings - Fork 0
/
Recorder.pm
205 lines (139 loc) · 4.71 KB
/
Recorder.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
package Jifty::Plugin::Recorder;
use strict;
use warnings;
use base qw/Jifty::Plugin Class::Data::Inheritable/;
__PACKAGE__->mk_accessors(qw/start path loghandle request_time logged_request memory_usage/);
use Time::HiRes 'time';
use Jifty::Util;
use Storable 'nfreeze';
our $VERSION = 0.01;
=head1 NAME
Jifty::Plugin::Recorder - record HTTP requests for playback
=head1 DESCRIPTION
This plugin will log all HTTP requests as YAML. The logfiles can be used by
C<jifty playback> (provided with this plugin) to replay the logged requests.
This can be handy for perfomance tuning, debugging, and testing.
=head1 USAGE
Add the following to your site_config.yml
framework:
Plugins:
- Recorder: {}
=head2 OPTIONS
=over 4
=item path
The path for creating request logs. Default: log/requests. This directory will
be created for you, if necessary.
=item memory_usage
Report how much memory (in bytes) Jifty is taking up. This uses
L<Proc::ProcessTable>. Default is off.
=back
=head1 METHODS
=head2 init
init installs the trigger needed before each HTTP request. It also establishes
the baseline for all times and creates the log path.
=cut
sub init {
my $self = shift;
my %args = (
path => 'log/requests',
memory_usage => 0,
@_,
);
return if $self->_pre_init;
$self->start(time);
$self->memory_usage($args{memory_usage});
if ($args{memory_usage}) {
require Proc::ProcessTable;
}
$self->path(Jifty::Util->absolute_path( $args{path} ));
Jifty::Util->make_path($self->path);
Jifty::Handler->add_trigger(
before_request => sub { $self->before_request(@_) }
);
Jifty::Handler->add_trigger(
before_cleanup => sub { $self->before_cleanup }
);
}
=head2 before_request
Log as much of the request state as we can.
=cut
sub before_request
{
my $self = shift;
my $handler = shift;
my $cgi = shift;
$self->logged_request(0);
$self->request_time(time);
eval {
my $delta = $self->request_time - $self->start;
my $request = {
cgi => nfreeze($cgi),
ENV => \%ENV,
time => $delta,
start => $self->request_time,
};
my $yaml = Jifty::YAML::Dump($request);
print { $self->get_loghandle } $yaml;
$self->logged_request(1);
};
$self->log->error("Unable to append to request log: $@") if $@;
}
=head2 before_cleanup
Append the current user to the request log. This isn't done in one fell swoop
because if the server explodes during a request, we would lose the request's
data for logging.
This, strictly speaking, isn't necessary. But we don't always want to lug the
sessions table around, so this gets us most of the way there.
C<logged_request> is checked to ensure that we don't append the current
user if the current request couldn't be logged for whatever reason (perhaps
a serialization error?).
=cut
sub before_cleanup {
my $self = shift;
if ($self->logged_request) {
eval {
print { $self->get_loghandle } "end: " . time . "\n";
print { $self->get_loghandle } "took: " . (time - $self->request_time) . "\n";
print { $self->get_loghandle } "current_user: " . (Jifty->web->current_user->id || 0) . "\n";
# get memory usage. yes, we really do need to go through these
# motions every request :(
if ($self->memory_usage) {
my $proc = Proc::ProcessTable->new;
for (@{ $proc->table }) {
next unless $_->pid == $$;
print { $self->get_loghandle } "memory: " . ($_->size||'?') . "\n";
return;
}
$self->log->error("Unable to find myself, pid $$, in Proc::ProcessTable.");
}
};
}
}
=head2 get_loghandle
Creates the loghandle. The created file is named C<PATH/BOOTTIME-PID.log>.
Returns C<undef> on error.
=cut
sub get_loghandle {
my $self = shift;
unless ($self->loghandle) {
my $name = sprintf '%s/%d-%d.log',
$self->path,
$self->start,
$$;
open my $loghandle, '>', $name or do {
$self->log->error("Unable to open $name for writing: $!");
return;
};
$loghandle->autoflush(1);
$self->log->info("Logging all HTTP requests to $name.");
$self->loghandle($loghandle);
}
return $self->loghandle;
}
=head1 SEE ALSO
L<Jifty::Plugin::Recorder::Command::Playback>, L<HTTP::Server::Simple::Recorder>
=head1 COPYRIGHT AND LICENSE
Copyright 2007 Best Practical Solutions
This is free software and may be modified and distributed under the same terms as Perl itself.
=cut
1;