How to Use Apport in your Programs

By default, apport provides the following functionality for all programs in Ubuntu:

Package Hooks

In addition to the generic information apport collects, arbitrary package-specific data can be included in the report by adding package hooks. For example:

Hooks can also cause a crash to be ignored, or the report suppressed with an explanation.

Since Ubuntu 9.10 ("Karmic"), hooks can also ask interactive questions, to get further information from the reporter. Currently supported are "yes/no" questions, multiple choice questions, file selector, and an information message box.

To create a hook, have your package install a file /usr/share/apport/package-hooks/<binarypackagename>.py or /usr/share/apport/package-hooks/source_<sourcepackagename>.py. The hook will be used whenever apport logs a report for the specified package. Here's a simple example of a hook:

from apport.hookutils import *

def add_info(report, ui=None):
    attach_file_if_exists(report, '/var/log/foo.log', key='FooLog')
    attach_related_packages(report, ['hello', 'hello-debhelper'])
    report['SuspiciousXErrors'] = xsession_errors(re.compile('CRITICAL.*assertion.*failed'))
    report['SecretMessage'] = 'my hook was here'

'report' is a Python dictionary which can be used to store arbitrary data using a string key (alphanumerical or dots (.), hyphens (-) and underscores (_)).

To test a package hook with a test package build, you can set APPORT_DISABLE_DISTRO_CHECK in your environment to force apport to create a report even though your package is not (yet) official.

For complete instructions, see /usr/share/doc/apport/package-hooks.txt.gz (online version).

For a list of convenience functions, refer to the apport.hookutils module:

It provides ready made and safe functions for many standard situations, such as getting a command's output, attaching a file's contents, attaching hardware related information, etc.

Hooks can also perform interactive actions. For this see the apport.ui module:

Brian Murray gave a class at Ubuntu Developer week regarding writing package hooks.

ProblemType

By the time a package hooks add_info() function gets called, apport will likely have already determined the type of the problem being reported. It does this by setting the ProblemType key in the report dictionary passed to add_info().

Possible values of ProblemType:

To check the value of ProblemType:

  if report.get('ProblemType', '') == 'Crash':
      # handle crash scenario
  elif report.get('ProblemType', '') == 'Bug':
      # handle bug scenario
  else:
      # handle remaining scenarios

Notes

Advice

General Hooks

These are the same as package hooks, but apply to all packages. They can be used, for example, to implement a 'taint check' which reports on whether certain problematic software is installed, regardless of which program is misbehaving. They are installed in /usr/share/apport/general-hooks/*.py.

For complete instructions, see /usr/share/doc/apport/package-hooks.txt

Symptoms

In some cases it is quite hard for a bug reporter to figure out which package to file a bug against, especially for functionality which spans multiple packages. For example, sound problems are divided between the kernel, alsa, pulseaudio, and gstreamer.

Apport supports an extension of the notion of package hooks to do an interactive "symptom based" bug reporting. Calling the UI with just "-f" and not specifying any package name shows the available symptoms, the user selects the matching category, and the symptom scripts can do some question & answer game to finally figure out which package to file it against and which information to collect. Alternatively, the UIs can be invoked with "-s symptom-name".

For instructions, see /usr/share/doc/apport/symptoms.txt (online version).

In Ubuntu, symptoms are shipped in the apport-symptoms package.

Bug patterns

Apport can suppress reports for bugs which have already been reported. Sometimes a bug affects so many people that we get hundreds of duplicates in a matter of days, which both creates a lot of needless reporting, triaging, and retracing work. In those cases, and when the bug can be uniquely identified with regular expressions on the apport report keys or file attachments, a "bug pattern" should be created which will prevent Apport from reporting the bug in the first place, and instead guide the user to the already existing bug page.

To avoid inventing a new file format and to be able to extend the functionality in the future (e. g. other operators than just regexp matching) the bug patterns are stored in an XML file.

The general syntax is:

For example:

The bug patterns are publicly accessible at http://people.canonical.com/~ubuntu-archive/bugpatterns/, where Apport (when running on the client side) will check for them. They are maintained in a bzr branch accessible to the Ubuntu bug control team, and copied to people.canonical.com every 15 minutes.

The bug patterns bzr branch also includes a tool, test-local, for testing bug patterns. Using the above example you can use test-local 2 and the Apport data from bug 2 will be downloaded and checked using your local copy of the bug patterns. Additionally, duplicate bugs will be checked so you will know whether or not the pattern could use expanding.

Custom Invocation

Apport needs to be "triggered" by something to create a report. For real crashes, that is the kernel itself (/proc/sys/kernel/core_pattern), for Python scripts this is a fallback exception handler in /etc/python2.5/sitecustomize.py.

For still greater flexibility, apport can be invoked explicitly under other conditions, to trap and report on custom events, even if no signal-driven crash (such as a segfault) has occurred. For example, GCC can trap internal compiler errors and calls /usr/share/apport/gcc_ice_hook if one happens; if apt sees that a package fails to install, it calls /usr/share/apport/package_hook.

These scripts should ensure that apport is enabled (as it is turned off for stable releases) before executing. This can be done by importing apport.packaging and using apport.packaging.enabled(). These scripts should create minimal apport.Report() object and set the Package and SourcePackage fields correctly, so that any matching package hook will be triggered as well. Finally, it should write the report to a file.

Additional information collection can either happen

In general you should collect as much information as possible in package hooks, especially when they require expensive operations. That way, the calling program does not have to block very long, and information is not collected if the user does not want to report a bug at all.

For an asynchronous script, see /usr/share/apport/kernel_hook and the accompanying package hook /usr/share/apport/package-hooks/source_linux.py. For a synchronous script, see /usr/share/apport/gcc_ice_hook (this also shows how to read bulk data directly from stdin).

Applications not included in Ubuntu's repositories but hosted on Launchpad

If you're using Launchpad as a bug tracker, you can install Apport hooks in your application even if your application is not included in Ubuntu repositories.

  1. Create an Apport hook named /usr/share/apport/package-hooks/<binarypackagename>.py or /usr/share/apport/package-hooks/source_<sourcepackagename>.py as described previously. In the add_info function, add this code:

    if not apport.packaging.is_distro_package(report['Package'].split()[0]):
        report['CrashDB'] = '<packagename>'
  2. Create a configuration file named <packagename>-crashdb.conf in /etc/apport/crashdb.conf.d/ with this content:

    <packagename> = {
     'impl' : 'launchpad',
     'project' : '<packagename>',
     'bug_pattern_base' : None,
     }

Obviously, you should replace <packagename> with the name of your package (without < and >). <packagename> cannot have any hyphen '-' characters in it. Replace them with underscores '_'. It is also important that the <packagename> in the crashdb.conf file match the <packagename> in the Python file.

Once the package is included in Ubuntu's repositories, Apport will know to report bugs to Ubuntu's bug tracker. For an example of an implementation of this, see /usr/share/apport/package-hooks/source_ubuntuone-client.py and /etc/apport/crashdb.conf.d/ubuntuone-client-crashdb.conf

Interactive Feedback

Apport is able to prompt the user by asking them questions.

Yes/No Question

def add_info(report, ui):

    attach_file = False

    if ui and ui.yesno('Attach some file?') == True:
        attach_file = True

    if attach_file == True:
        # user wants file to be attached

Choice List

The pseudo code below shows how to handle choices; if the response from ui.choice() is not None, it will be the zero-based index into the list passed as the second argument to ui.choice():

def add_info(report, ui):

    attach_file = False

    days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]

    response = ui.choice('What day is it?', days)

    if response == None:
        # user cancelled
        pass
    elif 0 in response:
        # 'Monday' selected
    elif 1 in response:
        # 'Tuesday' selected
    ...
    elif 6 in response:
        # 'Sunday' selected

Skeleton for a Package Hook

from apport.hookutils import *
import apport.packaging

msg = \
"""

The contents of file 'some-file' may help developers diagnose your bug
more quickly. However, if you have changed it, it may contain
sensitive information.

Do you want to include the files in your bug report?
(you will be able to review the data before it is sent)

"""

def add_info(report, ui):
    attach_files = False
    problem_type = report.get('ProblemType', '')

    if problem_type == 'Bug' and ui:
        # interactively ask the user a question and change behaviour based on their response.
        if ui.yesno(msg) == None:
            # user decided to cancel
            raise StopIteration

        # user is allowing files to be attached.
        attach_files = True

    elif problem_type == 'Crash':
        # crash bugs are private by default
        attach_files = True

    if attach_files == False:
        # do not attach any files
        return

    attach_file(report, '/etc/foo')
    attach_file_if_exists(report, '/var/foo.log')

Apport/DeveloperHowTo (last edited 2023-08-25 14:02:07 by cjwatson)