From 63332de8a5cfd28625ab183dfe96911a36c65987 Mon Sep 17 00:00:00 2001 From: at055612 Date: Fri, 8 Jan 2021 12:39:16 +0000 Subject: [PATCH] Add purpose arg to loggedXXX, improve readme --- CHANGELOG.md | 12 +- README.md | 262 ++++++++---------- docs/developing_this_lib.md | 95 +++++++ event-logging-api/build.gradle | 3 - .../logging/base/EventLoggingService.java | 186 ++++++++++++- .../base/impl/DefaultEventLoggingService.java | 6 +- .../impl/FluentEventLoggingServiceIT.java | 4 + .../main/java/event/logging/example/App.java | 4 - 8 files changed, 407 insertions(+), 165 deletions(-) create mode 100644 docs/developing_this_lib.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a91296..5fc31370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [v5.0-beta.10_schema-v4.0-beta.1] - 2021-01-08 + +* Add `Purpose` arg to `loggedXXX` methods. + +* Add example application + +* Improve README + + ## [v5.0-beta.9_schema-v4.0-beta.1] - 2021-01-07 * Add missing handling of unsuccessful logged work outcome. @@ -152,7 +161,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [v3.1.0] - 2017-04-05 Intial open source release -[Unreleased]: https://github.com/gchq/event-logging/compare/v5.0-beta.9_schema-v4.0-beta.1...HEAD +[Unreleased]: https://github.com/gchq/event-logging/compare/v5.0-beta.10_schema-v4.0-beta.1...HEAD +[v5.0-beta.10_schema-v4.0-beta.1]: https://github.com/gchq/event-logging/compare/v5.0-beta.9_schema-v4.0-beta.1...v5.0-beta.10_schema-v4.0-beta.1 [v5.0-beta.9_schema-v4.0-beta.1]: https://github.com/gchq/event-logging/compare/v5.0-beta.8_schema-v4.0-beta.1...v5.0-beta.9_schema-v4.0-beta.1 [v5.0-beta.8_schema-v4.0-beta.1]: https://github.com/gchq/event-logging/compare/v5.0-beta.7_schema-v4.0-beta.1...v5.0-beta.8_schema-v4.0-beta.1 [v5.0-beta.7_schema-v4.0-beta.1]: https://github.com/gchq/event-logging/compare/v5.0-beta.6_schema-v4.0-beta.1...v5.0-beta.7_schema-v4.0-beta.1 diff --git a/README.md b/README.md index 59db83c6..45c5e2b2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,34 @@ # Event Logging -_Event Logging_ is an API for logging events conforming to the [_Event Logging XML Schema_](https://github.com/gchq/event-logging-schema). -The API uses a generated Java JAXB model of _Event Logging XML Schema_. -_Event Logging_ can be incorporated into your Java application to provide a means of recording and outputting audit events. +_Event Logging_ is a Java API for logging audit events conforming to the [_Event Logging XML Schema_](https://github.com/gchq/event-logging-schema). +The API uses a generated Java JAXB model of the _Event Logging XML Schema_. +_Event Logging_ can be incorporated into your Java application to provide a means of recording and outputting audit events or user actions for compliance, security or monitoring. + +As this library is essentially a Java representation of the [_Event Logging XML Schema_](https://github.com/gchq/event-logging-schema) it helps to refer to the schema to better understand the Java model. +The [schema documentation](https://gchq.github.io/event-logging-schema/) provides a lot of detail about how to model events using the schema which will be helpful when using this library. + +This library requires Java 8 as a minimum. +The only dependencies it brings with it are `javax.xml.bind:jaxb-api` and `org.slf4j:slf4j-api`. + +By default the created events are serialised to XML and passed to a SLF4J logger which would typically be linked to a rolling file appender. + +## What to Log + +The aim of this API is to capture the following information about an action/event. + +* *Who* - Who performed the action, i.e the user ID or some other identifer to link the event to a user and details of the user's device. +* *Where* - Where did the event happen, i.e on what system, device, network address. +* *What* - The detail of what they did, e.g. copy a file named X from A to B. +* *When* - The time the event happend. +* *Why* - The purpose and/or justification for their action, e.g. where compliance rules dictate that certain actions have prior approval. + The requirement to capture the why is dependant on the rules in place for the system using this library. ## Using the _Event Logging_ API ### Gradle/Maven dependencies The Event Logging API is available as a Maven/Gradle dependency on [Bintray](https://bintray.com/stroom/event-logging/event-logging). +You will need to add our Bintray repository to your build tool. To include it in your Gradle build add the following: ```groovy @@ -17,10 +37,8 @@ repositories { maven { url "https://dl.bintray.com/stroom/event-logging" } } -//... - dependencies { - compile 'event-logging:event-logging:v4.0.7_schema-v3.2.4' + compile 'event-logging:event-logging:v5.0-beta.9_schema-v4.0-beta.1' } ``` @@ -29,9 +47,14 @@ Version 3.x.x+ of event-logging is compatible with Java 8+ The second version number in the version string is the version of the _event-logging-schema_ XML Schema that the library uses. +### Example Application + +A standalone example application can be found in `./example-logged-application` that shows how you can include logging in your application. +See [here](./example-logged-application/README.md) for details. + ### Calling the API -The API has two main parts, the class model that represents the schema and the classes involved with serialising the events. +The API has two main parts, the class model that represents the schema and the classes involved with serialising and logging the events. #### Event Class Model @@ -46,13 +69,14 @@ final Event event = Event.builder() #### Logging Service -The interface for logging audit events is `LoggingEventsService.java`. +The interface for logging audit events is `EventLoggingService.java`. A default implementation of this interface is included in the form of `DefaultEventLoggingService.java`. This simple implementation serialises the `Event` passed to it to an XML String and passes that to an implementation of `LogReceiver`. By default this library will use the `LoggerLogReceiver` implementation to receive and handle the XML events. This implementation will write the XML to an SLF4J logger called `event-logger`. You then need to add configuration to your logging framework, i.e. Log4J/Logback/etc. to handle the `event-logger` logs, e.g. writing them to rolled log files. +See [here](./example-logged-application/src/main/resources/logback.xml) for an example Logback configuration. Sending your rolled event logs to Stroom would then be done using something like [send_to_stroom.sh](https://github.com/gchq/stroom-clients/tree/master/bash) or [stroom-log-sender](https://hub.docker.com/r/gchq/stroom-log-sender). @@ -64,55 +88,99 @@ In order to use your own implementaion you need to set the system property `even Examples of how to construct various types of events and log them can be found in the test class `base/src/test/java/event/logging/EventLoggingServiceIT.java`. +These examples assume you have created your own implementation of EventLoggingService that overrides the `createEvent(...)` methods. +Using your own implemtation that extends `DefaultEventLoggingService` is the preferred approach to dealing with common values in your events. +See [CustomEventLoggingService](./example-logged-application/src/main/java/event/logging/example/CustomEventLoggingService.java) for an example. + The following is a very simple example of logging an _Authentication_ type event using the default logging service supplied with the API. +This example does not use any common event values so ```java -// Create the logging service -final EventLoggingService eventLoggingService = new DefaultEventLoggingService(); -// Create the event object -final Event event = eventLoggingService.buildEvent() - .withEventTime(EventTime.builder() - .withTimeCreated(new Date()) - .build()) - .withEventSource(EventSource.builder() - .withSystem(SystemDetail.builder() - .withName("Test System") - .withEnvironment("Test") - .build()) - .withGenerator("JUnit") - .withDevice(Device.builder() - .withIPAddress("123.123.123.123") - .build()) - .withUser(User.builder() - .withId("someuser") - .build()) - .build()) - .withEventDetail(EventDetail.builder() - .withTypeId("LOGON") - .withDescription("A user logon") - .withAuthenticate(AuthenticateEventAction.builder() - .withAction(AuthenticateAction.LOGON) - .withUser(User.builder() - .withId("someuser") - .build()) - .build()) +// Define your own implementation of EventLoggingService that provides common event values +public static class CustomEventLoggingService extends DefaultEventLoggingService { + + @Override + public Event createEvent(final String typeId, + final String description, + final Purpose purpose, + final EventAction eventAction) { + return Event.builder() + .withEventTime(EventTime.builder() + .withTimeCreated(new Date()) + .build()) + .withEventSource(EventSource.builder() + .withSystem(SystemDetail.builder() + .withName("My System Name") + .withEnvironment("Test") + .withVersion(getBuildVersion()) + .build()) + .withGenerator("CustomEventLoggingService") + .withClient(getClientDevice()) + .withDevice(getThisDevice()) + .withUser(User.builder() + .withId(getLoggedInUserId()) + .build()) + .build()) + .withEventDetail(EventDetail.builder() + .withTypeId(typeId) + .withDescription(description) + .withPurpose(purpose != null ? purpose : getSessionPurpose()) + .withEventAction(eventAction) + .build()) + .build(); + } + +} + +// Create the logging service +final EventLoggingService eventLoggingService = new CustomEventLoggingService(); + +// Log some work that produces a result. This will make use of createEvent for common event values. +final UserAccount userAccount = eventLoggingService.loggedResult( + "LOGON", + "User " + userId + " logged on", + AuthenticateEventAction.builder() + .withAction(AuthenticateAction.LOGON) + .withUser(User.builder() + .withId(userId) .build()) - .build(); + .build() + () -> { + // Perform authentication and logon -// Send the event -eventLoggingService.log(event); + // An exception here will cause an unsuccessful logon event to be logged + // then the original exception will be re-thrown. + + return account; + }); ``` -Alternatively you can use the fully fluent style (using the `end()` methods on the `Builder` classes) to build the event. -While more compact, this is heavily reliant on careful indentation to ensure readability. +The various forms of the `loggedXXX` methods provide the simplest means of logging an event and handle failure cases for you, setting Outcome/Success to false if an exception is thrown. +Some forms of `loggedXXX` allow the loggedWork lambda to return a LoggedOutcome object to indicate success/failure rather than using exceptions. +There are also forms of these methods that allow you to change the logged EventAction based on the work being performed, e.g. if you need to add in the details of the results of a search. +For examples of the various forms see [App.java](./example-logged-application/src/main/java/event/logging/example/App.java) + +For instances where you want to handle failure conditions manually you can use this approach: ```java -// Create the logging service -final EventLoggingService eventLoggingService = new DefaultEventLoggingService(); +eventLoggingService.log( + "LogoffNowBanner", + "User shown logoff now banner", + ViewEventAction.builder() + .addBanner(Banner.builder() + .withMessage("The system is about to be shutdown for maintenance, log off now!") + .build()) + .build()); +``` + +When building the Event model you can use the fully fluent style (using the `end()` methods on the `Builder` classes) to build the event. +While more compact, this is heavily reliant on careful indentation to ensure readability. + +```java // Create the event object -final Event event = eventLoggingService.buildEvent() +final Event event = Event.builder() .withEventTime() .withTimeCreated(new Date()) .end() @@ -140,15 +208,10 @@ final Event event = eventLoggingService.buildEvent() .end() .end() .build(); - -// Send the event -eventLoggingService.log(event); ``` -A standalone example application can be found in `example-logged-application` that shows how you can include logging in your application, see [here](./example-logged-application/README.md) for details. - _event-logging_ is used by [_Stroom_](https://github.com/gchq/stroom). -An example of how it used can be seen here: [`StroomEventLoggingServiceImpl`](https://github.com/gchq/stroom/blob/master/stroom-event-logging/stroom-event-logging-impl/src/main/java/stroom/event/logging/impl/StroomEventLoggingServiceImpl.java) +You can see how it is used in Stroom here: [`StroomEventLoggingServiceImpl`](https://github.com/gchq/stroom/blob/master/stroom-event-logging/stroom-event-logging-impl/src/main/java/stroom/event/logging/impl/StroomEventLoggingServiceImpl.java) ## Upgrading from v3/4 to v5 @@ -158,8 +221,8 @@ Unfortunately there were a number of fundamental issues with the previous versio ### Fluent Builders -v5 has introduced fluent style builder classes and methods for each class in the `Event` model. -These make creating your events easier and neater. +_event-logging_ v5 has introduced fluent style builder classes and methods for each class in the `Event` model. +These make creating events or sub-trees within events much easier and neater. There is however no requirement to change existing code to use the builders. ### Moved Classes @@ -223,95 +286,4 @@ For example all the possible event actions (e.g. send, create, delete, etc.) und ## Developing this library -### Generation of the JAXB artefacts - -The JAXB artefacts are generated using the _com.sun.tools.xjc_ tool that ships with Java. -This parses the XML Schema and builds a set of classes based on the schema. -_xjc_ is run using a number of additional plugins to handle the creation of fluent builders and common interfaces. -Prior to running _xjc_ the schema undergoes an automated tidy up process to rename many of the elements to improve the class names in the JAXB model, i.e. `FooComplexType` becomes `Foo`. - -The generation process is reliant on having the required version of the _Event Logging_ XML schema in the directory _event-logging-generator/schema/_. -This directory is ignored by source control. -The Gradle build will download the schema from GitHub as long as the following line in the root _build.gradle_ file has been set to the correct schema version. - -``` groovy -def eventLoggingSchemaVer = "v3.1.2" -``` - -The class that manages the code generation is `event.logging.gen.GenClasses`. -As well running `xjc` it copies various non-generated classes and resources into the _event-logging-api_ module from _event-logging-base_ which is what the published jar is ultimately built from. -As part of this copy process any packages or imports for `event.logging.base` are changed to `event.logging` to reflect their new home. - -#### Bindings - -To further fine tune the code that is generated from the schema, _xjc_ uses a bindings file `simple-binding.xjb`. -This allows us to do things like changing the name of generated classes or making classes implement a non-generated interface. -Note the bindings file is run against a modified version of the source schema (_schema.mod.xsd_) that is produced by _GenClasses_. -The main aim of this is to remove the suffix `ComplexType` from the complex types so that the Java classes don't have this as a suffix. -Because the bindings file operates against the modified schema any xpaths will need to be in terms of that scheme, i.e. `@name='Foo'` rather than `@name='FooComplexType'`. -Any changes to the schema may require changes to the bindings, e.g. the introduction of a new event action would require some additional bindings to make its class implement the `EventAction` interface. - -#### Generated Code Comparison - -Part of the build process is to compare the generated code against a previous release. -This allows you to see how any change to the schema has impacted the java code. -As the build is unable to determine what constitutes the previous release you need to specify it in the root build file. - -``` groovy -ext.previousReleaseVersion = "v4.0.7_schema-v3.2.4" -``` - -The Gradle build will generate the JAXB artefacts and go onto build the API jar. - -The _Event Logging_ XML schema is authored in [github.com/gchq/event-logging-schema](https://github.com/gchq/event-logging-schema). -The _Event Logging XML Schema_ in _event-logging-generator/schema/_ should never be edited directly. -It should always be a copy of the desired version from _event-loggin-schema_. - -### Building the _Event Logging_ API jar - -The API jar is built using Gradle. This will generate the JAXB artefacts, as well as copying the API classes, test classes and XML schema from the base module into the event-logging-api module. - -All files under event-logging-api/src are transient and will be generated or copied in as part of the Gradle build. - -The build is run as follows: - -`./gradlew clean build` - -To run the build with specific version number do something like: - -`./gradlew clean build -Pversion=v1.2.3_schema-v4.5.6` - -Towards the end of the build process, it will download the sources jar for the latest release of _event-logging_ from GitHub and compare the Java source files in it to those just built. -This provides a quick way of seeing the impact on the API from any changes in the schema. -For example some schema changes that would not be a breaking change as far as an XML document is concerned (e.g. a rename of a complex type), would become a breaking change in the JAXB classes. - -### Developing the schema in conjunction with the JAXB library - -By default the build will download the _-client_ variant of the schema from github. -This is not ideal when you are making changes to the schema and want to see the impact on the JAXB library. -If you want to run the build using a local copy of the schema you can do something like the following: - -```bash -./buildAgainstLocalSchema.sh -``` - -This will build the _client_ variant of the schema from whatever version of the master `event-logging.xsd` schema is in the local `event-logging-schema` repo, then build _event-logging_ from it. - -#### Documentation - -The Javadoc for the library is automatically pulled from the schema annotations by `jaxb2-rich-contract-plugin`. -Therefore it is important to ensure that all elements, types and complex types in the schema are fully annotated to provide a rich set of javadocs. - -### Releasing the _Event Logging_ API jar - -To perform a release simply tag the _master_ branch as follows: - -`git tag -a vX.Y.Z_schema-vA.B.C` - -Where `X.Y.Z` is the version of the _event-logging_ API library and `A.B.C` is the version of the event-logging XMLSchema. -The two version numbers are totally independent of each other and have different life-cycles, e.g. a minor release of the schema could trigger a breaking change and major release of the API. -Equally there may be a new release of the API with an identical schema version to the previous one. -The build process will validate the tag when it is used as the version property in Travis. - -When prompted to enter the commit message set the first line to `event-logging-vX.Y.Z_schema-vA.B.C` and lines 3+ to be the changes made, as extracted from the CHANGELOG.md file. -Once the tag is picked up by Travis, the build will be run and the build artefacts published to [GitHub releases](https://github.com/gchq/event-logging/releases). +The documention for developers contributing to this library see [here](./docs/developing_this_lib.md). diff --git a/docs/developing_this_lib.md b/docs/developing_this_lib.md new file mode 100644 index 00000000..7fdc0e97 --- /dev/null +++ b/docs/developing_this_lib.md @@ -0,0 +1,95 @@ +# Developing this library + +## Generation of the JAXB artefacts + +The JAXB artefacts are generated using the _com.sun.tools.xjc_ tool that ships with Java. +This parses the XML Schema and builds a set of classes based on the schema. +_xjc_ is run using a number of additional plugins to handle the creation of fluent builders and common interfaces. +Prior to running _xjc_ the schema undergoes an automated tidy up process to rename many of the elements to improve the class names in the JAXB model, i.e. `FooComplexType` becomes `Foo`. + +The generation process is reliant on having the required version of the _Event Logging_ XML schema in the directory _event-logging-generator/schema/_. +This directory is ignored by source control. +The Gradle build will download the schema from GitHub as long as the following line in the root _build.gradle_ file has been set to the correct schema version. + +``` groovy +def eventLoggingSchemaVer = "v3.1.2" +``` + +The class that manages the code generation is `event.logging.gen.GenClasses`. +As well running `xjc` it copies various non-generated classes and resources into the _event-logging-api_ module from _event-logging-base_ which is what the published jar is ultimately built from. +As part of this copy process any packages or imports for `event.logging.base` are changed to `event.logging` to reflect their new home. + +### Bindings + +To further fine tune the code that is generated from the schema, _xjc_ uses a bindings file `simple-binding.xjb`. +This allows us to do things like changing the name of generated classes or making classes implement a non-generated interface. +Note the bindings file is run against a modified version of the source schema (_schema.mod.xsd_) that is produced by _GenClasses_. +The main aim of this is to remove the suffix `ComplexType` from the complex types so that the Java classes don't have this as a suffix. +Because the bindings file operates against the modified schema any xpaths will need to be in terms of that scheme, i.e. `@name='Foo'` rather than `@name='FooComplexType'`. +Any changes to the schema may require changes to the bindings, e.g. the introduction of a new event action would require some additional bindings to make its class implement the `EventAction` interface. + +### Generated Code Comparison + +Part of the build process is to compare the generated code against a previous release. +This allows you to see how any change to the schema has impacted the java code. +As the build is unable to determine what constitutes the previous release you need to specify it in the root build file. + +``` groovy +ext.previousReleaseVersion = "v4.0.7_schema-v3.2.4" +``` + +The Gradle build will generate the JAXB artefacts and go onto build the API jar. + +The _Event Logging_ XML schema is authored in [github.com/gchq/event-logging-schema](https://github.com/gchq/event-logging-schema). +The _Event Logging XML Schema_ in _event-logging-generator/schema/_ should never be edited directly. +It should always be a copy of the desired version from _event-loggin-schema_. + +## Building the _Event Logging_ API jar + +The API jar is built using Gradle. This will generate the JAXB artefacts, as well as copying the API classes, test classes and XML schema from the base module into the event-logging-api module. + +All files under event-logging-api/src are transient and will be generated or copied in as part of the Gradle build. + +The build is run as follows: + +`./gradlew clean build` + +To run the build with specific version number do something like: + +`./gradlew clean build -Pversion=v1.2.3_schema-v4.5.6` + +Towards the end of the build process, it will download the sources jar for the latest release of _event-logging_ from GitHub and compare the Java source files in it to those just built. +This provides a quick way of seeing the impact on the API from any changes in the schema. +For example some schema changes that would not be a breaking change as far as an XML document is concerned (e.g. a rename of a complex type), would become a breaking change in the JAXB classes. + +## Developing the schema in conjunction with the JAXB library + +By default the build will download the _-client_ variant of the schema from github. +This is not ideal when you are making changes to the schema and want to see the impact on the JAXB library. +If you want to run the build using a local copy of the schema you can do something like the following: + +```bash +./buildAgainstLocalSchema.sh +``` + +This will build the _client_ variant of the schema from whatever version of the master `event-logging.xsd` schema is in the local `event-logging-schema` repo, then build _event-logging_ from it. + +### Documentation + +The Javadoc for the library is automatically pulled from the schema annotations by `jaxb2-rich-contract-plugin`. +Therefore it is important to ensure that all elements, types and complex types in the schema are fully annotated to provide a rich set of javadocs. + +## Releasing the _Event Logging_ API jar + +To perform a release simply tag the _master_ branch as follows: + +`git tag -a vX.Y.Z_schema-vA.B.C` + +Where `X.Y.Z` is the version of the _event-logging_ API library and `A.B.C` is the version of the event-logging XMLSchema. +The two version numbers are totally independent of each other and have different life-cycles, e.g. a minor release of the schema could trigger a breaking change and major release of the API. +Equally there may be a new release of the API with an identical schema version to the previous one. +The build process will validate the tag when it is used as the version property in Travis. + +When prompted to enter the commit message set the first line to `event-logging-vX.Y.Z_schema-vA.B.C` and lines 3+ to be the changes made, as extracted from the CHANGELOG.md file. +Once the tag is picked up by Travis, the build will be run and the build artefacts published to [GitHub releases](https://github.com/gchq/event-logging/releases). + diff --git a/event-logging-api/build.gradle b/event-logging-api/build.gradle index 8c0c8cac..7b65f2b3 100644 --- a/event-logging-api/build.gradle +++ b/event-logging-api/build.gradle @@ -14,9 +14,6 @@ dependencies { // The production code uses the SLF4J logging API at compile time compile "org.slf4j:slf4j-api:${versions.slf4j}" - //runtime "net.sf.saxon:Saxon-HE:${versions.saxon}" - //runtime "ch.qos.logback:logback-classic:${versions.logback}" - testImplementation "org.junit.jupiter:junit-jupiter-api:${versions.junit}" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${versions.junit}" testCompile "org.assertj:assertj-core:${versions.assertj}" diff --git a/event-logging-base/src/main/java/event/logging/base/EventLoggingService.java b/event-logging-base/src/main/java/event/logging/base/EventLoggingService.java index 1a314197..4e33662a 100644 --- a/event-logging-base/src/main/java/event/logging/base/EventLoggingService.java +++ b/event-logging-base/src/main/java/event/logging/base/EventLoggingService.java @@ -18,8 +18,10 @@ import event.logging.Event; import event.logging.EventAction; import event.logging.EventDetail; +import event.logging.EventTime; import event.logging.Purpose; +import java.util.Date; import java.util.function.Supplier; /** @@ -37,7 +39,7 @@ public interface EventLoggingService { */ default Event createEvent() { - return new Event(); + return createEvent(null, null, null); } /** @@ -59,8 +61,9 @@ default Event createEvent(final String typeId, /** * Creates an event that may have some common values set by default depending on the particular EventLoggingService * implementation being used. If this method is not implemented it will return an event that contains only - * the supplied typeId, description, purpose and eventAction. It is expected that this method is implemented to provide - * an event with all common values set, e.g. system name, environment, device, etc. + * the supplied typeId, description, purpose and eventAction. It also has the current time set on the event. + * It is expected that this method is implemented to provide an event with all common values set, + * e.g. system name, environment, device, etc. * * @param typeId The typeId of the event, see {@link EventDetail#setTypeId(String)} * @param description The description of the event, see {@link EventDetail#setDescription(String)} @@ -73,6 +76,9 @@ default Event createEvent(final String typeId, final Purpose purpose, final EventAction eventAction) { return Event.builder() + .withEventTime(EventTime.builder() + .withTimeCreated(new Date()) + .build()) .withEventDetail(EventDetail.builder() .withTypeId(typeId) .withDescription(description) @@ -121,7 +127,7 @@ default void log(final String typeId, } /** - * See also {@link EventLoggingService#loggedResult(String, String, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} * Use this form when you do not need to modify the event based on the result of the work and the work * has no result. * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the @@ -143,13 +149,44 @@ default void loggedAction( loggedResult( eventTypeId, description, + null, eventAction, loggedResultFunction, null); } /** - * See also {@link EventLoggingService#loggedResult(String, String, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * Use this form when you do not need to modify the event based on the result of the work and the work + * has no result. + * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the + * {@link EventAction} before it is logged and the exception re-thrown. + * @param loggedWork A {@link Runnable} of the work to be logged. If no exception is thrown success is assumed. + */ + default void loggedAction( + final String eventTypeId, + final String description, + final Purpose purpose, + final T_EVENT_ACTION eventAction, + final Runnable loggedWork) { + + final ComplexLoggedSupplier loggedResultFunction = eventAction2 -> { + loggedWork.run(); + // We don't have an outcome so assume success + return ComplexLoggedOutcome.of(LoggedOutcome.success(), eventAction2); + }; + + loggedResult( + eventTypeId, + description, + purpose, + eventAction, + loggedResultFunction, + null); + } + + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} * Use this form when you do not need to modify the event based on the result of the work and the work * has no result. * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the @@ -171,13 +208,44 @@ default void loggedAction( loggedResult( eventTypeId, description, + null, eventAction, loggedResultFunction, null); } /** - * See also {@link EventLoggingService#loggedResult(String, String, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * Use this form when you do not need to modify the event based on the result of the work and the work + * has no result. + * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the + * {@link EventAction} before it is logged and the exception re-thrown. + * @param loggedWork The work that is being logged with the outcome of the work being return as a + * {@link LoggedOutcome}. + */ + default void loggedAction( + final String eventTypeId, + final String description, + final Purpose purpose, + final T_EVENT_ACTION eventAction, + final LoggedRunnable loggedWork) { + + final ComplexLoggedSupplier loggedResultFunction = eventAction2 -> { + final LoggedOutcome loggedOutcome = loggedWork.run(); + return ComplexLoggedOutcome.of(loggedOutcome, eventAction2); + }; + + loggedResult( + eventTypeId, + description, + purpose, + eventAction, + loggedResultFunction, + null); + } + + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} * Use this form when the logged work has no result. * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the * {@link EventAction} before it is logged and the exception re-thrown. @@ -189,18 +257,67 @@ default void loggedAction( final ComplexLoggedRunnable loggedWork, final LoggedWorkExceptionHandler exceptionHandler) { - final ComplexLoggedSupplier loggedResultFunction = eventAction2 -> loggedWork.run(eventAction2); + loggedResult( + eventTypeId, + description, + null, + eventAction, + loggedWork::run, + exceptionHandler); + } + + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * Use this form when the logged work has no result. + * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the + * {@link EventAction} before it is logged and the exception re-thrown. + */ + default void loggedAction( + final String eventTypeId, + final String description, + final Purpose purpose, + final T_EVENT_ACTION eventAction, + final ComplexLoggedRunnable loggedWork, + final LoggedWorkExceptionHandler exceptionHandler) { loggedResult( eventTypeId, description, + purpose, eventAction, - loggedResultFunction, + loggedWork::run, exceptionHandler); } /** - * See also {@link EventLoggingService#loggedResult(String, String, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * Use this form when you do not need to modify the event based on the result of the work. + * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the + * {@link EventAction} before it is logged. + */ + default T_RESULT loggedResult( + final String eventTypeId, + final String description, + final T_EVENT_ACTION eventAction, + final Supplier loggedWork) { + + final ComplexLoggedSupplier loggedResultFunction = eventAction2 -> { + T_RESULT result = loggedWork.get(); + // We don't have an outcome so assume success + return ComplexLoggedOutcome.of(LoggedOutcome.success(result), eventAction2); + }; + + return loggedResult( + eventTypeId, + description, + null, + eventAction, + loggedResultFunction, + null); + } + + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} * Use this form when you do not need to modify the event based on the result of the work. * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the * {@link EventAction} before it is logged. @@ -208,6 +325,7 @@ default void loggedAction( default T_RESULT loggedResult( final String eventTypeId, final String description, + final Purpose purpose, final T_EVENT_ACTION eventAction, final Supplier loggedWork) { @@ -220,13 +338,38 @@ default T_RESULT loggedResult( return loggedResult( eventTypeId, description, + purpose, + eventAction, + loggedResultFunction, + null); + } + + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * Use this form when you do not need to modify the event based on the result of the work. + * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the + * {@link EventAction} before it is logged. + */ + default T_RESULT loggedResult( + final String eventTypeId, + final String description, + final T_EVENT_ACTION eventAction, + final LoggedSupplier loggedWork) { + + final ComplexLoggedSupplier loggedResultFunction = eventAction2 -> + ComplexLoggedOutcome.of(loggedWork.get(), eventAction2); + + return loggedResult( + eventTypeId, + description, + null, eventAction, loggedResultFunction, null); } /** - * See also {@link EventLoggingService#loggedResult(String, String, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} * Use this form when you do not need to modify the event based on the result of the work. * If an exception occurs in {@code loggedWork} then an unsuccessful outcome will be added to the * {@link EventAction} before it is logged. @@ -234,6 +377,7 @@ default T_RESULT loggedResult( default T_RESULT loggedResult( final String eventTypeId, final String description, + final Purpose purpose, final T_EVENT_ACTION eventAction, final LoggedSupplier loggedWork) { @@ -243,11 +387,31 @@ default T_RESULT loggedResult( return loggedResult( eventTypeId, description, + purpose, eventAction, loggedResultFunction, null); } + /** + * See also {@link EventLoggingService#loggedResult(String, String, Purpose, EventAction, ComplexLoggedSupplier, LoggedWorkExceptionHandler)} + */ + default T_RESULT loggedResult( + final String eventTypeId, + final String description, + final T_EVENT_ACTION eventAction, + final ComplexLoggedSupplier loggedWork, + final LoggedWorkExceptionHandler exceptionHandler) { + + return loggedResult( + eventTypeId, + description, + null, + eventAction, + loggedWork, + exceptionHandler); + } + /** * Performs {@code loggedWork} and logs an event using the supplied {@link EventAction}. * An event is logged if the work is successful or if an exception occurs. @@ -258,6 +422,7 @@ default T_RESULT loggedResult( * @param description A human readable description of the event being logged. Can include IDs/values specific * to the event, e.g. "Creating user account jbloggs". See also * {@link event.logging.EventDetail#setDescription(String)} + * @param purpose The purpose/justification for the action being logged. Can be null. * @param eventAction The skeleton {@link EventAction} that will be used to create the event unless * {@code loggedWork} of {@code exceptionHandler} provide an alternative. * @param loggedWork A lambda to perform the work that is being logged and to return the {@link EventAction} @@ -279,6 +444,7 @@ default T_RESULT loggedResult( T_RESULT loggedResult( final String eventTypeId, final String description, + final Purpose purpose, final T_EVENT_ACTION eventAction, final ComplexLoggedSupplier loggedWork, final LoggedWorkExceptionHandler exceptionHandler); diff --git a/event-logging-base/src/main/java/event/logging/base/impl/DefaultEventLoggingService.java b/event-logging-base/src/main/java/event/logging/base/impl/DefaultEventLoggingService.java index ccd07179..33afd1bc 100644 --- a/event-logging-base/src/main/java/event/logging/base/impl/DefaultEventLoggingService.java +++ b/event-logging-base/src/main/java/event/logging/base/impl/DefaultEventLoggingService.java @@ -18,10 +18,11 @@ import event.logging.BaseOutcome; import event.logging.Event; import event.logging.EventAction; -import event.logging.base.HasOutcome; +import event.logging.Purpose; import event.logging.base.ComplexLoggedOutcome; import event.logging.base.ComplexLoggedSupplier; import event.logging.base.EventLoggingService; +import event.logging.base.HasOutcome; import event.logging.base.LoggedWorkExceptionHandler; import event.logging.base.XMLValidator; import org.slf4j.Logger; @@ -103,6 +104,7 @@ public void log(final Event event) { public T_RESULT loggedResult( final String eventTypeId, final String description, + final Purpose purpose, final T_EVENT_ACTION eventAction, final ComplexLoggedSupplier loggedWork, final LoggedWorkExceptionHandler exceptionHandler) { @@ -117,7 +119,7 @@ public T_RESULT loggedResult( // result of the work e.g. if they are updating a record, they can capture the before state final ComplexLoggedOutcome complexLoggedOutcome = loggedWork.get(eventAction); - final Event event = createEvent(eventTypeId, description, complexLoggedOutcome.getEventAction()); + final Event event = createEvent(eventTypeId, description, purpose, complexLoggedOutcome.getEventAction()); // From a logging point of view the work may be unsuccessful even if no ex is thrown // so add the outcome to the event diff --git a/event-logging-base/src/test/java/event/logging/base/impl/FluentEventLoggingServiceIT.java b/event-logging-base/src/test/java/event/logging/base/impl/FluentEventLoggingServiceIT.java index 590efa06..75565f90 100644 --- a/event-logging-base/src/test/java/event/logging/base/impl/FluentEventLoggingServiceIT.java +++ b/event-logging-base/src/test/java/event/logging/base/impl/FluentEventLoggingServiceIT.java @@ -36,6 +36,7 @@ import event.logging.ImportEventAction; import event.logging.MultiObject; import event.logging.Outcome; +import event.logging.Purpose; import event.logging.Query; import event.logging.SearchEventAction; import event.logging.SendEventAction; @@ -228,6 +229,9 @@ void testLoggedResult_Advanced() { final Integer result = getEventLoggingService().loggedResult( "login", "User " + username + " logged in", + Purpose.builder() + .withJustification("Approval No. 23433393") + .build(), AuthenticateEventAction.builder() .withUser(User.builder() .withName(username) diff --git a/example-logged-application/src/main/java/event/logging/example/App.java b/example-logged-application/src/main/java/event/logging/example/App.java index f7614134..943178a4 100644 --- a/example-logged-application/src/main/java/event/logging/example/App.java +++ b/example-logged-application/src/main/java/event/logging/example/App.java @@ -13,7 +13,6 @@ import event.logging.MultiObject; import event.logging.OtherObject; import event.logging.Outcome; -import event.logging.Purpose; import event.logging.Query; import event.logging.SearchEventAction; import event.logging.SimpleQuery; @@ -71,9 +70,6 @@ public void run() { eventLoggingService.log( "LogoffNowBanner", "User shown logoff now banner", - Purpose.builder() - .withJustification("Just because!") - .build(), ViewEventAction.builder() .addBanner(Banner.builder() .withMessage("The system is about to be shutdown for maintenance, log off now!")