logging filters, RFC

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

Neels Hofmeyr nhofmeyr at sysmocom.de
Fri Feb 17 15:00:10 UTC 2017


Summary:
[1] some problems found in the current logging filter implementation.
[2]..[7] a long discussion on where we could take Osmocom logging.
  [2] logging "lifter" instead of a filter
  [3] setting logging levels for flagged objects
  [4] multiple log targets and multiple flagged objects
  [5] FSM instance logging
  [6] setting the log context
  [7] VTY logging commands


[1]

In the VLR development, I'm going to split the gsm_subscriber to a
bsc_subscriber and a vlr_subscriber. Hence I need to care about the logging
filter code, that e.g. drops all logging except that for a given subscriber.

At first I wanted to drop it altogether, but in fact filtering on a given
subscriber is an important feature. See https://gerrit.osmocom.org/1685

Looking further into adapting this to two types of subscriber struct being used
in the code, I found that in fact the logging filter code looks quite neglected
and degraded.

- One functional problem is that the constants for BSC_CTX_SUBSCR and
  GPRS_CTX_BVC actually have the same value: they both resolve to 1, and both
  thus access the same logging context pointer with conflicting types.
  openbsc debug.c's filter_fn appears to use both BSC_CTX_SUBSCR and
  GPRS_CTX_BVC, but GPRS_* are only set in libosmocore/src/gb/ code.  Could be
  justified by osmo-nitb and osmo-sgsn not overlapping much, but I doubt that
  it's on purpose.

- A log target's filter_data[] possibly holds a subscr_get() reference.  If a
  log target is closed, like a VTY closing, the reference count may leak,
  because there is no filter data tear down that could subscr_put() it back.

- log_set_nsvc_filter() and log_set_bvc_filter() are declared in
  openbsc/debug.h, but the only implementations I can find are *static* in
  libosmocore/src/gb/gprs_bssgp_vty.c and gprs_ns_vty.c, respectively.

- The openbsc/debug.h enum defining LOG_FILTER_* is never used, because it is
  bascially duplicated in debug.c, where the values are called FLT_* instead.

- The BSC_CTX_BTS and BSC_CTX_LCHAN are each set once by a call to
  log_set_context(), but no filter function ever reads them back out from the
  context array. So they are utterly unused.

Hence in libbsc/libmsc, the *only* logging filter practically used is the IMSI
filter.

The filter code screams for attention and more safety. I would collect the
various constants in a central place in libosmocore to avoid collisions (like
ports.h does), and would use the same constant for both...
  the filter ctx array index:  ctx[FLT_CONSTANT]
  and the bitmask:             filter_map & (1 << FLT_CONSTANT)
with a static assert to ensure that the filter category constants don't outgrow
the filter_data[], ctx[], filter_map sizes. I'd drop unused definitions, add a
tear down cb.



[2]

While talking about logging, I'm thinking back to 33c3 where we had a large
active network and might want to highlight a few subscribers in the logging...

Any comments on below bold plans to overhaul the logging semantics are welcome.
Despite the long discussion, implementation of this should not be a lot of work
really.

Nevertheless, there are pressing topics in need of attention, and this mail
doesn't mean that it will be implemented now. It's a unicorn's dream of where I
would take Osmocom logging, and it _might_ be a good time to do it now...

If you will, join me on an extensive journey through our logging:

So far the logging checking goes like this:

1. If a msg's logging category is disabled, drop the msg.
2. If a msg's logging level surpasses the currently set log level of that
   category, drop it. otherwise:
3. If filter_all is set (= "don't filter"), log the message; otherwise:
4. If no filter function is set, log the message (in libbsc/libmsc operation, a
   filter function is always set).
   If a filter function is set:
5. Ask the filter function whether to log:
   5.1 If no IMSI filter is set, drop the message. (!)
   5.2 If an IMSI filter is set and the ctx has a different subscriber, drop
       the message.
   5.3 If an IMSI filter is set and it *is* the subscriber, log the message.

The result is that in essence, we don't ever see any logging unless we say
'logging filter all 1'. And then, if filter_all is set, the IMSI filter has no
further effect. To see logging for a specific IMSI, we set 'logging filter all
0' and set the IMSI filter to the subscriber. In that situation, all logging
will cease except for those matching the selected IMSI (assuming that the
filter context is always set correctly). Any logging for this IMSI that would
be more detailed than the given category's global logging level is still
dropped, and parts of the code where the subscriber ctx is not set yet also
shows no logging. One could call it a prohibitive logging paradigm. I had a
hard time understanding the UI for it: makes sense only when reading the code.

My logging vision would be the other way around, more of a logging "lifter". In
a general logging landscape, I would like to flag certain subscribers (plural)
to stand out from this landscape with a more detailed logging level. For
example, I set all categories to level ERROR, but on top allow all MM, RSL and
RR logging on INFO level; but for subscribers 1234 and 5678 I also want to see
all DEBUG level logs across all (or specifically selected) categories.

It would look like this:

1. If there is a logging_lifter function:
   1.1 If any items are flagged for more detailed logging, ask the lifter:
   1.2 If the logging context is set to a subscriber, and this subscriber is
       flagged for more detailed logging, log the message; see also [3].
2. If a msg's logging level is within the currently set general log level of
   that category, log the message.
3. Otherwise, drop the message.

This way the general logging landscape remains freely configurable while
certain objects' logging can be made more verbose.

[3]

A problem with 1.2 is that we would always see all categories' logging in all
detail for that subscriber. We could a) set a full category mask per subscriber
for complete logging freedom; Instead I'd go for: b) define one global "raised
logging" category mask to lift flagged subscribers to, and for each subscriber
simply have a boolean flag set to true to make them log on the raised logging
levels -- but what about several logging targets? see [4].


[4]

So far so good, but there is still this clumsiness in my plan: there can be any
number of logging targets. Even if I'd like to highlight subscriber 1234 on my
VTY, I don't necessarily want stderr and other VTYs to raise logging for 1234
as well. So if we flagged raised logging in a subscriber struct, we'd basically
make it affect all logging targets in the same way.

In the current code, I can open N VTY telnets and on each of them show only
logging for a selected IMSI, because all logging flags are saved for each
logging target. But I *can't* lift more than one subscriber instance to raised
logging on *the same* log output, because for each target there is only one
IMSI; and I *can't* combine detailed logging of a given IMSI with a general
background of normal logging on the same log output, because I have to filter
all logging away before I can allow a specific IMSI ('logging filter all 0').

The easiest way to allow multiple subscribers with raised logging on the same
log target is to store the "raise-logging" flag with the subscriber instance,
but to keep the log targets separate, I would instead have in struct log_target
an llist of void* for each filter_data where we can point to N instances we'd
like to flag for raised logging. Then, if the current ctx[] matches any of
those, use the raised logging levels. (Expecting the list to remain short)

This can be filter_fn implementation specific: the filter_data can be used to
point at the first llist_head instead of just one subscriber.


[5]

There's another interesting logging topic: FSM instances. We have the
osmo_fsm_inst feature where we can set all logging emitted by an FSM instance
to a certain logging level. One "problem" with this is that the entire FSM logs
on the same level, and we can only raise or lower all of these messages as one.
The implementation is: remember a logging category and a logging level with the
FSM instance, and log all messages from the FSM on this category and level. The
state transitions and events logged from fsm.c are logged on this logging
level.

Because I wanted to specifically mark some logging as LOGL_ERROR, I've already
broken up that idea by adding the LOGPFSML() macro. It allows passing an
explicit logging level instead of using the FSM instance's. I like to think of
it like this: the FSM instance's logging level tells the fsm.c code on which
level to log state transitions and events (like Debug). The logging I write in my
FSM implementation that I want to stand out from the state transitions (read:
logging that's more than Debug) should be given more specific logging levels,
like any other code we write. We could actually make LOGPFSML() use
MAX(fsm_inst.log_level, log_level_arg) so that changing an FSM instance's log
level raises all log messaging.

One could now think, if I want raised logging for a given subscriber, I could
set its FSM instances' logging level so that it prints more logging. But, again
this implies the clumsiness like in [4]: the raised logging would be global
across all logging targets. In this case, there's no easy way to make the
feature log target specific, and neither do we need to...

FSM instance specific logging levels, while sounding very nice at first, are a
non-feature when taking multiple logging targets into account. The simpler way
to show FSM logging for a specific subscriber is to use above suggested
features: flag the subscriber for raised logging on a speicific log target and
set the raised logging level for DVLR to Debug, relying on the logging ctx to
be set correctly when the subscriber's FSMs are active [6]. No need to adjust
an FSM instance's logging level individually. So, the log level could also be
stored in the osmo_fsm definition instead of each separate osmo_fsm_inst.


[6]

Setting the log context needs to generally happen in these situations:
- message received (e.g. from the MS or HLR)
- timer callback called (e.g. state timeout, SMS queue)

Particularly for timeouts on FSM instances' states this is a bit tricky. If the
fsm.c code triggers the timeout, there isn't a general hook to set the log
context to the FSM's subscriber first. The FSM instances that have timeouts set
need to also set a specific timer_cb to update the log context and repeat the
timeout logging "Timeout of T123" that comes from fsm.c before timer_cb() is
called. (That means the timeout logging may be issued twice in some situations,
once by fsm.c and once by the FSM implementation's timer_cb after setting the
log context to make sure raised logging includes it.)

Maybe we can add a pre_timer_cb to osmo_fsm, or teach osmo_fsm to set a
specific log context whenever it logs things? Probably not that important.


[7]

In the VTY, my plan would look like this:

  logging = e:RSL,RR,n:MM,CC    ! replace all levels
  logging + n:PAG               ! set some more
  logging raised = d:MM,i:CC    ! applies to flagged logging contexts
  logging raised + d:PAG,NM
  logging raise subscriber (imsi|msisdn|...) 1234
  ...
  logging raise subscriber (imsi|msisdn|...) 1234 not

For setting the logging levels, I would like to introduce letters f, e, n, i, d
for the log levels (Fatal, Error, Notice, Info, Debug) instead of the level
numbers that are "hidden" in logging.h, plus o for Off to disable categories.

I would prefer the format:
  [l:]CAT[,[l:]CAT[,...]]
so that a level is followed by N categories to be on this level, possibly
followed by another level and another categories list. (i.e. from the current
format, swap meaning of ',' and ':', reverse the current <cat>[,<level>] order
to [<level>:]<cat> and carry the level over to following categories.)

I would also add the 'all' keyword in the logging category parsing:
  logging = n:all,i:MM,CC,PAG
which would put all levels on Notice, except MM, CC and PAG on Info.

To avoid confusion with the old 'logging categories' format, I would like to go
for '=' and '+' commands (yes, the VTY can handle these).  The old 'logging
categories' might even co-exist.

'logging = ...' and 'logging + ...' should imply 'logging enable'.

The 'logging raised *' sets the logging levels used for flagged subscribers;
they should default to d:all, i.e. everything on debug level.
'logging raise *' flags a given subscriber (or what else we want to add later).

To remove a raise flag from a subscriber, 'logging no raise *' makes sense, but
it is often useful to just recall a previous VTY command and append a keyword,
so I'd make it 'logging raise subscriber imsi 1234 not' for convenience.

The current 'logging level <CAT> <LVL>' can remain as-is; I would add a
'logging raised level' command.

I would completely drop the 'logging filter' commands. But it would still be
useful on a VTY to mute logging to get a usable prompt without the need to
discard the log target:

  logging (mute|off)
  logging (unmute|on)

These are shorter and more intuitive than 'logging filter all (0|1)'.

The current 'logging filter all everything' command is ambiguous. It sounds
like it would aggressively turn on every last logging message. But instead it
does the opposite: it can only add a general *restriction* on the logging level
for all categories of a log target. Any category that is not enabled will still
not log anything. Also, even if a log target has a more detailed level set, the
log target's global logging level potentially prevents the detailed logging. So
the command should sound more like 'logging restrict all to LEVEL'. It needs a
rename, but IMHO we could also just drop it.

Turning on every last log message could be done by 'logging = d:all'.
Restrictions can be imposed by 'logging + e:RSL,MM,...'. Going back to a
previous complex logging setup can be achieved by recalling the previous
'logging = ' commandline. The VTY could print such a paste-back-able line
showing the current logging levels, so that going back is just a copy-paste on
the VTY.


So much for the unicorn's dream. For now, I'll start off with tidying up the
things under [1] above.

~N
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://lists.osmocom.org/pipermail/openbsc/attachments/20170217/a45d1300/attachment.bin>


More information about the OpenBSC mailing list