File Coverage

blib/lib/Log/Log4perl/Appender/Chunk.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Log::Log4perl::Appender::Chunk;
2             {
3             $Log::Log4perl::Appender::Chunk::VERSION = '0.008';
4             }
5 4     4   189548 use Moose;
  0            
  0            
6              
7             use Carp;
8             use Class::Load;
9             use Data::Dumper;
10             use Log::Log4perl::MDC;
11              
12              
13             # State variables:
14              
15             # State can be:
16             # OFFCHUNK: No chunk is currently captured.
17             # INCHUNK: A chunk is currently captured in the buffer
18             # ENTERCHUNK: Entering a chunk from an OFFCHUNK state
19             # NEWCHUNK: Entering a NEW chunk from an INCHUNK state
20             # LEAVECHUNK: Leaving a chunk from an INCHUNK state
21             has 'state' => ( is => 'rw' , isa => 'Str', default => 'OFFCHUNK' );
22             has 'previous_chunk' => ( is => 'rw' , isa => 'Maybe[Str]' , default => undef , writer => '_set_previous_chunk' );
23             has 'messages_buffer' => ( is => 'rw' , isa => 'ArrayRef[Str]' , default => sub{ []; });
24              
25             # Settings:
26             has 'chunk_marker' => ( is => 'ro' , isa => 'Str', required => 1, default => 'chunk' );
27              
28             # Store:
29             has 'store' => ( is => 'ro', isa => 'Log::Log4perl::Appender::Chunk::Store',
30             required => 1, lazy_build => 1);
31             has 'store_class' => ( is => 'ro' , isa => 'Str' , default => 'Null' );
32             has 'store_args' => ( is => 'ro' , isa => 'HashRef' , default => sub{ {}; });
33              
34             has 'store_builder' => ( is => 'ro' , isa => 'CodeRef', required => 1, default => sub{
35             my ($self) = @_;
36             sub{
37             $self->_full_store_class()->new($self->store_args());
38             }
39             });
40              
41             sub _build_store{
42             my ($self) = @_;
43             return $self->store_builder()->();
44             }
45              
46             sub _full_store_class{
47             my ($self) = @_;
48             my $full_class = $self->store_class();
49             if( $full_class =~ /^\+/ ){
50             $full_class =~ s/^\+//;
51             }else{
52             $full_class = 'Log::Log4perl::Appender::Chunk::Store::'.$full_class;
53             }
54             Class::Load::load_class($full_class);
55             return $full_class;
56             }
57              
58              
59             sub log{
60             my ($self, %params) = @_;
61              
62             ## Any log within this method will be discarded.
63             if( Log::Log4perl::MDC->get(__PACKAGE__.'-reentrance') ){
64             return;
65             }
66             Log::Log4perl::MDC->put(__PACKAGE__.'-reentrance', 1);
67              
68             my $chunk = Log::Log4perl::MDC->get($self->chunk_marker());
69              
70             # Change the state according to the chunk param
71             $self->state( $self->_compute_state($chunk) );
72              
73             # Act according to the state.
74             my $m_name = '_on_'.$self->state();
75              
76             $self->$m_name(\%params);
77              
78             $self->_set_previous_chunk($chunk);
79             Log::Log4perl::MDC->put(__PACKAGE__.'-reentrance', undef);
80             }
81              
82             sub _on_OFFCHUNK{
83             my ($self, $params) = @_;
84             # Chunk is Off, nothing much to do.
85             }
86              
87             sub _on_ENTERCHUNK{
88             my ($self,$params) = @_;
89             # Push the message in the buffer.
90             push @{$self->messages_buffer()} , $params->{message};
91             }
92              
93             sub _on_INCHUNK{
94             my ($self, $params) = @_;
95             # Push the message in the buffer.
96             push @{$self->messages_buffer()} , $params->{message};
97             }
98              
99             sub _on_NEWCHUNK{
100             my ($self, $params) = @_;
101             # Leave the chunk
102             $self->_on_LEAVECHUNK($params);
103             # And we are entering the new one.
104             $self->_on_INCHUNK($params);
105             }
106              
107             sub _on_LEAVECHUNK{
108             my ($self) = @_;
109              
110             # The new message should not be pushed on the buffer,
111             # As we left a chunk for no chunk.
112              
113             # Flush the buffer in one big message.
114             my $big_message = join('',@{$self->{messages_buffer}});
115             $self->messages_buffer( [] );
116              
117             # The chunk ID is in the previous chunk. This should NEVER be null
118             my $chunk_id = $self->previous_chunk();
119             unless( defined $chunk_id ){
120             confess("Undefined previous chunk. This should never happen. Dont know where to put the big message:$big_message");
121             }
122             $self->store->store($chunk_id, $big_message);
123             }
124              
125              
126             sub DEMOLISH{
127             my ($self) = @_;
128             if( my $chunk_id = $self->previous_chunk() ){
129             # Simulate transitioning to an non chunked section of the log.
130             Log::Log4perl::MDC->put($self->chunk_marker() , undef );
131             # Output an empty log.
132             $self->log();
133             }
134             }
135              
136             sub _compute_state{
137             my ($self, $chunk) = @_;
138             my $previous_chunk = $self->previous_chunk();
139              
140             if( defined $chunk ){
141             if( defined $previous_chunk ){
142             if( $previous_chunk eq $chunk ){
143             # State is INCHUNK
144             return 'INCHUNK';
145             }else{
146             # Chunks are different
147             return 'NEWCHUNK';
148             }
149             }else{
150             # No previous chunk.
151             return 'ENTERCHUNK';
152             }
153             }else{
154             # No chunk defined.
155             if( defined $previous_chunk ){ # But a previous chunk
156             return 'LEAVECHUNK';
157             }else{
158             # No previous chunk neither
159             return 'OFFCHUNK';
160             }
161             }
162              
163             confess("UNKNOWN CASE. This should never be reached.");
164             }
165              
166             __PACKAGE__->meta->make_immutable();
167              
168             __END__
169              
170             =head1 NAME
171              
172             Log::Log4perl::Appender::Chunk - Group log messages in Identified chunks
173              
174             =head1 DESCRIPTION
175              
176             This appender will write group of Log lines (chunks) to the underlying store under
177             an ID that you choose.
178              
179             A number of Store classes are shipped ( in Log::Log4perl::Appender::Chunk::Store::* ),
180             but it's very easy to write your own store, as it's essentially a Key/Value storage.
181              
182             See L<Log::Log4perl::Appender::Chunk::Store> for more details.
183              
184             =head2 How to mark chunks of logs.
185              
186             Marking chunks of log rely on the Log4perl Mapped Diagnostic Context (MDC) mechanism.
187             See L<Log::Log4perl::MDC>
188              
189             Essentially, each time you set a MDC key 'chunk' to something, this appender will start
190             recording chunks and fetch them to the storage when the key 'chunk' is unset or changes.
191              
192             =head1 SYNOPSIS
193              
194             =head2 In your code
195              
196             Anywhere in your code:
197              
198              
199             # .. Use log4perl as usual ..
200              
201             ## Start capturing Log lines in an identified Chunk
202             Log::Log4perl::MDC->put('chunk', "Your-Log-Chunk-Unique-ID-Key");
203              
204             # .. Use Log4perl as usual ..
205              
206             ## Finish capturing in the identified Chunk
207             Log::Log4perl::MDC->put('chunk',undef);
208              
209             # .. Use Log4perl as usual ..
210             $logger->info("Blabla"); # Triggers storing the log chunk
211              
212             Then depending on the configured store, you will be able to retrieve your log chunks
213             from different places. See below.
214              
215             =head2 Configuration
216              
217              
218             =head3 with built-in store Memory
219              
220             Reference: L<Log::Log4perl::Appender::Chunk::Store::Memory>
221              
222             log4perl.conf:
223              
224             log4perl.rootLogger=TRACE, Chunk
225              
226             log4perl.appender.Chunk=Log::Log4perl::Appender::Chunk
227              
228             # Built-in store class S3
229             log4perl.appender.Chunk.store_class=Memory
230              
231             # Etc..
232             log4perl.appender.Chunk.layout=..
233              
234              
235             =head3 With built-in store S3
236              
237             log4perl.conf:
238              
239             log4perl.rootLogger=TRACE, Chunk
240              
241             log4perl.appender.Chunk=Log::Log4perl::Appender::Chunk
242              
243             # Built-in store class S3
244             log4perl.appender.Chunk.store_class=S3
245             # S3 Specific Arguments:
246             log4perl.appender.Chunk.store_args.bucket_name=MyLogChunks
247             log4perl.appender.Chunk.store_args.aws_access_key_id=YourAWSAccessKey
248             log4perl.appender.Chunk.store_args.aws_secret_access_key=YourAWS
249              
250             # Optional:
251             log4perl.appender.Chunk.store_args.retry=1
252             log4perl.appender.Chunk.store_args.vivify_bucket=1
253              
254             log4perl.appender.Chunk.store_args.expires_in_days=3
255             log4perl.appender.Chunk.store_args.acl_short=public-read
256              
257             # Etc..
258             log4perl.appender.Chunk.layout=...
259              
260             =head2 log
261              
262             L<Log::Log4perl::Appender> framework method.
263              
264             =head2 store
265              
266             The instance of L<Log::Log4perl::Appender::Chunk::Store> this logger uses.
267              
268             It's usually configured from the Log4perl configuration file as shown in the SYNOPSIS, but
269             you can also inject it from your application code:
270              
271             Log::Log4perl->appender_by_name('Chunk')->store($your_instance_of_storage);
272              
273              
274             =head2 DEMOLISH
275              
276             Will attempt to store whatever is left in the buffer if your program
277             finishes before it could output any log file outside a Chunk capturing section.
278              
279             =cut