From 232fab47b0fa6849ab5134fe3f3d13854861a24c Mon Sep 17 00:00:00 2001 From: Seoyoung Park Date: Tue, 9 May 2023 17:34:15 +0900 Subject: [PATCH] [#9631] Add exceptionTrace Module --- .../exceptiontrace-collector/pom.xml | 46 ++++ .../ExceptionTraceCollectorConfig.java | 37 +++ ...xceptionTraceCollectorPropertySources.java | 23 ++ .../ExceptionMetricKafkaConfiguration.java | 38 +++ .../collector/dao/ExceptionTraceDao.java | 28 +++ .../collector/dao/PinotExceptionTraceDao.java | 69 +++++ .../collector/model/SpanEventExceptionVo.java | 125 +++++++++ .../service/PinotExceptionTraceService.java | 102 ++++++++ .../src/main/resources/kafka-topic.properties | 1 + .../local/kafka-topic-exception.properties | 1 + .../release/kafka-topic-exception.properties | 1 + exceptiontrace/exceptiontrace-common/pom.xml | 40 +++ .../common/model/SpanEventException.java | 237 +++++++++++++++++ .../model/StackTraceElementWrapper.java | 110 ++++++++ .../common/pinot/PinotColumns.java | 44 ++++ .../exceptiontrace/common/util/HashUtils.java | 64 +++++ .../common/util/StringPrecondition.java | 31 +++ .../pinot/pinot-exceptionTrace-schema.json | 62 +++++ .../pinot/pinot-exceptionTrace-table.json | 55 ++++ exceptiontrace/exceptiontrace-web/pom.xml | 35 +++ .../web/ExceptionTraceWebConfig.java | 34 +++ .../web/ExceptionTraceWebPropertySources.java | 30 +++ .../ExceptionTracePinotDaoConfiguration.java | 77 ++++++ .../config/ExceptionTraceRegistryHandler.java | 48 ++++ .../controller/ExceptionTraceController.java | 180 +++++++++++++ .../web/dao/ExceptionTraceDao.java | 36 +++ .../web/dao/PinotExceptionTraceDao.java | 77 ++++++ .../web/mapper/StackTraceTypeHandler.java | 91 +++++++ .../web/mapper/ValueViewTypeHandler.java | 85 +++++++ .../web/model/ExceptionTraceGroup.java | 61 +++++ .../web/model/ExceptionTraceSummary.java | 68 +++++ .../web/model/ExceptionTraceValueView.java | 59 +++++ .../web/model/GroupByAttributes.java | 38 +++ .../web/model/GroupedFieldName.java | 47 ++++ .../web/service/ExceptionTraceService.java | 38 +++ .../service/ExceptionTraceServiceImpl.java | 112 +++++++++ .../util/ExceptionTraceQueryParameter.java | 160 ++++++++++++ .../web/view/ExceptionTraceView.java | 95 +++++++ .../exceptiontrace/ExceptionTraceMapper.xml | 238 ++++++++++++++++++ .../pinpoint-web-exceptiontrace.properties | 1 + .../pinpoint-web-exceptiontrace.properties | 1 + .../web/mapper/StackTraceTypeHandlerTest.java | 57 +++++ exceptiontrace/pom.xml | 21 ++ metric-module/collector-starter/pom.xml | 4 + .../multi/application/MultiApplication.java | 4 +- metric-module/web-starter/pom.xml | 4 + .../web/starter/multi/MetricAndWebApp.java | 5 +- pom.xml | 16 ++ 48 files changed, 2833 insertions(+), 3 deletions(-) create mode 100644 exceptiontrace/exceptiontrace-collector/pom.xml create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorConfig.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorPropertySources.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/config/ExceptionMetricKafkaConfiguration.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/ExceptionTraceDao.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/PinotExceptionTraceDao.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/model/SpanEventExceptionVo.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/resources/kafka-topic.properties create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/local/kafka-topic-exception.properties create mode 100644 exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/release/kafka-topic-exception.properties create mode 100644 exceptiontrace/exceptiontrace-common/pom.xml create mode 100644 exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/SpanEventException.java create mode 100644 exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/StackTraceElementWrapper.java create mode 100644 exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/pinot/PinotColumns.java create mode 100644 exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/HashUtils.java create mode 100644 exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/StringPrecondition.java create mode 100644 exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json create mode 100644 exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-table.json create mode 100644 exceptiontrace/exceptiontrace-web/pom.xml create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebConfig.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebPropertySources.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTracePinotDaoConfiguration.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/ExceptionTraceDao.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandler.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ValueViewTypeHandler.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceValueView.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupByAttributes.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupedFieldName.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceService.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/view/ExceptionTraceView.java create mode 100644 exceptiontrace/exceptiontrace-web/src/main/resources/mapper/exceptiontrace/ExceptionTraceMapper.xml create mode 100644 exceptiontrace/exceptiontrace-web/src/main/resources/profiles/local/pinpoint-web-exceptiontrace.properties create mode 100644 exceptiontrace/exceptiontrace-web/src/main/resources/profiles/release/pinpoint-web-exceptiontrace.properties create mode 100644 exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandlerTest.java create mode 100644 exceptiontrace/pom.xml diff --git a/exceptiontrace/exceptiontrace-collector/pom.xml b/exceptiontrace/exceptiontrace-collector/pom.xml new file mode 100644 index 0000000000000..ffc93f1097d9a --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/pom.xml @@ -0,0 +1,46 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.6.0-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..b1e2bc59da9bf --- /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:profiles/${pinpoint.profiles.active}/kafka-topic-exception.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..f616a2555451c --- /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..70c24442e2764 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/dao/PinotExceptionTraceDao.java @@ -0,0 +1,69 @@ +/* + * 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 com.navercorp.pinpoint.pinot.kafka.util.KafkaCallbacks; +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.kafka.support.SendResult; +import org.springframework.stereotype.Repository; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; + +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; + + private final ListenableFutureCallback> resultCallback + = KafkaCallbacks.loggingCallback("Kafka(SpanEventExceptionVo)", logger); + + + 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) { + ListenableFuture> response = this.kafkaSpanEventExceptionTemplate.send( + topic, SpanEventExceptionVo.valueOf(spanEventException) + ); + response.addCallback(resultCallback); + } + } +} 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..643ec96a89711 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/model/SpanEventExceptionVo.java @@ -0,0 +1,125 @@ +/* + * 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 SpanEventException spanEventException; + private List stackTrace; + + + public SpanEventExceptionVo( + SpanEventException spanEventException + ) { + this.spanEventException = spanEventException; + } + + public static SpanEventExceptionVo valueOf(SpanEventException spanEventException) { + return new SpanEventExceptionVo( + spanEventException + ); + } + + 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 this.spanEventException.getTimestamp(); + } + + public String getTransactionId() { + return this.spanEventException.getTransactionId(); + } + + public long getSpanId() { + return this.spanEventException.getSpanId(); + } + + public long getExceptionId() { + return this.spanEventException.getExceptionId(); + } + + public String getApplicationServiceType() { + return this.spanEventException.getApplicationServiceType(); + } + + public String getApplicationName() { + return this.spanEventException.getApplicationName(); + } + + public String getAgentId() { + return this.spanEventException.getAgentId(); + } + + public String getUriTemplate() { + return this.spanEventException.getUriTemplate(); + } + + public String getErrorClassName() { + return this.spanEventException.getErrorClassName(); + } + + public String getErrorMessage() { + return this.spanEventException.getErrorMessage(); + } + + public int getExceptionDepth() { + return this.spanEventException.getExceptionDepth(); + } + + public List getStackTrace() { + if (stackTrace == null) { + stackTrace = toJsonString(this.spanEventException.getStackTrace()); + } + return stackTrace; + } + + public String getStackTraceHash() { + return this.spanEventException.getStackTraceHash(); + } + + @Override + public String toString() { + return "SpanEventExceptionVo{" + + "spanEventException=" + spanEventException + + ", stackTrace=" + stackTrace + + '}'; + } +} 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..dcb8de99060e6 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java @@ -0,0 +1,102 @@ +/* + * 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, String uriTemplate) { + List spanEventExceptions = new ArrayList<>(); + for (SpanEventExceptionBo spanEventExceptionBo : spanEventExceptionBoList) { + spanEventExceptions.addAll(toSpanEventExceptions(spanEventExceptionBo, transactionId, spanId, applicationServiceType, applicationId, agentId, uriTemplate)); + } + exceptionTraceDao.insert(spanEventExceptions); + } + + private static List toSpanEventExceptions( + SpanEventExceptionBo spanEventExceptionBo, + TransactionId transactionId, long spanId, + ServiceType applicationServiceType, String applicationId, String agentId, + String uriTemplate + ) { + List spanEventExceptions = new ArrayList<>(); + List exceptions = spanEventExceptionBo.getExceptionWrappers(); + for (int i = 0; i < exceptions.size(); i++) { + spanEventExceptions.add( + toSpanEventException( + exceptions.get(i), i, + spanEventExceptionBo.getStartTime(), transactionId, spanId, spanEventExceptionBo.getExceptionId(), applicationServiceType, agentId, applicationId, + uriTemplate + ) + ); + } + return spanEventExceptions; + } + + private static SpanEventException toSpanEventException( + ExceptionWrapperBo exceptionWrapperBo, int exceptionDepth, + long startTime, + TransactionId transactionId, long spanId, long exceptionId, + ServiceType applicationServiceType, String agentId, String applicationId, + String uriTemplate + ) { + return SpanEventException.valueOf( + startTime, + transactionIdToString(transactionId), + spanId, + exceptionId, + applicationServiceType.getName(), + applicationId, + agentId, + uriTemplate, + 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-collector/src/main/resources/profiles/local/kafka-topic-exception.properties b/exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/local/kafka-topic-exception.properties new file mode 100644 index 0000000000000..e99d3db765d76 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/local/kafka-topic-exception.properties @@ -0,0 +1 @@ +kafka.exception.topic=exception-trace \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/release/kafka-topic-exception.properties b/exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/release/kafka-topic-exception.properties new file mode 100644 index 0000000000000..e99d3db765d76 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/resources/profiles/release/kafka-topic-exception.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..fb6603dfd8bfc --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/pom.xml @@ -0,0 +1,40 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.6.0-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..62febca23d115 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/SpanEventException.java @@ -0,0 +1,237 @@ +/* + * 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 uriTemplate; + + 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 uriTemplate, + 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.uriTemplate = uriTemplate; + 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 uriTemplate, + 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.uriTemplate = uriTemplate; + 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 uriTemplate, + String errorClassName, String errorMessage, int exceptionDepth, + List stackTraceElementWrapperBos + ) { + List wrappers = toStackTrace(stackTraceElementWrapperBos); + + return new SpanEventException( + timestamp, + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + uriTemplate, + 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 getUriTemplate() { + return uriTemplate; + } + + 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 (!Objects.equals(errorClassName, that.errorClassName)) + return false; + if (!Objects.equals(errorMessage, that.errorMessage)) return false; + return Objects.equals(stackTraceHash, that.stackTraceHash); + } + + @Override + public int hashCode() { + int result = errorClassName != null ? errorClassName.hashCode() : 0; + result = 31 * result + (errorMessage != null ? errorMessage.hashCode() : 0); + result = 31 * result + (stackTraceHash != null ? stackTraceHash.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SpanEventException{" + + "timestamp=" + timestamp + + ", transactionId='" + transactionId + '\'' + + ", spanId=" + spanId + + ", exceptionId=" + exceptionId + + ", applicationServiceType='" + applicationServiceType + '\'' + + ", applicationName='" + applicationName + '\'' + + ", agentId='" + agentId + '\'' + + ", uriTemplate='" + uriTemplate + '\'' + + ", 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/pinot/PinotColumns.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/pinot/PinotColumns.java new file mode 100644 index 0000000000000..5d0c1e711cd7c --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/pinot/PinotColumns.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.common.pinot; + +/** + * @author intr3p1d + */ +public enum PinotColumns { + TRANSACTION_ID("transactionId"), + SPAN_ID("spanId"), + EXCEPTION_ID("exceptionId"), + APPLICATION_SERVICE_TYPE("applicationServiceType"), + APPLICATION_NAME("applicationName"), + AGENT_ID("agentId"), + URI_TEMPLATE("uriTemplate"), + ERROR_CLASS_NAME("errorClassName"), + ERROR_MESSAGE("errorMessage"), + EXCEPTION_DEPTH("exceptionDepth"), + STACK_TRACE("stackTrace"), + STACK_TRACE_HASH("stackTraceHash"); + + private final String name; + + PinotColumns(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} 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..cc7d83033692f --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json @@ -0,0 +1,62 @@ +{ + "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": "uriTemplate", + "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..3e8825dad5273 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/pom.xml @@ -0,0 +1,35 @@ + + + + pinpoint-exceptiontrace-module + com.navercorp.pinpoint + 2.6.0-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 + + + + \ 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..b428c8cb8422c --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java @@ -0,0 +1,48 @@ +/* + * 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.mapper.ValueViewTypeHandler; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceValueView; +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("ExceptionTraceValueView", ExceptionTraceValueView.class); + typeAliasRegistry.registerAlias("StackTraceTypeHandler", StackTraceTypeHandler.class); + typeAliasRegistry.registerAlias("ValueViewTypeHandler", ValueViewTypeHandler.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..7c03d415791ef --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java @@ -0,0 +1,180 @@ +/* + * 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.model.ExceptionTraceValueView; +import com.navercorp.pinpoint.exceptiontrace.web.model.GroupByAttributes; +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; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@RestController +@RequestMapping(value = "/errors") +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/groupBy") + public List getListOfSpanEventExceptionWithDynamicGroupBy( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to, + + @RequestParam("groupBy") List groupByList + ) { + List groupByAttributes = groupByList.stream().map( + GroupByAttributes::valueOf + ).distinct().collect(Collectors.toList()); + + logger.info(groupByAttributes); + + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(DETAILED_TIME_PRECISION) + .addAllGroupBies(groupByAttributes) + .build(); + + return exceptionTraceService.getSummaries( + queryParameter + ); + } + + @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())) + .setTimeWindowRangeCount(timeWindow.getWindowRangeCount()) + .build(); + List exceptionTraceValueViews = exceptionTraceService.getValueViews( + queryParameter + ); + return ExceptionTraceView.newViewFromValueViews("total error occurs", timeWindow, exceptionTraceValueViews); + } + + @GetMapping("/chart/groupBy") + public ExceptionTraceView getCollectedSpanEventExceptionByGivenRange( + @RequestParam("applicationName") String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") long from, + @RequestParam("to") long to, + + @RequestParam("groupBy") List groupByList + ) { + List groupByAttributes = groupByList.stream().map( + GroupByAttributes::valueOf + ).distinct().collect(Collectors.toList()); + 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())) + .setTimeWindowRangeCount(timeWindow.getWindowRangeCount()) + .addAllGroupBies(groupByAttributes) + .build(); + List exceptionTraceValueViews = exceptionTraceService.getValueViews( + queryParameter + ); + return ExceptionTraceView.newViewFromValueViews("top5 error occurs", timeWindow, exceptionTraceValueViews); + } + +} 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..ca90d29a64858 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/ExceptionTraceDao.java @@ -0,0 +1,36 @@ +/* + * 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.model.ExceptionTraceValueView; +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); + List getValueViews(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..9282659ce31d8 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.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.dao; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceValueView; +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 static final String SELECT_VALUEVIEWS_QUERY = "selectValueViews"; + + 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); + } + + @Override + public List getValueViews(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + return this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_VALUEVIEWS_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..d0f6686a8feef --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceTypeHandler.java @@ -0,0 +1,91 @@ +/* + * 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 ps, + int i, + List stackTraceElementWrappers, + JdbcType jdbcType + ) throws SQLException { + ps.setString(i, new Gson().toJson(stackTraceElementWrappers)); + } + + @Override + public List getNullableResult(ResultSet resultSet, String columnName) throws SQLException { + return convertToList(resultSet.getString(columnName)); + } + + @Override + public List getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { + return convertToList(resultSet.getString(columnIndex)); + } + + @Override + public List getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { + return convertToList(callableStatement.getString(columnIndex)); + } + + 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/mapper/ValueViewTypeHandler.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ValueViewTypeHandler.java new file mode 100644 index 0000000000000..36c053a99d87b --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ValueViewTypeHandler.java @@ -0,0 +1,85 @@ +/* + * 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 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.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author intr3p1d + */ +public class ValueViewTypeHandler extends BaseTypeHandler> { + + private static final Logger logger = LogManager.getLogger(StackTraceTypeHandler.class); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, new Gson().toJson(parameter)); + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + String string = rs.getString(columnName); + return parseString(string); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String string = rs.getString(columnIndex); + return parseString(string); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String string = cs.getString(columnIndex); + return parseString(string); + } + + private List parseString(String s) { + if (StringUtils.isEmpty(s)) { + return Collections.emptyList(); + } + try { + List strings = objectMapper.readValue(s, new TypeReference<>() { + }); + List integers = new ArrayList<>(); + for (String str : strings) { + integers.add(objectMapper.readValue(str, Integer.class)); + } + return integers; + } 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..fb58f4b21b573 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java @@ -0,0 +1,61 @@ +/* + * 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.navercorp.pinpoint.metric.web.view.TimeSeriesValueView; +import com.navercorp.pinpoint.metric.web.view.TimeseriesValueGroupView; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +public class ExceptionTraceGroup implements TimeseriesValueGroupView { + + private final String groupName; + private final List values; + + private ExceptionTraceGroup(String groupName, List values) { + this.groupName = groupName; + this.values = values; + } + + public static ExceptionTraceGroup newGroupFromValueViews( + String groupName, + List exceptionTraceValueViews + ) { + List timeSeriesValueViews = exceptionTraceValueViews.stream().map( + (ExceptionTraceValueView e) -> (TimeSeriesValueView) e + ).collect(Collectors.toList()); + return new ExceptionTraceGroup( + groupName, + timeSeriesValueViews + ); + } + + @Override + public String getGroupName() { + return groupName; + } + + @Override + public List getMetricValues() { + 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..9755a1c8ba11c --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.java @@ -0,0 +1,68 @@ +/* + * 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 GroupedFieldName fieldName; + private final String mostRecentErrorClassAndMessage; + private final long count; + private final long firstOccurred; + private final long lastOccurred; + + public ExceptionTraceSummary(String fieldName, String mostRecentErrorClassAndMessage, long count, long firstOccurred, long lastOccured) { + this.fieldName = new GroupedFieldName(fieldName); + this.mostRecentErrorClassAndMessage = mostRecentErrorClassAndMessage; + this.count = count; + this.firstOccurred = firstOccurred; + this.lastOccurred = lastOccured; + } + + public GroupedFieldName getFieldName() { + return fieldName; + } + + public String getMostRecentErrorClassAndMessage() { + return mostRecentErrorClassAndMessage; + } + + public long getCount() { + return count; + } + + public long getFirstOccurred() { + return firstOccurred; + } + + public long getLastOccurred() { + return lastOccurred; + } + + @Override + public String toString() { + return "ExceptionTraceSummary{" + + "fieldName=" + fieldName + + ", mostRecentErrorClassAndMessage='" + mostRecentErrorClassAndMessage + '\'' + + ", count=" + count + + ", firstOccurred=" + firstOccurred + + ", lastOccurred=" + lastOccurred + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceValueView.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceValueView.java new file mode 100644 index 0000000000000..89acc2e988bb4 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceValueView.java @@ -0,0 +1,59 @@ +/* + * 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.metric.web.view.TimeSeriesValueView; + +import java.util.List; + +/** + * @author intr3p1d + */ +public class ExceptionTraceValueView implements TimeSeriesValueView { + + private final GroupedFieldName fieldName; + private final List values; + + public ExceptionTraceValueView(String fieldName, List values) { + this.fieldName = new GroupedFieldName(fieldName); + this.values = values; + } + + @Override + public String getFieldName() { + return fieldName.inAString(); + } + + @Override + public List getValues() { + return values; + } + + @Override + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getTags() { + return null; + } + + @Override + public String toString() { + return "ExceptionTraceValueView{" + + "fieldName='" + fieldName + '\'' + + ", values=" + values + + '}'; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupByAttributes.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupByAttributes.java new file mode 100644 index 0000000000000..ae50cdea79843 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupByAttributes.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.web.model; + +import com.navercorp.pinpoint.exceptiontrace.common.pinot.PinotColumns; + +/** + * @author intr3p1d + */ +public enum GroupByAttributes { + URI_TEMPLATE(PinotColumns.URI_TEMPLATE), + ERROR_CLASS_NAME(PinotColumns.ERROR_CLASS_NAME), + ERROR_MESSAGE(PinotColumns.ERROR_MESSAGE), + STACK_TRACE(PinotColumns.STACK_TRACE_HASH); + + private final PinotColumns column; + + GroupByAttributes(PinotColumns column) { + this.column = column; + } + + public String getAttributeName() { + return column.getName(); + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupedFieldName.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupedFieldName.java new file mode 100644 index 0000000000000..25c0c6fa35958 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupedFieldName.java @@ -0,0 +1,47 @@ +/* + * 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.navercorp.pinpoint.exceptiontrace.web.util.ExceptionTraceQueryParameter; + +import java.util.List; + +/** + * @author intr3p1d + */ +public class GroupedFieldName { + + private final List fields; + + public GroupedFieldName(String concatnatedString) { + this.fields = List.of(concatnatedString.split(ExceptionTraceQueryParameter.SEPARATOR)); + } + + public List getFields() { + return fields; + } + + public String inAString(){ + return String.join(", ", fields); + } + + @Override + public String toString() { + return "GroupedFieldName{" + + "fields=" + fields + + '}'; + } +} 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..bd8e28e81afdc --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceService.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.web.service; + +import com.navercorp.pinpoint.exceptiontrace.common.model.SpanEventException; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceValueView; +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 getSummaries(ExceptionTraceQueryParameter queryParameter); + + List getValueViews(ExceptionTraceQueryParameter queryParameter); +} 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..9434e963dbbc0 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.java @@ -0,0 +1,112 @@ +/* + * 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.model.ExceptionTraceValueView; +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.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 applyQueryFunction( + queryParameter, + this::getSpanEventExceptions + ); + } + + @Override + public List getExceptionsInRange( + ExceptionTraceQueryParameter queryParameter + ) { + return applyQueryFunction( + queryParameter, + this::getSimpleSpanEventExceptions + ); + } + + @Override + public List getSummaries(ExceptionTraceQueryParameter queryParameter) { + return applyQueryFunction( + queryParameter, + this::getExceptionTraceSummaries + ); + } + + @Override + public List getValueViews(ExceptionTraceQueryParameter queryParameter) { + return applyQueryFunction( + queryParameter, + this::getExceptionTraceValueViews + ); + } + + private List applyQueryFunction( + ExceptionTraceQueryParameter queryParameter, + Function> queryFunction + ) { + return queryFunction.apply(queryParameter); + } + + 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 List getExceptionTraceSummaries(ExceptionTraceQueryParameter queryParameter) { + List summaries = exceptionTraceDao.getSummaries(queryParameter); + logger.info(summaries.size()); + return summaries; + } + + private List getExceptionTraceValueViews(ExceptionTraceQueryParameter queryParameter) { + List spanEventExceptions = exceptionTraceDao.getValueViews(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..b572192bc9331 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java @@ -0,0 +1,160 @@ +/* + * 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.exceptiontrace.web.model.GroupByAttributes; +import com.navercorp.pinpoint.metric.web.util.QueryParameter; +import com.navercorp.pinpoint.metric.web.util.TimePrecision; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +public class ExceptionTraceQueryParameter extends QueryParameter { + + public final static String SEPARATOR = "&&&"; + private final static String TOTAL_FIELD_NAME = "total"; + + private final String applicationName; + private final String agentId; + + private final String transactionId; + private final long spanId; + private final long exceptionId; + private final int exceptionDepth; + + private final List groupByAttributes; + + private final long timeWindowRangeCount; + + + protected ExceptionTraceQueryParameter(Builder builder) { + super(builder.getRange(), builder.getTimePrecision(), builder.getLimit()); + this.applicationName = builder.applicationName; + this.agentId = builder.agentId; + this.transactionId = builder.transactionId; + this.spanId = builder.spanId; + this.exceptionId = builder.exceptionId; + this.exceptionDepth = builder.exceptionDepth; + this.groupByAttributes = builder.groupByAttributes; + this.timeWindowRangeCount = builder.timeWindowRangeCount; + } + + public static class Builder extends QueryParameter.Builder { + private static final int LIMIT = 65536; + private String applicationName; + private String agentId = null; + + + private String transactionId = null; + private long spanId = Long.MIN_VALUE; + private long exceptionId = Long.MIN_VALUE; + private int exceptionDepth = Integer.MIN_VALUE; + + private final List groupByAttributes = new ArrayList<>(); + + private long timeWindowRangeCount = 0; + + @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 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(); + } + + public Builder setTimeWindowRangeCount(long timeWindowRangeCount) { + this.timeWindowRangeCount = timeWindowRangeCount; + return self(); + } + + public Builder addAllGroupBies(Collection summaryGroupBIES) { + List attributes = summaryGroupBIES.stream().map(GroupByAttributes::getAttributeName).collect(Collectors.toList()); + + this.groupByAttributes.addAll( + summaryGroupBIES + ); + return self(); + } + + public long estimateLimit() { + if (this.range != null) { + return (range.getRange() / Math.max(timePrecision.getInterval(), 30000) + 1); + } else { + return LIMIT; + } + } + + @Override + public ExceptionTraceQueryParameter build() { + if (timePrecision == null) { + this.timePrecision = TimePrecision.newTimePrecision(TimeUnit.MILLISECONDS, 30000); + } + this.limit = this.estimateLimit(); + return new ExceptionTraceQueryParameter(this); + } + } + + @Override + public String toString() { + return "ExceptionTraceQueryParameter{" + + "applicationName='" + applicationName + '\'' + + ", agentId='" + agentId + '\'' + + ", transactionId='" + transactionId + '\'' + + ", spanId=" + spanId + + ", exceptionId=" + exceptionId + + ", exceptionDepth=" + exceptionDepth + + ", groupByAttributes=" + groupByAttributes + + ", timeWindowRangeCount=" + timeWindowRangeCount + + '}'; + } +} 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..b3463ccf94913 --- /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.ExceptionTraceValueView; +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 newViewFromValueViews( + String groupName, + TimeWindow timeWindow, + List exceptionTraceValueViews + ) { + Objects.requireNonNull(timeWindow, "timeWindow"); + Objects.requireNonNull(exceptionTraceValueViews, "exceptionTraceValueViews"); + + List timestampList = createTimeStampList(timeWindow); + List timeSeriesValueGroupViews = new ArrayList<>(); + timeSeriesValueGroupViews.add( + ExceptionTraceGroup.newGroupFromValueViews(groupName, exceptionTraceValueViews) + ); + + 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..59b6029f2ebc6 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/resources/mapper/exceptiontrace/ExceptionTraceMapper.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + exceptionTrace + + + + + ${attr.getAttributeName} + + + , #{SEPARATOR}) + + + + #{TOTAL_FIELD_NAME} + + + + + + DATETIME_CONVERT + ("timestamp", '1:MILLISECONDS:EPOCH', '1:MILLISECONDS:EPOCH', + '#{timePrecision.timeSize}:${timePrecision.timeUnit}') as "timestamp", + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + uriTemplate, + 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, + uriTemplate, + 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..349f8196ee3a7 --- /dev/null +++ b/exceptiontrace/pom.xml @@ -0,0 +1,21 @@ + + + + pinpoint + com.navercorp.pinpoint + 2.6.0-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 afdcbad88bc80..8075deb0e499b 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 4a955a901fa6c..8d75dddd021d4 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; @@ -49,9 +50,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 917669e816059..8202a0dbfd8b3 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 0deb3e8a55f17..d8ee5c0095a39 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 @@ -18,6 +18,7 @@ import com.navercorp.pinpoint.common.server.util.ServerBootLogger; import com.navercorp.pinpoint.login.basic.PinpointBasicLoginConfig; +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 c7e908e8420fe..cb23610bb2cbe 100644 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,7 @@ pinot metric-module uristat + exceptiontrace @@ -457,6 +458,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