release 0.006000
[p5sagit/Log-Contextual.git] / lib / Log / Contextual.pm
CommitLineData
0daa11f3 1package Log::Contextual;
2
a2777569 3use strict;
4use warnings;
2033c911 5
f61bcdd6 6our $VERSION = '0.006000';
b42fe5d2 7$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
2033c911 8
ae9785e2 9my @levels = qw(debug trace warn info error fatal);
10
675503c7 11use Exporter::Declare;
12use Exporter::Declare::Export::Generator;
f11f9542 13use Data::Dumper::Concise;
5b094c87 14use Scalar::Util 'blessed';
2033c911 15
249b9eb6 16my @dlog = ((map "Dlog_$_", @levels), (map "DlogS_$_", @levels));
17
18my @log = ((map "log_$_", @levels), (map "logS_$_", @levels));
19
b144ba01 20eval {
21 require Log::Log4perl;
22 die if $Log::Log4perl::VERSION < 1.29;
23 Log::Log4perl->wrapper_register(__PACKAGE__)
24};
25
5fd26f45 26# ____ is because tags must have at least one export and we don't want to
27# export anything but the levels selected
6ae293d7 28sub ____ { }
5fd26f45 29
249b9eb6 30exports ('____',
31 @dlog, @log,
32 qw( set_logger with_logger )
33);
f11f9542 34
5fd26f45 35export_tag dlog => ('____');
36export_tag log => ('____');
675503c7 37import_arguments qw(logger package_logger default_logger);
38
139ded54 39sub router {
0092c26a 40 our $Router_Instance ||= do {
41 require Log::Contextual::Router;
42 Log::Contextual::Router->new
6ae293d7 43 }
0092c26a 44}
45
e06303cd 46sub default_import {
47 my ($class) = shift;
48
49 die 'Log::Contextual does not have a default import list';
50
51 ()
52}
53
6ae293d7 54sub arg_logger { $_[1] }
55sub arg_levels { $_[1] || [qw(debug trace warn info error fatal)] }
8112b699 56sub arg_package_logger { $_[1] }
57sub arg_default_logger { $_[1] }
58
675503c7 59sub before_import {
60 my ($class, $importer, $spec) = @_;
6ae293d7 61 my $router = $class->router;
62 my $exports = $spec->exports;
63 my %router_args = (
64 exporter => $class,
65 target => $importer,
66 arguments => $spec->argument_info
67 );
f11f9542 68
e06303cd 69 my @tags = $class->default_import($spec)
6ae293d7 70 if $spec->config->{default};
675503c7 71
e06303cd 72 for (@tags) {
73 die "only tags are supported for defaults at this time"
74 unless $_ =~ /^:(.*)$/;
75
76 $spec->config->{$1} = 1;
77 }
78
a5454e75 79 $router->before_import(%router_args);
8112b699 80
84d7d9ee 81 if ($exports->{'&set_logger'}) {
82 die ref($router) . " does not support set_logger()"
83 unless $router->does('Log::Contextual::Role::Router::SetLogger');
4d605164 84
84d7d9ee 85 $spec->add_export('&set_logger', sub { $router->set_logger(@_) })
86 }
4d605164 87
84d7d9ee 88 if ($exports->{'&with_logger'}) {
89 die ref($router) . " does not support with_logger()"
90 unless $router->does('Log::Contextual::Role::Router::WithLogger');
4d605164 91
84d7d9ee 92 $spec->add_export('&with_logger', sub { $router->with_logger(@_) })
93 }
4d605164 94
5fd26f45 95 my @levels = @{$class->arg_levels($spec->config->{levels})};
675503c7 96 for my $level (@levels) {
249b9eb6 97 if ($spec->config->{log} || $exports->{"&log_$level"}) {
6ae293d7 98 $spec->add_export(
99 "&log_$level",
100 sub (&@) {
101 my ($code, @args) = @_;
102 $router->handle_log_request(
103 exporter => $class,
104 caller_package => scalar(caller),
105 caller_level => 1,
106 message_level => $level,
107 message_sub => $code,
108 message_args => \@args,
109 );
110 return @args;
111 });
249b9eb6 112 }
113 if ($spec->config->{log} || $exports->{"&logS_$level"}) {
6ae293d7 114 $spec->add_export(
115 "&logS_$level",
116 sub (&@) {
117 my ($code, @args) = @_;
118 $router->handle_log_request(
119 exporter => $class,
120 caller_package => scalar(caller),
121 caller_level => 1,
122 message_level => $level,
123 message_sub => $code,
124 message_args => \@args,
125 );
126 return $args[0];
127 });
675503c7 128 }
249b9eb6 129 if ($spec->config->{dlog} || $exports->{"&Dlog_$level"}) {
6ae293d7 130 $spec->add_export(
131 "&Dlog_$level",
132 sub (&@) {
133 my ($code, @args) = @_;
134 my $wrapped = sub {
135 local $_ = (@_ ? Data::Dumper::Concise::Dumper @_ : '()');
136 &$code;
137 };
138 $router->handle_log_request(
139 exporter => $class,
140 caller_package => scalar(caller),
141 caller_level => 1,
142 message_level => $level,
143 message_sub => $wrapped,
144 message_args => \@args,
145 );
146 return @args;
147 });
249b9eb6 148 }
149 if ($spec->config->{dlog} || $exports->{"&DlogS_$level"}) {
6ae293d7 150 $spec->add_export(
151 "&DlogS_$level",
152 sub (&$) {
153 my ($code, $ref) = @_;
154 my $wrapped = sub {
155 local $_ = Data::Dumper::Concise::Dumper($_[0]);
156 &$code;
157 };
158 $router->handle_log_request(
159 exporter => $class,
160 caller_package => scalar(caller),
161 caller_level => 1,
162 message_level => $level,
163 message_sub => $wrapped,
164 message_args => [$ref],
165 );
166 return $ref;
167 });
a2777569 168 }
169 }
675503c7 170}
171
a5454e75 172sub after_import {
173 my ($class, $importer, $spec) = @_;
6ae293d7 174 my %router_args = (
175 exporter => $class,
176 target => $importer,
177 arguments => $spec->argument_info
178 );
a5454e75 179 $class->router->after_import(%router_args);
180}
709d11fe 181
31285520 182for (qw(set with)) {
183 no strict 'refs';
184 my $sub = "${_}_logger";
185 *{"Log::Contextual::$sub"} = sub {
186 die "$sub is no longer a direct sub in Log::Contextual. " .
187 'Note that this feature was never tested nor documented. ' .
188 "Please fix your code to import $sub instead of trying to use it directly"
189 }
190}
191
0daa11f3 1921;
0a3750e2 193
194__END__
195
2daff231 196=head1 NAME
197
8bc568d2 198Log::Contextual - Simple logging interface with a contextual log
2daff231 199
200=head1 SYNOPSIS
201
9b8e24d5 202 use Log::Contextual qw( :log :dlog set_logger with_logger );
5b094c87 203 use Log::Contextual::SimpleLogger;
204 use Log::Log4perl ':easy';
205 Log::Log4perl->easy_init($DEBUG);
2daff231 206
5b094c87 207 my $logger = Log::Log4perl->get_logger;
208
209 set_logger $logger;
2daff231 210
9b8e24d5 211 log_debug { 'program started' };
2daff231 212
213 sub foo {
f9bf084b 214
215 my $minilogger = Log::Contextual::SimpleLogger->new({
216 levels => [qw( trace debug )]
217 });
218
2ae9c121 219 my @args = @_;
220
f9bf084b 221 with_logger $minilogger => sub {
21431192 222 log_trace { 'foo entered' };
2ae9c121 223 my ($foo, $bar) = Dlog_trace { "params for foo: $_" } @args;
2daff231 224 # ...
21431192 225 log_trace { 'foo left' };
f9bf084b 226 };
2daff231 227 }
228
5b094c87 229 foo();
230
9fe4eeb3 231Beginning with version 1.008 L<Log::Dispatchouli> also works out of the box
232with C<Log::Contextual>:
233
234 use Log::Contextual qw( :log :dlog set_logger );
235 use Log::Dispatchouli;
236 my $ld = Log::Dispatchouli->new({
237 ident => 'slrtbrfst',
238 to_stderr => 1,
239 debug => 1,
240 });
241
242 set_logger $ld;
243
244 log_debug { 'program started' };
245
2daff231 246=head1 DESCRIPTION
247
30d7027a 248Major benefits:
249
250=over 2
251
252=item * Efficient
253
254The logging functions take blocks, so if a log level is disabled, the
255block will not run:
256
257 # the following won't run if debug is off
258 log_debug { "the new count in the database is " . $rs->count };
259
260Similarly, the C<D> prefixed methods only C<Dumper> the input if the level is
261enabled.
262
263=item * Handy
264
265The logging functions return their arguments, so you can stick them in
266the middle of expressions:
267
268 for (log_debug { "downloading:\n" . join qq(\n), @_ } @urls) { ... }
269
270=item * Generic
271
272C<Log::Contextual> is an interface for all major loggers. If you log through
273C<Log::Contextual> you will be able to swap underlying loggers later.
274
275=item * Powerful
276
39cd2f65 277C<Log::Contextual> chooses which logger to use based on L<< user defined C<CodeRef>s|/LOGGER CODEREF >>.
30d7027a 278Normally you don't need to know this, but you can take advantage of it when you
279need to later
280
281=item * Scalable
282
283If you just want to add logging to your extremely basic application, start with
284L<Log::Contextual::SimpleLogger> and then as your needs grow you can switch to
285L<Log::Dispatchouli> or L<Log::Dispatch> or L<Log::Log4perl> or whatever else.
286
287=back
288
289This module is a simple interface to extensible logging. It exists to
290abstract your logging interface so that logging is as painless as possible,
291while still allowing you to switch from one logger to another.
3dc9bd3c 292
30d7027a 293It is bundled with a really basic logger, L<Log::Contextual::SimpleLogger>,
294but in general you should use a real logger instead of that. For something
295more serious but not overly complicated, try L<Log::Dispatchouli> (see
296L</SYNOPSIS> for example.)
a2af6976 297
e36f2183 298=head1 A WORK IN PROGRESS
299
300This module is certainly not complete, but we will not break the interface
301lightly, so I would say it's safe to use in production code. The main result
302from that at this point is that doing:
303
304 use Log::Contextual;
305
306will die as we do not yet know what the defaults should be. If it turns out
307that nearly everyone uses the C<:log> tag and C<:dlog> is really rare, we'll
308probably make C<:log> the default. But only time and usage will tell.
309
310=head1 IMPORT OPTIONS
311
312See L</SETTING DEFAULT IMPORT OPTIONS> for information on setting these project
313wide.
3dc9bd3c 314
c154d18a 315=head2 -logger
316
3dc9bd3c 317When you import this module you may use C<-logger> as a shortcut for
436c4b82 318L</set_logger>, for example:
3dc9bd3c 319
320 use Log::Contextual::SimpleLogger;
9b8e24d5 321 use Log::Contextual qw( :dlog ),
322 -logger => Log::Contextual::SimpleLogger->new({ levels => [qw( debug )] });
3dc9bd3c 323
324sometimes you might want to have the logger handy for other stuff, in which
325case you might try something like the following:
326
327 my $var_log;
328 BEGIN { $var_log = VarLogger->new }
9b8e24d5 329 use Log::Contextual qw( :dlog ), -logger => $var_log;
3dc9bd3c 330
5fd26f45 331=head2 -levels
332
333The C<-levels> import option allows you to define exactly which levels your
334logger supports. So the default,
335C<< [qw(debug trace warn info error fatal)] >>, works great for
336L<Log::Log4perl>, but it doesn't support the levels for L<Log::Dispatch>. But
337supporting those levels is as easy as doing
338
339 use Log::Contextual
340 -levels => [qw( debug info notice warning error critical alert emergency )];
341
e2b4b29c 342=head2 -package_logger
343
344The C<-package_logger> import option is similar to the C<-logger> import option
44d86a53 345except C<-package_logger> sets the logger for the current package.
e2b4b29c 346
347Unlike L</-default_logger>, C<-package_logger> cannot be overridden with
348L</set_logger>.
349
350 package My::Package;
351 use Log::Contextual::SimpleLogger;
352 use Log::Contextual qw( :log ),
353 -package_logger => Log::Contextual::WarnLogger->new({
354 env_prefix => 'MY_PACKAGE'
355 });
356
357If you are interested in using this package for a module you are putting on
358CPAN we recommend L<Log::Contextual::WarnLogger> for your package logger.
359
c154d18a 360=head2 -default_logger
361
362The C<-default_logger> import option is similar to the C<-logger> import option
44d86a53 363except C<-default_logger> sets the B<default> logger for the current package.
c154d18a 364
365Basically it sets the logger to be used if C<set_logger> is never called; so
366
367 package My::Package;
368 use Log::Contextual::SimpleLogger;
369 use Log::Contextual qw( :log ),
370 -default_logger => Log::Contextual::WarnLogger->new({
ae59bbe3 371 env_prefix => 'MY_PACKAGE'
c154d18a 372 });
373
e36f2183 374=head1 SETTING DEFAULT IMPORT OPTIONS
3dc9bd3c 375
e36f2183 376Eventually you will get tired of writing the following in every single one of
377your packages:
3dc9bd3c 378
e36f2183 379 use Log::Log4perl;
380 use Log::Log4perl ':easy';
381 BEGIN { Log::Log4perl->easy_init($DEBUG) }
3dc9bd3c 382
e36f2183 383 use Log::Contextual -logger => Log::Log4perl->get_logger;
384
385You can set any of the import options for your whole project if you define your
386own C<Log::Contextual> subclass as follows:
387
388 package MyApp::Log::Contextual;
389
390 use base 'Log::Contextual';
391
392 use Log::Log4perl ':easy';
393 Log::Log4perl->easy_init($DEBUG)
394
2b40dee5 395 sub arg_default_logger { $_[1] || Log::Log4perl->get_logger }
e36f2183 396 sub arg_levels { [qw(debug trace warn info error fatal custom_level)] }
e06303cd 397 sub default_import { ':log' }
e36f2183 398
2b40dee5 399 # or maybe instead of default_logger
e36f2183 400 sub arg_package_logger { $_[1] }
e36f2183 401
2b40dee5 402 # and almost definitely not this, which is only here for completeness
403 sub arg_logger { $_[1] }
e36f2183 404
2b40dee5 405Note the C<< $_[1] || >> in C<arg_default_logger>. All of these methods are
406passed the values passed in from the arguments to the subclass, so you can
407either throw them away, honor them, die on usage, or whatever. To be clear,
408if you define your subclass, and someone uses it as follows:
e36f2183 409
2b40dee5 410 use MyApp::Log::Contextual -default_logger => $foo,
411 -levels => [qw(bar baz biff)];
412
413Your C<arg_default_logger> method will get C<$foo> and your C<arg_levels>
e36f2183 414will get C<[qw(bar baz biff)]>;
2daff231 415
e06303cd 416Additionally, the C<default_import> method is what happens if a user tries to
417use your subclass with no arguments. The default just dies, but if you'd like
418to change the default to import a tag merely return the tags you'd like to
419import. So the following will all work:
420
421 sub default_import { ':log' }
422
423 sub default_import { ':dlog' }
424
425 sub default_import { qw(:dlog :log ) }
426
436c4b82 427See L<Log::Contextual::Easy::Default> for an example of a subclass of
428C<Log::Contextual> that makes use of default import options.
429
2daff231 430=head1 FUNCTIONS
431
432=head2 set_logger
433
434 my $logger = WarnLogger->new;
21431192 435 set_logger $logger;
436
27141a7a 437Arguments: L</LOGGER CODEREF>
2daff231 438
21431192 439C<set_logger> will just set the current logger to whatever you pass it. It
440expects a C<CodeRef>, but if you pass it something else it will wrap it in a
06e908c3 441C<CodeRef> for you. C<set_logger> is really meant only to be called from a
442top-level script. To avoid foot-shooting the function will warn if you call it
443more than once.
2daff231 444
445=head2 with_logger
446
447 my $logger = WarnLogger->new;
21431192 448 with_logger $logger => sub {
2daff231 449 if (1 == 0) {
450 log_fatal { 'Non Logical Universe Detected' };
451 } else {
452 log_info { 'All is good' };
453 }
80c3e48b 454 };
2daff231 455
27141a7a 456Arguments: L</LOGGER CODEREF>, C<CodeRef $to_execute>
2daff231 457
21431192 458C<with_logger> sets the logger for the scope of the C<CodeRef> C<$to_execute>.
0e13e261 459As with L</set_logger>, C<with_logger> will wrap C<$returning_logger> with a
21431192 460C<CodeRef> if needed.
2daff231 461
21431192 462=head2 log_$level
2daff231 463
0e13e261 464Import Tag: C<:log>
3dc9bd3c 465
0e13e261 466Arguments: C<CodeRef $returning_message, @args>
2daff231 467
a4d67519 468C<log_$level> functions all work the same except that a different method
21431192 469is called on the underlying C<$logger> object. The basic pattern is:
2daff231 470
0e13e261 471 sub log_$level (&@) {
21431192 472 if ($logger->is_$level) {
0e13e261 473 $logger->$level(shift->(@_));
21431192 474 }
0e13e261 475 @_
21431192 476 }
2daff231 477
0e13e261 478Note that the function returns it's arguments. This can be used in a number of
479ways, but often it's convenient just for partial inspection of passthrough data
480
481 my @friends = log_trace {
482 'friends list being generated, data from first friend: ' .
483 Dumper($_[0]->TO_JSON)
484 } generate_friend_list();
485
486If you want complete inspection of passthrough data, take a look at the
487L</Dlog_$level> functions.
488
a4d67519 489Which functions are exported depends on what was passed to L</-levels>. The
490default (no C<-levels> option passed) would export:
2daff231 491
a4d67519 492=over 2
2daff231 493
a4d67519 494=item log_trace
2daff231 495
a4d67519 496=item log_debug
2daff231 497
a4d67519 498=item log_info
2daff231 499
a4d67519 500=item log_warn
2daff231 501
a4d67519 502=item log_error
2daff231 503
a4d67519 504=item log_fatal
2daff231 505
a4d67519 506=back
2daff231 507
0e13e261 508=head2 logS_$level
509
510Import Tag: C<:log>
511
512Arguments: C<CodeRef $returning_message, Item $arg>
513
514This is really just a special case of the L</log_$level> functions. It forces
515scalar context when that is what you need. Other than that it works exactly
516same:
517
518 my $friend = logS_trace {
519 'I only have one friend: ' . Dumper($_[0]->TO_JSON)
520 } friend();
521
522See also: L</DlogS_$level>.
523
21431192 524=head2 Dlog_$level
525
0e13e261 526Import Tag: C<:dlog>
3dc9bd3c 527
0e13e261 528Arguments: C<CodeRef $returning_message, @args>
2daff231 529
0e13e261 530All of the following six functions work the same as their L</log_$level>
9b8e24d5 531brethren, except they return what is passed into them and put the stringified
21431192 532(with L<Data::Dumper::Concise>) version of their args into C<$_>. This means
533you can do cool things like the following:
534
535 my @nicks = Dlog_debug { "names: $_" } map $_->value, $frew->names->all;
536
537and the output might look something like:
538
539 names: "fREW"
540 "fRIOUX"
541 "fROOH"
542 "fRUE"
543 "fiSMBoC"
544
a4d67519 545Which functions are exported depends on what was passed to L</-levels>. The
546default (no C<-levels> option passed) would export:
21431192 547
a4d67519 548=over 2
21431192 549
a4d67519 550=item Dlog_trace
21431192 551
a4d67519 552=item Dlog_debug
21431192 553
a4d67519 554=item Dlog_info
21431192 555
a4d67519 556=item Dlog_warn
21431192 557
a4d67519 558=item Dlog_error
2daff231 559
a4d67519 560=item Dlog_fatal
2daff231 561
a4d67519 562=back
2daff231 563
83b33eb5 564=head2 DlogS_$level
565
0e13e261 566Import Tag: C<:dlog>
3dc9bd3c 567
0e13e261 568Arguments: C<CodeRef $returning_message, Item $arg>
83b33eb5 569
0e13e261 570Like L</logS_$level>, these functions are a special case of L</Dlog_$level>.
571They only take a single scalar after the C<$returning_message> instead of
572slurping up (and also setting C<wantarray>) all the C<@args>
83b33eb5 573
574 my $pals_rs = DlogS_debug { "pals resultset: $_" }
575 $schema->resultset('Pals')->search({ perlers => 1 });
576
27141a7a 577=head1 LOGGER CODEREF
578
579Anywhere a logger object can be passed, a coderef is accepted. This is so
580that the user can use different logger objects based on runtime information.
581The logger coderef is passed the package of the caller the caller level the
582coderef needs to use if it wants more caller information. The latter is in
583a hashref to allow for more options in the future.
584
37a8266a 585Here is a basic example of a logger that exploits C<caller> to reproduce the
586output of C<warn> with a logger:
587
588 my @caller_info;
589 my $var_log = Log::Contextual::SimpleLogger->new({
590 levels => [qw(trace debug info warn error fatal)],
591 coderef => sub { chomp($_[0]); warn "$_[0] at $caller_info[1] line $caller_info[2].\n" }
592 });
593 my $warn_faker = sub {
594 my ($package, $args) = @_;
595 @caller_info = caller($args->{caller_level});
596 $var_log
597 };
598 set_logger($warn_faker);
599 log_debug { 'test' };
600
27141a7a 601The following is an example that uses the information passed to the logger
602coderef. It sets the global logger to C<$l3>, the logger for the C<A1>
603package to C<$l1>, except the C<lol> method in C<A1> which uses the C<$l2>
604logger and lastly the logger for the C<A2> package to C<$l2>.
605
37a8266a 606Note that it increases the caller level as it dispatches based on where
607the caller of the log function, not the log function itself.
608
27141a7a 609 my $complex_dispatcher = do {
610
611 my $l1 = ...;
612 my $l2 = ...;
613 my $l3 = ...;
614
615 my %registry = (
616 -logger => $l3,
617 A1 => {
618 -logger => $l1,
619 lol => $l2,
620 },
621 A2 => { -logger => $l2 },
622 );
623
624 sub {
625 my ( $package, $info ) = @_;
626
627 my $logger = $registry{'-logger'};
628 if (my $r = $registry{$package}) {
629 $logger = $r->{'-logger'} if $r->{'-logger'};
37a8266a 630 my (undef, undef, undef, $sub) = caller($info->{caller_level} + 1);
27141a7a 631 $sub =~ s/^\Q$package\E:://g;
632 $logger = $r->{$sub} if $r->{$sub};
633 }
634 return $logger;
635 }
636 };
637
638 set_logger $complex_dispatcher;
639
3dc9bd3c 640=head1 LOGGER INTERFACE
641
642Because this module is ultimately pretty looking glue (glittery?) with the
643awesome benefit of the Contextual part, users will often want to make their
644favorite logger work with it. The following are the methods that should be
645implemented in the logger:
646
647 is_trace
648 is_debug
649 is_info
650 is_warn
651 is_error
652 is_fatal
653 trace
654 debug
655 info
656 warn
657 error
658 fatal
659
660The first six merely need to return true if that level is enabled. The latter
661six take the results of whatever the user returned from their coderef and log
662them. For a basic example see L<Log::Contextual::SimpleLogger>.
663
eab2ca3c 664=head1 LOG ROUTING
665
a5454e75 666In between the loggers and the log functions is a log router that is responsible for
eab2ca3c 667finding a logger to handle the log event and passing the log information to the
a5454e75 668logger. This relationship is described in the documentation for C<Log::Contextual::Role::Router>.
eab2ca3c 669
a5454e75 670C<Log::Contextual> and packages that extend it will by default share a router singleton that
671implements the with_logger() and set_logger() functions and also respects the -logger,
672-package_logger, and -default_logger import options with their associated default value
673functions. The router singleton is available as the return value of the router() function. Users
674of Log::Contextual may overload router() to return instances of custom log routers that
675could for example work with loggers that use a different interface.
eab2ca3c 676
a5454e75 677=head1 AUTHOR
2daff231 678
679frew - Arthur Axel "fREW" Schmidt <frioux@gmail.com>
680
a5454e75 681=head1 CONTRIBUTORS
682
436c4b82 683=encoding utf8
684
eab2ca3c 685triddle - Tyler Riddle <t.riddle@shadowcat.co.uk>
686
436c4b82 687voj - Jakob Voß <voss@gbv.de>
688
2daff231 689=head1 DESIGNER
690
691mst - Matt S. Trout <mst@shadowcat.co.uk>
692
693=head1 COPYRIGHT
694
a6e29e27 695Copyright (c) 2012 the Log::Contextual L</AUTHOR> and L</DESIGNER> as listed
2daff231 696above.
697
698=head1 LICENSE
699
700This library is free software and may be distributed under the same terms as
701Perl 5 itself.
702
703=cut
704