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}