ASL: Searching

2008-01-23 16:35:47 UTC

Putting messages into the log is one thing. But what if you want to look up messages that are already in it?

Well, the first thing you need to do is create a message.

No! Wait! Come back! I didn’t put that in the wrong section!

This is what that type argument to asl_new is for. The other valid value is ASL_TYPE_QUERY, which is the type of message you need to construct to hold your search parameters.

You specify those parameters by setting them as properties of the query message. You don’t ordinarily use asl_set for this; instead, you use asl_set_query:

int asl_set_query(aslmsg msg, const char *key, const char *value, uint32_t op);

The difference is that fourth argument, op. The operators defined as of Mac OS X 10.4 and 10.5 are:

  • ASL_QUERY_OP_EQUAL
  • ASL_QUERY_OP_LESS
  • ASL_QUERY_OP_LESS_EQUAL
  • ASL_QUERY_OP_GREATER
  • ASL_QUERY_OP_GREATER_EQUAL
  • ASL_QUERY_OP_NOT_EQUAL
  • ASL_QUERY_OP_TRUE

That last one doesn’t compare values at all; you can just pass NULL. It tests whether the given key is present in a message, regardless of its value (hence the name: as far as values are concerned, their comparison is always true).

If you use any of the other operators, you can bitwise-OR one or more of these values into it to modify its behavior:

  • ASL_QUERY_OP_CASEFOLD: Case-insensitive matching.
  • ASL_QUERY_OP_PREFIX: Tests whether the given value is at the start of the value in the log.
  • ASL_QUERY_OP_SUFFIX: Tests whether the given value is at the end of the value in the log.
  • ASL_QUERY_OP_SUBSTRING: Tests whether the given value is anywhere within the value in the log.
  • ASL_QUERY_OP_NUMERIC: Compares values as numbers, rather than as strings. This is the solution to the old “100 < 90” problem.
  • ASL_QUERY_OP_REGEX: Does what it says on the box.

Incidentally, you can use asl_set with a query message; it’s equivalent to calling asl_set_query with the ASL_QUERY_OP_EQUAL operator.

A single query message can hold multiple query parameters. The ASL server joins them all with the logical AND; there is no way to do logical OR, other than to run multiple queries and union the results yourself.

You can set more than one parameter with the same key. This should work with any key, though it’s probably not that useful for keys with string values, such as the message key. It’s more useful for numeric keys, and most useful for ranges of timestamps: You can specify that a message’s time must be ≥ start and < (or ≤) end.

Once you’ve assembled a query message, it’s time to perform the search.

aslresponse asl_search(aslclient asl, aslmsg msg);

As usual, you can pass NULL for the client connection. You can’t pass NULL for the query message; it will give you NULL back. (“You want to search for nothing?! Well, you found it!”) However, you can use an empty query message (one with no properties set) to search for everything: the search will return all messages from the database, even those that don’t have an ASL_KEY_MSG property.

An aslresponse is a really basic iterator object (like NSEnumerator). ASL provides exactly two functions for working with one:

aslmsg aslresponse_next(aslresponse r);
void aslresponse_free(aslresponse a);

Just like with any other iterator, each call to aslresponse_next returns another message from the response, until the response is exhausted; when you call it with an already-exhausted response, it returns NULL.

There is no way to count the contents of an aslresponse, other than to count them yourself (i.e., iterate the entire response and increment a counter on each pass through your loop).

The aslresponse owns the aslmsgs that it returns, so don’t call asl_free on them yourself; the response will do that for you when you free it with aslresponse_free.

Searches are not live. Once you hit the end of a response, you will never get another message from it, even if more messages come in that would match the query. This has the unfortunate side effect that you must poll if you wish to monitor the log using only public interfaces; you could use kevent or select to watch the ASL database file, but that file is not guaranteed to always be in the same place, and it didn’t even exist in Tiger (it was a plain-text log file instead).

When you obtain a message from aslresponse_next, you’ll want to retrieve some properties from it. You do this using asl_get:

const char *asl_get(aslmsg msg, const char *key);

If you want to test whether a message possesses a certain property, just call that function anyway. Just like -[NSDictionary objectForKey:], it returns NULL if the key isn’t present in that message.

To walk all the keys of a message, use asl_key:

const char *asl_key(aslmsg msg, uint32_t n);

As you can see, it takes the index of the key name to retrieve. Similarly to asl_get, if you pass an index that’s out of bounds, it returns NULL.

Note: On Tiger, if the sender of a message didn’t set its ASL_KEY_MSG property before sending it, the message you get back in the search response will have an ASL_KEY_MSG key with a NULL value (you’ll know this because asl_key will find and return the key, but asl_get will return NULL). I think that this is actually a bug in the search code that parses the ASL log file, not a bug in asl_send; on Leopard, the bug is gone, which, I suspect, is because Leopard now uses a database instead of a log file for searches.

The nature of asl_key suggests that the implementation does not hash keys, but simply keeps parallel arrays. This makes sense, because ASL messages have fewer than a dozen keys, and O(n) can take less real time than O(1) for small n.


Next in the ASL series: Console

4 Responses to “ASL: Searching”

  1. Ahruman Says:

    Massive super-nitpick: Unix time is not the number of seconds since the epoch. It’s the number of nominal (clock) seconds since the epoch. This difference (currently about 25 seconds) matters, because Unix time does not increase monotonously – it skips negative seconds and repeats positive leap seconds. If you’re expecting, say, log messages to always be in-order based on Unix time, you’re setting yourself up for a potential failure in the middle of the night once every few years.

  2. Peter Hosey Says:

    Ahruman: I’ve added a note about that.

    Keep in mind that the actual specification of ASL_KEY_TIME‘s value is not documented anywhere. The only documentation is of its value is the erroneous comment in asl.h. So all I see is a count of seconds since 1970.

    Do you know of a way that I can tell whether it’s giving me leap seconds or not? (Preferably without changing my computer’s clock.)

  3. Cédric Luthi Says:

    As of 10.5.8 and 10.6.3, asl_send uses gettimeofday(2) and falls back to time(3) if gettimeofday fails. I don’t see how gettimeofday could possibly fail.

  4. Peter Hosey Says:

    Cédric Luthi: That’s not the most amusing thing. Look at the implementation of time(3).

Leave a Reply

Do not delete the second sentence.