{
our
$VERSION
=
'1.8.0'
; }
our
(
$TRACE
,
$DEBUG
,
$INFO
,
$WARN
,
$ERROR
,
$FATAL
,
$OFF
,
$DEAD
);
my
(
$_instance
,
%name_of
,
%format_for
,
%id_for
);
my
$LOGDIE_MESSAGE_ON_STDERR
= 1;
sub
import
{
my
(
$exporter
,
@list
) =
@_
;
my
(
$caller
,
$file
,
$line
) =
caller
();
no
strict
'refs'
;
if
(
grep
{
$_
eq
':full_or_fake'
}
@list
) {
@list
=
grep
{
$_
ne
':full_or_fake'
}
@list
;
my
$sue
=
'use Log::Log4perl (@list)'
;
eval
"
package
$caller
;
$sue
;
1;
" and
return
;
unshift
@list
,
':fake'
;
}
my
(
%done
,
$level_set
);
ITEM:
for
my
$item
(
@list
) {
next
ITEM
if
$done
{
$item
};
$done
{
$item
} = 1;
if
(
$item
=~ /^[a-zA-Z]/mxs) {
*{
$caller
.
'::'
.
$item
} = \&{
$exporter
.
'::'
.
$item
};
}
elsif
(
$item
eq
':levels'
) {
for
my
$level
(
qw( TRACE DEBUG INFO WARN ERROR FATAL OFF DEAD )
) {
*{
$caller
.
'::'
.
$level
} = \${
$exporter
.
'::'
.
$level
};
}
}
elsif
(
$item
eq
':subs'
) {
push
@list
,
qw(
ALWAYS TRACE DEBUG INFO WARN ERROR FATAL
LOGWARN LOGDIE LOGEXIT LOGCARP LOGCLUCK LOGCROAK LOGCONFESS
get_logger
)
;
}
elsif
(
$item
=~ /\A : (mimic | mask | fake) \z/mxs) {
if
(!
'Log::Log4perl'
->can(
'easy_init'
)) {
$INC
{
'Log/Log4perl.pm'
} = __FILE__;
*Log::Log4perl::import
=
sub
{ };
*Log::Log4perl::easy_init
=
sub
{
my
(
$pack
,
$conf
) =
@_
;
if
(
ref
$conf
) {
$_instance
= __PACKAGE__->new(
$conf
);
$_instance
->level(
$conf
->{level})
if
exists
$conf
->{level};
$_instance
->
format
(
$conf
->{
format
})
if
exists
$conf
->{
format
};
$_instance
->
format
(
$conf
->{layout})
if
exists
$conf
->{layout};
$_instance
->filter(
$conf
->{filter})
if
exists
$conf
->{filter};
}
elsif
(
defined
$conf
) {
$_instance
->level(
$conf
);
}
};
}
}
elsif
(
$item
eq
':easy'
) {
push
@list
,
qw( :levels :subs :fake )
;
}
elsif
(
lc
(
$item
) eq
':dead_if_first'
) {
get_logger()->_set_level_if_first(
$DEAD
);
$level_set
= 1;
}
elsif
(
lc
(
$item
) eq
':no_extra_logdie_message'
) {
$LOGDIE_MESSAGE_ON_STDERR
= 0;
}
}
if
(!
$level_set
) {
my
$logger
= get_logger();
$logger
->_set_level_if_first(
$INFO
);
$logger
->level(
$logger
->level());
}
return
;
}
sub
new {
my
$package
=
shift
;
my
%args
=
ref
(
$_
[0]) ? %{
$_
[0]} :
@_
;
$args
{
format
} =
$args
{layout}
if
exists
$args
{layout};
my
$channels_input
= [
fh
=> \
*STDERR
];
if
(
exists
$args
{channels}) {
$channels_input
=
$args
{channels};
}
else
{
for
my
$key
(
qw< file_append file_create file_insecure file fh >
) {
next
unless
exists
$args
{
$key
};
$channels_input
= [
$key
=>
$args
{
$key
}];
last
;
}
}
my
$channels
= build_channels(
$channels_input
);
$channels
=
$channels
->[0]
if
@$channels
== 1;
my
$self
=
bless
{
fh
=>
$channels
,
level
=>
$INFO
,
},
$package
;
for
my
$accessor
(
qw( level fh format filter )
) {
next
unless
defined
$args
{
$accessor
};
$self
->
$accessor
(
$args
{
$accessor
});
}
$self
->
format
(
'[%d] [%5p] %m%n'
)
unless
exists
$self
->{
format
};
if
(
exists
$args
{loglocal}) {
my
$local
=
$args
{loglocal};
$self
->loglocal(
$_
,
$local
->{
$_
})
for
keys
%$local
;
}
return
$self
;
}
sub
build_channels {
my
@pairs
= (
@_
&&
ref
(
$_
[0])) ? @{
$_
[0]} :
@_
;
my
@channels
;
while
(
@pairs
) {
my
(
$key
,
$value
) =
splice
@pairs
, 0, 2;
croak
"build_channels(): undefined key in list"
unless
defined
$key
;
croak
"build_channels(): undefined value for key $key"
unless
defined
$value
;
my
(
$channel
,
$set_autoflush
);
if
(
$key
=~ m{\A(?: fh |
sub
| code | channel )\z}mxs) {
$channel
=
$value
;
}
elsif
(
$key
eq
'file_append'
) {
open
$channel
,
'>>'
,
$value
or croak
"open('$value') for appending: $!"
;
$set_autoflush
= 1;
}
elsif
(
$key
eq
'file_create'
) {
open
$channel
,
'>'
,
$value
or croak
"open('$value') for creating: $!"
;
$set_autoflush
= 1;
}
elsif
(
$key
=~ m{\A file (?: _insecure )? \z}mxs) {
open
$channel
,
$value
or croak
"open('$value'): $!"
;
$set_autoflush
= 1;
}
else
{
croak
"unsupported channel key '$key'"
;
}
if
(
$set_autoflush
) {
my
$previous
=
select
(
$channel
);
$|++;
select
(
$previous
);
}
push
@channels
,
$channel
;
}
return
\
@channels
;
}
sub
get_logger {
return
$_instance
||= __PACKAGE__->new(); }
sub
LOGLEVEL {
return
get_logger()->level(
@_
); }
sub
LEVELID_FOR {
my
$level
=
shift
;
return
$id_for
{
$level
}
if
exists
$id_for
{
$level
};
return
;
}
sub
LEVELNAME_FOR {
my
$id
=
shift
;
return
$name_of
{
$id
}
if
exists
$name_of
{
$id
};
return
$id
if
exists
$id_for
{
$id
};
return
;
}
sub
loglocal {
my
$self
=
shift
;
my
$key
=
shift
;
my
$retval
=
delete
$self
->{loglocal}{
$key
};
$self
->{loglocal}{
$key
} =
shift
if
@_
;
return
$retval
;
}
sub
LOGLOCAL {
return
get_logger->loglocal(
@_
) }
sub
filter {
my
$self
=
shift
;
$self
->{filter} =
shift
if
@_
;
return
$self
->{filter};
}
sub
FILTER {
return
get_logger->filter(
@_
) }
sub
format
{
my
$self
=
shift
;
if
(
@_
) {
$self
->{
format
} =
shift
;
$self
->{args} = \
my
@args
;
my
$replace
=
sub
{
if
(
defined
$_
[2]) {
my
(
$num
,
$opts
,
$op
) =
@_
[0 .. 2];
push
@args
, [
$op
,
$opts
];
return
"%$num$format_for{$op}[0]"
;
}
if
(
defined
$_
[4]) {
my
(
$num
,
$op
) =
@_
[3, 4];
push
@args
, [
$op
];
return
"%$num$format_for{$op}[0]"
;
}
my
$char
= ((!
defined
(
$_
[5])) || (
$_
[5] eq
'%'
)) ?
''
:
$_
[5];
return
'%%'
.
$char
;
};
my
(
$with_options
,
$standalone
) = (
''
,
''
);
for
my
$key
(
keys
%format_for
) {
my
$type
=
$format_for
{
$key
}[2] ||
''
;
$with_options
.=
$key
if
$type
;
$standalone
.=
$key
if
$type
ne
'required'
;
}
$_
=
length
(
$_
) ?
quotemeta
(
$_
) :
'^\\w\\W'
for
(
$with_options
,
$standalone
);
$self
->{
format
} =~ s<
%
(?:
(?:
( -? \d* (?:\.\d+)? )
( (?:\{ .*? \}) )
([
$with_options
])
)
| (?:
( -? \d* (?:\.\d+)? )
([
$standalone
])
)
| (.)
| \z
)
>
{
$replace
->($1, $2, $3, $4, $5, $6);
}gsmex;
}
return
$self
->{
format
};
}
*layout
= \
&format
;
sub
emit_log {
my
(
$self
,
$message
) =
@_
;
my
$fh
=
$self
->{fh};
for
my
$channel
((
ref
(
$fh
) eq
'ARRAY'
) ? (
@$fh
) : (
$fh
)) {
(
ref
(
$channel
) eq
'CODE'
)
?
$channel
->(
$message
,
$self
)
:
print
{
$channel
}
$message
;
}
return
$message
;
}
sub
log
{
my
$self
=
shift
;
return
if
$self
->{level} ==
$DEAD
;
my
$level
=
shift
;
return
if
$level
>
$self
->{level};
my
%data_for
= (
level
=>
$level
,
message
=> \
@_
,
(
exists
(
$self
->{loglocal}) ? (
loglocal
=>
$self
->{loglocal}) : ()),
);
my
$message
=
sprintf
$self
->{
format
},
map
{
$format_for
{
$_
->[0]}[1]->(\
%data_for
,
@$_
); } @{
$self
->{args}};
$message
=
$self
->{filter}->(
$message
)
if
$self
->{filter};
return
$self
->emit_log(
$message
);
}
sub
ALWAYS {
return
$_instance
->
log
(
$OFF
,
@_
); }
sub
_exit {
my
$self
=
shift
||
$_instance
;
exit
$self
->{logexit_code}
if
defined
$self
->{logexit_code};
exit
$Log::Log4perl::LOGEXIT_CODE
if
defined
$Log::Log4perl::LOGEXIT_CODE
;
exit
1;
}
sub
logwarn {
my
$self
=
shift
;
my
@message
;
@message
= __expand_message_list({
message
=> \
@_
})
if
$self
->is_warn() ||
$LOGDIE_MESSAGE_ON_STDERR
;
$self
->
warn
(
@message
);
if
(
$LOGDIE_MESSAGE_ON_STDERR
) {
push
@message
,
"Warning: something's wrong"
unless
@message
;
my
(
undef
,
$file
,
$line
) =
caller
(1);
push
@message
,
sprintf
" at %s line %d.\n"
,
$file
,
$line
if
substr
(
$message
[-1], -1, 1) ne
"\n"
;
CORE::
warn
(
@message
);
}
return
}
sub
logdie {
my
$self
=
shift
;
my
@message
;
@message
= __expand_message_list({
message
=> \
@_
})
if
$self
->is_fatal() ||
$LOGDIE_MESSAGE_ON_STDERR
;
$self
->fatal(
@message
);
if
(
$LOGDIE_MESSAGE_ON_STDERR
) {
push
@message
,
"Died"
unless
@message
;
my
(
undef
,
$file
,
$line
) =
caller
(1);
push
@message
,
sprintf
" at %s line %d.\n"
,
$file
,
$line
if
substr
(
$message
[-1], -1, 1) ne
"\n"
;
CORE::
die
(
@message
);
}
$self
->_exit();
}
sub
logexit {
my
$self
=
shift
;
$self
->fatal(
@_
);
$self
->_exit();
}
sub
_carpstuff {
my
$self
=
shift
;
my
$renderer
=
shift
;
my
$emitter
=
shift
;
my
$log_level
=
shift
;
my
$emit_log
=
$self
->can(
"is_$log_level"
)->(
$self
);
local
$Carp::Internal
{
''
. __PACKAGE__} = 1;
local
$Carp::CarpLevel
=
$Carp::CarpLevel
+ 2;
my
@message
;
@message
= __expand_message_list({
message
=> \
@_
})
if
$emit_log
||
$LOGDIE_MESSAGE_ON_STDERR
;
if
(
$emit_log
) {
my
$message
= Carp->can(
$renderer
)->(
@message
);
my
$method
=
$self
->can(
$log_level
);
$self
->
$method
(
$_
)
for
split
m{\n}mxs,
$message
;
}
if
(
$LOGDIE_MESSAGE_ON_STDERR
) {
Carp->can(
$emitter
)->(
@message
);
}
return
;
}
sub
logcarp {
my
$self
=
shift
;
return
$self
->_carpstuff(
qw< shortmess carp warn >
,
@_
);
}
sub
logcluck {
my
$self
=
shift
;
return
$self
->_carpstuff(
qw< longmess cluck warn >
,
@_
);
}
sub
logcroak {
my
$self
=
shift
;
$self
->_carpstuff(
qw< shortmess croak fatal >
,
@_
);
$self
->_exit();
}
sub
logconfess {
my
$self
=
shift
;
$self
->_carpstuff(
qw< longmess confess fatal >
,
@_
);
$self
->_exit();
}
sub
level {
my
$self
=
shift
;
$self
=
$_instance
unless
ref
$self
;
if
(
@_
) {
my
$level
=
shift
;
return
unless
exists
$id_for
{
$level
};
$self
->{level} =
$id_for
{
$level
};
$self
->{_count}++;
}
return
$self
->{level};
}
sub
_set_level_if_first {
my
(
$self
,
$level
) =
@_
;
if
(!
$self
->{_count}) {
$self
->level(
$level
);
delete
$self
->{_count};
}
return
;
}
sub
__expand_message_list {
join
(
(
defined
$, ? $, :
''
),
map
{
ref
(
$_
) eq
'CODE'
?
$_
->() :
$_
; } @{
shift
->{message}}
);
}
BEGIN {
my
$has_time_hires
;
my
$gtod
=
sub
{
return
(
time
(), 0) };
eval
{
$has_time_hires
= 1;
$gtod
= \
&Time::HiRes::gettimeofday
;
};
my
$start_time
= [
$gtod
->()];
my
$last_log
=
$start_time
;
my
$strftime_has_tz_offset
=
POSIX::strftime(
'%z'
,
localtime
()) =~ m<\A [-+] \d{4} \z>mxs;
if
(!
$strftime_has_tz_offset
) {
}
{
no
strict
'refs'
;
*caller_depth
=
*Log::Log4perl::caller_depth
;
}
our
$caller_depth
;
$caller_depth
||= 0;
%format_for
= (
c
=> [
s
=>
sub
{
'main'
}],
C
=> [
s
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$max_i
= 5;
my
$i
= 1;
my
$package
;
while
(
$i
<=
$max_i
) {
(
$package
) =
caller
$i
;
return
'*undef*'
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$i
;
}
return
'*undef'
if
$i
>
$max_i
;
(
$package
) =
caller
(
$i
+=
$caller_depth
)
if
$caller_depth
;
return
$package
;
},
],
d
=> [
s
=>
sub
{
my
(
$epoch
) = @{
shift
->{tod} ||= [
$gtod
->()]};
return
POSIX::strftime(
'%Y/%m/%d %H:%M:%S'
,
localtime
(
$epoch
));
},
],
D
=> [
s
=>
sub
{
my
(
$data
,
$op
,
$options
) =
@_
;
$options
=
'{}'
unless
defined
$options
;
$options
=
substr
$options
, 1,
length
(
$options
) - 2;
my
%flag_for
=
map
{
$_
=> 1 }
split
/\s*,\s*/,
lc
(
$options
);
my
(
$s
,
$u
) = @{
$data
->{tod} ||= [
$gtod
->()]};
$u
=
substr
"000000$u"
, -6, 6;
return
POSIX::strftime(
"%Y-%m-%d %H:%M:%S.$u+0000"
,
gmtime
$s
)
if
$flag_for
{utc};
my
@localtime
=
localtime
$s
;
return
POSIX::strftime(
"%Y-%m-%d %H:%M:%S.$u%z"
,
@localtime
)
if
$strftime_has_tz_offset
;
my
$sign
=
'+'
;
my
$offset
= Time::Local::timegm(
@localtime
) -
$s
;
(
$sign
,
$offset
) = (
'-'
, -
$offset
)
if
$offset
< 0;
my
$z
=
sprintf
'%s%02d%02d'
,
$sign
,
int
(
$offset
/ 3600),
(
int
(
$offset
/ 60) % 60);
return
POSIX::strftime(
"%Y-%m-%d %H:%M:%S.$u$z"
,
@localtime
);
},
'optional'
],
e
=> [
s
=>
sub
{
my
(
$data
,
$op
,
$options
) =
@_
;
$data
->{tod} ||= [
$gtod
->()];
my
$local
=
$data
->{loglocal} or
return
''
;
my
$key
=
substr
$options
, 1,
length
(
$options
) - 2;
return
''
unless
exists
$local
->{
$key
};
my
$target
=
$local
->{
$key
};
return
''
unless
defined
$target
;
my
$reft
=
ref
$target
or
return
$target
;
return
''
unless
$reft
eq
'CODE'
;
return
$target
->(
$data
,
$op
,
$options
);
},
'required'
,
],
F
=> [
s
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$i
= 1;
my
(
$package
,
$file
);
while
(
$i
<= 4) {
(
$package
,
$file
) =
caller
$i
;
return
'*undef*'
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$i
;
}
return
'*undef'
if
$i
> 4;
(
undef
,
$file
) =
caller
(
$i
+=
$caller_depth
)
if
$caller_depth
;
return
$file
;
},
],
H
=> [
s
=>
sub
{
||
''
;
},
],
l
=> [
s
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$i
= 1;
my
(
$package
,
$filename
,
$line
);
while
(
$i
<= 4) {
(
$package
,
$filename
,
$line
) =
caller
$i
;
return
'*undef*'
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$i
;
}
return
'*undef'
if
$i
> 4;
(
undef
,
$filename
,
$line
) =
caller
(
$i
+=
$caller_depth
)
if
$caller_depth
;
my
(
undef
,
undef
,
undef
,
$subroutine
) =
caller
(
$i
+ 1);
$subroutine
=
"main::"
unless
defined
$subroutine
;
return
sprintf
'%s %s (%d)'
,
$subroutine
,
$filename
,
$line
;
},
],
L
=> [
d
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$i
= 1;
my
(
$package
,
$line
);
while
(
$i
<= 4) {
(
$package
,
undef
,
$line
) =
caller
$i
;
return
-1
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$i
;
}
return
-1
if
$i
> 4;
(
undef
,
undef
,
$line
) =
caller
(
$i
+=
$caller_depth
)
if
$caller_depth
;
return
$line
;
},
],
m
=> [
s
=> \
&__expand_message_list
,],
M
=> [
s
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$max_i
= 5;
my
$i
= 1;
while
(
$i
<=
$max_i
) {
my
(
$package
) =
caller
$i
;
return
'*undef*'
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$i
;
}
return
'*undef'
if
$i
>
$max_i
;
$i
+=
$caller_depth
if
$caller_depth
;
my
(
undef
,
undef
,
undef
,
$subroutine
) =
caller
(
$i
+ 1);
$subroutine
=
"main::"
unless
defined
$subroutine
;
return
$subroutine
;
},
],
n
=> [
s
=>
sub
{
"\n"
},],
p
=> [
s
=>
sub
{
$name_of
{
shift
->{level}} },],
P
=> [
d
=>
sub
{ $$ },],
r
=> [
d
=>
sub
{
my
(
$s
,
$u
) = @{
shift
->{tod} ||= [
$gtod
->()]};
$s
-=
$start_time
->[0];
my
$m
=
int
((
$u
-
$start_time
->[1]) / 1000);
(
$s
,
$m
) = (
$s
- 1,
$m
+ 1000)
if
$m
< 0;
return
$m
+ 1000 *
$s
;
},
],
R
=> [
d
=>
sub
{
my
(
$sx
,
$ux
) = @{
shift
->{tod} ||= [
$gtod
->()]};
my
$s
=
$sx
-
$last_log
->[0];
my
$m
=
int
((
$ux
-
$last_log
->[1]) / 1000);
(
$s
,
$m
) = (
$s
- 1,
$m
+ 1000)
if
$m
< 0;
$last_log
= [
$sx
,
$ux
];
return
$m
+ 1000 *
$s
;
},
],
T
=> [
s
=>
sub
{
my
(
$internal_package
) =
caller
0;
my
$level
= 1;
while
(
$level
<= 4) {
my
(
$package
) =
caller
$level
;
return
'*undef*'
unless
defined
$package
;
last
if
$package
ne
$internal_package
;
++
$level
;
}
return
'*undef'
if
$level
> 4;
local
$Carp::CarpLevel
=
$Carp::CarpLevel
+
$level
+
$caller_depth
;
chomp
(
my
$longmess
= Carp::longmess());
$longmess
=~ s{(?:\A\s
*at
.*?\n|^\s*)}{}mxsg;
$longmess
=~ s{\n}{, }g;
return
$longmess
;
},
],
);
no
strict
'refs'
;
for
my
$name
(
qw( FATAL ERROR WARN INFO DEBUG TRACE )
) {
*{__PACKAGE__ .
'::'
.
lc
(
$name
)} =
sub
{
my
$self
=
shift
;
return
$self
->
log
(
$$name
,
@_
);
};
*{__PACKAGE__ .
'::is'
.
ucfirst
(
lc
(
$name
)) .
'Enabled'
} =
*{__PACKAGE__ .
'::is_'
.
lc
(
$name
)} =
sub
{
return
0
if
$_
[0]->{level} ==
$DEAD
||
$$name
>
$_
[0]->{level};
return
1;
};
}
for
my
$name
(
qw(
FATAL ERROR WARN INFO DEBUG TRACE
LOGWARN LOGDIE LOGEXIT
LOGCARP LOGCLUCK LOGCROAK LOGCONFESS
)
)
{
*{__PACKAGE__ .
'::'
.
$name
} =
sub
{
$_instance
->can(
lc
$name
)->(
$_instance
,
@_
);
};
}
for
my
$accessor
(
qw( fh logexit_code )
) {
*{__PACKAGE__ .
'::'
.
$accessor
} =
sub
{
my
$self
=
shift
;
$self
=
$_instance
unless
ref
$self
;
$self
->{
$accessor
} =
shift
if
@_
;
return
$self
->{
$accessor
};
};
}
my
$index
= -1;
for
my
$name
(
qw( DEAD OFF FATAL ERROR WARN INFO DEBUG TRACE )
) {
$name_of
{
$$name
=
$index
} =
$name
;
$id_for
{
$name
} =
$index
;
$id_for
{
$index
} =
$index
;
++
$index
;
}
get_logger();
}
1;