001/*
002 * Copyright 2022-2025 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import javax.annotation.Nonnull;
020import javax.annotation.Nullable;
021import javax.annotation.concurrent.NotThreadSafe;
022import javax.annotation.concurrent.ThreadSafe;
023import java.util.Objects;
024import java.util.Optional;
025
026import static java.lang.String.format;
027import static java.util.Objects.requireNonNull;
028
029/**
030 * An informational "loggable" event that occurs during Soklet's internal processing - for example, if an error occurs while handling a request.
031 * <p>
032 * These events are exposed via {@link LifecycleInterceptor#didReceiveLogEvent(LogEvent)}.
033 * <p>
034 * Instances can be acquired via the {@link #with(LogEventType, String)} builder factory method.
035 * <p>
036 * Documentation is available at <a href="https://www.soklet.com/docs/request-lifecycle#event-logging">https://www.soklet.com/docs/request-lifecycle#event-logging</a>.
037 *
038 * @author <a href="https://www.revetkn.com">Mark Allen</a>
039 */
040@ThreadSafe
041public final class LogEvent {
042        @Nonnull
043        private final LogEventType logEventType;
044        @Nonnull
045        private final String message;
046        @Nullable
047        private final Throwable throwable;
048        @Nullable
049        private final Request request;
050        @Nullable
051        private final ResourceMethod resourceMethod;
052        @Nullable
053        private final MarshaledResponse marshaledResponse;
054
055        /**
056         * Acquires a builder for {@link LogEvent} instances.
057         *
058         * @param logEventType what kind of log event this is
059         * @param message      the message for this log event
060         * @return the builder
061         */
062        @Nonnull
063        public static Builder with(@Nonnull LogEventType logEventType,
064                                                                                                                 @Nonnull String message) {
065                requireNonNull(logEventType);
066                requireNonNull(message);
067
068                return new Builder(logEventType, message);
069        }
070
071        /**
072         * Vends a mutable copier seeded with this instance's data, suitable for building new instances.
073         *
074         * @return a copier for this instance
075         */
076        @Nonnull
077        public Copier copy() {
078                return new Copier(this);
079        }
080
081        protected LogEvent(@Nonnull Builder builder) {
082                requireNonNull(builder);
083
084                this.logEventType = builder.logEventType;
085                this.message = builder.message;
086                this.throwable = builder.throwable;
087                this.request = builder.request;
088                this.resourceMethod = builder.resourceMethod;
089                this.marshaledResponse = builder.marshaledResponse;
090        }
091
092        @Override
093        @Nonnull
094        public String toString() {
095                return format("%s{logEventType=%s, message=%s, throwable=%s}", getClass().getSimpleName(),
096                                getLogEventType(), getMessage(), getThrowable().orElse(null));
097        }
098
099        @Override
100        public boolean equals(@Nullable Object object) {
101                if (this == object)
102                        return true;
103
104                if (!(object instanceof LogEvent logEvent))
105                        return false;
106
107                return Objects.equals(getLogEventType(), logEvent.getLogEventType())
108                                && Objects.equals(getMessage(), logEvent.getMessage())
109                                && Objects.equals(getThrowable(), logEvent.getThrowable())
110                                && Objects.equals(getRequest(), logEvent.getRequest())
111                                && Objects.equals(getResourceMethod(), logEvent.getResourceMethod())
112                                && Objects.equals(getMarshaledResponse(), logEvent.getMarshaledResponse());
113        }
114
115        @Override
116        public int hashCode() {
117                return Objects.hash(getLogEventType(), getMessage(), getThrowable(), getRequest(), getResourceMethod(), getMarshaledResponse());
118        }
119
120        /**
121         * The type of log event this is.
122         *
123         * @return the log event type
124         */
125        @Nonnull
126        public LogEventType getLogEventType() {
127                return this.logEventType;
128        }
129
130        /**
131         * The message for this log event.
132         *
133         * @return the message
134         */
135        @Nonnull
136        public String getMessage() {
137                return this.message;
138        }
139
140        /**
141         * The throwable for this log event, if available.
142         *
143         * @return the throwable, or {@link Optional#empty()} if not available
144         */
145        @Nonnull
146        public Optional<Throwable> getThrowable() {
147                return Optional.ofNullable(this.throwable);
148        }
149
150        /**
151         * The request associated with this log event, if available.
152         *
153         * @return the request, or {@link Optional#empty()} if not available
154         */
155        @Nonnull
156        public Optional<Request> getRequest() {
157                return Optional.ofNullable(this.request);
158        }
159
160        /**
161         * The <em>Resource Method</em> associated with this log event, if available.
162         *
163         * @return the <em>Resource Method</em>, or {@link Optional#empty()} if not available
164         */
165        @Nonnull
166        public Optional<ResourceMethod> getResourceMethod() {
167                return Optional.ofNullable(this.resourceMethod);
168        }
169
170        /**
171         * The response associated with this log event, if available.
172         *
173         * @return the response, or {@link Optional#empty()} if not available
174         */
175        @Nonnull
176        public Optional<MarshaledResponse> getMarshaledResponse() {
177                return Optional.ofNullable(this.marshaledResponse);
178        }
179
180        /**
181         * Builder used to construct instances of {@link LogEvent} via {@link LogEvent#with(LogEventType, String)}.
182         * <p>
183         * This class is intended for use by a single thread.
184         *
185         * @author <a href="https://www.revetkn.com">Mark Allen</a>
186         */
187        @NotThreadSafe
188        public static final class Builder {
189                @Nonnull
190                private LogEventType logEventType;
191                @Nonnull
192                private String message;
193                @Nullable
194                private Throwable throwable;
195                @Nullable
196                private Request request;
197                @Nullable
198                private ResourceMethod resourceMethod;
199                @Nullable
200                private MarshaledResponse marshaledResponse;
201
202                protected Builder(@Nonnull LogEventType logEventType,
203                                                                                        @Nonnull String message) {
204                        requireNonNull(logEventType);
205                        requireNonNull(message);
206
207                        this.logEventType = logEventType;
208                        this.message = message;
209                }
210
211                @Nonnull
212                public Builder logEventType(@Nonnull LogEventType logEventType) {
213                        requireNonNull(logEventType);
214                        this.logEventType = logEventType;
215                        return this;
216                }
217
218                @Nonnull
219                public Builder message(@Nonnull String message) {
220                        requireNonNull(message);
221                        this.message = message;
222                        return this;
223                }
224
225                @Nonnull
226                public Builder throwable(@Nullable Throwable throwable) {
227                        this.throwable = throwable;
228                        return this;
229                }
230
231                @Nonnull
232                public Builder request(@Nullable Request request) {
233                        this.request = request;
234                        return this;
235                }
236
237                @Nonnull
238                public Builder resourceMethod(@Nullable ResourceMethod resourceMethod) {
239                        this.resourceMethod = resourceMethod;
240                        return this;
241                }
242
243                @Nonnull
244                public Builder marshaledResponse(@Nullable MarshaledResponse marshaledResponse) {
245                        this.marshaledResponse = marshaledResponse;
246                        return this;
247                }
248
249                @Nonnull
250                public LogEvent build() {
251                        return new LogEvent(this);
252                }
253        }
254
255        /**
256         * Builder used to copy instances of {@link LogEvent} via {@link LogEvent#copy()}.
257         * <p>
258         * This class is intended for use by a single thread.
259         *
260         * @author <a href="https://www.revetkn.com">Mark Allen</a>
261         */
262        @NotThreadSafe
263        public static final class Copier {
264                @Nonnull
265                private final Builder builder;
266
267                Copier(@Nonnull LogEvent logEvent) {
268                        requireNonNull(logEvent);
269
270                        this.builder = new Builder(logEvent.getLogEventType(), logEvent.getMessage())
271                                        .throwable(logEvent.getThrowable().orElse(null))
272                                        .request(logEvent.getRequest().orElse(null))
273                                        .resourceMethod(logEvent.getResourceMethod().orElse(null))
274                                        .marshaledResponse(logEvent.getMarshaledResponse().orElse(null));
275                }
276
277                @Nonnull
278                public Copier logEventType(@Nonnull LogEventType logEventType) {
279                        requireNonNull(logEventType);
280                        this.builder.logEventType(logEventType);
281                        return this;
282                }
283
284                @Nonnull
285                public Copier message(@Nonnull String message) {
286                        requireNonNull(message);
287                        this.builder.message(message);
288                        return this;
289                }
290
291                @Nonnull
292                public Copier throwable(@Nullable Throwable throwable) {
293                        this.builder.throwable(throwable);
294                        return this;
295                }
296
297                @Nonnull
298                public Copier request(@Nullable Request request) {
299                        this.builder.request(request);
300                        return this;
301                }
302
303                @Nonnull
304                public Copier resourceMethod(@Nullable ResourceMethod resourceMethod) {
305                        this.builder.resourceMethod(resourceMethod);
306                        return this;
307                }
308
309                @Nonnull
310                public Copier marshaledResponse(@Nullable MarshaledResponse marshaledResponse) {
311                        this.builder.marshaledResponse(marshaledResponse);
312                        return this;
313                }
314
315                @Nonnull
316                public LogEvent finish() {
317                        return this.builder.build();
318                }
319        }
320}