File Coverage

Logger.xs
Criterion Covered Total %
statement 188 200 94.0
branch 115 162 70.9
condition n/a
subroutine n/a
pod n/a
total 303 362 83.7


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