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.core;
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 * 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>.
035 *
036 * @author <a href="https://www.revetkn.com">Mark Allen</a>
037 */
038@ThreadSafe
039public class LogEvent {
040        @Nonnull
041        private final LogEventType logEventType;
042        @Nonnull
043        private final String message;
044        @Nullable
045        private final Throwable throwable;
046        @Nullable
047        private final Request request;
048        @Nullable
049        private final ResourceMethod resourceMethod;
050        @Nullable
051        private final MarshaledResponse marshaledResponse;
052
053        /**
054         * Acquires a builder for {@link LogEvent} instances.
055         *
056         * @param logEventType what kind of log event this is
057         * @param message      the message for this log event
058         * @return the builder
059         */
060        @Nonnull
061        public static Builder with(@Nonnull LogEventType logEventType,
062                                                                                                                 @Nonnull String message) {
063                requireNonNull(logEventType);
064                requireNonNull(message);
065
066                return new Builder(logEventType, message);
067        }
068
069        /**
070         * Vends a mutable copier seeded with this instance's data, suitable for building new instances.
071         *
072         * @return a copier for this instance
073         */
074        @Nonnull
075        public Copier copy() {
076                return new Copier(this);
077        }
078
079        protected LogEvent(@Nonnull Builder builder) {
080                requireNonNull(builder);
081
082                this.logEventType = builder.logEventType;
083                this.message = builder.message;
084                this.throwable = builder.throwable;
085                this.request = builder.request;
086                this.resourceMethod = builder.resourceMethod;
087                this.marshaledResponse = builder.marshaledResponse;
088        }
089
090        @Override
091        @Nonnull
092        public String toString() {
093                return format("%s{logEventType=%s, message=%s, throwable=%s}", getClass().getSimpleName(),
094                                getLogEventType(), getMessage(), getThrowable().orElse(null));
095        }
096
097        @Override
098        public boolean equals(@Nullable Object object) {
099                if (this == object)
100                        return true;
101
102                if (!(object instanceof LogEvent logEvent))
103                        return false;
104
105                return Objects.equals(getLogEventType(), logEvent.getLogEventType())
106                                && Objects.equals(getMessage(), logEvent.getMessage())
107                                && Objects.equals(getThrowable(), logEvent.getThrowable())
108                                && Objects.equals(getRequest(), logEvent.getRequest())
109                                && Objects.equals(getResourceMethod(), logEvent.getResourceMethod())
110                                && Objects.equals(getMarshaledResponse(), logEvent.getMarshaledResponse());
111        }
112
113        @Override
114        public int hashCode() {
115                return Objects.hash(getLogEventType(), getMessage(), getThrowable(), getRequest(), getResourceMethod(), getMarshaledResponse());
116        }
117
118        /**
119         * The type of log event this is.
120         *
121         * @return the log event type
122         */
123        @Nonnull
124        public LogEventType getLogEventType() {
125                return this.logEventType;
126        }
127
128        /**
129         * The message for this log event.
130         *
131         * @return the message
132         */
133        @Nonnull
134        public String getMessage() {
135                return this.message;
136        }
137
138        /**
139         * The throwable for this log event, if available.
140         *
141         * @return the throwable, or {@link Optional#empty()} if not available
142         */
143        @Nonnull
144        public Optional<Throwable> getThrowable() {
145                return Optional.ofNullable(this.throwable);
146        }
147
148        /**
149         * The request associated with this log event, if available.
150         *
151         * @return the request, or {@link Optional#empty()} if not available
152         */
153        @Nonnull
154        public Optional<Request> getRequest() {
155                return Optional.ofNullable(this.request);
156        }
157
158        /**
159         * The <em>Resource Method</em> associated with this log event, if available.
160         *
161         * @return the <em>Resource Method</em>, or {@link Optional#empty()} if not available
162         */
163        @Nonnull
164        public Optional<ResourceMethod> getResourceMethod() {
165                return Optional.ofNullable(this.resourceMethod);
166        }
167
168        /**
169         * The response associated with this log event, if available.
170         *
171         * @return the response, or {@link Optional#empty()} if not available
172         */
173        @Nonnull
174        public Optional<MarshaledResponse> getMarshaledResponse() {
175                return Optional.ofNullable(this.marshaledResponse);
176        }
177
178        /**
179         * Builder used to construct instances of {@link LogEvent} via {@link LogEvent#with(LogEventType, String)}.
180         * <p>
181         * This class is intended for use by a single thread.
182         *
183         * @author <a href="https://www.revetkn.com">Mark Allen</a>
184         */
185        @NotThreadSafe
186        public static class Builder {
187                @Nonnull
188                private LogEventType logEventType;
189                @Nonnull
190                private String message;
191                @Nullable
192                private Throwable throwable;
193                @Nullable
194                private Request request;
195                @Nullable
196                private ResourceMethod resourceMethod;
197                @Nullable
198                private MarshaledResponse marshaledResponse;
199
200                protected Builder(@Nonnull LogEventType logEventType,
201                                                                                        @Nonnull String message) {
202                        requireNonNull(logEventType);
203                        requireNonNull(message);
204
205                        this.logEventType = logEventType;
206                        this.message = message;
207                }
208
209                @Nonnull
210                public Builder logEventType(@Nonnull LogEventType logEventType) {
211                        requireNonNull(logEventType);
212                        this.logEventType = logEventType;
213                        return this;
214                }
215
216                @Nonnull
217                public Builder message(@Nonnull String message) {
218                        requireNonNull(message);
219                        this.message = message;
220                        return this;
221                }
222
223                @Nonnull
224                public Builder throwable(@Nullable Throwable throwable) {
225                        this.throwable = throwable;
226                        return this;
227                }
228
229                @Nonnull
230                public Builder request(@Nullable Request request) {
231                        this.request = request;
232                        return this;
233                }
234
235                @Nonnull
236                public Builder resourceMethod(@Nullable ResourceMethod resourceMethod) {
237                        this.resourceMethod = resourceMethod;
238                        return this;
239                }
240
241                @Nonnull
242                public Builder marshaledResponse(@Nullable MarshaledResponse marshaledResponse) {
243                        this.marshaledResponse = marshaledResponse;
244                        return this;
245                }
246
247                @Nonnull
248                public LogEvent build() {
249                        return new LogEvent(this);
250                }
251        }
252
253        /**
254         * Builder used to copy instances of {@link LogEvent} via {@link LogEvent#copy()}.
255         * <p>
256         * This class is intended for use by a single thread.
257         *
258         * @author <a href="https://www.revetkn.com">Mark Allen</a>
259         */
260        @NotThreadSafe
261        public static class Copier {
262                @Nonnull
263                private final Builder builder;
264
265                Copier(@Nonnull LogEvent logEvent) {
266                        requireNonNull(logEvent);
267
268                        this.builder = new Builder(logEvent.getLogEventType(), logEvent.getMessage())
269                                        .throwable(logEvent.getThrowable().orElse(null))
270                                        .request(logEvent.getRequest().orElse(null))
271                                        .resourceMethod(logEvent.getResourceMethod().orElse(null))
272                                        .marshaledResponse(logEvent.getMarshaledResponse().orElse(null));
273                }
274
275                @Nonnull
276                public Copier logEventType(@Nonnull LogEventType logEventType) {
277                        requireNonNull(logEventType);
278                        this.builder.logEventType(logEventType);
279                        return this;
280                }
281
282                @Nonnull
283                public Copier message(@Nonnull String message) {
284                        requireNonNull(message);
285                        this.builder.message(message);
286                        return this;
287                }
288
289                @Nonnull
290                public Copier throwable(@Nullable Throwable throwable) {
291                        this.builder.throwable(throwable);
292                        return this;
293                }
294
295                @Nonnull
296                public Copier request(@Nullable Request request) {
297                        this.builder.request(request);
298                        return this;
299                }
300
301                @Nonnull
302                public Copier resourceMethod(@Nullable ResourceMethod resourceMethod) {
303                        this.builder.resourceMethod(resourceMethod);
304                        return this;
305                }
306
307                @Nonnull
308                public Copier marshaledResponse(@Nullable MarshaledResponse marshaledResponse) {
309                        this.builder.marshaledResponse(marshaledResponse);
310                        return this;
311                }
312
313                @Nonnull
314                public LogEvent finish() {
315                        return this.builder.build();
316                }
317        }
318}