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}