File Coverage

Logger.xs
Criterion Covered Total %
statement 198 213 92.9
branch 130 184 70.6
condition n/a
subroutine n/a
pod n/a
total 328 397 82.6


line stmt bran cond sub pod time code
1             #define PERL_EXT_XS_LOG 1
2             #include
3             #include
4             #include
5             #include
6             #include
7             #include
8             #include
9             #include
10             #include
11             #include
12             #include
13             #include "logger.h"
14              
15             char*
16 71           get_default_file_path() {
17             char *path;
18 71           SV *sv = get_sv( "XS::Logger::PATH_FILE", 0 );
19              
20 71 50         if ( sv && SvPOK(sv) )
    50          
21 71 50         path = SvPV_nolen( sv );
22             else
23 0           path = (char *) DEFAULT_LOG_FILE; /* fallback to default path */
24              
25 71           return path;
26             }
27              
28             char*
29 68           _file_path_for_logger(MyLogger *self) {
30 68 100         if ( strlen(self->filepath) )
31 34           return self->filepath;
32             else
33 34           return get_default_file_path();
34             }
35              
36             /* c internal functions */
37             void
38 100           do_log(MyLogger *mylogger, logLevel level, const char *fmt, int num_args, ...) {
39 100           FILE *fhandle = NULL;
40 100           char *path = NULL;
41 100           SV *sv = NULL;
42             /* Get current time */
43 100           time_t t = time(NULL);
44 100           struct tm lt = {0};
45             char buf[32];
46 100           bool has_logger_object = true;
47 100           bool hold_lock = false;
48             pid_t pid;
49 100           bool quiet = false; /* do not display messages on stderr when quiet mode enabled */
50              
51 100           localtime_r(&t, <);
52              
53 100 50         if ( level == LOG_DISABLE ) /* to move earlier */
54 0           return;
55              
56 100           buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", <)] = '\0';
57              
58 100           pid = getpid();
59              
60             /* Note: *mylogger can be a NULL pointer => would fall back to a GV string or a constant from .c to get the filename */
61 100 100         if ( mylogger ) { /* we got a mylogger pointer */
62 63           path = _file_path_for_logger( mylogger );
63              
64 63 100         if ( mylogger->pid && mylogger->pid != pid ) {
    100          
65 1 50         if (mylogger->fhandle) fclose(mylogger->fhandle);
66 1           mylogger->fhandle = NULL;
67             }
68 63 100         if ( ! mylogger->fhandle ) {
69 14 50         if ( (fhandle = fopen( path, "a" )) == NULL ) /* open in append mode */
70 0           croak("Failed to open file \"%s\"", path);
71 14           mylogger->fhandle = fhandle; /* save the fhandle for future reuse */
72 14           mylogger->pid = pid; /* store the pid which open the file */
73              
74 14 50         ACQUIRE_LOCK_ONCE(fhandle); /* get a lock before moving to the end */
75 14           fseek(fhandle, 0, SEEK_END);
76             }
77 63           fhandle = mylogger->fhandle;
78 63           quiet = mylogger->quiet;
79             } else {
80 37           path = get_default_file_path();
81 37           has_logger_object = false;
82              
83 37 50         if ( (fhandle = fopen( path, "a" )) == NULL ) /* open in append mode */
84 0           croak("Failed to open file \"%s\"", path);
85              
86 37 50         ACQUIRE_LOCK_ONCE(fhandle); /* get a lock before moving to the end */
87 37           fseek(fhandle, 0, SEEK_END);
88             }
89              
90 100 50         if ( fhandle ) {
91             va_list args;
92 100           int abs_gmtoff = lt.tm_gmtoff >= 0 ? lt.tm_gmtoff : -1 * lt.tm_gmtoff;
93 100 100         if (num_args) va_start(args, num_args);
94              
95 100 100         ACQUIRE_LOCK_ONCE(fhandle);
96              
97             /* write the message */
98             /* header: [timestamp tz] pid LEVEL */
99 100 100         if ( mylogger && mylogger->use_color ) {
    100          
100 56 50         M_FPRINTF( fhandle, "[%s %s%02d%02d] %s%-5s%s",
    100          
    50          
101             buf,
102             lt.tm_gmtoff >= 0 ? "+" : "-",
103             (int) abs_gmtoff / 3600,
104             ( abs_gmtoff % 3600) / 60,
105             LEVEL_COLORS[level], LOG_LEVEL_NAMES[level], END_COLOR
106             );
107             } else {
108 44 50         M_FPRINTF( fhandle, "[%s %s%02d%02d] %-5s",
    100          
    50          
109             buf,
110             lt.tm_gmtoff >= 0 ? "+" : "-",
111             (int) abs_gmtoff / 3600, ( abs_gmtoff % 3600) / 60,
112             LOG_LEVEL_NAMES[level]
113             );
114             }
115             {
116 100           SV *const dollar_0 = get_sv("0",GV_ADDWARN); /* $0 - application name */
117             char *str_dollar_0;
118              
119 100 50         if ( !SvPOK(dollar_0) ) { /* probably a better helper to simply get the PV at all cost */
120 0 0         if ( SvIOK(dollar_0) )
121 0 0         SvUPGRADE(dollar_0, SVt_PVIV);
122             else
123 0           croak("dollar_0 is not a string?!");
124             }
125 100 50         str_dollar_0 = SvPV_nolen( dollar_0 );
126 100 100         M_FPRINTF( fhandle, " %u [%s] ", (unsigned int) pid, str_dollar_0 ); /* print the source */
127             /* with the pid ? */
128             /* fprintf( fhandle, " [%u %s] ", (unsigned int) pid, str_dollar_0 ); */
129             }
130             {
131 100           int len = 0;
132              
133             //PerlIO_printf( PerlIO_stderr(), "# num_args %d\n", num_args );
134 100 50         if ( fmt && (len=strlen(fmt)) ) {
    100          
135 58 50         if (num_args == 0) /* no need to use sprintf when not needed */
136 0 0         M_FPUTS( fmt, fhandle )
137             else
138 58 100         M_VFPRINTF( fhandle, fmt, args )
    100          
139             }
140             // only add "\n" if missing from fmt
141 100 100         if ( !len || fmt[len-1] != '\n')
    100          
142 99 100         M_FPUTS( "\n", fhandle );
143             }
144 100 100         if (has_logger_object) fflush(fhandle); /* otherwise we are going to close the ffhandle just after */
145 100 100         if (num_args) va_end(args);
146             }
147              
148 100 50         RELEASE_LOCK(fhandle); /* only release if acquired before */
149              
150 100 100         if ( !has_logger_object ) fclose( fhandle );
151              
152 100           return;
153             }
154              
155             /* function exposed to the module */
156             /* maybe a bad idea to use a prefix */
157             MODULE = XS__Logger PACKAGE = XS::Logger PREFIX = xlog_
158              
159             TYPEMAP: <
160              
161             MyLogger* T_PTROBJ
162             XS::Logger T_PTROBJ
163              
164             HERE
165              
166             XS::Logger
167             xlog_new(class, ...)
168             char* class;
169             PREINIT:
170             MyLogger* mylogger;
171 21           HV* opts = NULL;
172             SV **svp;
173             CODE:
174             {
175 21           Newxz( mylogger, 1, MyLogger );
176 21           RETVAL = mylogger;
177              
178 21 100         if( items > 1 ) { /* could also probably use va_start, va_list, ... */
179 16           SV *extra = (SV*) ST(1);
180              
181 16 50         if ( SvROK(extra) && SvTYPE(SvRV(extra)) == SVt_PVHV )
    50          
182 16           opts = (HV*) SvRV( extra );
183             }
184              
185             /* default (non zero) values */
186 21           mylogger->use_color = true; /* maybe use a GV from the stash to set the default value */
187 21 100         if ( opts ) {
188 16 100         if ( (svp = hv_fetchs(opts, "color", FALSE)) ) {
189 3 50         if (!SvIOK(*svp)) croak("invalid color option value: should be a boolean 1/0");
190 3 50         mylogger->use_color = (bool) SvIV(*svp);
191             }
192 16 100         if ( (svp = hv_fetchs(opts, "level", FALSE)) ) {
193 6 50         if (!SvIOK(*svp)) croak("invalid log level: should be one integer");
194 6 50         mylogger->level = (logLevel) SvIV(*svp);
195             }
196 16 100         if ( (svp = hv_fetchs(opts, "quiet", FALSE)) ) {
197 13 50         if (!SvIOK(*svp)) croak("invalid quiet value: should be one integer 0 or 1");
198 13 50         mylogger->quiet = (logLevel) SvIV(*svp);
199             }
200 16 100         if ( (svp = hv_fetchs(opts, "logfile", FALSE)) || (svp = hv_fetchs(opts, "path", FALSE)) ) {
    100          
201             STRLEN len;
202             char *src;
203              
204 6 50         if (!SvPOK(*svp)) croak("invalid logfile path: must be a string");
205 6 50         src = SvPV(*svp, len);
206 6 50         if (len >= sizeof(mylogger->filepath))
207 0           croak("file path too long max=256!");
208 6           strcpy(mylogger->filepath, src); /* do a copy to the object */
209             }
210             }
211             }
212             OUTPUT:
213             RETVAL
214              
215             void
216             xlog_loggers(...)
217             ALIAS:
218             XS::Logger::info = 1
219             XS::Logger::warn = 2
220             XS::Logger::error = 3
221             XS::Logger::die = 4
222             XS::Logger::panic = 5
223             XS::Logger::fatal = 6
224             XS::Logger::debug = 7
225             PREINIT:
226             SV *ret;
227             SV* self; /* optional */
228             CODE:
229             {
230 115           logLevel level = LOG_DISABLE;
231 115           bool dolog = true;
232 115           MyLogger* mylogger = NULL; /* can be null when not called on an object */
233 115           int args_start_at = 0;
234 115           bool should_die = false;
235             const char *fmt;
236 115           MultiValue targs[10] = {0}; /* no need to malloc limited to 10 */
237              
238 115           switch (ix) {
239             case 1: /* info */
240 38           level = LOG_INFO;
241 38           break;
242             case 2: /* warn */
243 13           level = LOG_WARN;
244 13           break;
245             case 3: /* error */
246 12           level = LOG_ERROR;
247 12           break;
248             case 4: /* die */
249 6           level = LOG_ERROR;
250 6           should_die = true;
251 6           break;
252             case 5: /* panic */
253             case 6: /* fatal */
254 18           level = LOG_FATAL;
255 18           should_die = true;
256 18           break;
257             case 7:
258 28           level = LOG_DEBUG;
259 28           break;
260             default:
261 0           level = LOG_DISABLE;
262             }
263              
264             /* check if called as function or method call */
265 115 100         if ( items && SvROK(ST(0)) && SvOBJECT(SvRV(ST(0))) ) { /* check if self is an object */
    100          
    50          
266 78           self = ST(0);
267 78           args_start_at = 1;
268 78 50         mylogger = INT2PTR(MyLogger*, SvIV(SvRV(self)));
269             /* check the caller level */
270 78 100         if ( level < mylogger->level )
271 10           dolog = false;
272             }
273              
274 115 100         if (dolog) {
275             SV **list;
276 105 100         if ( items < (1 + args_start_at) ) {
277 42           fmt = EMPTY_STR;
278 42           do_log( mylogger, level, fmt, 0 ); /* do a simple call */
279 63 100         } else if ( items <= ( 11 + args_start_at ) ) { /* set a cap on the maximum of item we can use: 10 arguments + 1 format + 1 for self */
280             IV i;
281 58           I32 nitems = items - args_start_at; /* for self */
282              
283             //Newx(list, nitems, SV*);
284 183 100         for ( i = args_start_at ; i < items ; ++i ) {
285 125           SV *sv = ST(i);
286 125 50         if ( !SvOK(sv) )
    0          
    0          
287 0           croak( "Invalid element item %i - not an SV.", (int) i );
288             else {
289             /* do a switch on the type */
290 125 100         if ( i == args_start_at ) { /* the first entry shoulkd be the format */
291 58 100         if ( !SvPOK(sv) ) { /* maybe upgrade to a PV */
292 1 50         if ( SvIOK(sv) )
293 1 50         SvUPGRADE(sv, SVt_PVIV);
294             else
295 0           croak("First argument must be a string.");
296             }
297 58 100         fmt = SvPV_nolen( sv );
298             } else {
299 67           int ix = i - 1 - args_start_at;
300              
301 67 100         if ( SvIOK(sv) ) { /* SvTYPE(sv) == SVt_IV */
302 61 50         targs[ix].ival = SvIV(sv);
303 6 100         } else if ( SvNOK(sv) ) { // not working for now
304             //PerlIO_printf( PerlIO_stderr(), "# SV SV %f\n", 1.345 );
305             //PerlIO_printf( PerlIO_stderr(), "# SV SV %f\n", SvNV(sv) );
306 1 50         targs[ix].fval = SvNV(sv);
307             } else {
308 5 50         targs[ix].sval = SvPV_nolen(sv);
309             }
310             }
311             }
312             }
313             /* not really necessary but probaby better for performance */
314 58           switch ( nitems ) {
315             case 1:
316 42           do_log( mylogger, level, fmt, nitems,
317             targs[0]
318             );
319 42           break;
320             case 2:
321 4           do_log( mylogger, level, fmt, nitems,
322             targs[0], targs[1]
323             );
324 4           break;
325             case 3:
326 2           do_log( mylogger, level, fmt, nitems,
327             targs[0], targs[1], targs[2]
328             );
329 2           break;
330             case 4:
331 2           do_log( mylogger, level, fmt, nitems,
332             targs[0], targs[1], targs[2], targs[3]
333             );
334 2           break;
335             case 5:
336 2           do_log( mylogger, level, fmt, nitems,
337             targs[0], targs[1], targs[2], targs[3], targs[4]
338             );
339 2           break;
340             case 6:
341 1           do_log( mylogger, level, fmt, nitems,
342             targs[0], targs[1], targs[2], targs[3], targs[4],
343             targs[5]
344             );
345 1           break;
346             case 7:
347 1           do_log( mylogger, level, fmt, nitems,
348             targs[0], targs[1], targs[2], targs[3], targs[4],
349             targs[5], targs[6]
350             );
351 1           break;
352             case 8:
353 1           do_log( mylogger, level, fmt, nitems,
354             targs[0], targs[1], targs[2], targs[3], targs[4],
355             targs[5], targs[6], targs[7]
356             );
357 1           break;
358             case 9:
359 1           do_log( mylogger, level, fmt, nitems,
360             targs[0], targs[1], targs[2], targs[3], targs[4],
361             targs[5], targs[6], targs[7], targs[8]
362             );
363 1           break;
364             case 10:
365 1           do_log( mylogger, level, fmt, nitems,
366             targs[0], targs[1], targs[2], targs[3], targs[4],
367             targs[5], targs[6], targs[7], targs[8], targs[9]
368             );
369 1           break;
370             default:
371 58           do_log( mylogger, level, fmt, nitems,
372             targs[0], targs[1], targs[2], targs[3], targs[4],
373             targs[5], targs[6], targs[7], targs[8], targs[9]
374             );
375             }
376             } else {
377 5           croak("Too many args to the caller (max=10).");
378             }
379             } /* end of dolog */
380              
381 110 100         if ( should_die ) /* maybe fatal needs to exit */ {
382             /* FIXME: right now only using the fmt without the args */
383             /* exit level [panic] [pid=6904] (This is a message) */
384 24           croak( "exit level [%s] [pid=%d] (%s)\n", LOG_LEVEL_NAMES_lc[level], getpid(),
385             fmt
386             );
387             }
388              
389             /* no need to return anything there */
390 86           XSRETURN_EMPTY;
391             }
392              
393             SV*
394             xlog_getters(self)
395             XS::Logger self;
396             ALIAS:
397             XS::Logger::get_pid = 1
398             XS::Logger::use_color = 2
399             XS::Logger::get_level = 3
400             XS::Logger::get_quiet = 4
401             XS::Logger::get_file_path = 5
402             XS::Logger::logfile = 5
403             PREINIT:
404             MyLogger* mylogger;
405             char *fp;
406             CODE:
407             { /* some getters: mainly used for test for now to access internals */
408              
409 14           switch (ix) {
410             case 1:
411 1           RETVAL = newSViv( self->pid );
412 1           break;
413             case 2:
414 2           RETVAL = newSViv( self->use_color );
415 2           break;
416             case 3:
417 3           RETVAL = newSViv( (int) self->level );
418 3           break;
419             case 4:
420 3           RETVAL = newSViv( (int) self->quiet );
421 3           break;
422             case 5:
423 5           fp = _file_path_for_logger( self );
424 5           RETVAL = newSVpv( fp, strlen(fp) );
425 5           break;
426             default:
427 0           XSRETURN_EMPTY;
428             }
429             }
430             OUTPUT:
431             RETVAL
432              
433             void
434             xlog_setters(self, value)
435             XS::Logger self;
436             SV* value;
437             ALIAS:
438             XS::Logger::set_level = 1
439             XS::Logger::set_quiet = 2
440             PREINIT:
441             MyLogger* mylogger;
442             CODE:
443             { /* improve protection on self/logger here */
444            
445 2           switch (ix) {
446             case 1:
447 1 50         if ( !SvIOK(value) ) croak("invalid level: must be interger.");
448 1 50         self->level = SvIV(value);
449 1           break;
450             case 2:
451 1 50         if ( !SvIOK(value) ) croak("invalid quiet value: must be interger.");
452 1 50         self->quiet = SvIV(value);
453 1           break;
454             default:
455 0           croak("undefined setter");
456             }
457              
458 2           XSRETURN_EMPTY;
459             }
460              
461             void xlog_DESTROY(self)
462             XS::Logger self;
463             PREINIT:
464             I32* temp;
465             PPCODE:
466             {
467 21           temp = PL_markstack_ptr++;
468              
469 21 50         if ( self ) {
470             /* close the file fhandle on destroy if exists */
471 21 100         if ( self->fhandle )
472 13           fclose( self->fhandle );
473             /* free the logger... maybe more to clear from struct */
474 21           Safefree(self);
475             }
476              
477 21 50         if (PL_markstack_ptr != temp) {
478             /* truly void, because dXSARGS not invoked */
479 21           PL_markstack_ptr = temp;
480 21           XSRETURN_EMPTY;
481             /* return empty stack */
482             } /* must have used dXSARGS; list context implied */
483              
484 0           return; /* assume stack size is correct */
485             }
486              
487             BOOT:
488             {
489             HV *stash;
490             SV *sv;
491 9           stash = gv_stashpvn("XS::Logger", 10, TRUE);
492              
493 9           newCONSTSUB(stash, "_loaded", newSViv(1) );
494 9           newCONSTSUB(stash, "DEBUG_LOG_LEVEL", newSViv( LOG_DEBUG ) );
495 9           newCONSTSUB(stash, "INFO_LOG_LEVEL", newSViv( LOG_INFO ) );
496 9           newCONSTSUB(stash, "WARN_LOG_LEVEL", newSViv( LOG_WARN ) );
497 9           newCONSTSUB(stash, "ERROR_LOG_LEVEL", newSViv( LOG_ERROR ) );
498 9           newCONSTSUB(stash, "FATAL_LOG_LEVEL", newSViv( LOG_FATAL ) );
499 9           newCONSTSUB(stash, "DISABLE_LOG_LEVEL", newSViv( LOG_DISABLE ) );
500              
501 9           sv = get_sv("XS::Logger::PATH_FILE", GV_ADD|GV_ADDMULTI);
502 9 100         if ( ! SvPOK(sv) ) { /* preserve any value set before loading the module */
503 8           SvREFCNT_inc(sv);
504 8           sv_setpv(sv, DEFAULT_LOG_FILE);
505             }
506             }