File Coverage

lib/Text/Prefix.pm
Criterion Covered Total %
statement 101 116 87.0
branch 55 86 63.9
condition 18 33 54.5
subroutine 14 15 93.3
pod 2 3 66.6
total 190 253 75.1


line stmt bran cond sub pod time code
1             package Text::Prefix;
2              
3             # ABSTRACT: Prepend strings with timestamps and potentially other contextually-relevant information.
4              
5 2     2   229427 use strict;
  2         4  
  2         47  
6 2     2   8 use warnings;
  2         3  
  2         56  
7              
8 2     2   997 use Sys::Hostname;
  2         1873  
  2         119  
9 2     2   929 use File::Valet qw(rd_f wr_f ap_f);
  2         21676  
  2         139  
10 2     2   17 use Time::HiRes;
  2         3  
  2         19  
11 2     2   1261 use Time::TAI::Simple;
  2         72291  
  2         145  
12              
13 2     2   19 use vars qw(@EXPORT @EXPORT_OK @ISA $VERSION);
  2         5  
  2         175  
14              
15             BEGIN {
16 2     2   14 require Exporter;
17 2         22 @ISA = qw(Exporter);
18 2         6 $VERSION = '1.00';
19 2         2003 @EXPORT = @EXPORT_OK = ();
20             }
21              
22             sub new {
23 5     5 1 2030 my ($class, %opt_hr) = @_;
24 5         18 my $self = {
25             opt_hr => \%opt_hr
26             };
27 5         13 bless ($self, $class);
28              
29 5         10 foreach my $k0 (keys %{$self->{opt_hr}}) {
  5         32  
30 10         33 my $k1 = join('_', split(/-/, $k0));
31 10 50       32 next if ($k0 eq $k1);
32 0         0 $self->{opt_hr}->{$k1} = $self->{opt_hr}->{$k0};
33 0         0 delete $self->{opt_hr}->{$k0};
34             }
35              
36 5         34 $self->{data_label} = $self->opt('label', 'd');
37 5         23 $self->{format} = $self->opt('format','space');
38 5 50       20 $self->{format} = 'kvp' if ($self->opt('kvp'));
39 5         25 $self->{host} = hostname();
40 5 50       64 $self->{host} = $1 if ($self->{host} =~ /(.+?)\./);
41 5 100       15 if (my $mask = $self->opt('host_sans')) {
42 2 50       48 $self->{host} = $1 if ($self->{host} =~ /(.+?)$mask$/);
43             }
44 5         22 $self->{perlcode} = $self->opt('perl', '');
45 5 50       26 if (my $pf = $self->opt('perlf')) {
46 0 0       0 die "no such file (passed via perlf): '$pf'" unless (-e $pf);
47 0         0 $self->{perlcode} = File::Valet::rd_f($pf);
48             }
49 5 100       47 $ENV{HOSTNAME} = $ENV{HOST} = $self->{host} if ($self->{perlcode} ne '');
50 5 100       16 if ($self->opt('order')) {
51 2         8 $self->{order_ar} = [split(/\s*,\s*/, $self->opt('order'))];
52             } else {
53 3         8 my @order_list;
54 3 50 66     11 push @order_list, 'lt' unless ($self->opt('no_date') || $self->opt('no_time') || $self->opt('no_human_date'));
      66        
55 3 50 66     11 push @order_list, 'tm' unless ($self->opt('no_date') || $self->opt('no_time') || $self->opt('no_epoch'));
      66        
56 3 50 33     11 push @order_list, 'hn' if ($self->opt('host') || $self->opt('host_sans'));
57 3 100       12 push @order_list, 'st' if ($self->opt('with'));
58 3 50 33     10 push @order_list, 'pl' if ($self->opt('perl') || $self->opt('perlf'));
59 3         29 push @order_list, $self->{data_label};
60 3         11 $self->{order_ar} = \@order_list;
61             }
62              
63 5 100       23 if (my $tai = $self->opt('tai')) {
64 2         5 my $tai_mode = 'tai10';
65 2 50       8 $tai_mode = 'tai35' if ($tai eq '35');
66 2 50       13 $tai_mode = 'tai' if ($tai eq '0');
67 2         24 $self->{tai_or} = Time::TAI::Simple->new(mode => $tai_mode);
68             }
69              
70 5         1829 return $self;
71             }
72              
73             sub prefix {
74 3     3 1 44 my ($self, $s) = @_;
75 3 50       9 ap_f($self->opt('pretee'), $s) if ($self->opt('pretee'));
76 3         8 chomp($s);
77 3         5 my $pl = '';
78 3 100       61 $pl = join(' ', split(/[\r\n]+/, eval($self->{perlcode}))) if ($self->{perlcode} ne '');
79 3         11 my $hr = {$self->{data_label} => $s};
80 3 100 66     14 $hr->{tm} = $self->_tm() unless ($self->opt('no_date') || $self->opt('no_epoch'));
81 3 100 66     9 $hr->{lt} = $self->_lt() unless ($self->opt('no_date') || $self->opt('no_human_date'));
82 3 100 66     9 $hr->{hn} = $self->{host} if ($self->opt('host') || $self->opt('host_sans'));
83 3 100       8 $hr->{st} = $self->opt('with') if ($self->opt('with'));
84 3 100 66     8 $hr->{pl} = $pl if ($self->opt('perl') || $self->opt('perlf'));
85 3         5 my $output = '';
86 3 50       11 my $pad = $self->opt('no_space') ? '' : ' ';
87 3         6 foreach my $k (@{$self->{order_ar}}) {
  3         8  
88 9 50       18 next unless(defined($hr->{$k}));
89 9         12 my $v = $hr->{$k};
90 9 50       31 if ($self->{format} eq 'kvp') {
    50          
    50          
91 0         0 $output .= "$k=$v\t";
92             }
93             elsif ($self->{format} eq 'csv') {
94 0         0 $output .= "\"$v\",";
95             }
96             elsif ($self->{format} eq 'tab') {
97 0         0 $output .= "$v\t";
98             }
99             else { # assume 'space'
100 9         18 $output .= "$v$pad";
101             }
102             }
103 3 50       8 chop($output) if ($pad);
104 3 50       6 ap_f($self->opt('tee'), "$output\n") if ($self->opt('tee'));
105 3         12 return $output;
106             }
107              
108             sub _isotime {
109 0     0   0 my ($self, $tm) = @_;
110 0 0       0 $tm = $self->_tm() unless(defined($tm));
111 0         0 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($tm);
112 0         0 my $iso_date = sprintf('%04d-%02d-%02d', $year + 1900, $mon + 1, $mday);
113 0         0 my $iso_time = sprintf('%02d:%02d:%02d', $hour, $min, $sec);
114 0 0 0     0 $iso_time .= substr($tm-int($tm), 1, 5) if ($self->opt('hires') || $self->opt('tai'));
115 0         0 return "$iso_date $iso_time";
116             }
117              
118             sub _tm {
119 4     4   10 my ($self) = @_;
120 4         7 my $tm;
121 4 100       17 $tm = $self->{tai_or}->time() if (defined($self->{tai_or}));
122 4 50       17 $tm = Time::HiRes::time() if ($self->opt('hires'));
123 4 100       20 $tm = time() unless($tm);
124 4 100       33 if ($tm =~ /\.\d+/) {
125 2 50       11 if (length($tm) >= 15) {
126 2         8 $tm = substr($tm, 0, 15);
127             } else {
128 0         0 $tm .= '0'x(15-length($tm));
129             }
130             }
131 4         11 return $tm;
132             }
133              
134             sub _lt {
135 2     2   7 my ($self, $tm) = @_;
136 2 50       10 $tm = $self->_tm() unless(defined($tm));
137 2 50       9 return $self->_isotime($tm) if ($self->opt('iso'));
138 2         172 my $lt = localtime($tm);
139 2 50       8 return substr($lt, 4, 15) if ($self->opt('short'));
140 2 50       6 return substr($lt, 11, 5) if ($self->opt('shorter'));
141 2         6 return $lt;
142             }
143              
144             sub opt {
145 115     115 0 207 my ($self, $name, $default_value, $alt_hr) = @_;
146 115         343 return _def($self->{opt_hr}->{$name}, $alt_hr->{$name}, $default_value);
147             }
148              
149             sub _def {
150 115 100   115   182 foreach my $v (@_) { return $v if (defined($v)); }
  307         709  
151 83         305 return undef;
152             }
153              
154             1;
155              
156             =head1 NAME
157              
158             Text::Prefix - Prepend strings with timestamps and potentially other contextually-relevant information.
159              
160             =head1 SYNOPSIS
161              
162             use Text::Prefix;
163              
164             # Simple case: prepend strings with timestamps.
165             #
166             my $px = Text::Prefix->new(); # default just prepends timestamp
167             my $s = $px->prefix("some string");
168             #
169             # $s is now: "Fri Jun 9 16:45:25 2017 1497051925 some string"
170              
171             # More complex case: ISO timestamp, no epoch timestamp, high-resolution
172             # TAI-10 time, hostname, and length of string, in CSV format
173             #
174             my $px = Text::Prefix->new(
175             format => 'csv',
176             host => 1,
177             iso => 1,
178             no_epoch => 1,
179             perl => 'length($s)',
180             tai => 1
181             );
182             my $s = $px->prefix("another string");
183             #
184             # $s is now: '"2017-06-09 16:50:59.9161","xiombarg","14","another string"'
185              
186             =head1 DESCRIPTION
187              
188             B contains the logic implementing the B utility (included in this package). It takes arbitrary strings as
189             input and produces output with various contextually-relevant information preceding the string. A variety of output formats are also
190             supported, as well as output field reordering.
191              
192             This is handy, for instance, when tailing a logfile which does not contain timestamps. B adds a timestamp prefix to each
193             line it is given.
194              
195             =head1 METHODS
196              
197             There are only two methods provided by this package, C and C.
198              
199             =over 4
200              
201             =item B (%options)
202              
203             =over 4
204              
205             (Class method) Returns a new instance of B. The object's default attributes are overridden by any options given.
206              
207             Currently the following attributes may be set:
208              
209             =over 4
210              
211             B =E kvp, tab, csv, space
212              
213             Format the output as a kvp (tab-delimited "key=value" pairs), tab-delimited, comma-delimited, or space-delimited values.
214              
215             (default: "space")
216              
217             B =E 0, 1
218              
219             Set to 1 to use high-resolution timestamps.
220              
221             (default: 0)
222              
223             B =E 0, 1
224              
225             Set to 1 to prefix output with the local hostname.
226              
227             (default: 0)
228              
229             B =E regular expression string
230              
231             Set to a string to exclude the matching part of the hostname from prefix. Implies setting B.
232              
233             (default: none)
234              
235             B =E 0, 1
236              
237             Set to 1 to use ISO-8601 formatted timestamps (more or less).
238              
239             (default: 0)
240              
241             B
242              
243             When output format is "kvp", use the provided string as the key value for the field containing the input string.
244              
245             (default: "d")
246              
247             B =E 0, 1
248              
249             Set to 1 to omit any timestamps from prefixed text (corresponding to output fields "lt" and "tm").
250              
251             (default: 0)
252              
253             B =E 0, 1
254              
255             Set to 1 to omit human-readable timestamps from prefixed text (corresponding to output field "lt").
256              
257             (default: 0)
258              
259             B =E 0, 1
260              
261             Set to 1 to omit epoch timestamps from prefixed text (corresponding to output field "tm").
262              
263             (default: 0)
264              
265             B =E CSV string
266              
267             Given a comma-separated list of key names, change the ordering of the named output fields.
268              
269             Currently supported output fields are:
270              
271             =over 4
272              
273             B - Human-readable timestamp string (mnemonic, "localtime")
274              
275             B - Epoch timestamp
276              
277             B - Hostname
278              
279             B - Literal string provided via passing C parameter to C
280              
281             B - Value returned by evaluating perl provided via C or C parameters passed to C
282              
283             B - Original input string, potentially modified via C or C side-effects. Key may be renamed via C
284              
285             =back
286              
287             (default: "lt, tm, hn, st, pl, d")
288              
289             B =E string containing perl code
290              
291             The provided string will be C'd for every line of input, and its return value included in the output prefix. The input string is available to this code in the variable "$s".
292              
293             (default: none)
294              
295             B =E filename
296              
297             Just like B except the perl code is read from the given file.
298              
299             (default: none)
300              
301             B =E filename
302              
303             When provided, input is appended to the file of the given name before C evaluation or any other reformatting.
304              
305             (default: none)
306              
307             B =E 0, 1
308              
309             Set to 1 to shorten the human-readable timestamp field somewhat.
310              
311             (default: 0)
312              
313             B =E 0, 1
314              
315             Set to 1 to shorten the human-readable timestamp to only the hour and minute (HH:MM).
316              
317             (default: 0)
318              
319             B =E 0, 10, 35
320              
321             When provided, timestamps will reflect TAI-0, TAI-10, or TAI-35 time instead of system time. If option's value is anything other than 0 or 10 or 35, TAI-10 will be assumed. See also: L. TAI time is a high-resolution time, so a fractional second will be included in prefix timestamps.
322              
323             (default: none)
324              
325             B =E filename
326              
327             Just like C, but the output string will be appended to the named file.
328              
329             (default: none)
330              
331             B =E string
332              
333             When provided, the output will include the literal string in its prefix.
334              
335             (default: none)
336              
337             =back
338              
339             =back
340              
341             =item B (string)
342              
343             =over 4
344              
345             Returns the given string after applying the formatting and prefixing rules passed to C.
346              
347             =back
348              
349             =back
350              
351             =head1 TODO
352              
353             Since this module was implemented specifically to support the functionality of the C tool, it lacks some obvious features which a programmer using the module directly might expect:
354              
355             =over 4
356              
357             C should probably take a C option, to supplement C and C.
358              
359             C should support a format option which causes C to return a hashref or arrayref instead of a string.
360              
361             =back
362              
363             =head1 HISTORY
364              
365             =over 4
366              
367             C started life in 2001 as an extremely simple throwaway script. Like many "throwaway" scripts, this one grew haphazardly with little
368             regard to best practices. The author has used it almost every day since then, and was intensely embarrassed by the state of its source code, but
369             it took him until 2017 to get around to refactoring it into C.
370              
371             =back
372              
373             =head1 AUTHORS
374              
375             =over 4
376              
377             TTKCIAR
378              
379             =back
380              
381             =head1 COPYRIGHT AND LICENSE
382              
383             =over 4
384              
385             Copyright (C) 2017 Bill "TTK" Moyer. All rights reserved.
386              
387             This library is free software. You may use it, redistribute it and/or modify it under the same terms as Perl itself.
388              
389             =back