Nuxeo Server

Audit

Updated: March 18, 2024

The Audit Service is used for logging and retrieving audit data into a data store. The service can be accessed directly with the Java API for reading or writing audit entries but the main source for Audit entries is the Nuxeo event bus: the Audit Service listens to all events that may occur on the platform (document creation, user logging in, workflow started ...) and according to the configuration an Audit record will be created.

Architecture

The Audit Service is mainly a data store service. It defines a data record structure that will be used for storing audit information.

The data record structure is defined in Java by the LogEntry and ExtendedInfo Java classes. The Audit Service receives events from the Event Service. Then the Audit Service filters and converts them into log entries. The LogEntry class is mainly obtained from a DocumentEventContext.

Nuxeo documents and events can have a lot of custom properties, so if you want to log some specific events or document properties, the Extended Info allows for a Key/Value type storage that will be associated to the main LogEntry record. These informations are extracted from the event message using and EL (Expression Language) expression and stored into a map.

By default, since Nuxeo LTS 2015, the data store relies on the Elasticsearch Back-end. To disable Elasticsearch for Audit logs and use the Legacy SQL Back-end please refer to the Disabling Elasticsearch for Audit Logs section.

Elasticsearch Back-end

The audit entries are stored in the Elasticsearch index named by the audit.elasticsearch.indexName property in nuxeo.conf.

Fore more information about the global Elasticsearch setup, see Elasticsearch Setup.

Legacy SQL Back-end

If Elasticsearch is disabled for Audit logs, the data store is built over a relational database back-end.

The LogEntry and ExtendedInfo Java classes are mapped onto the datastore using JPA (Java Persistence API) annotations.

There are three tables used by the Audit Service: NXP_LOGS, NXP_LOGS_EXTINFO and NXP_LOGS_MAPEXTINFOS. NXP_LOGS is the main table, it is used most of the time. The two others are used only when the extendedInfo extension point is defined.

Querying the Audit Data Store

The Service API is composed of three services:

  • AuditReader: service for reading data from the audit logs. More details.
  • AuditLogger: service for adding data into the audit logs. More details.
  • AuditAdmin: service for administrating the Audit Service.

A set of methods allows the user to do common queries quiet easily like getting all the log entries for a document, getting a specific log by its id, etc.

AuditReader reader = Framework.getService(AuditReader.class);

// Getting of the logs for the document 'doc'
List<LogEntry> logEntries = reader.getLogEntriesFor(doc.getId());

// Same method but with a filter
FilterMapEntry filter = new FilterMapEntry();
filter.setColumnName("eventId");
filter.setOperator("=");
filter.setQueryParameterName("eventId");
filter.setObject(DocumentEventTypes.DOCUMENT_CREATED);
Map<String, FilterMapEntry> filterMap = new HashMap<String, FilterMapEntry>();
filterMap.put("eventId", filter);
List<LogEntry> logEntriesFiltered = reader.getLogEntriesFor(doc.getId(), filterMap, true);

You can perform some simple queries using the Elasticsearch API, here is an example of getting all the logs of the category 'MyExport' ordered by the date of the event:

List<LogEntry> entries = new ArrayList<>();
ElasticSearchAdmin esa = Framework.getService(ElasticSearchAdmin.class);
SearchRequestBuilder builder = esa.getClient()
                                  .prepareSearch(esa.getIndexNameForType(ElasticSearchConstants.ENTRY_TYPE))
                                  .setTypes(ElasticSearchConstants.ENTRY_TYPE)
                                  .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
builder.setQuery(QueryBuilders.termQuery("category", "MyExport"));
builder.addSort("eventDate", SortOrder.DESC);
SearchResponse searchResponse = builder.execute().actionGet();
for (SearchHit hit : searchResponse.getHits()) {
    try {
        entries.add(AuditEntryJSONReader.read(hit.getSourceAsString()));
    } catch (IOException e) {
        log.error("Error while reading Audit Entry from ES", e);
    }
}

When using the legacy SQL back-end, you can use AuditReader to do simple queries using the JPA Query language:

StringBuffer query = new StringBuffer("from LogEntry log where ");
query.append(" log.category='");
query.append("MyExport");
query.append("'  ORDER BY log.eventDate DESC");
AuditReader reader = Framework.getService(AuditReader.class);
List<LogEntry> result = (List<LogEntry>)reader.nativeQuery(query.toString(), 1, 1);

There are two PageProviders that can be used for querying the Audit data store:

  • AuditPageProvider: allows to generate simple queries against Audit entries.
  • DocumentHistoryReader: allows to compute history for a given document.

    More details on the explorer.

A schema has been defined for basic Audit search: basicauditsearch.xsd. This schema is helpful for building a PageProvider feeding a ContentView with data from the Audit data store. An object BasicAuditSearch could be used to define queries on the audit data store.

Extending the Audit Service

There a few extension points used to contribute to the Audit Service:

  • event
  • extendedInfo
  • adapter
  • listener

Two others extension points can be used to configure the datastorage for Audit:

  • queues
  • hibernate ‐ for the legacy SQL back-end only

Event

Those default auditable events match the Nuxeo core base events:

  • documentCreated
  • documentCreatedByCopy
  • documentDuplicated
  • documentMoved
  • documentRemoved
  • documentModified
  • documentLocked
  • documentUnlocked
  • documentSecurityUpdated
  • lifecycle_transition_event
  • loginSuccess
  • loginFailed
  • logout
  • documentCheckedIn
  • versionRemoved
  • documentProxyPublished
  • sectionContentPublished
  • documentRestored

If you are sending new Nuxeo core events and want them to be audited, you have to extend the event extension point. Here is an example of a contribution to this extension point:

<extension target="org.nuxeo.ecm.platform.audit.service.NXAuditEventsService" point="event">
    <event name="documentCreated" />
    <event name="documentCreatedByCopy" />
    <event name="documentDuplicated" />
    <event name="documentMoved" />
    <event name="documentRemoved" />
    <event name="documentModified" />
    <event name="documentLocked" />
    <event name="documentUnlocked" />
    <event name="documentSecurityUpdated" />
    <event name="lifecycle_transition_event" />
    <event name="loginSuccess" />
    <event name="loginFailed" />
    <event name="logout" />
    <event name="documentCheckedIn" />
    <event name="versionRemoved" />
    <event name="documentProxyPublished" />
    <event name="sectionContentPublished" />
    <event name="documentRestored" />
</extension>

More details on the explorer.

Extended Info

This service is used to evaluate EL expressions using a document as context and registering results into a Map indexed by names.

Just after converting a received DocumentEventContext instance into the corresponding LogEntry instance, the Audit Service allows you to extract information from the handling context and to store them.

To do this, you have to define an EL expression and associate it with a key. You can access to the following variables:

  • message: Document event context describing the event.
  • source: Document from which the event is from.
  • principal: Identity of the event owner.

If you want to contribute to the extended info of the service, you have to use the extendedInfo extension point. Here is an example of a contribution to this extension point

<extension point="extendedInfo" target="org.nuxeo.ecm.platform.audit.service.NXAuditEventsService">
    <extendedInfo expression="${source.dublincore.title}" key="title" />
    <extendedInfo expression="${message.cacheKey}" key="key" />
    <extendedInfo expression="${principal.name}" key="user" />
</extension>

You can also extend the audit info per event name:

<extension target="org.nuxeo.ecm.platform.audit.service.NXAuditEventsService"
    point="event">
    <event name="afterWorkflowStarted">
      <extendedInfos>
        <extendedInfo expression="${message.properties.modelId}" key="modelId" />
        <extendedInfo expression="${message.properties.modelName}" key="modelName" />
        <extendedInfo expression="${message.properties.workflowInitiator}" key="workflowInitiator" />
        <extendedInfo expression="${message.properties.workflowVariables}" key="workflowVariables" />
      </extendedInfos>
    </event>
</extension>

For instance, the above contribution will add modelId, modelName, worklowInitiator, workflowVarriables to the extendedInfo only for the afterWorkflowStarted event.

When the extension point is contributed, the data are stored into the audit.elasticsearch.indexName index for the Elastcisearch back-end and into the NXP_LOGS_EXTINFO and NXP_LOGS_MAPEXTINFOS tables for the legacy SQL back-end.

More details on the explorer.

Adapter

The contribution to the adapter extension point of the org.nuxeo.ecm.platform.audit.service.NXAuditEventsService component allows to define the adapter that will be injected in the EL context. Here is an example of a contribution to this extension point.

<extension target="org.nuxeo.ecm.platform.audit.service.NXAuditEventsService" point="adapter">
    <adapter name="myadapter" class="org.nuxeo.ecm.core.api.facet.VersioningDocument"/>
</extension>

More details on the explorer.

Listener

A post commit asynchronous listener is defined and an Event Bundle, which is an ordered set of events raised during a user operation, is pushed into the Audit log. Here is an example of a contribution to the listener extension point.

<extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
    <listener name="auditLoggerListener" async="true" postCommit="true"
        class="org.nuxeo.ecm.platform.audit.listener.AuditEventLogger" />
</extension>

More details on the explorer.

Queues

It is also possible to configure queues used by the Audit Service. Each queue is using a separate queue and a single thread for logging. The extension point used to define the queues' parameters is queue for the org.nuxeo.ecm.core.work.service target.

<extension target="org.nuxeo.ecm.core.work.service" point="queues">
  <queue id="audit">
    <name>Audit queue</name>
    <maxThreads>1</maxThreads>
    <category>auditLoggerListener</category>
    <!-- clear completed work instances older than 5 min -->
    <clearCompletedAfterSeconds>300</clearCompletedAfterSeconds>
  </queue>
</extension>

More details on the explorer.

Hibernate - Legacy SQL Back-end Only

In the legacy SQL back-end, the Audit Service uses Hibernate as a JPA provider. The configuration is done in the hibernate extension point for the org.nuxeo.ecm.core.persistence.PersistenceComponent target. This extension point lets you override the default Hibernate configuration.

<extension target="org.nuxeo.ecm.core.persistence.PersistenceComponent" point="hibernate">
  <hibernateConfiguration name="nxaudit-logs">
    <datasource>nxaudit-logs</datasource>
    <properties>
      <property name="hibernate.hbm2ddl.auto">update</property>
    </properties>
  </hibernateConfiguration>
</extension>

More details on the explorer.