Writing Audit Log Destination Connectors¶
The audit log running in an OMAG Server is implemented by the Open Metadata Repository Services (OMRS). It is called OMRSAuditLog and extends the AuditLog class defined in the Audit Log Framework (ALF). The ALF supports an audit log with a single destination, which is an AuditLogDestination. The OMRS extends this capability with OMRSAuditLogDestination, which acts as an audit log record distributor that passes every audit log record to each of the audit log destination connectors configured in the OMAG Server. Each connector then decides whether to keep the record based on its severity and the connector's supported severities.
Before passing a record to the connectors, OMRSAuditLogDestination adds details of the originating server (server name, server type, organization name and metadata collection id) to the record. The severities themselves are defined by the AuditLogRecordSeverityLevel enum in the ALF (Information, Event, Decision, Action, Error, Exception, Security, Startup, Shutdown, Asset, Types and so on).
flowchart LR
C[Server component] -->|logMessage| AL[OMRSAuditLog]
AL --> D[OMRSAuditLogDestination]
D -->|storeLogRecord| S1[Audit log destination connector 1]
D -->|storeLogRecord| S2[Audit log destination connector 2]
D -->|storeLogRecord| S3[Audit log destination connector ...]
The audit log destination connector interface is defined in the repository-services-apis module in the org.odpi.openmetadata.repositoryservices.connectors.stores.auditlogstore Java package.
In that package you will see the following Java classes:
OMRSAuditLogRecord- provides a carrier for details about a single log record in the OMRS audit log. It extends theAuditLogRecordclass from the ALF with deprecated fields from the original OMRS implementation to provide backward compatible support to the original implementation of the audit log destination connectors.OMRSAuditLogRecordOriginator- describes the server that originated an audit log record. This is useful if an organization is aggregating log records from different servers together into a single physical destination.OMRSAuditLogReportingComponentdescribes the component issuing the audit log record. It implements theComponentDescriptioninterface from the ALF.-
OMRSAuditLogStoreis the main interface of the audit log destination connector. It includes the following methods:getDestinationName- used by the OMRS in its logging and in the audit log report.getSupportedSeverities- returns the list of audit log record severities that have been configured for this destination instance. These are passed to the connector in its configuration properties passed in its connection that is set up in the server's configuration document. The name of the property issupportedSeverities.storeLogRecord- called for every log record written to the audit log.getAuditLogRecord- optional method to retrieve a specific log record.getAuditLogRecordsByTimeStamp- optional method to retrieve a list of log records written in a specified time period. The offset and maximumRecords parameters support paging.getAuditLogRecordsBySeverity- optional method to retrieve a list of log records that have a specific severity. The offset and maximumRecords parameters support paging.getAuditLogRecordsByComponent- optional method to retrieve a list of log records originating from a specific component. The offset and maximumRecords parameters support paging.
-
OMRSAuditLogStoreConnectorBaseis the abstract base class for the audit log destination connector. It extendsConnectorBasefrom the Open Connector Framework (OCF) and implementsOMRSAuditLogStore, providing default methods for all butstoreLogRecord(OMRSAuditLogRecord). The default query methods throwFunctionNotSupportedException. It also implementsinitializeto set the destination name from the display name of the connection (or its connector type) and to extract thesupportedSeveritiesfrom the connection's configuration properties. In addition, it includes the following protected methods to aid the writer of a connector:validateLogRecord- validates that a new log record is properly filled out (it has originator properties and an originator component) and throws anInvalidParameterExceptionif not.isSupportedSeverity- checks whether an incoming log record has a supported severity. IfsupportedSeveritiesis not set, or is an empty list, all severities are supported.getJSONLogRecord- converts a log record to a JSON string.
-
OMRSAuditLogStoreProviderBaseis an abstract base class for the audit log destination connector's connector provider. It extendsOpenConnectorProviderBaseand defines thesupportedSeveritiesPropertyconstant (supportedSeverities) along with a protected method calledgetRecognizedConfigurationPropertiesthat returns a list containing this property name.
Severity filtering is done by the connector
OMRSAuditLogDestination calls storeLogRecord on every configured connector for every log record. It is the connector's responsibility to call isSupportedSeverity (or apply its own filtering) so that only records with a configured severity are written to its destination.
Example Implementations¶
Implementations of this type of connector are located in the adapters/open-connectors/repository-services-connectors/audit-log-connectors module. They write audit log records to the console (ConsoleAuditLogStoreConnector), to the console with the full event payload (EventDisplayAuditLogStoreConnector), to a directory of JSON files (FileBasedAuditLogStoreConnector), to the SLF4J logging ecosystem (SLF4JAuditLogStoreConnector) and to an event topic (EventTopicAuditLogStoreConnector).
Observing activity from the audit log
The audit log records published to an event topic are also the source of Egeria's metadata observability. The Pepys Activity Observer integration connector in the nanny-connectors module listens to the topics that the OMAG Servers write their audit logs to and maintains the UsageActivity classification of the elements whose use is reported there. See Metadata Observability for details. If you want to analyse how open metadata is used, consider building on this rather than writing a new audit log destination connector.
Build Dependencies¶
<dependencies>
<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>open-connector-framework</artifactId>
</dependency>
<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>audit-log-framework</artifactId>
</dependency>
<dependency>
<groupId>org.odpi.egeria</groupId>
<artifactId>repository-services-apis</artifactId>
</dependency>
</dependencies>
dependencies {
compileOnly project(':open-metadata-implementation:frameworks:open-connector-framework')
compileOnly project(':open-metadata-implementation:frameworks:audit-log-framework')
compileOnly project(':open-metadata-implementation:repository-services:repository-services-apis')
}
Writing the Connector Provider¶
Each connector provider extends the following abstract class:
org.odpi.openmetadata.frameworks.connectors.ConnectorProvider
There is a base class that provides much of the implementation for a connector provider:
org.odpi.openmetadata.frameworks.connectors.ConnectorProviderBase
and a subclass of it that sets up the connector provider from a single description of the connector:
org.odpi.openmetadata.frameworks.connectors.OpenConnectorProviderBase
These base classes assume:
- There is a single connector implementation class for the connector.
- The connector is instantiated with the default constructor. This means all of its configuration information is contained in the Connection object supplied on the
initialize()method.
If your connector implementation matches these requirements, its connector provider implementation need only implement a constructor to configure the base class's function with details of itself and the Java class of the connector it needs using:
- a GUID for the connector type
- a qualified name and display name for the connector type.
- a description of what the connector is for and how to configure it.
- the connector class it instantiates.
- a list of the additional properties, configuration properties and secured properties needed to configure instances of the connector.
- a description of the connector for its audit log (if the connector implements
AuditLoggingComponent).
Use the base class for your type of connector
Many types of connector have their own connector provider base class that extends OpenConnectorProviderBase, for example IntegrationConnectorProvider for integration connectors and OMRSRepositoryConnectorProviderBase for repository connectors. Extend the base class for the type of connector you are writing.
Using OpenConnectorProviderBase¶
The descriptive information about the connector is supplied through an implementation of the OpenConnectorDefinition interface. This is typically an enum that describes all the connectors in a module. Egeria's own connectors are described in the EgeriaOpenConnectorDefinition enum in the org.odpi.openmetadata.adapters.connectors package. An OpenConnectorDefinition returns:
getConnectorComponentId()- unique identifier of the connector for the audit log.getConnectorTypeGUID()- unique identifier of the connector type.getConnectorQualifiedName(),getConnectorDisplayName(),getConnectorDescription()andgetConnectorWikiPage()- descriptive information for the connector type and audit log.getConnectorProviderClassName()- the class name of the connector provider.OpenConnectorProviderBasechecks that this matches the class of the connector provider being constructed.getConnectorDevelopmentStatus()- theComponentDevelopmentStatusof the connector.getSupportedAssetTypeName()andgetSupportedDeployedImplementationType()- the type of asset and the deployed implementation type that the connector works with.getComponentDescription()- theAuditLogReportingComponentfor the connector's audit log messages.
The connector provider then passes this definition to the OpenConnectorProviderBase constructor, along with the connector class name and the names of its recognized configuration properties. The constructor builds the connector type and audit log component description from these values.
/**
* XXXStoreProvider is the OCF connector provider for the XXX connector.
*/
public class XXXStoreProvider extends OpenConnectorProviderBase
{
/*
* Define the name of the connector implementation.
*/
private static final String connectorClassName = "packagename.XXXStoreConnector";
/*
* Define the name of configuration properties.
*/
public static final String TEMPLATE_QUALIFIED_NAME_CONFIGURATION_PROPERTY = "templateQualifiedName";
/**
* Constructor used to initialize the ConnectorProviderBase class.
*/
public XXXStoreProvider()
{
super(MyOrgConnectorDefinition.XXX_STORE_CONNECTOR,
connectorClassName,
List.of(TEMPLATE_QUALIFIED_NAME_CONFIGURATION_PROPERTY));
}
}
There is also a constructor that takes the list of interfaces that the connector supports and a description of the data format that it expects. After calling super(...), the constructor may set up the supportedTechnologyTypes, supportedConfigurationProperties and supportedTemplateTypes variables inherited from ConnectorProvider.
Example: BasicFileStoreProvider
The BasicFileStoreProvider passes EgeriaOpenConnectorDefinition.BASIC_FILE_STORE_CONNECTOR, its connector class name and the BasicFileStore interface to OpenConnectorProviderBase, and then sets supportedTechnologyTypes to show that it works with files.
Using ConnectorProviderBase directly¶
Alternatively, the connector provider can extend ConnectorProviderBase and set up the connector type and component description itself.
/**
* XXXStoreProvider is the OCF connector provider for the XXX connector.
*/
public class XXXStoreProvider extends ConnectorProviderBase
{
/*
* Unique identifier of the connector for the audit log.
*/
private static final int connectorComponentId = 10001; /* Add unique number here */
/*
* Unique identifier for the connector type.
*/
private static final String connectorTypeGUID = "Add unique GUID here";
/*
* Descriptive information about the connector for the connector type and audit log.
*/
private static final String connectorQualifiedName = "MyOrg:XXXStoreConnector";
private static final String connectorDisplayName = "XXX Store Connector";
private static final String connectorDescription = "Connector supports ... add details here.";
private static final String connectorWikiPage = "Add url to documentation here";
/*
* Define the name of the connector implementation.
*/
private static final String connectorClassName = "packagename.XXXStoreConnector";
/*
* Define the name of configuration properties.
*/
public static final String TEMPLATE_QUALIFIED_NAME_CONFIGURATION_PROPERTY = "templateQualifiedName";
/**
* Constructor used to initialize the ConnectorProviderBase class.
*/
public XXXStoreProvider()
{
super();
/*
* Set up the class name of the connector that this provider creates.
*/
super.setConnectorClassName(connectorClassName);
/*
* Set up the connector type that should be included in a connection used to configure this connector.
*/
ConnectorType connectorType = new ConnectorType();
connectorType.setGUID(connectorTypeGUID);
connectorType.setQualifiedName(connectorQualifiedName);
connectorType.setDisplayName(connectorDisplayName);
connectorType.setDescription(connectorDescription);
connectorType.setConnectorProviderClassName(this.getClass().getName());
List<String> recognizedConfigurationProperties = new ArrayList<>();
recognizedConfigurationProperties.add(TEMPLATE_QUALIFIED_NAME_CONFIGURATION_PROPERTY);
connectorType.setRecognizedConfigurationProperties(recognizedConfigurationProperties);
super.connectorTypeBean = connectorType;
/*
* Set up the component description used in the connector's audit log messages.
*/
AuditLogReportingComponent componentDescription = new AuditLogReportingComponent();
componentDescription.setComponentId(connectorComponentId);
componentDescription.setComponentName(connectorQualifiedName);
componentDescription.setComponentDescription(connectorDescription);
componentDescription.setComponentWikiURL(connectorWikiPage);
super.setConnectorComponentDescription(componentDescription);
}
}
The connector provider for an audit log destination connector extends OMRSAuditLogStoreProviderBase and passes the supportedSeveritiesProperty as one of its recognized configuration properties. This is how the console audit log destination's provider is written:
/**
* XXXAuditLogStoreProvider is the OCF connector provider for the XXX audit log destination connector.
*/
public class XXXAuditLogStoreProvider extends OMRSAuditLogStoreProviderBase
{
/*
* Class of the connector.
*/
private static final String connectorClassName = "packagename.XXXAuditLogStoreConnector";
/**
* Constructor used to initialize the OpenConnectorProviderBase with the Java class name of the specific
* audit log store implementation.
*/
public XXXAuditLogStoreProvider()
{
super(MyOrgConnectorDefinition.XXX_AUDIT_LOG_DESTINATION_CONNECTOR,
connectorClassName,
Collections.singletonList(supportedSeveritiesProperty));
}
}
MyOrgConnectorDefinition is your own enum implementing OpenConnectorDefinition. Egeria's own audit log destinations are described by values such as CONSOLE_AUDIT_LOG_DESTINATION_CONNECTOR and FILE_AUDIT_LOG_DESTINATION_CONNECTOR in EgeriaOpenConnectorDefinition, with a deployed implementation type of AUDIT_LOG_DESTINATION_CONNECTOR.
Writing the Connector¶
The audit log destination connector extends OMRSAuditLogStoreConnectorBase and needs to implement the storeLogRecord method. This needs to efficiently move the log record into the destination. The code snippet below shows the use of the super class methods to determine which records to process.
/**
* Store the audit log record in the audit log destination.
*
* @param logRecord log record to store
* @return unique identifier assigned to the log record
* @throws InvalidParameterException indicates that the logRecord parameter is invalid.
*/
@Override
public String storeLogRecord(OMRSAuditLogRecord logRecord) throws InvalidParameterException
{
final String methodName = "storeLogRecord";
super.validateLogRecord(logRecord, methodName);
if (super.isSupportedSeverity(logRecord))
{
// Put implementation here
}
return logRecord.getGUID();
}
If the destination supports querying then you can also override the query methods: getAuditLogRecord, getAuditLogRecordsByTimeStamp, getAuditLogRecordsBySeverity and getAuditLogRecordsByComponent.
Finally, the connector should use the start and disconnect methods to initialize and close down the access to the destination technology.
Configuring the Connector¶
Once the connector's JAR file is on the OMAG Server Platform's classpath, it is added to a server's configuration document with a POST request to /open-metadata/admin-services/servers/{serverName}/audit-log-destinations/connection, passing the connection for the connector in the request body. Set supportedSeverities in the connection's configuration properties to the list of severity names that the destination should receive. See Configuring the audit log for the related administration commands.
Raise an issue or comment below