Added a rudimentary yet very easy to use logger; can log to one or more targets at...
authorskeezix <skeezix@flotsam-vm.(none)>
Thu, 12 Nov 2009 18:56:55 +0000 (13:56 -0500)
committerskeezix <skeezix@flotsam-vm.(none)>
Thu, 12 Nov 2009 18:56:55 +0000 (13:56 -0500)
Makefile
include/pnd_logger.h [new file with mode: 0644]
lib/pnd_logger.c [new file with mode: 0644]
test/loggertest.c [new file with mode: 0644]

index 4bfd9cf..77cbf2b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -21,12 +21,12 @@ LIB = libpnd.a
 SOLIB = libpnd.so.1         # canonicle name
 SOLIB1 = libpnd.so.1.0.1    # versioned name
 XMLOBJ = lib/tinyxml/tinystr.o lib/tinyxml/tinyxml.o lib/tinyxml/tinyxmlerror.o lib/tinyxml/tinyxmlparser.o
-ALLOBJ = pnd_conf.o pnd_container.o pnd_discovery.o pnd_pxml.o pnd_notify.o pnd_locate.o pnd_tinyxml.o pnd_pndfiles.o pnd_apps.o pnd_utility.o pnd_desktop.o pnd_io_gpio.o
+ALLOBJ = pnd_conf.o pnd_container.o pnd_discovery.o pnd_pxml.o pnd_notify.o pnd_locate.o pnd_tinyxml.o pnd_pndfiles.o pnd_apps.o pnd_utility.o pnd_desktop.o pnd_io_gpio.o pnd_logger.o
 
-all: ${SOLIB} ${LIB} conftest discotest notifytest pndnotifyd rawpxmltest pndvalidator
+all: ${SOLIB} ${LIB} conftest discotest notifytest pndnotifyd rawpxmltest pndvalidator loggertest
 
 clean:
-       ${RM} -f ${ALLOBJ} ${XMLOBJ} ${LIB} ${SOLIB1} locatetest.o bin/locatetest conftest.o bin/conftest discotest.o bin/discotest bin/notifytest notifytest.o bin/rawpxmltest rawpxmltest.o bin/pndnotifyd pndnotifyd.o ${SOLIB} testdata/dotdesktop/*.desktop testdata/apps/*.pnd testdata/dotdesktop/*.png deployment/usr/lib/libpnd* deployment/usr/bin/pndnotifyd deployment/usr/pandora/scripts/* deployment/etc/sudoers deployment/etc/init.d/pndnotifyd bin/pndvalidator pndvalidator.o
+       ${RM} -f ${ALLOBJ} ${XMLOBJ} ${LIB} ${SOLIB1} locatetest.o bin/locatetest conftest.o bin/conftest discotest.o bin/discotest loggertest.o bin/loggertest bin/notifytest notifytest.o bin/rawpxmltest rawpxmltest.o bin/pndnotifyd pndnotifyd.o ${SOLIB} testdata/dotdesktop/*.desktop testdata/apps/*.pnd testdata/dotdesktop/*.png deployment/usr/lib/libpnd* deployment/usr/bin/pndnotifyd deployment/usr/pandora/scripts/* deployment/etc/sudoers deployment/etc/init.d/pndnotifyd bin/pndvalidator pndvalidator.o
        ${RM} -rf deployment/media
        find . -name "*~*" -exec rm {} \; -print
 
@@ -98,3 +98,6 @@ locatetest:   locatetest.o ${SOLIB1}
 
 rawpxmltest:    rawpxmltest.o ${LIB}
        ${CC} -lstdc++ -o bin/rawpxmltest rawpxmltest.o ${LIB}
+
+loggertest:    loggertest.o ${LIB}
+       ${CC} -lstdc++ -o bin/loggertest loggertest.o libpnd.a
diff --git a/include/pnd_logger.h b/include/pnd_logger.h
new file mode 100644 (file)
index 0000000..dd9fe0d
--- /dev/null
@@ -0,0 +1,44 @@
+
+#ifndef h_pnd_logger_h
+#define h_pnd_logger_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* rudimentary logger; note that this is meant to be a no-brainer to use, so no setup is needed.
+ */
+
+// defaults will have no filtering, so any message will be emitted to all targets
+// default target is nothing, however, so logger is silent unless activated
+unsigned char pnd_log ( unsigned char level, char *format, ... );           // returns true if emitted; \n is implied!
+unsigned char pnd_log_to_stdout ( void );                                   // same as pnd_log_to_stream ( stdout );
+unsigned char pnd_log_to_stderr ( void );                                   // same as pnd_log_to_stream ( stderr );
+
+/* the below is all optional, for when you need more control
+ */
+
+// logging is additive; you can log to multiple targets at once. Returns 'true' if accepted, false if could not set up.
+void pnd_log_to_nil ( void );                                               // stop logging to anywhere; does not close streams/etc
+unsigned char pnd_log_to_stream ( FILE * );                                 // 'stdout', 'stderr', or your own FILE* are good values
+unsigned char pnd_log_to_syslog ( char *facility );                         // NYI
+typedef void (*pnd_log_callback_f)( char *text, void *userdata );
+unsigned char pnd_log_to_callback ( pnd_log_callback_f f, void *userdata ); // NYI
+
+// pass NULL to free any pre-text, otherwise it'll be kept. Passed in string is duplicated, so you may free yours if you like.
+void pnd_log_set_pretext ( char * );                                        // example: your app-name, or app+function-names, say.
+
+// set a 'filter level'; any log message of higher-or-equal level than current filter-level will be emitted. Thus, to remove filters
+// just set to level 0. Returns existing setting.
+unsigned char pnd_log_set_filter ( unsigned char newlevel );                // ex: app-specific enum/#defines for your levels
+
+// how many targets can be opened, entirely? this is a compile time limit, for sanity.
+unsigned char pnd_log_max_targets ( void );
+
+#ifdef __cplusplus
+} /* "C" */
+#endif
+
+#endif
diff --git a/lib/pnd_logger.c b/lib/pnd_logger.c
new file mode 100644 (file)
index 0000000..c3bcc43
--- /dev/null
@@ -0,0 +1,181 @@
+
+#include <stdarg.h> // va-args
+#include <stdlib.h> // malloc/free
+#include <string.h> // strdup
+#include "pnd_logger.h"
+
+char *log_pretext = NULL;
+unsigned char log_filterlevel = 0;
+
+typedef enum {
+  pndl_nil = 0,
+  pndl_stream,
+  pndl_syslog,
+  pndl_callback
+} pnd_logger_e;
+
+typedef struct {
+  pnd_logger_e type;
+  union {
+    FILE *stream;
+    pnd_log_callback_f callback;
+  };
+} pnd_log_target_t;
+
+#define PND_LOG_MAX 5
+static pnd_log_target_t log_targets [ PND_LOG_MAX ]; // implicitly nil
+
+static int pnd_log_empty_slot ( void ) {
+  unsigned char i;
+
+  for ( i = 0; i < PND_LOG_MAX; i++ ) {
+    if ( log_targets [ i ].type == pndl_nil ) {
+      return ( i );
+    }
+  }
+
+  return ( -1 );
+}
+
+unsigned char pnd_log_set_filter ( unsigned char newlevel ) {
+  unsigned char foo = log_filterlevel;
+  log_filterlevel = newlevel;
+  return ( foo );
+}
+
+void pnd_log_set_pretext ( char *pre ) {
+
+  if ( log_pretext ) {
+    free ( log_pretext );
+    log_pretext = NULL;
+  }
+
+  if ( pre ) {
+    log_pretext = strdup ( pre );
+  }
+
+  return;
+}
+
+void pnd_log_to_nil ( void ) {
+  memset ( log_targets, '\0', sizeof(pnd_log_target_t) * PND_LOG_MAX );
+  return;
+}
+
+unsigned char pnd_log_to_stream ( FILE *f ) {
+  int i = pnd_log_empty_slot();
+
+  if ( i < 0 ) {
+    return ( 0 ); // fail!
+  }
+
+  log_targets [ i ].type = pndl_stream;
+  log_targets [ i ].stream = f;
+
+  return ( 1 );
+}
+
+unsigned char pnd_log_to_syslog ( char *facility ) {
+  return ( 0 ); // NYI
+}
+
+unsigned char pnd_log_to_callback ( pnd_log_callback_f f, void *userdata ) {
+  return ( 0 ); // NYI
+}
+
+unsigned char pnd_log_to_stdout ( void ) {
+  return ( pnd_log_to_stream ( stdout ) );
+}
+
+unsigned char pnd_log_to_stderr ( void ) {
+  return ( pnd_log_to_stream ( stderr ) );
+}
+
+unsigned char pnd_log_max_targets ( void ) {
+  return ( PND_LOG_MAX );
+}
+
+void pnd_log_emit ( char *message ) {
+  unsigned char i;
+
+  // iterate across targets and attempt to emit
+  for ( i = 0; i < PND_LOG_MAX; i++ ) {
+
+    switch ( log_targets [ i ].type ) {
+
+    case pndl_nil:
+      // nop
+      break;
+
+    case pndl_stream:
+      if ( log_pretext ) {
+       fprintf ( log_targets [ i ].stream, "%s\t", log_pretext );
+      }
+      if ( message ) {
+       fprintf ( log_targets [ i ].stream, "%s\n", message );
+      }
+      break;
+
+    case pndl_syslog:
+      // NYI
+      break;
+
+    case pndl_callback:
+      // NYI
+      break;
+
+    } // switch
+
+  } // for
+
+  return;
+}
+
+unsigned char pnd_log ( unsigned char level, char *fmt, ... ) {
+
+  if ( level < log_filterlevel ) {
+    return ( 0 ); // too low level
+  }
+
+  // format the actual log string
+  int n, size = 100;
+  char *p, *np;
+  va_list ap;
+
+  if ( ( p = malloc ( size ) ) == NULL ) {
+    return ( 0 ); // fail!
+  }
+
+  while  ( 1 ) {
+
+    /* Try to print in the allocated space. */
+    va_start ( ap, fmt );
+    n = vsnprintf ( p, size, fmt, ap );
+    va_end ( ap );
+
+    /* If that worked, return the string. */
+    if ( n > -1 && n < size ) {
+      pnd_log_emit ( p );
+      break;
+    }
+
+    /* Else try again with more space. */
+    if ( n > -1 )   /* glibc 2.1 */
+      size = n + 1; /* precisely what is needed */
+    else           /* glibc 2.0 */
+      size *= 2;  /* twice the old size */
+    if ( ( np = realloc ( p, size ) ) == NULL ) {
+      free(p);
+      return ( 0 ); // fail!
+    } else {
+      p = np;
+    }
+
+  } // while
+
+  if ( p ) {
+    free ( p );
+  }
+
+  return ( 1 );
+}
diff --git a/test/loggertest.c b/test/loggertest.c
new file mode 100644 (file)
index 0000000..f9255cf
--- /dev/null
@@ -0,0 +1,39 @@
+
+#include "pnd_logger.h"
+
+// arbitrary warning level
+#define PLOG_LOW    0
+#define PLOG_MEDIUM 1
+#define PLOG_HIGH   2
+
+int main ( void ) {
+
+  pnd_log ( PLOG_LOW, "low message, should be ignored" );
+
+  /* normal operation -------------- */
+  pnd_log_to_stdout();
+  pnd_log ( PLOG_LOW, "low message, should go stdout once" );
+  /* ------------------------------- */
+
+  /* extra testing vvvvvvvvvvvvvvvvv
+   */
+
+  pnd_log_to_stdout();
+  pnd_log ( PLOG_LOW, "low message, should go stdout twice" );
+
+  pnd_log_to_nil();
+  pnd_log_to_stdout();
+
+  pnd_log_set_pretext ( "loggertest" );
+  pnd_log ( PLOG_LOW, "low message, emit once, with pretext" );
+
+  pnd_log_set_filter ( PLOG_MEDIUM );
+  pnd_log ( PLOG_LOW, "low message, emit once, filter is medium+" );
+  pnd_log ( PLOG_MEDIUM, "medium message, emit once, filter is medium+" );
+  pnd_log ( PLOG_HIGH, "high message, emit once, filter is medium+" );
+
+  pnd_log_set_filter ( PLOG_LOW );
+  pnd_log ( PLOG_LOW, "low message, emit once, filter is low+ again" );
+
+  return ( 0 );
+}