diff options
authorDaniel Silverstone <>2017-08-03 16:03:24 -0400
committerDaniel Silverstone <>2017-08-03 16:03:24 -0400
commit73860cdc8625b4f8443cb8531019d6f57f8b59ce (patch)
parent659477920e8fdbfee6e2776254d8f0463e7723fb (diff)
Add a main document for libnslog
2 files changed, 163 insertions, 1 deletions
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 29be490..870b8ad 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -655,7 +655,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = include
+INPUT = include docs
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -773,6 +773,8 @@ FILTER_SOURCE_FILES = NO
# configuration options related to source browsing
diff --git a/docs/ b/docs/
new file mode 100644
index 0000000..1d29aee
--- /dev/null
+++ b/docs/
@@ -0,0 +1,160 @@
+The purpose of the NSLog library is to provide a flexible logging macro which
+permits logging with categories, and at levels. Then the library can queue
+log entries and permit filtering for log readers.
+Since logging may have to happen during static initialisation, or before the
+logging library is fully initialised, categories are progressively added to
+the logging system as they are encountered. Since, at their core, categories
+are essentially just strings, we can write filters which will be resolved to
+categories later as and when they are encountered.
+Libraries which use NSLog should *NOT* act as clients of the library excepting
+in tools, and test drivers. By implementing a client in the test drivers,
+libraries can verify the log messages sent. If a library might produce a lot
+of logging then it must implement a client in its test drivers or it might run
+out of RAM while running tests.
+How to log stuff
+In order to log messages, you need to have a category to log under. We
+recommend that you have one category per library or application, and that you
+create sub-categories beneath that to divide the library (or app) into logical
+Categories should be declared in headers, and defined in C files. You declare
+a category thusly:
+In that `catname` is a valid suffix for a C identifier, and will be used as
+the logical name of the category for the purpose of NSLOG statements.
+You define a category thusly:
+ NSLOG_DEFINE_CATEGORY(catname, description);
+In that `catname` matches the above, and `description` is a C string (or
+identifier to a C string) which describes the category.
+You define a subcategory thusly:
+ NSLOG_DEFINE_SUBCATEGORY(parentcatname, catname, description);
+In that `parentcategory` is the `catname` of another category which will be
+considered the parent of this subcategory. `catname` and `description` as
+above. Subcategories are rendered with slashes between their names when shown
+as text, so a category of `foo` with a subcategory of `bar` would result in
+two categories, one `foo` and one `foo/bar`.
+Once you have categories, you can log stuff. Your categories don't have to be
+entirely public, but you will need access to the symbol to be able to log
+things with the given category. To log something, you use:
+ NSLOG(catname, level, logmsg, args...);
+In that `catname` is a `catname` from the above, `level` is a bareword logging
+level which will be prefixed with `NSLOG_LEVEL_` for use (e.g. you can use
+`WARNING` directly). `logmsg` and `args...` are a `printf()` style log message
+which will be rendered by the time `NSLOG()` returns, so you don't have to
+worry about lifetimes.
+Lib NSLOG lets you define a minimum compiled-in logging level which can be used
+to elide deep debugging in release builds. The `NSLOG()` macro expands to a
+constant comparison which will be resolved at compile time (assuming
+optimisations are turned on) to result in logging which doesn't need to be
+compiled in, being compiled out (and thus is is basically zero-cost to sprinkle
+deep debugging in your code).
+Being a libnslog client
+In order for code which uses `NSLOG()` to be hosted effectively, the client must
+do two things. Firstly a callback must be provided. The simplest callback
+would be:
+ static nslog__client_callback(void *context, nslog_entry_context_t *ctx,
+ const char *fmt, va_list args)
+ {
+ (void)context;
+ (void)ctx;
+ (void)fmt;
+ va_start(args, fmt);
+ va_end(args);
+ }
+In that `context` is a `void*` which is passed through to the client code
+directly, allowing a client to provide some struct or other through to the
+logging client callback. `ctx` is an NSLOG context which you can find out more
+about by reading the docs. It contains the information about the logging
+*site* such as the filename, line number, function name, level, and category
+used to make the log entry. `fmt` is the `logmsg` from above, and `args`
+is the variadic argument set used to format the arguments.
+This callback is registered into Lib NSLOG with something like:
+ nslog_set_render_callback(nslog__client_callback, NULL);
+If a callback is not set, then bad things will happen when the next thing is
+run. The final thing which must happen before the client will receive log
+statements is that the client must _uncork_ the logging library. Since logging
+can occur **before** the client is ready, the library will render and store the
+log messages in a list, awaiting the call to uncork. Once uncorked, log
+messages will be passed through as flatly as possible to the client. To uncork
+the library, call:
+ nslog_uncork();
+All corked messages will be passed to the client callback, in the order they
+were logged, before the uncork call returns.
+Filtering logs
+The primary way to filter logs is to simply compile out the logging levels you
+do not wish to exist. Sadly this is a very coarse filter and is best used to
+exclude deep debug (and possibly debug) from release builds. If you wish to
+filter your logs more flexibly at runtime, then you need to set up a log
+filter. You *can* build the filters "by hand" but much easier is to use the
+text filter parser as such:
+ nslog_filter_t *filter = NULL;
+ if (nslog_filter_from_text(my_filter_text, &output) == NSLOG_NO_ERROR) {
+ nslog_filter_set_active(filter, NULL);
+ nslog_filter_unref(filter);
+ }
+The filter syntax is fairly simple:
+* A `filter` is one of a `NOT` an `AND`, `OR`, or `XOR`, or a `simple` filter.
+* a `NOT` filter is of the form `!filter`
+* an `AND` filter is of the form `(filter && filter)`
+* an `OR` filter is of the form `(filter || filter)`
+* an `XOR` filter is of the form `(filter ^ filter)`
+* A `simple` filter is one of a `level`, `category`, `filename`, `dirname`,
+ or `funcname` filter.
+* a `level` filter is of the form `level: FOOLEVEL` where `FOOLEVEL` is one of
+* a `category` filter is of the form `cat: catname` where `catname` is a
+ category name. If it is of the form `foo` then it will match all categories
+ `foo` or `foo/*` but not `foobar`
+* a `filename` filter is of the form `file: filename` and is essentially a
+ suffix match on the filename. `foo.c` will match `foo.c` and `bar/foo.c`
+ but not `barfoo.c`
+* a `dirname` filter is of the form `dir: dirname` and is essentially a prefix
+ match on the filename. `foo` will match `foo/bar.c` and `foo/bar/baz.c` but
+ not `foobar.c`
+Between tokens, any amount of whitespace is valid, but canonically only a small
+amount will be used. You can take any filter you have and re-render it in its
+canonical text form by using `nslog_filter_sprintf()` which is documented.
+Filters are evaluated in standard order, with parentheses doing the obvious
+thing. In addition, the `AND` and `OR` filters will short-circuit as you'd
+expect, so you can use that to your advantage to make your filters efficient.
+Internally, Lib NSLOG will cache filtering intermediates to make things as fast
+as possible, but a badly designed filter will end up slowing things down