diff --git a/exceptiontrace/exceptiontrace-collector/pom.xml b/exceptiontrace/exceptiontrace-collector/pom.xml new file mode 100644 index 0000000000000..53fadedc39dc5 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/pom.xml @@ -0,0 +1,46 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.5.1-SNAPSHOT + + 4.0.0 + + pinpoint-exceptiontrace-collector + + + 11 + ${env.JAVA_11_HOME} + + + + + com.navercorp.pinpoint + pinpoint-pinot-kafka + + + com.navercorp.pinpoint + pinpoint-exceptiontrace-common + + + com.navercorp.pinpoint + pinpoint-metric + + + com.navercorp.pinpoint + pinpoint-commons-server + + + org.springframework.kafka + spring-kafka + 2.9.4 + + + com.navercorp.pinpoint + pinpoint-collector + + + \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorConfig.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorConfig.java new file mode 100644 index 0000000000000..478ca388aa5c1 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.collector; + +import com.navercorp.pinpoint.exceptiontrace.collector.config.ExceptionMetricKafkaConfiguration; +import com.navercorp.pinpoint.pinot.config.PinotConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.PropertySource; + +/** + * @author intr3p1d + */ +@Profile("exception") +@Configuration +@Import({PinotConfiguration.class, ExceptionMetricKafkaConfiguration.class}) +@ComponentScan({"com.navercorp.pinpoint.exceptiontrace.collector.service", "com.navercorp.pinpoint.exceptiontrace.collector.dao"}) +@PropertySource({ExceptionTraceCollectorConfig.KAFKA_TOPIC_PROPERTIES}) +public class ExceptionTraceCollectorConfig { + public static final String KAFKA_TOPIC_PROPERTIES = "classpath:kafka-topic.properties"; +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorPropertySources.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorPropertySources.java new file mode 100644 index 0000000000000..423bbdb300ddb --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorPropertySources.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.collector; + +/** + * @author intr3p1d + */ +public class ExceptionTraceCollectorPropertySources { +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/config/ExceptionMetricKafkaConfiguration.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/config/ExceptionMetricKafkaConfiguration.java new file mode 100644 index 0000000000000..2efdba2d37dfb --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/config/ExceptionMetricKafkaConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.collector.config; + +import com.navercorp.pinpoint.exceptiontrace.collector.model.SpanEventExceptionVo; +import com.navercorp.pinpoint.pinot.kafka.KafkaConfiguration; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +/** + * @author intr3p1d + */ +@Configuration +@Import({KafkaConfiguration.class}) +public class ExceptionMetricKafkaConfiguration { + + @Bean + public KafkaTemplate kafkaSpanEventExceptionTemplate(@Qualifier("kafkaProducerFactory") ProducerFactory producerFactory) { + return new KafkaTemplate<>(producerFactory); + } +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/ExceptionTraceDao.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/ExceptionTraceDao.java new file mode 100644 index 0000000000000..8a21bdae21275 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/ExceptionTraceDao.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.collector.dao; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; + +import java.util.List; + +/** + * @author intr3p1d + */ +public interface ExceptionTraceDao { + void insert(List spanEventException); +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/PinotExceptionTraceDao.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/PinotExceptionTraceDao.java new file mode 100644 index 0000000000000..0f23adae6adb8 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/PinotExceptionTraceDao.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.collector.dao; + +import com.navercorp.pinpoint.exceptiontrace.collector.model.SpanEventExceptionVo; +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.common.util.StringPrecondition; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +@Repository +public class PinotExceptionTraceDao implements ExceptionTraceDao { + private final Logger logger = LogManager.getLogger(this.getClass()); + + private final KafkaTemplate kafkaSpanEventExceptionTemplate; + + private final String topic; + + public PinotExceptionTraceDao(@Qualifier("kafkaSpanEventExceptionTemplate") KafkaTemplate kafkaSpanEventExceptionTemplate, + @Value("${kafka.exception.topic}") String topic) { + this.kafkaSpanEventExceptionTemplate = Objects.requireNonNull(kafkaSpanEventExceptionTemplate, "kafkaSpanEventExceptionTemplate"); + this.topic = StringPrecondition.requireHasLength(topic, "topic"); + } + + @Override + public void insert(List spanEventExceptions) { + Objects.requireNonNull(spanEventExceptions); + logger.info("Pinot data insert: {}", spanEventExceptions.toString()); + + for (SpanEventException spanEventException : spanEventExceptions) { + this.kafkaSpanEventExceptionTemplate.send( + topic, SpanEventExceptionVo.valueOf(spanEventException) + ); + } + } +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/model/SpanEventExceptionVo.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/model/SpanEventExceptionVo.java new file mode 100644 index 0000000000000..2b4bc48714952 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/model/SpanEventExceptionVo.java @@ -0,0 +1,163 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.collector.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author intr3p1d + */ +public class SpanEventExceptionVo { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final long timestamp; + + private final String transactionId; + private final long spanId; + private final long exceptionId; + + private final String applicationServiceType; + private final String applicationName; + private final String agentId; + + private final String errorClassName; + private final String errorMessage; + private final int exceptionDepth; + private final List stackTrace; + private final String stackTraceHash; + + + public SpanEventExceptionVo(long timestamp, String transactionId, long spanId, long exceptionId, String applicationServiceType, String applicationName, String agentId, String errorClassName, String errorMessage, int exceptionDepth, List stackTrace, String stackTraceHash) { + this.timestamp = timestamp; + this.transactionId = transactionId; + this.spanId = spanId; + this.exceptionId = exceptionId; + this.applicationServiceType = applicationServiceType; + this.applicationName = applicationName; + this.agentId = agentId; + this.errorClassName = errorClassName; + this.errorMessage = errorMessage; + this.exceptionDepth = exceptionDepth; + this.stackTrace = stackTrace; + this.stackTraceHash = stackTraceHash; + } + + + public static SpanEventExceptionVo valueOf(SpanEventException spanEventException) { + return new SpanEventExceptionVo( + spanEventException.getTimestamp(), + spanEventException.getTransactionId(), + spanEventException.getSpanId(), + spanEventException.getExceptionId(), + spanEventException.getApplicationServiceType(), + spanEventException.getApplicationName(), + spanEventException.getAgentId(), + spanEventException.getErrorClassName(), + spanEventException.getErrorMessage(), + spanEventException.getExceptionDepth(), + toJsonString(spanEventException.getStackTrace()), + spanEventException.getStackTraceHash() + ); + } + + + private static List toJsonString(List stackTrace) { + List strings = new ArrayList<>(); + stackTrace.forEach( + (StackTraceElementWrapper s) -> { + try { + strings.add(OBJECT_MAPPER.writeValueAsString(s)); + } catch (JsonProcessingException ignored) { + // do nothing + } + } + ); + return strings; + } + + public long getTimestamp() { + return timestamp; + } + + public String getTransactionId() { + return transactionId; + } + + public long getSpanId() { + return spanId; + } + + public long getExceptionId() { + return exceptionId; + } + + public String getApplicationServiceType() { + return applicationServiceType; + } + + public String getApplicationName() { + return applicationName; + } + + public String getAgentId() { + return agentId; + } + + public String getErrorClassName() { + return errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public int getExceptionDepth() { + return exceptionDepth; + } + + public List getStackTrace() { + return stackTrace; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + @Override + public String toString() { + return "SpanEventExceptionVo{" + + "timestamp=" + timestamp + + ", transactionId='" + transactionId + '\'' + + ", spanId=" + spanId + + ", exceptionId=" + exceptionId + + ", applicationServiceType='" + applicationServiceType + '\'' + + ", applicationName='" + applicationName + '\'' + + ", agentId='" + agentId + '\'' + + ", errorClassName='" + errorClassName + '\'' + + ", errorMessage='" + errorMessage + '\'' + + ", exceptionDepth=" + exceptionDepth + + ", stackTrace=" + stackTrace + + ", stackTraceHash='" + stackTraceHash + '\'' + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java new file mode 100644 index 0000000000000..c4ee8bd8b5bbd --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.collector.service; + +import com.navercorp.pinpoint.collector.service.ExceptionTraceService; +import com.navercorp.pinpoint.common.profiler.util.TransactionId; +import com.navercorp.pinpoint.common.profiler.util.TransactionIdUtils; +import com.navercorp.pinpoint.common.server.bo.exception.ExceptionWrapperBo; +import com.navercorp.pinpoint.common.server.bo.exception.SpanEventExceptionBo; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.exceptiontrace.collector.dao.ExceptionTraceDao; +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +@Service +@Profile("metric") +public class PinotExceptionTraceService implements ExceptionTraceService { + private final ExceptionTraceDao exceptionTraceDao; + + public PinotExceptionTraceService(ExceptionTraceDao exceptionTraceDao) { + this.exceptionTraceDao = Objects.requireNonNull(exceptionTraceDao, "exceptionTraceDao"); + } + + @Override + public void save(List spanEventExceptionBoList, ServiceType applicationServiceType, String applicationId, String agentId, TransactionId transactionId, long spanId) { + List spanEventExceptions = new ArrayList<>(); + for (SpanEventExceptionBo spanEventExceptionBo : spanEventExceptionBoList) { + spanEventExceptions.addAll(toSpanEventExceptions(spanEventExceptionBo, applicationServiceType, applicationId, agentId, transactionId, spanId)); + } + exceptionTraceDao.insert(spanEventExceptions); + } + + private static List toSpanEventExceptions( + SpanEventExceptionBo spanEventExceptionBo, + ServiceType applicationServiceType, String applicationId, String agentId, + TransactionId transactionId, long spanId) { + List spanEventExceptions = new ArrayList<>(); + List exceptions = spanEventExceptionBo.getExceptionWrappers(); + for (int i = 0; i < exceptions.size(); i++) { + spanEventExceptions.add( + toSpanEventException( + exceptions.get(i), i, + applicationServiceType, applicationId, agentId, + transactionId, spanId, + spanEventExceptionBo.getExceptionId(), + spanEventExceptionBo.getStartTime() + ) + ); + } + return spanEventExceptions; + } + + private static SpanEventException toSpanEventException( + ExceptionWrapperBo exceptionWrapperBo, + int exceptionDepth, + ServiceType applicationServiceType, String applicationId, String agentId, + TransactionId transactionId, long spanId, + long exceptionId, + long startTime) { + return SpanEventException.valueOf( + startTime, + transactionIdToString(transactionId), + spanId, + exceptionId, + applicationServiceType.getName(), + applicationId, + agentId, + exceptionWrapperBo.getExceptionClassName(), + exceptionWrapperBo.getExceptionMessage(), + exceptionDepth, + exceptionWrapperBo.getStackTraceElements() + ); + } + + private static String transactionIdToString(TransactionId transactionId) { + return TransactionIdUtils.formatString(transactionId); + } + +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/resources/kafka-topic.properties b/exceptiontrace/exceptiontrace-collector/src/main/resources/kafka-topic.properties new file mode 100644 index 0000000000000..e99d3db765d76 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/resources/kafka-topic.properties @@ -0,0 +1 @@ +kafka.exception.topic=exception-trace \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-common/pom.xml b/exceptiontrace/exceptiontrace-common/pom.xml new file mode 100644 index 0000000000000..19e23083f2ea7 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/pom.xml @@ -0,0 +1,40 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.5.1-SNAPSHOT + + 4.0.0 + + pinpoint-exceptiontrace-common + + + 11 + ${env.JAVA_11_HOME} + + + + + com.fasterxml.jackson.core + jackson-annotations + compile + + + org.springframework + spring-core + + + commons-logging + commons-logging + + + + + com.navercorp.pinpoint + pinpoint-commons-server + + + \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/SpanEventException.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/SpanEventException.java new file mode 100644 index 0000000000000..080ff2f84c6f9 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/SpanEventException.java @@ -0,0 +1,224 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.common.model; + +import com.navercorp.pinpoint.common.server.bo.exception.StackTraceElementWrapperBo; +import com.navercorp.pinpoint.exceptiontrace.common.util.HashUtils; +import com.navercorp.pinpoint.exceptiontrace.common.util.StringPrecondition; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +public class SpanEventException { + + private final long timestamp; + + private final String transactionId; + private final long spanId; + private final long exceptionId; + + private final String applicationServiceType; + private final String applicationName; + private final String agentId; + + private final String errorClassName; + private final String errorMessage; + private final int exceptionDepth; + private final List stackTrace; + + private final String stackTraceHash; + + public SpanEventException( + long timestamp, + String transactionId, + long spanId, + long exceptionId, + String applicationServiceType, + String applicationName, + String agentId, + String errorClassName, + String errorMessage, + int exceptionDepth, + List stackTrace, + String stackTraceHash + ) { + this.timestamp = timestamp; + this.transactionId = StringPrecondition.requireHasLength(transactionId, "transactionId"); + this.spanId = spanId; + this.exceptionId = exceptionId; + this.applicationServiceType = StringPrecondition.requireHasLength(applicationServiceType, "applicationServiceType"); + this.applicationName = StringPrecondition.requireHasLength(applicationName, "applicationName"); + this.agentId = StringPrecondition.requireHasLength(agentId, "agentId"); + this.errorClassName = StringPrecondition.requireHasLength(errorClassName, "errorClassName"); + this.errorMessage = StringPrecondition.requireHasLength(errorMessage, "errorMessage"); + this.exceptionDepth = exceptionDepth; + this.stackTrace = stackTrace; + this.stackTraceHash = stackTraceHash; + } + + public SpanEventException( + long timestamp, + String transactionId, + long spanId, + long exceptionId, + String applicationServiceType, + String applicationName, + String agentId, + String errorClassName, + String errorMessage, + int exceptionDepth, + String stackTraceHash + ) { + this.timestamp = timestamp; + this.transactionId = StringPrecondition.requireHasLength(transactionId, "transactionId"); + this.spanId = spanId; + this.exceptionId = exceptionId; + this.applicationServiceType = StringPrecondition.requireHasLength(applicationServiceType, "applicationServiceType"); + this.applicationName = StringPrecondition.requireHasLength(applicationName, "applicationName"); + this.agentId = StringPrecondition.requireHasLength(agentId, "agentId"); + this.errorClassName = StringPrecondition.requireHasLength(errorClassName, "errorClassName"); + this.errorMessage = StringPrecondition.requireHasLength(errorMessage, "errorMessage"); + this.exceptionDepth = exceptionDepth; + this.stackTrace = Collections.emptyList(); + this.stackTraceHash = stackTraceHash; + } + + public static SpanEventException valueOf(long timestamp, String transactionId, long spanId, long exceptionId, + String applicationServiceType, String applicationName, String agentId, + String errorClassName, String errorMessage, int exceptionDepth, + List stackTraceElementWrapperBos) { + List wrappers = toStackTrace(stackTraceElementWrapperBos); + + return new SpanEventException( + timestamp, + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + errorClassName, + errorMessage, + exceptionDepth, + wrappers, + toStackTraceHash(wrappers) + ); + } + + public static List toStackTrace(List stackTraceElementWrapperBos) { + return stackTraceElementWrapperBos.stream().map( + (StackTraceElementWrapperBo s) -> new StackTraceElementWrapper(s.getClassName(), s.getFileName(), s.getLineNumber(), s.getMethodName()) + ).collect(Collectors.toList()); + } + + public static String toStackTraceHash(List stackTraceElementWrappers) { + return HashUtils.wrappersToHashString(stackTraceElementWrappers); + } + + public long getTimestamp() { + return timestamp; + } + + public String getTransactionId() { + return transactionId; + } + + public long getSpanId() { + return spanId; + } + + public long getExceptionId() { + return exceptionId; + } + + public String getApplicationServiceType() { + return applicationServiceType; + } + + public String getApplicationName() { + return applicationName; + } + + public String getAgentId() { + return agentId; + } + + public String getErrorClassName() { + return errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public int getExceptionDepth() { + return exceptionDepth; + } + + public List getStackTrace() { + return stackTrace; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SpanEventException)) return false; + + SpanEventException that = (SpanEventException) o; + + if (timestamp != that.timestamp) return false; + if (spanId != that.spanId) return false; + if (!transactionId.equals(that.transactionId)) return false; + if (!applicationServiceType.equals(that.applicationServiceType)) return false; + if (!applicationName.equals(that.applicationName)) return false; + if (!agentId.equals(that.agentId)) return false; + if (!errorClassName.equals(that.errorClassName)) return false; + if (!errorMessage.equals(that.errorMessage)) return false; + return stackTrace.equals(that.stackTrace); + } + + @Override + public int hashCode() { + return Objects.hash(applicationName, agentId, errorClassName, errorMessage, stackTrace); + } + + @Override + public String toString() { + return "SpanEventException{" + + "timestamp=" + timestamp + + ", transactionId='" + transactionId + '\'' + + ", spanId=" + spanId + + ", applicationServiceType='" + applicationServiceType + '\'' + + ", applicationName='" + applicationName + '\'' + + ", agentId='" + agentId + '\'' + + ", errorClassName='" + errorClassName + '\'' + + ", errorMessage='" + errorMessage + '\'' + + ", exceptionDepth=" + exceptionDepth + + ", stackTrace=" + stackTrace + + ", stackTraceHash='" + stackTraceHash + '\'' + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/StackTraceElementWrapper.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/StackTraceElementWrapper.java new file mode 100644 index 0000000000000..17bd1a6375b9f --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/StackTraceElementWrapper.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.common.model; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +/** + * @author intr3p1d + */ +@JsonAutoDetect +public class StackTraceElementWrapper { + private String className; + private String fileName; + private int lineNumber; + private String methodName; + + public StackTraceElementWrapper() { + } + + public StackTraceElementWrapper(@JsonProperty("className") String className, + @JsonProperty("fileName") String fileName, + @JsonProperty("lineNumber") int lineNumber, + @JsonProperty("methodName") String methodName) { + this.className = Objects.requireNonNull(className, "className"); + this.fileName = Objects.requireNonNull(fileName, "fileName"); + this.lineNumber = lineNumber; + this.methodName = Objects.requireNonNull(methodName, "methodName"); + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof StackTraceElementWrapper)) return false; + + StackTraceElementWrapper that = (StackTraceElementWrapper) o; + + if (lineNumber != that.lineNumber) return false; + if (!className.equals(that.className)) return false; + if (!fileName.equals(that.fileName)) return false; + return methodName.equals(that.methodName); + } + + @Override + public int hashCode() { + int result = className.hashCode(); + result = 31 * result + fileName.hashCode(); + result = 31 * result + lineNumber; + result = 31 * result + methodName.hashCode(); + return result; + } + + @Override + public String toString() { + return "StackTraceElementWrapper{" + + "className='" + className + '\'' + + ", fileName='" + fileName + '\'' + + ", lineNumber=" + lineNumber + + ", methodName='" + methodName + '\'' + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/HashUtils.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/HashUtils.java new file mode 100644 index 0000000000000..48589fe418e4f --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/HashUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.common.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.hadoop.hbase.shaded.org.apache.commons.codec.binary.Hex; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + * @author intr3p1d + */ +public class HashUtils { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + public static String wrappersToHashString(List objects) { + String s = objectsToJsonString(objects); + return toHexString( + toHashBytes(s) + ); + } + + private static String objectsToJsonString(List objects) { + try { + return OBJECT_MAPPER.writeValueAsString(objects); + } catch (JsonProcessingException ignored) { + // do nothing + } + return ""; + } + + private static byte[] toHashBytes(String string) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest( + string.getBytes(StandardCharsets.UTF_8) + ); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + + private static String toHexString(byte[] bytes) { + return Hex.encodeHexString(bytes); + } +} diff --git a/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/StringPrecondition.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/StringPrecondition.java new file mode 100644 index 0000000000000..3eb64d35fd791 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/StringPrecondition.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.common.util; + +import org.springframework.util.StringUtils; + +public final class StringPrecondition { + private StringPrecondition() { + } + + public static String requireHasLength(String str, String variableName) { + if (StringUtils.hasLength(str)) { + return str; + } + throw new IllegalArgumentException(variableName + " must not be empty"); + } +} diff --git a/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json new file mode 100644 index 0000000000000..bb4716a2f9ae8 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json @@ -0,0 +1,58 @@ +{ + "schemaName": "exceptionTrace", + "dimensionFieldSpecs": [ + { + "name": "transactionId", + "dataType": "STRING" + }, + { + "name": "spanId", + "dataType": "LONG" + }, + { + "name": "exceptionId", + "dataType": "LONG" + }, + { + "name": "applicationServiceType", + "dataType": "STRING" + }, + { + "name": "applicationName", + "dataType": "STRING" + }, + { + "name": "agentId", + "dataType": "STRING" + }, + { + "name": "errorClassName", + "dataType": "STRING" + }, + { + "name": "errorMessage", + "dataType": "STRING" + }, + { + "name": "exceptionDepth", + "dataType": "INT" + }, + { + "name": "stackTrace", + "dataType": "STRING", + "singleValueField": false + }, + { + "name": "stackTraceHash", + "dataType": "BYTES" + } + ], + "dateTimeFieldSpecs": [ + { + "name": "timestamp", + "dataType": "TIMESTAMP", + "format": "1:MILLISECONDS:EPOCH", + "granularity": "1:MILLISECONDS" + } + ] +} \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-table.json b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-table.json new file mode 100644 index 0000000000000..77d203ccc8280 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-table.json @@ -0,0 +1,55 @@ +{ + "REALTIME": { + "tableName": "exceptionTrace_REALTIME", + "tableType": "REALTIME", + "segmentsConfig": { + "schemaName": "exceptionTrace", + "replication": "1", + "replicasPerPartition": "1", + "timeColumnName": "timestamp", + "minimizeDataMovement": false + }, + "tenants": { + "broker": "DefaultTenant", + "server": "DefaultTenant", + "tagOverrideConfig": {} + }, + "tableIndexConfig": { + "invertedIndexColumns": [], + "noDictionaryColumns": [], + "streamConfigs": { + "streamType": "kafka", + "stream.kafka.topic.name": "exception-trace", + "stream.kafka.broker.list": "localhost:19092", + "stream.kafka.consumer.type": "lowlevel", + "stream.kafka.consumer.prop.auto.offset.reset": "smallest", + "stream.kafka.consumer.factory.class.name": "org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory", + "stream.kafka.decoder.class.name": "org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder", + "realtime.segment.flush.threshold.rows": "0", + "realtime.segment.flush.threshold.time": "24h", + "realtime.segment.flush.segment.size": "100M" + }, + "rangeIndexColumns": [], + "rangeIndexVersion": 2, + "sortedColumn": [], + "bloomFilterColumns": [], + "loadMode": "MMAP", + "onHeapDictionaryColumns": [], + "enableDefaultStarTree": false, + "aggregateMetrics": false, + "nullHandlingEnabled": false, + "autoGeneratedInvertedIndex": false, + "varLengthDictionaryColumns": [], + "enableDynamicStarTreeCreation": false, + "optimizeDictionaryForMetrics": false, + "noDictionarySizeRatioThreshold": 0, + "createInvertedIndexDuringSegmentGeneration": false + }, + "metadata": {}, + "quota": {}, + "routing": {}, + "query": {}, + "ingestionConfig": {}, + "isDimTable": false + } +} \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/pom.xml b/exceptiontrace/exceptiontrace-web/pom.xml new file mode 100644 index 0000000000000..ccc514bcbd726 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/pom.xml @@ -0,0 +1,36 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.5.1-SNAPSHOT + + 4.0.0 + + pinpoint-exceptiontrace-web + + + + 11 + ${env.JAVA_11_HOME} + + + + + com.navercorp.pinpoint + pinpoint-commons + + + com.navercorp.pinpoint + pinpoint-metric + + + com.navercorp.pinpoint + pinpoint-exceptiontrace-common + 2.5.1-SNAPSHOT + + + + \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebConfig.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebConfig.java new file mode 100644 index 0000000000000..0087336abf766 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web; + +import com.navercorp.pinpoint.exceptiontrace.web.config.ExceptionTracePinotDaoConfiguration; +import com.navercorp.pinpoint.pinot.config.PinotConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; + +/** + * @author intr3p1d + */ +@Configuration +@ComponentScan(basePackages = "com.navercorp.pinpoint.exceptiontrace.web") +@Import({ExceptionTraceWebPropertySources.class, ExceptionTracePinotDaoConfiguration.class, PinotConfiguration.class}) +@Profile("exception") +public class ExceptionTraceWebConfig { +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebPropertySources.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebPropertySources.java new file mode 100644 index 0000000000000..40d98b7fc9715 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebPropertySources.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web; + +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.annotation.PropertySources; + +/** + * @author intr3p1d + */ +@PropertySources({ + @PropertySource(name = "ExceptionTracePropertySources", value = {ExceptionTraceWebPropertySources.EXCEPTION_TRACE}), +}) +public class ExceptionTraceWebPropertySources { + public static final String EXCEPTION_TRACE = "classpath:profiles/${pinpoint.profiles.active:release}/pinpoint-web-exceptiontrace.properties"; +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTracePinotDaoConfiguration.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTracePinotDaoConfiguration.java new file mode 100644 index 0000000000000..fd9d9589d321c --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTracePinotDaoConfiguration.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.web.config; + +import com.navercorp.pinpoint.metric.collector.config.MyBatisRegistryHandler; +import com.navercorp.pinpoint.pinot.mybatis.MyBatisConfiguration; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.Resource; + +import javax.sql.DataSource; + +/** + * @author intr3p1d + */ +public class ExceptionTracePinotDaoConfiguration { + + @Bean + public SqlSessionFactory exceptionTracePinotSessionFactory( + @Qualifier("pinotDataSource") DataSource dataSource, + @Value("classpath:mapper/exceptiontrace/*Mapper.xml") Resource[] mappers + ) throws Exception { + SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); + + sessionFactoryBean.setDataSource(dataSource); + + sessionFactoryBean.setConfiguration(newConfiguration()); + sessionFactoryBean.setMapperLocations(mappers); + sessionFactoryBean.setFailFast(true); + sessionFactoryBean.setTransactionFactory(transactionFactory()); + + return sessionFactoryBean.getObject(); + } + + private ManagedTransactionFactory transactionFactory() { + return new ManagedTransactionFactory(); + } + + private Configuration newConfiguration() { + Configuration config = MyBatisConfiguration.defaultConfiguration(); + + MyBatisRegistryHandler registryHandler = registryHandler(); + registryHandler.registerTypeAlias(config.getTypeAliasRegistry()); + registryHandler.registerTypeHandler(config.getTypeHandlerRegistry()); + return config; + } + + private MyBatisRegistryHandler registryHandler() { + return new ExceptionTraceRegistryHandler(); + } + + @Bean + public SqlSessionTemplate exceptionTracePinotSessionTemplate( + @Qualifier("exceptionTracePinotSessionFactory") SqlSessionFactory sessionFactory + ) { + return new SqlSessionTemplate(sessionFactory); + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java new file mode 100644 index 0000000000000..c7d06b9e3bbde --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.web.config; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import com.navercorp.pinpoint.exceptiontrace.web.mapper.StackTraceTypeHandler; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; +import com.navercorp.pinpoint.metric.collector.config.MyBatisRegistryHandler; +import org.apache.ibatis.type.TypeAliasRegistry; +import org.apache.ibatis.type.TypeHandlerRegistry; + +/** + * @author intr3p1d + */ +public class ExceptionTraceRegistryHandler implements MyBatisRegistryHandler { + @Override + public void registerTypeAlias(TypeAliasRegistry typeAliasRegistry) { + typeAliasRegistry.registerAlias("SpanEventException", SpanEventException.class); + typeAliasRegistry.registerAlias("StackTraceElementWrapper", StackTraceElementWrapper.class); + typeAliasRegistry.registerAlias("ExceptionTraceSummary", ExceptionTraceSummary.class); + typeAliasRegistry.registerAlias("StackTraceTypeHandler", StackTraceTypeHandler.class); + typeAliasRegistry.registerAlias("ExceptionTraceQueryParameter", ExceptionTraceQueryParameter.class); + } + + @Override + public void registerTypeHandler(TypeHandlerRegistry typeHandlerRegistry) { + + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java new file mode 100644 index 0000000000000..cd8b5c9ff9738 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java @@ -0,0 +1,184 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.controller; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.service.ExceptionTraceService; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; +import com.navercorp.pinpoint.metric.web.util.Range; +import com.navercorp.pinpoint.metric.web.util.TimePrecision; +import com.navercorp.pinpoint.metric.web.util.TimeWindow; +import com.navercorp.pinpoint.metric.web.util.TimeWindowSampler; +import com.navercorp.pinpoint.metric.web.util.TimeWindowSlotCentricSampler; +import com.navercorp.pinpoint.exceptiontrace.web.view.ExceptionTraceView; +import com.navercorp.pinpoint.pinot.tenant.TenantProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author intr3p1d + */ +@RestController +@RequestMapping(value = "/exceptionTrace") +public class ExceptionTraceController { + + private static final TimePrecision DETAILED_TIME_PRECISION = TimePrecision.newTimePrecision(TimeUnit.MILLISECONDS, 1); + + private final ExceptionTraceService exceptionTraceService; + + private final Logger logger = LogManager.getLogger(this.getClass()); + private final TimeWindowSampler DEFAULT_TIME_WINDOW_SAMPLER = new TimeWindowSlotCentricSampler(30000L, 200); + private final TenantProvider tenantProvider; + + public ExceptionTraceController(ExceptionTraceService exceptionTraceService, TenantProvider tenantProvider) { + this.exceptionTraceService = Objects.requireNonNull(exceptionTraceService, "exceptionTraceService"); + this.tenantProvider = Objects.requireNonNull(tenantProvider, "tenantProvider"); + } + + @GetMapping("/transactionInfo") + public List getSpanEventExceptionFromTransactionId( + @RequestParam("applicationName") String applicationName, + @RequestParam("agentId") String agentId, + @RequestParam("traceId") String traceId, + @RequestParam("spanId") long spanId, + @RequestParam("exceptionId") long exceptionId + ) { + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setTransactionId(traceId) + .setSpanId(spanId) + .setExceptionId(exceptionId) + .setTimePrecision(DETAILED_TIME_PRECISION) + .build(); + return exceptionTraceService.getTransactionExceptions( + queryParameter + ); + } + + @GetMapping("/errorList") + public List getListOfSpanEventExceptionByGivenRange( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to + ) { + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(DETAILED_TIME_PRECISION) + .build(); + return exceptionTraceService.getExceptionsInRange( + queryParameter + ); + } + + @GetMapping("/errorList/similar") + public List getListOfSpanEventExceptionByGivenRange( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to, + + @RequestParam("traceId") String traceId, + @RequestParam("spanId") long spanId, + @RequestParam("exceptionId") long exceptionId, + @RequestParam("exceptionDepth") int exceptionDepth + ) { + ExceptionTraceQueryParameter targetQuery = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setTransactionId(traceId) + .setSpanId(spanId) + .setExceptionId(exceptionId) + .setExceptionDepth(exceptionDepth).build(); + + ExceptionTraceQueryParameter.Builder queryBuilder = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(DETAILED_TIME_PRECISION); + + return exceptionTraceService.getSimilarExceptions( + targetQuery, queryBuilder + ); + } + + @GetMapping("/chart") + public ExceptionTraceView getCollectedSpanEventExceptionByGivenRange( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to + ) { + + TimeWindow timeWindow = new TimeWindow(Range.newRange(from, to), DEFAULT_TIME_WINDOW_SAMPLER); + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(TimePrecision.newTimePrecision(TimeUnit.MILLISECONDS, (int) timeWindow.getWindowSlotSize())) + .build(); + List exceptionTraceSummaries = exceptionTraceService.getSummaryInRange( + queryParameter + ); + return ExceptionTraceView.newViewFromSummaries("", timeWindow, exceptionTraceSummaries); + } + + @GetMapping("/chart/similar") + public ExceptionTraceView getCollectedSpanEventExceptionByGivenRange( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to, + + @RequestParam("traceId") String traceId, + @RequestParam("spanId") long spanId, + @RequestParam("exceptionId") long exceptionId, + @RequestParam("exceptionDepth") int exceptionDepth + ) { + TimeWindow timeWindow = new TimeWindow(Range.newRange(from, to), DEFAULT_TIME_WINDOW_SAMPLER); + ExceptionTraceQueryParameter targetQuery = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setTransactionId(traceId) + .setSpanId(spanId) + .setExceptionId(exceptionId) + .setExceptionDepth(exceptionDepth).build(); + + ExceptionTraceQueryParameter.Builder queryBuilder = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(TimePrecision.newTimePrecision(TimeUnit.MILLISECONDS, (int) timeWindow.getWindowSlotSize())); + + List exceptionTraceSummaries = exceptionTraceService.getSummaryOfSimilarExceptions( + targetQuery, queryBuilder + ); + return ExceptionTraceView.newViewFromSummaries("", timeWindow, exceptionTraceSummaries); + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/ExceptionTraceDao.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/ExceptionTraceDao.java new file mode 100644 index 0000000000000..6e85e313dec0f --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/ExceptionTraceDao.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.dao; + + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; + +import java.util.List; + +/** + * @author intr3p1d + */ +public interface ExceptionTraceDao { + List getExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter); + List getSimpleExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter); + SpanEventException getException(ExceptionTraceQueryParameter exceptionTraceQueryParameter); + List getSummaries(ExceptionTraceQueryParameter exceptionTraceQueryParameter); +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.java new file mode 100644 index 0000000000000..c9418d6095956 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.dao; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +@Repository +public class PinotExceptionTraceDao implements ExceptionTraceDao { + private final Logger logger = LogManager.getLogger(this.getClass()); + + private static final String NAMESPACE = PinotExceptionTraceDao.class.getName() + "."; + + private static final String SELECT_QUERY = "selectExceptions"; + private static final String SELECT_SIMPLE_QUERY = "selectSimpleExceptions"; + private static final String SELECT_EXACT_QUERY = "selectExactException"; + private static final String SELECT_SUMMARIES_QUERY = "selectSummaries"; + + private final SqlSessionTemplate sqlPinotSessionTemplate; + + public PinotExceptionTraceDao(@Qualifier("exceptionTracePinotSessionTemplate") SqlSessionTemplate sqlPinotSessionTemplate) { + this.sqlPinotSessionTemplate = Objects.requireNonNull(sqlPinotSessionTemplate, "sqlPinotSessionTemplate"); + } + + @Override + public List getExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + return this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_QUERY, exceptionTraceQueryParameter); + } + + @Override + public List getSimpleExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + return this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_SIMPLE_QUERY, exceptionTraceQueryParameter); + } + + @Override + public SpanEventException getException(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + return this.sqlPinotSessionTemplate.selectOne(NAMESPACE + SELECT_EXACT_QUERY, exceptionTraceQueryParameter); + } + + @Override + public List getSummaries(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + return this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_SUMMARIES_QUERY, exceptionTraceQueryParameter); + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandler.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandler.java new file mode 100644 index 0000000000000..3fd59677ce203 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.mapper; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.navercorp.pinpoint.common.util.StringUtils; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import org.apache.hadoop.hbase.shaded.com.google.gson.Gson; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author intr3p1d + */ +public class StackTraceTypeHandler extends BaseTypeHandler> { + + private static final Logger logger = LogManager.getLogger(StackTraceTypeHandler.class); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + + @Override + public void setNonNullParameter( + PreparedStatement preparedStatement, + int i, + List stackTraceElementWrappers, + JdbcType jdbcType + ) throws SQLException { + preparedStatement.setString(i, new Gson().toJson(stackTraceElementWrappers)); + } + + @Override + public List getNullableResult(ResultSet resultSet, String s) throws SQLException { + return convertToList(resultSet.getString(s)); + } + + @Override + public List getNullableResult(ResultSet resultSet, int i) throws SQLException { + return convertToList(resultSet.getString(i)); + } + + @Override + public List getNullableResult(CallableStatement callableStatement, int i) throws SQLException { + return convertToList(callableStatement.getString(i)); + } + + protected List convertToList(String s) { + if (StringUtils.isEmpty(s)) { + return Collections.emptyList(); + } + try { + List strings = objectMapper.readValue(s, new TypeReference<>() { + }); + List stackTraceElementWrapperList = new ArrayList<>(); + for(String str : strings) { + stackTraceElementWrapperList.add(objectMapper.readValue(str, StackTraceElementWrapper.class)); + } + return stackTraceElementWrapperList; + } catch (IOException e) { + logger.error(e); + } + return Collections.emptyList(); + } + +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java new file mode 100644 index 0000000000000..5abf1b08b7cb1 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java @@ -0,0 +1,114 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.metric.web.util.TimeWindow; +import com.navercorp.pinpoint.metric.web.view.TimeSeriesValueView; +import com.navercorp.pinpoint.metric.web.view.TimeseriesValueGroupView; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +public class ExceptionTraceGroup implements TimeseriesValueGroupView { + + private final String exceptionClass; + private final List values; + + private ExceptionTraceGroup(String exceptionClass, List values) { + this.exceptionClass = exceptionClass; + this.values = values; + } + + public static ExceptionTraceGroup newGroupFromSummaries( + TimeWindow timeWindow, + String exceptionClass, + List exceptionTraceSummaries + ) { + return new ExceptionTraceGroup( + exceptionClass, + ExceptionTraceValue.createValueListGroupedBySimilarity(timeWindow, exceptionTraceSummaries) + ); + } + + @Override + public String getGroupName() { + return exceptionClass; + } + + @Override + public List getMetricValues() { + return values; + } + + public static class ExceptionTraceValue implements TimeSeriesValueView { + + private final String fieldName; + private final List values; + + private static List createValueListGroupedBySimilarity( + TimeWindow timeWindow, + List exceptionTraceSummaries + ) { + int[] timeseriesvalues = new int[(int) timeWindow.getWindowRangeCount()]; + Arrays.fill(timeseriesvalues, 0); + + for (ExceptionTraceSummary e : exceptionTraceSummaries) { + timeseriesvalues[timeWindow.getWindowIndex(e.getTimestamp())] += e.getCount(); + } + + return List.of(new ExceptionTraceValue( + "", + Arrays.stream(timeseriesvalues).boxed().collect(Collectors.toList()) + )); + } + + public ExceptionTraceValue(String fieldName, List values) { + this.fieldName = fieldName; + this.values = values; + } + + @Override + public String getFieldName() { + return this.fieldName; + } + + @Override + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getTags() { + return null; + } + + @Override + public List getValues() { + return values; + } + } + +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.java new file mode 100644 index 0000000000000..f63522fbb3fb7 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.web.model; + +/** + * @author intr3p1d + */ +public class ExceptionTraceSummary { + + private static final String EMPTY_STRING = null; + private final long timestamp; + private final long count; + + private final String errorClassName; + private final String errorMessage; + private final String stackTraceHash; + + public ExceptionTraceSummary(long timestamp, String errorClassName, String errorMessage, String stackTraceHash, long count) { + this.timestamp = timestamp; + this.errorClassName = errorClassName; + this.errorMessage = errorMessage; + this.stackTraceHash = stackTraceHash; + this.count = count; + } + + public ExceptionTraceSummary(long timestamp, long count) { + this.timestamp = timestamp; + this.count = count; + this.errorClassName = EMPTY_STRING; + this.errorMessage = EMPTY_STRING; + this.stackTraceHash = EMPTY_STRING; + } + + public long getTimestamp() { + return timestamp; + } + + public String getErrorClassName() { + return errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public long getCount() { + return count; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceService.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceService.java new file mode 100644 index 0000000000000..6f217a22b9a02 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.service; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; + +import java.util.List; + +/** + * @author intr3p1d + */ +public interface ExceptionTraceService { + + List getTransactionExceptions(ExceptionTraceQueryParameter queryParameter); + + List getExceptionsInRange(ExceptionTraceQueryParameter queryParameter); + + List getSimilarExceptions(ExceptionTraceQueryParameter targetQuery, ExceptionTraceQueryParameter.Builder queryBuilder); + + List getSummaryInRange(ExceptionTraceQueryParameter queryParameter); + + List getSummaryOfSimilarExceptions(ExceptionTraceQueryParameter targetQuery, ExceptionTraceQueryParameter.Builder queryBuilder); +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.java new file mode 100644 index 0000000000000..13ca85fbe9794 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.service; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.dao.ExceptionTraceDao; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * @author intr3p1d + */ +@Service +public class ExceptionTraceServiceImpl implements ExceptionTraceService { + + private final Logger logger = LogManager.getLogger(this.getClass()); + + private ExceptionTraceDao exceptionTraceDao; + + public ExceptionTraceServiceImpl(ExceptionTraceDao exceptionTraceDao) { + this.exceptionTraceDao = Objects.requireNonNull(exceptionTraceDao, "exceptionTraceDao"); + } + + @Override + public List getTransactionExceptions( + ExceptionTraceQueryParameter queryParameter + ) { + return getTransactionExceptions( + queryParameter, + this::getSpanEventExceptions + ); + } + + @Override + public List getExceptionsInRange( + ExceptionTraceQueryParameter queryParameter + ) { + return getExceptionsInRange( + queryParameter, + this::getSimpleSpanEventExceptions + ); + } + + @Override + public List getSimilarExceptions( + ExceptionTraceQueryParameter targetQuery, ExceptionTraceQueryParameter.Builder queryBuilder + ) { + return getSimilarExceptions( + targetQuery, + queryBuilder, + this::getSimpleSpanEventExceptions + ); + } + + @Override + public List getSummaryInRange( + ExceptionTraceQueryParameter queryParameter + ) { + return getExceptionsInRange( + queryParameter, + this::getExceptionTraceSummaries + ); + } + + @Override + public List getSummaryOfSimilarExceptions( + ExceptionTraceQueryParameter targetQuery, + ExceptionTraceQueryParameter.Builder queryBuilder + ) { + return getSimilarExceptions( + targetQuery, + queryBuilder, + this::getExceptionTraceSummaries + ); + } + + private List getTransactionExceptions( + ExceptionTraceQueryParameter queryParameter, + Function> queryFunction + ) { + return queryFunction.apply(queryParameter); + } + + private List getExceptionsInRange( + ExceptionTraceQueryParameter queryParameter, + Function> queryFunction + ) { + return queryFunction.apply(queryParameter); + } + + private List getSimilarExceptions( + ExceptionTraceQueryParameter targetQuery, + ExceptionTraceQueryParameter.Builder queryBuilder, + Function> queryFunction + ) { + final SpanEventException targetException = getTheExactException( + targetQuery + ); + + if (targetException == null) { + return Collections.emptyList(); + } + ExceptionTraceQueryParameter.Builder builder = queryBuilder + .setSpanEventException(targetException); + + return queryFunction.apply( + builder.build() + ); + } + + private List getSpanEventExceptions(ExceptionTraceQueryParameter queryParameter) { + List spanEventExceptions = exceptionTraceDao.getExceptions(queryParameter); + logger.info(spanEventExceptions.size()); + return spanEventExceptions; + } + + private List getSimpleSpanEventExceptions(ExceptionTraceQueryParameter queryParameter) { + List spanEventExceptions = exceptionTraceDao.getSimpleExceptions(queryParameter); + logger.info(spanEventExceptions.size()); + return spanEventExceptions; + } + + private SpanEventException getTheExactException(ExceptionTraceQueryParameter queryParameter) { + return exceptionTraceDao.getException(queryParameter); + } + + private List getExceptionTraceSummaries(ExceptionTraceQueryParameter queryParameter) { + List spanEventExceptions = exceptionTraceDao.getSummaries(queryParameter); + logger.info(spanEventExceptions.size()); + return spanEventExceptions; + } + +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java new file mode 100644 index 0000000000000..31329fb6de584 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java @@ -0,0 +1,128 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.util; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.metric.web.util.QueryParameter; +import com.navercorp.pinpoint.metric.web.util.TimePrecision; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author intr3p1d + */ +public class ExceptionTraceQueryParameter extends QueryParameter { + private final String applicationName; + private final String agentId; + private final SpanEventException spanEventException; + + private final String transactionId; + private final long spanId; + private final long exceptionId; + private final int exceptionDepth; + + protected ExceptionTraceQueryParameter(Builder builder) { + super(builder.getRange(), builder.getTimePrecision(), builder.getLimit()); + this.applicationName = builder.applicationName; + this.agentId = builder.agentId; + this.spanEventException = builder.spanEventException; + this.transactionId = builder.transactionId; + this.spanId = builder.spanId; + this.exceptionId = builder.exceptionId; + this.exceptionDepth = builder.exceptionDepth; + } + + public static class Builder extends QueryParameter.Builder { + private static final int LIMIT = 65536; + private String applicationName; + private String agentId = null; + + private SpanEventException spanEventException = null; + + private String transactionId = null; + private long spanId = Long.MIN_VALUE; + private long exceptionId = Long.MIN_VALUE; + private int exceptionDepth = Integer.MIN_VALUE; + + @Override + protected Builder self() { + return this; + } + + public Builder setApplicationName(String applicationName) { + this.applicationName = applicationName; + return self(); + } + + public Builder setExceptionDepth(int exceptionDepth) { + this.exceptionDepth = exceptionDepth; + return self(); + } + + public Builder setAgentId(String agentId) { + this.agentId = agentId; + return self(); + } + + public Builder setSpanEventException(SpanEventException spanEventException) { + this.spanEventException = Objects.requireNonNull(spanEventException, "spanEventException"); + return self(); + } + + public Builder setTransactionId(String transactionId) { + this.transactionId = transactionId; + return self(); + } + + public Builder setSpanId(long spanId) { + this.spanId = spanId; + return self(); + } + + public Builder setExceptionId(long exceptionId) { + this.exceptionId = exceptionId; + return self(); + } + + @Override + public ExceptionTraceQueryParameter build() { + if (timePrecision == null) { + this.timePrecision = TimePrecision.newTimePrecision(TimeUnit.MILLISECONDS, 30000); + } + if (this.range != null) { + this.limit = estimateLimit(); + } else { + this.limit = LIMIT; + } + return new ExceptionTraceQueryParameter(this); + } + } + + @Override + public String toString() { + return "ExceptionTraceQueryParameter{" + + "applicationName='" + applicationName + '\'' + + ", agentId='" + agentId + '\'' + + ", spanEventException=" + spanEventException + + ", transactionId='" + transactionId + '\'' + + ", spanId=" + spanId + + ", exceptionId=" + exceptionId + + ", exceptionDepth=" + exceptionDepth + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/view/ExceptionTraceView.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/view/ExceptionTraceView.java new file mode 100644 index 0000000000000..4db12267171f8 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/view/ExceptionTraceView.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.view; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceGroup; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.metric.web.util.TimeWindow; +import com.navercorp.pinpoint.metric.web.view.TimeSeriesView; +import com.navercorp.pinpoint.metric.web.view.TimeseriesValueGroupView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +public class ExceptionTraceView implements TimeSeriesView { + + private static final String TITLE = "exceptionTrace"; + + private final List timestampList; + + private final List exceptionTrace = new ArrayList<>(); + + private ExceptionTraceView(List timestampList, List exceptionTraces) { + this.timestampList = timestampList; + this.exceptionTrace.addAll(exceptionTraces); + } + + public static ExceptionTraceView newViewFromSummaries( + String exceptionClass, + TimeWindow timeWindow, + List exceptionTraceSummaries + ) { + Objects.requireNonNull(timeWindow, "timeWindow"); + Objects.requireNonNull(exceptionTraceSummaries, "exceptionTraceSummaries"); + + List timestampList = createTimeStampList(timeWindow); + List timeseriesValueGroupViews = new ArrayList<>(); + timeseriesValueGroupViews.add( + ExceptionTraceGroup.newGroupFromSummaries(timeWindow, exceptionClass, exceptionTraceSummaries) + ); + + return new ExceptionTraceView( + timestampList, timeseriesValueGroupViews + ); + } + + private static List createTimeStampList(TimeWindow timeWindow) { + List timestampList = new ArrayList<>((int) timeWindow.getWindowRangeCount()); + + for (Long timestamp : timeWindow) { + timestampList.add(timestamp); + } + + return timestampList; + } + + @Override + public String getTitle() { + return TITLE; + } + + @Override + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getUnit() { + return null; + } + + @Override + public List getTimestamp() { + return timestampList; + } + + @Override + public List getMetricValueGroups() { + return this.exceptionTrace; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/resources/mapper/exceptiontrace/ExceptionTraceMapper.xml b/exceptiontrace/exceptiontrace-web/src/main/resources/mapper/exceptiontrace/ExceptionTraceMapper.xml new file mode 100644 index 0000000000000..d841bc76cabd0 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/resources/mapper/exceptiontrace/ExceptionTraceMapper.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + exceptionTrace + + + DATETIME_CONVERT + ("timestamp", '1:MILLISECONDS:EPOCH', '1:MILLISECONDS:EPOCH', + '#{timePrecision.timeSize}:${timePrecision.timeUnit}') as "timestamp", + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + errorClassName, + errorMessage, + exceptionDepth, + stackTrace, + stackTraceHash + + + + DATETIME_CONVERT + ("timestamp", '1:MILLISECONDS:EPOCH', '1:MILLISECONDS:EPOCH', + '#{timePrecision.timeSize}:${timePrecision.timeUnit}') as "timestamp", + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + errorClassName, + errorMessage, + exceptionDepth, + stackTraceHash + + + + + + + + + + + \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/local/pinpoint-web-exceptiontrace.properties b/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/local/pinpoint-web-exceptiontrace.properties new file mode 100644 index 0000000000000..87b78431e4e19 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/local/pinpoint-web-exceptiontrace.properties @@ -0,0 +1 @@ +config.show.exceptionTrace=true \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/release/pinpoint-web-exceptiontrace.properties b/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/release/pinpoint-web-exceptiontrace.properties new file mode 100644 index 0000000000000..87b78431e4e19 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/resources/profiles/release/pinpoint-web-exceptiontrace.properties @@ -0,0 +1 @@ +config.show.exceptionTrace=true \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandlerTest.java b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandlerTest.java new file mode 100644 index 0000000000000..c1c75bb9f1467 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandlerTest.java @@ -0,0 +1,57 @@ +package com.navercorp.pinpoint.exceptiontrace.web.mapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author intr3p1d + */ +public class StackTraceTypeHandlerTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final static StackTraceTypeHandler stackTraceTypeHandler = new StackTraceTypeHandler(); + + private List newStackTraceElementWrappers(Throwable throwable) { + List elements = List.of(throwable.getStackTrace()); + return elements.stream().map( + (StackTraceElement s) -> new StackTraceElementWrapper( + s.getClassName(), + s.getFileName(), + s.getLineNumber(), + s.getMethodName() + ) + ).collect(Collectors.toList()); + } + + private String serializedStackTraceElementWrappers(List wrappers) throws JsonProcessingException { + List strings = new ArrayList<>(); + wrappers.forEach( + (StackTraceElementWrapper s) -> { + try { + strings.add(OBJECT_MAPPER.writeValueAsString(s)); + } catch (JsonProcessingException ignored) { + // do nothing + } + } + ); + return OBJECT_MAPPER.writeValueAsString(strings); + } + + @Test + public void testConvertToList() throws JsonProcessingException { + Throwable exception = new RuntimeException("sample exception"); + List expected = newStackTraceElementWrappers(exception); + List actual = stackTraceTypeHandler.convertToList(serializedStackTraceElementWrappers(expected)); + assertThat(actual).isEqualTo(expected); + } + +} diff --git a/exceptiontrace/pom.xml b/exceptiontrace/pom.xml new file mode 100644 index 0000000000000..14f8f75a6c103 --- /dev/null +++ b/exceptiontrace/pom.xml @@ -0,0 +1,21 @@ + + + + pinpoint + com.navercorp.pinpoint + 2.5.1-SNAPSHOT + + 4.0.0 + + pinpoint-exceptiontrace-module + pom + + + exceptiontrace-common + exceptiontrace-collector + exceptiontrace-web + + + \ No newline at end of file diff --git a/metric-module/collector-starter/pom.xml b/metric-module/collector-starter/pom.xml index e39d437bc5e21..5822c30d6a7a3 100644 --- a/metric-module/collector-starter/pom.xml +++ b/metric-module/collector-starter/pom.xml @@ -34,6 +34,10 @@ com.navercorp.pinpoint pinpoint-uristat-collector + + com.navercorp.pinpoint + pinpoint-exceptiontrace-collector + diff --git a/metric-module/collector-starter/src/main/java/com/navercorp/pinpoint/collector/starter/multi/application/MultiApplication.java b/metric-module/collector-starter/src/main/java/com/navercorp/pinpoint/collector/starter/multi/application/MultiApplication.java index 6c406397362b8..b6a99f3515d4d 100644 --- a/metric-module/collector-starter/src/main/java/com/navercorp/pinpoint/collector/starter/multi/application/MultiApplication.java +++ b/metric-module/collector-starter/src/main/java/com/navercorp/pinpoint/collector/starter/multi/application/MultiApplication.java @@ -6,6 +6,7 @@ import com.navercorp.pinpoint.common.server.env.ExternalEnvironmentListener; import com.navercorp.pinpoint.common.server.env.ProfileResolveListener; import com.navercorp.pinpoint.common.server.util.ServerBootLogger; +import com.navercorp.pinpoint.exceptiontrace.collector.ExceptionTraceCollectorConfig; import com.navercorp.pinpoint.metric.collector.CollectorType; import com.navercorp.pinpoint.metric.collector.CollectorTypeParser; import com.navercorp.pinpoint.metric.collector.MetricCollectorApp; @@ -44,9 +45,10 @@ public static void main(String[] args) { if (types.hasType(CollectorType.BASIC)) { logger.info(String.format("Start %s collector", CollectorType.BASIC)); - SpringApplicationBuilder collectorAppBuilder = createAppBuilder(builder, 15400, BasicCollectorApp.class, UriStatCollectorConfig.class); + SpringApplicationBuilder collectorAppBuilder = createAppBuilder(builder, 15400, BasicCollectorApp.class, UriStatCollectorConfig.class, ExceptionTraceCollectorConfig.class); collectorAppBuilder.listeners(new AdditionalProfileListener("metric")); collectorAppBuilder.listeners(new AdditionalProfileListener("uri")); + collectorAppBuilder.listeners(new AdditionalProfileListener("exception")); collectorAppBuilder.build().run(args); } diff --git a/metric-module/web-starter/pom.xml b/metric-module/web-starter/pom.xml index 0fde50cb86c33..d5708b12c482f 100644 --- a/metric-module/web-starter/pom.xml +++ b/metric-module/web-starter/pom.xml @@ -44,6 +44,10 @@ com.navercorp.pinpoint pinpoint-uristat-web + + com.navercorp.pinpoint + pinpoint-exceptiontrace-web + diff --git a/metric-module/web-starter/src/main/java/com/navercorp/pinpoint/web/starter/multi/MetricAndWebApp.java b/metric-module/web-starter/src/main/java/com/navercorp/pinpoint/web/starter/multi/MetricAndWebApp.java index 5d8022412d6a0..b43302195c4db 100644 --- a/metric-module/web-starter/src/main/java/com/navercorp/pinpoint/web/starter/multi/MetricAndWebApp.java +++ b/metric-module/web-starter/src/main/java/com/navercorp/pinpoint/web/starter/multi/MetricAndWebApp.java @@ -17,6 +17,7 @@ package com.navercorp.pinpoint.web.starter.multi; import com.navercorp.pinpoint.common.server.util.ServerBootLogger; +import com.navercorp.pinpoint.exceptiontrace.web.ExceptionTraceWebConfig; import com.navercorp.pinpoint.metric.web.MetricWebApp; import com.navercorp.pinpoint.uristat.web.UriStatWebConfig; import com.navercorp.pinpoint.web.AuthorizationConfig; @@ -47,8 +48,8 @@ public class MetricAndWebApp { public static void main(String[] args) { try { - WebStarter starter = new WebStarter(MetricAndWebApp.class, PinpointBasicLoginConfig.class, AuthorizationConfig.class, MetricWebApp.class, UriStatWebConfig.class); - starter.addProfiles("uri", "metric"); + WebStarter starter = new WebStarter(MetricAndWebApp.class, PinpointBasicLoginConfig.class, AuthorizationConfig.class, MetricWebApp.class, UriStatWebConfig.class, ExceptionTraceWebConfig.class); + starter.addProfiles("uri", "metric", "exception"); starter.start(args); } catch (Exception exception) { logger.error("[WebApp] could not launch app.", exception); diff --git a/pom.xml b/pom.xml index 6995bfafd5420..764b6fde5161a 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,7 @@ pinot metric-module uristat + exceptiontrace @@ -449,6 +450,21 @@ pinpoint-uristat-collector ${project.version} + + com.navercorp.pinpoint + pinpoint-exceptiontrace-common + ${project.version} + + + com.navercorp.pinpoint + pinpoint-exceptiontrace-collector + ${project.version} + + + com.navercorp.pinpoint + pinpoint-exceptiontrace-web + ${project.version} + com.navercorp.pinpoint pinpoint-metric