001/*
002 * Copyright 2022-2026 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.otel;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import javax.annotation.concurrent.ThreadSafe;
024import java.util.Objects;
025
026import static java.lang.String.format;
027import static java.util.Objects.requireNonNull;
028
029/**
030 * Controls which Soklet lifecycle events are converted into OpenTelemetry spans or span events.
031 *
032 * @author <a href="https://www.revetkn.com">Mark Allen</a>
033 */
034@ThreadSafe
035public final class SpanPolicy {
036        @NonNull
037        private static final SpanPolicy DEFAULT_INSTANCE;
038
039        static {
040                DEFAULT_INSTANCE = builder().build();
041        }
042
043        @NonNull
044        private final Boolean recordHttpRequestSpans;
045        @NonNull
046        private final Boolean recordStreamingResponseSpans;
047        @NonNull
048        private final Boolean recordSseConnectionSpans;
049        @NonNull
050        private final Boolean recordSseWriteEvents;
051        @NonNull
052        private final Boolean recordMcpRequestSpans;
053        @NonNull
054        private final Boolean recordMcpSessionEvents;
055        @NonNull
056        private final Boolean recordMcpSseStreamSpans;
057        @NonNull
058        private final Boolean recordClientAddress;
059        @NonNull
060        private final Boolean recordRequestId;
061
062        @NonNull
063        public static Builder builder() {
064                return new Builder();
065        }
066
067        @NonNull
068        public static SpanPolicy defaultInstance() {
069                return DEFAULT_INSTANCE;
070        }
071
072        private SpanPolicy(@NonNull Builder builder) {
073                requireNonNull(builder);
074
075                this.recordHttpRequestSpans = builder.recordHttpRequestSpans;
076                this.recordStreamingResponseSpans = builder.recordStreamingResponseSpans;
077                this.recordSseConnectionSpans = builder.recordSseConnectionSpans;
078                this.recordSseWriteEvents = builder.recordSseWriteEvents;
079                this.recordMcpRequestSpans = builder.recordMcpRequestSpans;
080                this.recordMcpSessionEvents = builder.recordMcpSessionEvents;
081                this.recordMcpSseStreamSpans = builder.recordMcpSseStreamSpans;
082                this.recordClientAddress = builder.recordClientAddress;
083                this.recordRequestId = builder.recordRequestId;
084        }
085
086        @NonNull
087        public Boolean recordHttpRequestSpans() {
088                return this.recordHttpRequestSpans;
089        }
090
091        @NonNull
092        public Boolean recordStreamingResponseSpans() {
093                return this.recordStreamingResponseSpans;
094        }
095
096        @NonNull
097        public Boolean recordSseConnectionSpans() {
098                return this.recordSseConnectionSpans;
099        }
100
101        @NonNull
102        public Boolean recordSseWriteEvents() {
103                return this.recordSseWriteEvents;
104        }
105
106        @NonNull
107        public Boolean recordMcpRequestSpans() {
108                return this.recordMcpRequestSpans;
109        }
110
111        @NonNull
112        public Boolean recordMcpSessionEvents() {
113                return this.recordMcpSessionEvents;
114        }
115
116        @NonNull
117        public Boolean recordMcpSseStreamSpans() {
118                return this.recordMcpSseStreamSpans;
119        }
120
121        @NonNull
122        public Boolean recordClientAddress() {
123                return this.recordClientAddress;
124        }
125
126        @NonNull
127        public Boolean recordRequestId() {
128                return this.recordRequestId;
129        }
130
131        @Override
132        @NonNull
133        public String toString() {
134                return format("%s{recordHttpRequestSpans=%s, recordStreamingResponseSpans=%s, recordSseConnectionSpans=%s, recordSseWriteEvents=%s, recordMcpRequestSpans=%s, recordMcpSessionEvents=%s, recordMcpSseStreamSpans=%s, recordClientAddress=%s, recordRequestId=%s}",
135                                getClass().getSimpleName(), recordHttpRequestSpans(), recordStreamingResponseSpans(), recordSseConnectionSpans(),
136                                recordSseWriteEvents(), recordMcpRequestSpans(), recordMcpSessionEvents(),
137                                recordMcpSseStreamSpans(), recordClientAddress(), recordRequestId());
138        }
139
140        @Override
141        public boolean equals(@Nullable Object object) {
142                if (this == object)
143                        return true;
144
145                if (!(object instanceof SpanPolicy spanPolicy))
146                        return false;
147
148                return Objects.equals(recordHttpRequestSpans(), spanPolicy.recordHttpRequestSpans())
149                                && Objects.equals(recordStreamingResponseSpans(), spanPolicy.recordStreamingResponseSpans())
150                                && Objects.equals(recordSseConnectionSpans(), spanPolicy.recordSseConnectionSpans())
151                                && Objects.equals(recordSseWriteEvents(), spanPolicy.recordSseWriteEvents())
152                                && Objects.equals(recordMcpRequestSpans(), spanPolicy.recordMcpRequestSpans())
153                                && Objects.equals(recordMcpSessionEvents(), spanPolicy.recordMcpSessionEvents())
154                                && Objects.equals(recordMcpSseStreamSpans(), spanPolicy.recordMcpSseStreamSpans())
155                                && Objects.equals(recordClientAddress(), spanPolicy.recordClientAddress())
156                                && Objects.equals(recordRequestId(), spanPolicy.recordRequestId());
157        }
158
159        @Override
160        public int hashCode() {
161                return Objects.hash(recordHttpRequestSpans(), recordStreamingResponseSpans(), recordSseConnectionSpans(),
162                                recordSseWriteEvents(), recordMcpRequestSpans(), recordMcpSessionEvents(),
163                                recordMcpSseStreamSpans(), recordClientAddress(), recordRequestId());
164        }
165
166        /**
167         * Mutable builder for {@link SpanPolicy} instances.
168         */
169        @NotThreadSafe
170        public static final class Builder {
171                @NonNull
172                private Boolean recordHttpRequestSpans;
173                @NonNull
174                private Boolean recordStreamingResponseSpans;
175                @NonNull
176                private Boolean recordSseConnectionSpans;
177                @NonNull
178                private Boolean recordSseWriteEvents;
179                @NonNull
180                private Boolean recordMcpRequestSpans;
181                @NonNull
182                private Boolean recordMcpSessionEvents;
183                @NonNull
184                private Boolean recordMcpSseStreamSpans;
185                @NonNull
186                private Boolean recordClientAddress;
187                @NonNull
188                private Boolean recordRequestId;
189
190                private Builder() {
191                        this.recordHttpRequestSpans = true;
192                        this.recordStreamingResponseSpans = true;
193                        this.recordSseConnectionSpans = true;
194                        this.recordSseWriteEvents = false;
195                        this.recordMcpRequestSpans = true;
196                        this.recordMcpSessionEvents = true;
197                        this.recordMcpSseStreamSpans = true;
198                        this.recordClientAddress = false;
199                        this.recordRequestId = false;
200                }
201
202                @NonNull
203                public Builder recordHttpRequestSpans(@NonNull Boolean recordHttpRequestSpans) {
204                        this.recordHttpRequestSpans = requireNonNull(recordHttpRequestSpans);
205                        return this;
206                }
207
208                @NonNull
209                public Builder recordStreamingResponseSpans(@NonNull Boolean recordStreamingResponseSpans) {
210                        this.recordStreamingResponseSpans = requireNonNull(recordStreamingResponseSpans);
211                        return this;
212                }
213
214                @NonNull
215                public Builder recordSseConnectionSpans(@NonNull Boolean recordSseConnectionSpans) {
216                        this.recordSseConnectionSpans = requireNonNull(recordSseConnectionSpans);
217                        return this;
218                }
219
220                @NonNull
221                public Builder recordSseWriteEvents(@NonNull Boolean recordSseWriteEvents) {
222                        this.recordSseWriteEvents = requireNonNull(recordSseWriteEvents);
223                        return this;
224                }
225
226                @NonNull
227                public Builder recordMcpRequestSpans(@NonNull Boolean recordMcpRequestSpans) {
228                        this.recordMcpRequestSpans = requireNonNull(recordMcpRequestSpans);
229                        return this;
230                }
231
232                @NonNull
233                public Builder recordMcpSessionEvents(@NonNull Boolean recordMcpSessionEvents) {
234                        this.recordMcpSessionEvents = requireNonNull(recordMcpSessionEvents);
235                        return this;
236                }
237
238                @NonNull
239                public Builder recordMcpSseStreamSpans(@NonNull Boolean recordMcpSseStreamSpans) {
240                        this.recordMcpSseStreamSpans = requireNonNull(recordMcpSseStreamSpans);
241                        return this;
242                }
243
244                @NonNull
245                public Builder recordClientAddress(@NonNull Boolean recordClientAddress) {
246                        this.recordClientAddress = requireNonNull(recordClientAddress);
247                        return this;
248                }
249
250                @NonNull
251                public Builder recordRequestId(@NonNull Boolean recordRequestId) {
252                        this.recordRequestId = requireNonNull(recordRequestId);
253                        return this;
254                }
255
256                @NonNull
257                public SpanPolicy build() {
258                        return new SpanPolicy(this);
259                }
260        }
261}