Lib NSLOG ========= 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 units. Categories should be declared in headers, and defined in C files. You declare a category thusly: NSLOG_DECLARE_CATEGORY(catname); 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 `DEEPDEBUG`, `DEBUG`, `VERBOSE`.... * 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 dramatically.