LibComponentLogging

Logging for Objective-C with log levels and log components.

Fork me on GitHub

LibComponentLogging is a small logging library for Objective-C on Mac OS X and iOS which provides log levels, log components, and pluggable logging back-ends, e.g. writing log messages to a file, or sending them to the system log.

LibComponentLogging is open source, licensed under the terms of the MIT license. Copyright (c) 2008-2014 Arne Harren, ah@0xc0.de, @aharren.

Found a bug? Missing a feature? Drop a line to logging@0xc0.de, or create an issue in one of the repositories on GitHub, or fork and contribute.

Download Overview Getting started Read more

A short overview of LibComponentLogging:

Log levels

LibComponentLogging provides built-in log levels for distinguishing between error messages, informational messages, and fine-grained trace messages for debugging.

Log components

Log components can be used to identify different parts of your application or framework. A log component contains a unique identifier, a short name which is used as a header in a log message, and a full name which can be used in a user interface.

Level × component

Each log component has its own active log level. This way it's possible to enable/disable logging only for certain parts of your application or framework.

Low overhead

Logging is based on a log macro which checks the active log level before constructing the log message and before evaluating log message arguments. Your log statements can stay in your code, they consume only little runtime overhead if logging is disabled.

Code completion

Symbols for log components and log levels work with Xcode's code completion:

Meta data

Public data structures expose information about log components, e.g. headers and names, and log levels.

Logging back-ends

The core part of LibComponentLogging focuses on log levels and log components. It provides a simple delegation mechanism for plugging in a specific logging back-end based on your application/framework's requirements, e.g. a back-end which writes to the system log, or a back-end which writes to a log file, or a back-end which integrates a remote logger. The logging back-ends define the actual log output format and add additional details, e.g. time stamp, file name, line number, class and method.

Extensions

LibComponentLogging's core files contain a simple include mechanism for extension modules. Extensions may provide additional log macros or mechanisms to store/restore log level settings from user defaults, files, environment variables, etc.

Assembled at build-time

LibComponentLogging uses a set of configuration header files to configure log components and to register the logging back-end and extensions at build-time. You only need to include lcl.h in your prefix header file; the configuration files for log components, the logging back-end and extensions are included internally.

Optionally ARCed

LibComponentLogging can be used with Automatic Referencing Counting (ARC), or with traditional code that uses retain/release/autorelease calls.

1. Download

Download the core files and one logging back-end from the project pages on GitHub:

core

back-end

Optionally download extensions:

extension

2. Install

Extract the files and copy the extracted files to your application/framework's source directory.

Open Xcode and add all files of the library to your project. Xcode will automatically add the library's implementation files to your project's target.

3. Configure

Create your application/framework's lcl_config_components.h file.

Create a lcl_config_logger.h file and set up the logging back-end, e.g. set the maximum file size and the name of the log file for the LogFile back-end.

Create lcl_config_extensions.h for extensions and optionally add #import statements for extensions you'd like to use.

Add an #import "lcl.h" statement to your source files, e.g. to your project's prefix header file.

4. Use

Define your log components in lcl_config_components.h.

Add lcl_log(...) or lcl_log_if(...) log statements to your code.

Configure the active log levels for your log components, e.g.

lcl_configure_by_identifier("*", lcl_vWarning);

Note: Since version 1.3 of the library core, an NSLog-based default logging back-end will be used if no back-end is configured via lcl_config_logger.h.


Quick start

For a quick start with the LogFile logging back-end, simply clone the LogFile example project from GitHub:

git clone git://github.com/aharren/LibComponentLogging-LogFile-Example.git

The project contains an Xcode project, the core files of LibComponentLogging, the LogFile logging back-end, the qlog extension, some preconfigured log components, and some example log statements in the main.m file.


CocoaPods

See the CocoaPods section below for information about using LibComponentLogging with the CocoaPods library manager.

Some examples of using LibComponentLogging's core logging interface, compared to NSLog-based logging:

Log statements

Simple NSLog-based logging:

NSLog(@"message, %@", arg1);

LibComponentLogging-based logging with log components and log levels:

lcl_log(lcl_cMain, lcl_vInfo, @"message, %@", arg1);

Or, when using the qlog extension:

#define ql_component lcl_cMain
qlinfo(@"message, %@", arg1);

The lcl_log log macro checks the log component's active log level before constructing the log message and before evaluating log message arguments. So, if Main's log level is not at least set to Info, no log message will be written.

Conditional logging

Conditional logging with NSLog:

if (condition) {
  NSLog(@"message, %@", arg1);
}

Conditional logging with LibComponentLogging:

lcl_log_if(lcl_cMain, lcl_vInfo, condition, @"message, %@", arg1);

Or with the qlog extension:

qlinfo_if(condition, @"message, %@", arg1);

The condition is only evaluated if component Main is at least configured with log level Info.

Turn logging off, and on

Remove NSLog statements manually:

// NSLog(@"message, %@", arg1);

Or, use an #ifdef to include them only in debug builds:

#ifdef DEBUG
NSLog(@"message, %@", arg1);
#endif

Or, create your own logging macro and hide all the details in the macro definition:

#ifdef DEBUG
#define MyLog(format, ...)         \
  do {                             \
    NSLog(format, ##__VA_ARGS__);  \
  } while (0)
#else
#define MyLog(format, ...)         \
  do {                             \
  } while (0)
#endif
MyLog(@"message, %@", arg1);

Or, introduce a logging flag for checking at runtime:

if (logging_enabled) {
  NSLog(@"message, %@", arg1);
}

Or, ...

With LibComponentLogging, simply keep your log statements in your code.

And, disable logging for all components at runtime:

lcl_configure_by_identifier("*", lcl_vOff);

Or, enable logging for all components at level Warning and higher, and component Main at level Trace and above:

lcl_configure_by_identifier("*", lcl_vWarning)
lcl_configure_by_component(lcl_cMain, lcl_vTrace);

Or, use the UserDefaults extension to restore log level settings from the standard user defaults:

[LCLUserDefaults restoreLogLevelSettingsFromStandardUserDefaults];

Or, remove all log statements at build-time, if you really need it:

Define _LCL_NO_LOGGING in your build settings or in your prefix header file before importing lcl.h.

LibComponentLogging is split up into a library core accompanied by different logging back-ends which implement certain logging strategies, e.g. one back-end may write log messages to application-specific log files while another back-end may send them to the system log, etc. This separation makes LibComponentLogging deployable for different scenarios while using the same logging interface.

The library core comprises the following files:

  • lcl.h: main header file
  • lcl.m: core implementation

and

  • lcl_config_components.h: configuration of log components
  • lcl_config_logger.h: configuration of the logging back-end
  • lcl_config_extensions.h: configuration of logging extensions

The main header lcl.h contains all definitions of the library core and serves as a main include file for all configuration files and other parts of the library. Therefore, your code should only contain an #import "lcl.h" statement, e.g. in your project's prefix header file.

Symbols for log levels and log components, which work with Xcode's code completion, are automatically provided by the main header file.

All symbols, e.g. values or functions, which are relevant when using the logging interface in an application, are prefixed with lcl_. Log levels share the common prefix lcl_v and log components the prefix lcl_c.

Internal symbols, which are needed when working with meta data, when defining log components, or when writing a logging back-end, are prefixed with _lcl_.

The main interface for writing a message to the application's log is the macro lcl_log which creates a log message from the given arguments and sends it to the configured logging back-end if logging is enabled for the given log component.

The syntax of lcl_log is similiar to NSLog's syntax, with an additional log component and log level:

lcl_log( <component>, <level>, @"<format>"[, <arg1>[, ...]] );

where

  • <component> is the identifier of a log component,
  • <level> is the log level for the current log message,
  • <format> is a printf-style format string of type NSString which may include '%@' placeholders, and
  • <arg...> are optional arguments required by the format string.

Version 1.2 of the core library introduces a lcl_log_if log macro which takes an additional predicate argument which is checked before logging:

lcl_log_if( <component>, <level>, <predicate>, @"<format>"[, <arg1>[, ...]] );

Note that the predicate is only evaluated if the given log level is active.

Using a log macro enables checking of the active log level before constructing the log message and before evaluating log message arguments. Therefore, if logging is disabled, the runtime-overhead of log statements is very low and they can stay in your code.

If really needed, all log statements can be stripped from your code by re-defining the log macros to an empty effect. Since version 1.1, the core's lcl.h file contains definitions of empty log macros which are used if the preprocessor define _LCL_NO_LOGGING is defined. So, simply define _LCL_NO_LOGGING in your release build settings if you like.

The library core provides the following log levels:

  • Critical: critical situation.
  • Error: error situation.
  • Warning: warning.
  • Info: informational message.
  • Debug: coarse-grained debugging information.
  • Trace: fine-grained debugging information.

The corresponding code symbols are lcl_vCritical, lcl_vError, lcl_vWarning, lcl_vInfo, lcl_vDebug, lcl_vTrace.

All log components of an application are defined in the configuration file lcl_config_components.h by using the _lcl_component macro.

_lcl_component has the following syntax:

_lcl_component( <identifier>, "<header>", "<name>" )

where

  • <identifier> is the unique identifier of a log component which is used in calls to lcl_log, lcl_configure_by_component, etc.,
  • <header> is a C string in UTF-8 which should be used by the logger when writing a log message for the log component, and
  • <name> is a C string in UTF-8 which contains the name of the log component and its grouping information based on a path syntax.

Note that the _lcl_component macro is used without a closing semicolon.

Example of a lcl_config_components.h file:

_lcl_component(UIC1, "ui.c1", "User Interface/Component 1") 
_lcl_component(UIC2, "ui.c2", "User Interface/Component 2") 
_lcl_component(UIC3, "ui.c3", "User Interface/Component 3")

The example defines three log components 'Component 1', 'Component 2', and 'Component 3', which form the group 'User Interface'. Their log message headers are 'ui.c1', 'ui.c2', and 'ui.c3', and their identifiers are 'UIC1', 'UIC2', and 'UIC3'.

Based on the defined log component identifiers, the library core automatically defines the code symbols lcl_cUIC1, lcl_cUIC2, and lcl_cUIC3, which must be used in calls to lcl_log, lcl_configure_by_component, etc.

At runtime, logging can be enabled/disabled for one or more log components by calling one of the following configure functions:

lcl_configure_by_component( <component>, <level> );
lcl_configure_by_identifier( <identifier>, <level> );
lcl_configure_by_header( <header>, <level> );
lcl_configure_by_name( <name>, <level> );

where

  • <component> is a log component identifier as a lcl_c code symbol,
  • <identifier> is a log component identifier as a C string,
  • <header> is a log component's header as a C string,
  • <name> is the full name of a log component as a C string,
  • <level> is the lcl_v code symbol for the log level.

<identifier>, <header>, and <name> may include the * wildcard suffix in order to set the given log level for all components which share the given identifier/header/name prefix.

LogFile is an Objective-C class which writes log messages to an application/framework-specific log file.

The log file is opened automatically when the first log message needs to be written. If the log file reaches a configured maximum size, it gets rotated and all previous messages will be moved to a backup file. This backup log file is kept until the next rotation.

LogFile can be used as a logging back-end for LibComponentLogging, but it can also be used as a standalone logger without the core files of LibComponentLogging, e.g. in combination with some simple DebugLog macros which do not know about log components.

The class uses the following format when writing log messages:

<date> <time> <pid>:<tid> <level> <component>:<file>:<line>:<function> <message>

where the file name, the line number and the function name are optional, based on the configuration of the LogFile class.

Example:

2012-07-01 12:38:32.796 4964:10b D component1:main.m:28:-[Class method] Message 1
2012-07-01 12:38:32.798 4964:10b D component2:main.m:32:-[Class method] Message 2
2012-07-01 12:38:32.799 4964:10b D component3:main.m:36:-[Class method] Message 3

LogFile requires a LCLLogFileConfig.h configuration file for configuring class names, the location and maximum size of log files, whether to append to an existing log file on start-up, or whether to show file names, line numbers, etc. A corresponding template is included in the LogFile package.

LogFile back-end on GitHub: github.com/aharren/LibComponentLogging-LogFile.

SystemLog is a logging back-end that sends log messages to the Apple System Log (ASL) facility.

With ASL, log messages are stored as structured messages in a data store. The syslog utility or the Console application can be used to retrieve messages from this data store, e.g.

syslog -F '$(Time) $(Sender)[$(PID):$(Thread)] $(Level0) $(Message) ($(Facility):$(File):$(Line))' -T utc -k Level0 -k Sender eq Example

retrieves all messages from the data store where the value associated with the Sender key (the identifier of an application) is equal to Example and where a value for the Level0 key exists. The key Level0 is used by SystemLog to store the log level in addition to a mapped ASL priority level (Level key). All retrieved messages will be printed by using the UTC time format and the display format specified via -F:

2012.07.01 12:38:32 UTC Example[6717:10b] D Message 1 (example.f1:main.m:28) 
2012.07.01 12:38:32 UTC Example[6717:10b] C Message 2 (example.f2:main.m:32) 
2012.07.01 12:38:32 UTC Example[6717:10b] I Message 3 (example.f3:main.m:36)

By default, the default data store will only save log messages which have an ASL priority level between Emergency and Notice (level 0 to 5). Log messages with level Info (level 6) or Debug (level 7) will not be written to the data store. The command line

sudo syslog -c syslog -d

can be used to tell syslogd to store messages up to priority level Debug.

Alternatively, SystemLog can be configured to use only ASL priority levels up to a specific last level, e.g. Notice. All log messages with a higher level will be mapped to the configured last level, e.g. Debug messages will be logged with the ASL level Notice while the Level0 field will still contain the level information Debug.

The SystemLog class can be used as a logging back-end for LibComponentLogging or as a standalone logger without the core files of LibComponentLogging, e.g. wrapped by your own DebugLog macros.

SystemLog requires a LCLSystemLogConfig.h configuration file for configuring class names, per-thread ASL connections, whether to show file names, line numbers, etc. A corresponding template is included in the SystemLog package.

SystemLog back-end on GitHub: github.com/aharren/LibComponentLogging-SystemLog.

NSLog is a very simple back-end which redirects to NSLog, but adds information about the log level, the log component, and the log statement's location including the file name, line number, and function.

The NSLog back-end uses the following format:

<NSLog prefix> <level> <component>:<file>:<line>:<function> <message>

where NSLog prefix is the header information written by NSLog. This header contains the current date and time, the application's name, the process id, and the thread id.

Example:

2012-07-01 12:38:32.796 Example[4964:10b] D c1:main.m:28:-[Class method] Message 1
2012-07-01 12:38:32.798 Example[4964:10b] D c2:main.m:32:-[Class method] Message 2
2012-07-01 12:38:32.799 Example[4964:10b] D c3:main.m:36:-[Class method] Message 3

NSLog back-end on GitHub: github.com/aharren/LibComponentLogging-NSLog.

NSLogger is a back-end implementation for LibComponentLogging which integrates Florent Pillet's NSLogger logging client. NSLogger supports remote logging for Mac OS X and iOS devices, and also features a very nice viewer application for Mac OS X.

See github.com/fpillet/NSLogger for more details about NSLogger.

The NSLogger back-end requires a LCLNSLoggerConfig.h configuration file for configuring class names, NSLogger settings, etc. A corresponding template is included in the NSLogger back-end package.

Additionally, the NSLogger iOS logging client components from github.com/fpillet/NSLogger are required. You will need the files LoggerClient.h, LoggerClient.m, and LoggerCommon.h.

NSLogger back-end on GitHub: github.com/aharren/LibComponentLogging-NSLogger.

Crashlytics is a LibComponentLogging back-end which integrates the logging subsystem of the Crashlytics crash reporting solution.

This back-end is maintained by Daniel Schneller.

Crashlytics back-end on GitHub: github.com/CenterDevice/LibComponentLogging-Crashlytics.

qlog is an extension to LibComponentLogging which provides a set of quick logging macros.

qlog just consists of the qlog.h header file which defines a short logging macro for every log level of LibComponentLogging, e.g. qlerror() for error messages and qltrace() for trace messages.

All qlog macros support a format string and arguments:

qlerror(@"code %d", code);

All logging macros take the current log component from the ql_component preprocessor define which can be (re)defined in your application per file, per section, or at global scope.

If you want to include the log component in your log statements instead of using the ql_component define, you can use the _c variants of the qlog macros which take the log component as the first argument:

qlerror_c(lcl_cMain);
qltrace_c(lcl_cMain, @"message").

To install qlog, just copy the qlog.h header file to your project and add an import of qlog.h to your prefix header file or to your LibComponentLogging extensions configuration file lcl_config_extensions.h:

#import "qlog.h"

Then, define the preprocessor symbol ql_component at a global scope with your default log component, e.g. add a define to your prefix header file:

#define ql_component lcl_cDefaultLogComponent

Now, log statements can be added to your application by simply using the qlog macros instead of LibComponentLogging's lcl_log macros:

qlinfo(@"initialized");
qlerror(@"file '%@' does not exist", file);
qltrace();

All these log statements will use the log component from the ql_component define which is visible at the location of the log statement.

If you want to use a specific log component for all log statements in a file, you can simply redefine ql_component to match this log component, e.g. by adding a #undef #define sequence at the top of the file:

#undef ql_component
#define ql_component lcl_cFileLevelComponent

qlog extension on GitHub: github.com/aharren/LibComponentLogging-qlog.

UserDefaults is an extension which stores/restores settings to/from the user defaults.

The following code shows a simple usage pattern for UserDefaults in your application/framework's main.m file:

#include "lcl.h"
#include "LCLUserDefaults.h" // or put the include in lcl_config_extensions.h

int main(int argc, char *argv[]) {
    // restore the log level settings from the standard user defaults
    [LCLUserDefaults restoreLogLevelSettingsFromStandardUserDefaults];

    // start your application
    ...
}

UserDefaults uses the following format for storing log level settings in your application's domain:

"logging:<bundle identifier>:<log component name>:level" = <integer>

Examples:

"logging:com.yourcompany.YourApplication:Application/Component 1:level" = 5
"logging:com.yourcompany.YourApplication:Application/Component 2:level" = 3

You can change the log level settings from the command line by using the defaults command, e.g.

defaults write <application> "logging:com.yourcompany.YourApplication:Application/Component 1:level" -int 2

where <application> is the identifier of your application.

If you like to have a different naming scheme for your log level settings, simply modify the keyPrefix, keyForComponent:prefix:suffix:, or logLevelKeyForComponent:prefix: methods.

UserDefaults extension on GitHub: github.com/aharren/LibComponentLogging-UserDefaults.

lcl_embed is a tiny tool to create an embedded variant of LibComponentLogging for usage in libraries or frameworks.

An embedded variant of LibComponentLogging is characterized by having your library/framework's unique prefix in each of LibComponentLogging's code symbols and file names. This way it's possible to use LibComponentLogging in your library/framework and expose it to the clients of your library/framework, while these clients can use a normal installation of LibComponentLogging for their own logging.

Usage:

lcl_embed <project-name> <symbol-prefix> <folder>

lcl_embed will modify all files which contain LibComponentLogging symbols and add your library/framework's unique prefix as a prefix to all symbols, e.g. if your project name is MyProject and the unique prefix is MP, all log components will have the prefix MPlcl_c instead of lcl_c and log levels will have the prefix MPlcl_v instead of lcl_v. lcl_embed will also rename all files from LibComponentLogging and add the unique prefix as a suffix to the file names, e.g. it will rename lcl.h to lcl_MP.h and lcl_config_components.h to lcl_config_components_MP.h. lcl_embed will also adjust your Xcode project file.

lcl_embed on GitHub: github.com/aharren/LibComponentLogging-embedded.

The CocoaPods Objective-C library manager provides an easy way for integrating libraries into your project. The set of libraries is configured via a text file named Podfile which lists the names of the libraries and the required versions. CocoaPods will then resolve the dependencies of these libraries, download the source code, and set up an Xcode workspace for your project. Please refer to cocoapods.org for more information about CocoaPods.

Even with CocoaPods, LibComponentLogging needs its configuration header files lcl_config_components.h, lcl_config_logger.h, and lcl_config_extensions.h. Logging back-ends may require additional configuration files, e.g. the LogFile back-end needs LCLLogFileConfig.h. Note that these configuration files belong to your project, e.g. the LogFile configuration contains class prefixes which are unique to your application/framework.

You can create these configuration files manually, based on the .template files which are included in the LibComponentLogging components, and adapt the header search paths HEADER_SEARCH_PATHS, PODS_PUBLIC_HEADERS_SEARCH_PATHS and PODS_BUILD_HEADERS_SEARCH_PATHS in Pods.xconfig accordingly.

An auto-configuration pod for LibComponentLogging and CocoaPods is currently available as an experimental version. It creates the configuration files automatically and also configures the logging back-end.

Please note that this auto-configuration pod and its behavior may change in the future as it depends on internals of CocoaPods.

If you want to try it, install the latest CocoaPods version (at least 0.11) and add the LibComponentLogging-pods auto-configuration pod to your Podfile:

platform :ios

pod 'LibComponentLogging-Core'
pod 'LibComponentLogging-LogFile'
pod 'LibComponentLogging-qlog'

pod 'LibComponentLogging-pods'

And, run pod install:

$ pod install
Updating spec repo `master'
Installing LibComponentLogging-Core (1.3.1)
Installing LibComponentLogging-LogFile (1.2.1)
Installing LibComponentLogging-pods (0.0.1)
Installing LibComponentLogging-qlog (1.1.1)
Generating support files
[!] LibComponentLogging-pods is in an experimental state, see http://0xc0.de/LibComponentLogging#CocoaPods for details
Creating LibComponentLogging configuration
[!] Use 'lcl_config_components.h.pods' to configure log components
[!] Use 'lcl_config_extensions.h.pods' to configure additional log extensions
Configuring LibComponentLogging logging back-end 'LogFile'
[!] Configuration file 'LCLLogFileConfig.h' needs to be adapted before compiling your project
Configuring LibComponentLogging extension 'qlog'
$ _

The LibComponentLogging-pods pod creates the required LibComponentLogging configuration files for you and automatically adds the logging back-end and extensions, LogFile and qlog in this example. You can use the files lcl_config_components.h.pods and lcl_config_extensions.h.pods for configuring your log components and additional log extensions.

If you are using the LibComponentLogging-pods pod and your Podfile references a pod which contains an embedded variant of LibComponentLogging, then the LibComponentLogging-pods pod will un-embed the embedded variant and integrate all log components into the normal lcl_ symbol space.

In case of problems, questions, ideas, etc.: ah@0xc0.de, @aharren

Pods specs on GitHub: github.com/aharren/LibComponentLogging-CocoaPods-Specs.

Development of LibComponentLogging is split up into a dedicated GitHub repository for each component:

core

extension

integration

The repositories usually contain the following branches:

  • master: the master branch contains stable builds of the component which are tagged with version numbers.
  • devel: the devel branch is the development branch for the component. It contains an Xcode project and unit tests. The code in this branch is not stable.

Found a bug? Missing a feature? Drop a line to logging@0xc0.de, or create an issue in one of the repositories, or fork and contribute.


LibComponentLogging is licensed under the terms of the MIT license. Copyright (c) 2008-2014 Arne Harren, ah@0xc0.de, @aharren.