diff --git a/commons-server/pom.xml b/commons-server/pom.xml index 730bc2ef350e..589e16e8ed7b 100644 --- a/commons-server/pom.xml +++ b/commons-server/pom.xml @@ -160,6 +160,10 @@ + + org.mapstruct + mapstruct + diff --git a/commons-server/src/main/java/com/navercorp/pinpoint/common/server/mapper/MapStructUtils.java b/commons-server/src/main/java/com/navercorp/pinpoint/common/server/mapper/MapStructUtils.java new file mode 100644 index 000000000000..caa2ecdf003b --- /dev/null +++ b/commons-server/src/main/java/com/navercorp/pinpoint/common/server/mapper/MapStructUtils.java @@ -0,0 +1,87 @@ +/* + * 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.common.server.mapper; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.navercorp.pinpoint.common.server.util.json.Jackson; +import com.navercorp.pinpoint.common.server.util.json.JsonRuntimeException; +import com.navercorp.pinpoint.common.util.CollectionUtils; +import com.navercorp.pinpoint.common.util.StringUtils; +import org.mapstruct.Qualifier; +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@Component +public class MapStructUtils { + private final ObjectMapper mapper; + + public MapStructUtils(ObjectMapper mapper) { + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface JsonStrToList { + + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface listToJsonStr { + + } + + + @JsonStrToList + public List jsonStrToList(String s) { + if (StringUtils.isEmpty(s)) { + return Collections.emptyList(); + } + try { + return mapper.readValue(s, new TypeReference<>() { + }); + } catch (JacksonException e) { + throw new JsonRuntimeException("Json read error", e); + } + } + + @listToJsonStr + public String listToJsonStr(List lists) { + if (CollectionUtils.isEmpty(lists)) { + return ""; + } + try { + return mapper.writeValueAsString(lists); + } catch (JacksonException e) { + throw new JsonRuntimeException("Json Write error", e); + } + } + +} diff --git a/exceptiontrace/exceptiontrace-collector/pom.xml b/exceptiontrace/exceptiontrace-collector/pom.xml new file mode 100644 index 000000000000..f2c08733e675 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/pom.xml @@ -0,0 +1,84 @@ + + + + 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 + ${spring.kafka.version} + + + com.navercorp.pinpoint + pinpoint-collector + + + org.mapstruct + mapstruct + + + org.springframework + spring-test + test + + + org.springframework.boot + spring-boot-test + + + org.testng + testng + 6.13.1 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + \ 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 000000000000..e6f9e064c4c6 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/ExceptionTraceCollectorConfig.java @@ -0,0 +1,42 @@ +/* + * 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.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; + +/** + * @author intr3p1d + */ +@Configuration +@Import({PinotConfiguration.class, ExceptionMetricKafkaConfiguration.class}) +@ComponentScan({ + "com.navercorp.pinpoint.common.server.mapper", + "com.navercorp.pinpoint.exceptiontrace.collector.service", + "com.navercorp.pinpoint.exceptiontrace.collector.dao", + "com.navercorp.pinpoint.exceptiontrace.collector.mapper", +}) +@PropertySource({ExceptionTraceCollectorConfig.KAFKA_TOPIC_PROPERTIES}) +@ConditionalOnProperty(name = "pinpoint.modules.collector.exceptiontrace.enabled", havingValue = "true") +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 000000000000..423bbdb300dd --- /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 000000000000..039e99ed4c0c --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/config/ExceptionMetricKafkaConfiguration.java @@ -0,0 +1,40 @@ +/* + * 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.entity.ExceptionMetaDataEntity; +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 kafkaExceptionMetaDataTemplate( + @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 000000000000..a1454a30e257 --- /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.ExceptionMetaData; + +import java.util.List; + +/** + * @author intr3p1d + */ +public interface ExceptionTraceDao { + void insert(List exceptionMetaData); +} 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 000000000000..b38fec720ad6 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/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.collector.dao; + +import com.navercorp.pinpoint.common.server.util.StringPrecondition; +import com.navercorp.pinpoint.exceptiontrace.collector.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.collector.mapper.ExceptionMetaDataMapper; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +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 kafkaExceptionMetaDataTemplate; + + private final ExceptionMetaDataMapper mapper; + + private final String topic; + + private final ListenableFutureCallback> resultCallback + = KafkaCallbacks.loggingCallback("Kafka(ExceptionMetaDataEntity)", logger); + + + public PinotExceptionTraceDao( + @Qualifier("kafkaExceptionMetaDataTemplate") KafkaTemplate kafkaExceptionMetaDataTemplate, + @Value("${kafka.exception.topic}") String topic, + ExceptionMetaDataMapper mapper + ) { + this.kafkaExceptionMetaDataTemplate = Objects.requireNonNull(kafkaExceptionMetaDataTemplate, "kafkaExceptionMetaDataTemplate"); + this.topic = StringPrecondition.requireHasLength(topic, "topic"); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + public void insert(List exceptionMetaData) { + Objects.requireNonNull(exceptionMetaData); + logger.info("Pinot data insert: {}", exceptionMetaData); + + for (ExceptionMetaData e : exceptionMetaData) { + ExceptionMetaDataEntity dataEntity = mapper.toEntity(e); + ListenableFuture> response = this.kafkaExceptionMetaDataTemplate.send( + topic, dataEntity + ); + response.addCallback(resultCallback); + } + } +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/entity/ExceptionMetaDataEntity.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/entity/ExceptionMetaDataEntity.java new file mode 100644 index 000000000000..77f65f0b11bf --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/entity/ExceptionMetaDataEntity.java @@ -0,0 +1,175 @@ +/* + * 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.entity; + +import java.util.List; + +/** + * @author intr3p1d + */ +public class ExceptionMetaDataEntity { + private long timestamp; + + private String transactionId; + private long spanId; + private long exceptionId; + + private String applicationServiceType; + private String applicationName; + private String agentId; + private String uriTemplate; + + private String errorClassName; + private String errorMessage; + private int exceptionDepth; + + private List stackTraceClassName; + private List stackTraceFileName; + private List stackTraceLineNumber; + private List stackTraceMethodName; + private String stackTraceHash; + + public ExceptionMetaDataEntity() { + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public long getSpanId() { + return spanId; + } + + public void setSpanId(long spanId) { + this.spanId = spanId; + } + + public long getExceptionId() { + return exceptionId; + } + + public void setExceptionId(long exceptionId) { + this.exceptionId = exceptionId; + } + + public String getApplicationServiceType() { + return applicationServiceType; + } + + public void setApplicationServiceType(String applicationServiceType) { + this.applicationServiceType = applicationServiceType; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + this.uriTemplate = uriTemplate; + } + + public String getErrorClassName() { + return errorClassName; + } + + public void setErrorClassName(String errorClassName) { + this.errorClassName = errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public int getExceptionDepth() { + return exceptionDepth; + } + + public void setExceptionDepth(int exceptionDepth) { + this.exceptionDepth = exceptionDepth; + } + + public List getStackTraceClassName() { + return stackTraceClassName; + } + + public void setStackTraceClassName(List stackTraceClassName) { + this.stackTraceClassName = stackTraceClassName; + } + + public List getStackTraceFileName() { + return stackTraceFileName; + } + + public void setStackTraceFileName(List stackTraceFileName) { + this.stackTraceFileName = stackTraceFileName; + } + + public List getStackTraceLineNumber() { + return stackTraceLineNumber; + } + + public void setStackTraceLineNumber(List stackTraceLineNumber) { + this.stackTraceLineNumber = stackTraceLineNumber; + } + + public List getStackTraceMethodName() { + return stackTraceMethodName; + } + + public void setStackTraceMethodName(List stackTraceMethodName) { + this.stackTraceMethodName = stackTraceMethodName; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public void setStackTraceHash(String stackTraceHash) { + this.stackTraceHash = stackTraceHash; + } +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapper.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapper.java new file mode 100644 index 000000000000..6ddf673353a5 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapper.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.mapper; + +import com.navercorp.pinpoint.exceptiontrace.collector.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * @author intr3p1d + */ +@Mapper(componentModel = "spring", uses = {StackTraceMapper.class}) +public interface ExceptionMetaDataMapper { + + @Mappings({ + @Mapping(source = "stackTrace", target = "stackTraceClassName", qualifiedBy = StackTraceMapper.StackTraceToClassNames.class), + @Mapping(source = "stackTrace", target = "stackTraceFileName", qualifiedBy = StackTraceMapper.StackTraceToFileNames.class), + @Mapping(source = "stackTrace", target = "stackTraceLineNumber", qualifiedBy = StackTraceMapper.StackTraceToLineNumbers.class), + @Mapping(source = "stackTrace", target = "stackTraceMethodName", qualifiedBy = StackTraceMapper.StackTraceToMethodNames.class) + }) + ExceptionMetaDataEntity toEntity(ExceptionMetaData model); + +} diff --git a/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/StackTraceMapper.java b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/StackTraceMapper.java new file mode 100644 index 000000000000..520a1061453c --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/StackTraceMapper.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.collector.mapper; + +import com.navercorp.pinpoint.common.server.mapper.MapStructUtils; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import org.mapstruct.Qualifier; +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@Component +public class StackTraceMapper { + + private final MapStructUtils mapStructUtils; + + public StackTraceMapper(MapStructUtils mapStructUtils) { + this.mapStructUtils = Objects.requireNonNull(mapStructUtils, "mapStructUtils"); + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface StackTraceToClassNames { + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface StackTraceToFileNames { + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface StackTraceToLineNumbers { + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface StackTraceToMethodNames { + } + + @StackTraceToClassNames + public List stackTraceToClassNames(List classNames) { + return classNames.stream() + .map(StackTraceElementWrapper::getClassName) + .collect(Collectors.toList()); + } + + @StackTraceToFileNames + public List stackTraceToFileNames(List fileNames) { + return fileNames.stream() + .map(StackTraceElementWrapper::getFileName) + .collect(Collectors.toList()); + } + + @StackTraceToLineNumbers + public List stackTraceToLineNumber(List lineNumbers) { + return lineNumbers.stream() + .map(StackTraceElementWrapper::getLineNumber) + .collect(Collectors.toList()); + } + + @StackTraceToMethodNames + public List stackTraceToMethodNames(List methodNames) { + return methodNames.stream() + .map(StackTraceElementWrapper::getMethodName) + .collect(Collectors.toList()); + } + +} 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 000000000000..4c77bde9c672 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/main/java/com/navercorp/pinpoint/exceptiontrace/collector/service/PinotExceptionTraceService.java @@ -0,0 +1,98 @@ +/* + * 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.ExceptionMetaDataService; +import com.navercorp.pinpoint.common.profiler.util.TransactionId; +import com.navercorp.pinpoint.common.profiler.util.TransactionIdUtils; +import com.navercorp.pinpoint.common.server.bo.exception.ExceptionMetaDataBo; +import com.navercorp.pinpoint.common.server.bo.exception.ExceptionWrapperBo; +import com.navercorp.pinpoint.common.server.bo.exception.StackTraceElementWrapperBo; +import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.exceptiontrace.collector.dao.ExceptionTraceDao; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import com.navercorp.pinpoint.loader.service.ServiceTypeRegistryService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@Service +@ConditionalOnProperty(name = "pinpoint.modules.collector.exceptiontrace.enabled", havingValue = "true") +@Validated +public class PinotExceptionTraceService implements ExceptionMetaDataService { + private final ExceptionTraceDao exceptionTraceDao; + private final ServiceTypeRegistryService registry; + + public PinotExceptionTraceService(ExceptionTraceDao exceptionTraceDao, ServiceTypeRegistryService registry) { + this.exceptionTraceDao = Objects.requireNonNull(exceptionTraceDao, "exceptionTraceDao"); + this.registry = Objects.requireNonNull(registry, "serviceTypeRegistryService"); + } + + @Override + public void save(@Valid ExceptionMetaDataBo exceptionMetaDataBo) { + List exceptionMetaData = toExceptionMetaData(exceptionMetaDataBo); + exceptionTraceDao.insert(exceptionMetaData); + } + + private List toExceptionMetaData( + ExceptionMetaDataBo exceptionMetaDataBo + ) { + List exceptionMetaData = new ArrayList<>(); + final ServiceType serviceType = registry.findServiceType(exceptionMetaDataBo.getServiceType()); + for (ExceptionWrapperBo e : exceptionMetaDataBo.getExceptionWrapperBos()) { + final List wrappers = traceElementWrappers(e.getStackTraceElements()); + exceptionMetaData.add( + ExceptionMetaData.valueOf( + e.getStartTime(), + transactionIdToString(exceptionMetaDataBo.getTransactionId()), + exceptionMetaDataBo.getSpanId(), + e.getExceptionId(), + serviceType.getName(), + exceptionMetaDataBo.getApplicationName(), + exceptionMetaDataBo.getAgentId(), + exceptionMetaDataBo.getUriTemplate(), + e.getExceptionClassName(), + e.getExceptionMessage(), + e.getExceptionDepth(), + wrappers + ) + ); + } + return exceptionMetaData; + } + + private static List traceElementWrappers(List wrapperBos) { + return wrapperBos.stream().map( + (StackTraceElementWrapperBo s) -> new StackTraceElementWrapper(s.getClassName(), s.getFileName(), s.getLineNumber(), s.getMethodName()) + ).collect(Collectors.toList()); + } + + 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 000000000000..e99d3db765d7 --- /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 000000000000..e99d3db765d7 --- /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 000000000000..e99d3db765d7 --- /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-collector/src/test/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapperTest.java b/exceptiontrace/exceptiontrace-collector/src/test/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapperTest.java new file mode 100644 index 000000000000..175913d04fe0 --- /dev/null +++ b/exceptiontrace/exceptiontrace-collector/src/test/java/com/navercorp/pinpoint/exceptiontrace/collector/mapper/ExceptionMetaDataMapperTest.java @@ -0,0 +1,117 @@ +package com.navercorp.pinpoint.exceptiontrace.collector.mapper; + + +import com.navercorp.pinpoint.common.server.mapper.MapStructUtils; +import com.navercorp.pinpoint.exceptiontrace.collector.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@ContextConfiguration(classes = { + ExceptionMetaDataMapperImpl.class, + StackTraceMapper.class, + MapStructUtils.class, + JacksonAutoConfiguration.class +}) +@ExtendWith(SpringExtension.class) +class ExceptionMetaDataMapperTest { + + private final Logger logger = LogManager.getLogger(getClass()); + private final Random random = new Random(); + + + @Autowired + ExceptionMetaDataMapper mapper; + + @Autowired + MapStructUtils mapStructUtils; + + + @Test + public void testModelToEntity() { + Throwable throwable = new RuntimeException(); + + ExceptionMetaData expected = newRandomExceptionMetaData(throwable); + ExceptionMetaDataEntity actual = mapper.toEntity(expected); + + Assertions.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + Assertions.assertEquals(expected.getTransactionId(), actual.getTransactionId()); + Assertions.assertEquals(expected.getSpanId(), actual.getSpanId()); + Assertions.assertEquals(expected.getExceptionId(), actual.getExceptionId()); + + Assertions.assertEquals(expected.getApplicationServiceType(), actual.getApplicationServiceType()); + Assertions.assertEquals(expected.getApplicationName(), actual.getApplicationName()); + Assertions.assertEquals(expected.getAgentId(), actual.getAgentId()); + Assertions.assertEquals(expected.getUriTemplate(), actual.getUriTemplate()); + + Assertions.assertEquals(expected.getErrorClassName(), actual.getErrorClassName()); + Assertions.assertEquals(expected.getErrorMessage(), actual.getErrorMessage()); + Assertions.assertEquals(expected.getExceptionDepth(), actual.getExceptionDepth()); + + Assertions.assertEquals(expected.getStackTraceHash(), actual.getStackTraceHash()); + + StackTraceElement[] expectedStackTrace = throwable.getStackTrace(); + int size = throwable.getStackTrace().length; + + List classNames = actual.getStackTraceClassName(); + List fileNames = actual.getStackTraceFileName(); + List lineNumbers = actual.getStackTraceLineNumber(); + List methodNames = actual.getStackTraceMethodName(); + + for (int i = 0; i < throwable.getStackTrace().length; i++) { + StackTraceElement stackTraceElement = expectedStackTrace[i]; + Assertions.assertEquals(stackTraceElement.getClassName(), classNames.get(i)); + Assertions.assertEquals(stackTraceElement.getFileName(), fileNames.get(i)); + Assertions.assertEquals(stackTraceElement.getLineNumber(), lineNumbers.get(i)); + Assertions.assertEquals(stackTraceElement.getMethodName(), methodNames.get(i)); + } + } + + + private ExceptionMetaData newRandomExceptionMetaData(Throwable throwable) { + List wrapperList = wrapperList(throwable); + + return new ExceptionMetaData( + random.nextLong(), + "transactionId", + random.nextLong(), + random.nextLong(), + "applicationServiceType", + "applicationName", + "agentId", + "uriTemplate", + "errorClassName", + "errorMessage", + random.nextInt(), + wrapperList, + "stackTraceHash" + ); + } + + private List wrapperList(Throwable throwable) { + StackTraceElement[] stackTrace = throwable.getStackTrace(); + + return Arrays.stream(stackTrace).map( + (StackTraceElement s) -> new StackTraceElementWrapper( + s.getClassName(), s.getFileName(), s.getLineNumber(), s.getMethodName() + ) + ).collect(Collectors.toList()); + } + +} \ 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 000000000000..3fa3a44a9f24 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/pom.xml @@ -0,0 +1,49 @@ + + + + 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 + + + com.google.guava + guava + + + org.mapstruct + mapstruct + + + + \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/ExceptionMetaData.java b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/ExceptionMetaData.java new file mode 100644 index 000000000000..34bd3571b632 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/ExceptionMetaData.java @@ -0,0 +1,228 @@ +/* + * 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.util.StringPrecondition; +import com.navercorp.pinpoint.exceptiontrace.common.util.HashUtils; + + +import java.util.List; + +/** + * @author intr3p1d + */ +public class ExceptionMetaData { + + private long timestamp; + + private String transactionId; + private long spanId; + private long exceptionId; + + private String applicationServiceType; + private String applicationName; + private String agentId; + private String uriTemplate; + + private String errorClassName; + private String errorMessage; + private int exceptionDepth; + + private List stackTrace; + + private String stackTraceHash; + + public ExceptionMetaData() { + } + + public ExceptionMetaData( + 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 static ExceptionMetaData valueOf( + long timestamp, String transactionId, long spanId, long exceptionId, + String applicationServiceType, String applicationName, String agentId, + String uriTemplate, + String errorClassName, String errorMessage, int exceptionDepth, + List wrappers + ) { + return new ExceptionMetaData( + timestamp, + transactionId, + spanId, + exceptionId, + applicationServiceType, + applicationName, + agentId, + uriTemplate, + errorClassName, + errorMessage, + exceptionDepth, + wrappers, + HashUtils.objectsToHashString(wrappers, StackTraceElementWrapper.funnel()) + ); + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public long getSpanId() { + return spanId; + } + + public void setSpanId(long spanId) { + this.spanId = spanId; + } + + public long getExceptionId() { + return exceptionId; + } + + public void setExceptionId(long exceptionId) { + this.exceptionId = exceptionId; + } + + public String getApplicationServiceType() { + return applicationServiceType; + } + + public void setApplicationServiceType(String applicationServiceType) { + this.applicationServiceType = applicationServiceType; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + this.uriTemplate = uriTemplate; + } + + public String getErrorClassName() { + return errorClassName; + } + + public void setErrorClassName(String errorClassName) { + this.errorClassName = errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public int getExceptionDepth() { + return exceptionDepth; + } + + public void setExceptionDepth(int exceptionDepth) { + this.exceptionDepth = exceptionDepth; + } + + public List getStackTrace() { + return stackTrace; + } + + public void setStackTrace(List stackTrace) { + this.stackTrace = stackTrace; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public void setStackTraceHash(String stackTraceHash) { + this.stackTraceHash = stackTraceHash; + } + + @Override + public String toString() { + return "ExceptionMetaData{" + + "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 000000000000..f42ffaad68f9 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/model/StackTraceElementWrapper.java @@ -0,0 +1,98 @@ +/* + * 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 com.google.common.hash.Funnel; + +import java.nio.charset.StandardCharsets; +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 static Funnel funnel() { + return (wrapper, into) -> into + .putString(wrapper.className, StandardCharsets.UTF_8) + .putString(wrapper.fileName, StandardCharsets.UTF_8) + .putInt(wrapper.lineNumber) + .putString(wrapper.methodName, StandardCharsets.UTF_8); + } + + 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 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 000000000000..de7a45786a74 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/pinot/PinotColumns.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.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_CLASS_NAME("stackTraceClassName"), + STACK_TRACE_FILE_NAME("stackTraceFileName"), + STACK_TRACE_LINE_NUMBER("stackTraceLineNumber"), + STACK_TRACE_METHOD_NAME("stackTraceMethodName"), + 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 000000000000..7ca07a3f4558 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/java/com/navercorp/pinpoint/exceptiontrace/common/util/HashUtils.java @@ -0,0 +1,53 @@ +/* + * 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.google.common.hash.Funnel; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; + +/** + * @author intr3p1d + */ +public final class HashUtils { + + private HashUtils() { + } + + private static final HashFunction HASH = Hashing.murmur3_128(); + + public static Hasher newHasher() { + return HASH.newHasher(); + } + + public static String objectsToHashString(Iterable objects, Funnel funnel) { + return objectsToHashCode(objects, funnel).toString(); + } + + public static HashCode objectsToHashCode(Iterable objects, Funnel funnel) { + Hasher hc = newHasher(); + for (T element: objects) { + funnel.funnel(element, hc); + } + return hc.hash(); + } + + + +} 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 000000000000..dc15e3b96e40 --- /dev/null +++ b/exceptiontrace/exceptiontrace-common/src/main/pinot/pinot-exceptionTrace-schema.json @@ -0,0 +1,77 @@ +{ + "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": "stackTraceClassName", + "dataType": "STRING", + "singleValueField": false + }, + { + "name": "stackTraceFileName", + "dataType": "STRING", + "singleValueField": false + }, + { + "name": "stackTraceLineNumber", + "dataType": "INT", + "singleValueField": false + }, + { + "name": "stackTraceMethodName", + "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 000000000000..77d203ccc828 --- /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 000000000000..edffb42b2f68 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/pom.xml @@ -0,0 +1,62 @@ + + + + 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 + + + org.mapstruct + mapstruct + + + org.springframework + spring-test + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + + \ 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 000000000000..e0cc56f8b9ed --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/ExceptionTraceWebConfig.java @@ -0,0 +1,45 @@ +/* + * 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.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * @author intr3p1d + */ +@Configuration +@ComponentScan({ + "com.navercorp.pinpoint.common.server.mapper", + "com.navercorp.pinpoint.exceptiontrace.web.config", + "com.navercorp.pinpoint.exceptiontrace.web.controller", + "com.navercorp.pinpoint.exceptiontrace.web.dao", + "com.navercorp.pinpoint.exceptiontrace.web.mapper", + "com.navercorp.pinpoint.exceptiontrace.web.service", +}) +@Import({ + ExceptionTraceWebPropertySources.class, + ExceptionTracePinotDaoConfiguration.class, + PinotConfiguration.class +}) +@ConditionalOnProperty(name = "pinpoint.modules.web.exceptiontrace.enabled", havingValue = "true") +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 000000000000..40d98b7fc971 --- /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 000000000000..9bbe03d38261 --- /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:exceptiontrace/mapper/*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 000000000000..64d822251ade --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/config/ExceptionTraceRegistryHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.web.config; + +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceSummaryEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceValueViewEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.GroupedFieldNameEntity; +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("ExceptionMetaDataEntity", ExceptionMetaDataEntity.class); + typeAliasRegistry.registerAlias("GroupedFieldNameEntity", GroupedFieldNameEntity.class); + typeAliasRegistry.registerAlias("ExceptionTraceSummaryEntity", ExceptionTraceSummaryEntity.class); + typeAliasRegistry.registerAlias("ExceptionTraceValueViewEntity", ExceptionTraceValueViewEntity.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 000000000000..934b7dd62ccd --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/controller/ExceptionTraceController.java @@ -0,0 +1,184 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.controller; + +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +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.validation.annotation.Validated; +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 javax.validation.constraints.NotBlank; +import javax.validation.constraints.PositiveOrZero; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@RestController +@RequestMapping(value = "/errors") +@Validated +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 getListOfExceptionMetaDataFromTransactionId( + @RequestParam("applicationName") @NotBlank String applicationName, + @RequestParam("agentId") @NotBlank String agentId, + @RequestParam("traceId") @NotBlank 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 getListOfExceptionMetaDataByGivenRange( + @RequestParam("applicationName") @NotBlank String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") @PositiveOrZero long from, + @RequestParam("to") @PositiveOrZero long to + ) { + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(Range.newRange(from, to)) + .setTimePrecision(DETAILED_TIME_PRECISION) + .build(); + return exceptionTraceService.getSummarizedExceptionsInRange( + queryParameter + ); + } + + @GetMapping("/errorList/groupBy") + public List getListOfExceptionMetaDataWithDynamicGroupBy( + @RequestParam("applicationName") @NotBlank String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") @PositiveOrZero long from, + @RequestParam("to") @PositiveOrZero long to, + + @RequestParam("groupBy") List groupByList + ) { + List groupByAttributes = groupByList.stream().map( + GroupByAttributes::valueOf + ).distinct().sorted().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 getCollectedExceptionMetaDataByGivenRange( + @RequestParam("applicationName") @NotBlank String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") @PositiveOrZero long from, + @RequestParam("to") @PositiveOrZero long to + ) { + + TimeWindow timeWindow = new TimeWindow(Range.newRange(from, to), DEFAULT_TIME_WINDOW_SAMPLER); + ExceptionTraceQueryParameter queryParameter = new ExceptionTraceQueryParameter.Builder() + .setApplicationName(applicationName) + .setAgentId(agentId) + .setRange(timeWindow.getWindowRange()) + .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 getCollectedExceptionMetaDataByGivenRange( + @RequestParam("applicationName") @NotBlank String applicationName, + @RequestParam(value = "agentId", required = false) String agentId, + @RequestParam("from") @PositiveOrZero long from, + @RequestParam("to") @PositiveOrZero long to, + + @RequestParam("groupBy") List groupByList + ) { + List groupByAttributes = groupByList.stream().map( + GroupByAttributes::valueOf + ).distinct().sorted().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(timeWindow.getWindowRange()) + .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 000000000000..85c0dcf20ef8 --- /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.ExceptionMetaData; +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 getSummarizedExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter); + ExceptionMetaData 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 000000000000..ab26ce168ad0 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/dao/PinotExceptionTraceDao.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.pinpoint.exceptiontrace.web.dao; + +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceSummaryEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceValueViewEntity; +import com.navercorp.pinpoint.exceptiontrace.web.mapper.ExceptionMetaDataEntityMapper; +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; +import java.util.stream.Collectors; + +/** + * @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_SUMMARIZED_QUERY = "selectSummarizedExceptions"; + 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; + + private final ExceptionMetaDataEntityMapper mapper; + + public PinotExceptionTraceDao( + @Qualifier("exceptionTracePinotSessionTemplate") SqlSessionTemplate sqlPinotSessionTemplate, + ExceptionMetaDataEntityMapper mapper + ) { + this.sqlPinotSessionTemplate = Objects.requireNonNull(sqlPinotSessionTemplate, "sqlPinotSessionTemplate"); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + public List getExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + List dataEntities = this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_QUERY, exceptionTraceQueryParameter); + return dataEntities.stream() + .map(mapper::toModel) + .collect(Collectors.toList()); + } + + @Override + public List getSummarizedExceptions(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + List dataEntities = this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_SUMMARIZED_QUERY, exceptionTraceQueryParameter); + return dataEntities.stream() + .map(mapper::toModel) + .collect(Collectors.toList()); + } + + @Override + public ExceptionMetaData getException(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + ExceptionMetaDataEntity entity = this.sqlPinotSessionTemplate.selectOne(NAMESPACE + SELECT_EXACT_QUERY, exceptionTraceQueryParameter); + return mapper.toModel(entity); + } + + @Override + public List getSummaries(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + List entities = this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_SUMMARIES_QUERY, exceptionTraceQueryParameter); + return entities.stream() + .map(mapper::entityToExceptionTraceSummary) + .collect(Collectors.toList()); + } + + @Override + public List getValueViews(ExceptionTraceQueryParameter exceptionTraceQueryParameter) { + List valueViewEntities = this.sqlPinotSessionTemplate.selectList(NAMESPACE + SELECT_VALUEVIEWS_QUERY, exceptionTraceQueryParameter); + return valueViewEntities.stream() + .map(mapper::entityToExceptionTraceValueView) + .collect(Collectors.toList()); + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionMetaDataEntity.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionMetaDataEntity.java new file mode 100644 index 000000000000..ecc3d7191375 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionMetaDataEntity.java @@ -0,0 +1,174 @@ +/* + * 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.entity; + + +/** + * @author intr3p1d + */ +public class ExceptionMetaDataEntity { + private long timestamp; + + private String transactionId; + private long spanId; + private long exceptionId; + + private String applicationServiceType; + private String applicationName; + private String agentId; + private String uriTemplate; + + private String errorClassName; + private String errorMessage; + private int exceptionDepth; + + private String stackTraceClassName; + private String stackTraceFileName; + private String stackTraceLineNumber; + private String stackTraceMethodName; + private String stackTraceHash; + + public ExceptionMetaDataEntity() { + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public long getSpanId() { + return spanId; + } + + public void setSpanId(long spanId) { + this.spanId = spanId; + } + + public long getExceptionId() { + return exceptionId; + } + + public void setExceptionId(long exceptionId) { + this.exceptionId = exceptionId; + } + + public String getApplicationServiceType() { + return applicationServiceType; + } + + public void setApplicationServiceType(String applicationServiceType) { + this.applicationServiceType = applicationServiceType; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + this.uriTemplate = uriTemplate; + } + + public String getErrorClassName() { + return errorClassName; + } + + public void setErrorClassName(String errorClassName) { + this.errorClassName = errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public int getExceptionDepth() { + return exceptionDepth; + } + + public void setExceptionDepth(int exceptionDepth) { + this.exceptionDepth = exceptionDepth; + } + + public String getStackTraceClassName() { + return stackTraceClassName; + } + + public void setStackTraceClassName(String stackTraceClassName) { + this.stackTraceClassName = stackTraceClassName; + } + + public String getStackTraceFileName() { + return stackTraceFileName; + } + + public void setStackTraceFileName(String stackTraceFileName) { + this.stackTraceFileName = stackTraceFileName; + } + + public String getStackTraceLineNumber() { + return stackTraceLineNumber; + } + + public void setStackTraceLineNumber(String stackTraceLineNumber) { + this.stackTraceLineNumber = stackTraceLineNumber; + } + + public String getStackTraceMethodName() { + return stackTraceMethodName; + } + + public void setStackTraceMethodName(String stackTraceMethodName) { + this.stackTraceMethodName = stackTraceMethodName; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public void setStackTraceHash(String stackTraceHash) { + this.stackTraceHash = stackTraceHash; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceSummaryEntity.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceSummaryEntity.java new file mode 100644 index 000000000000..2292d29a2f10 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceSummaryEntity.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.navercorp.pinpoint.exceptiontrace.web.entity; + +/** + * @author intr3p1d + */ +public class ExceptionTraceSummaryEntity extends GroupedFieldNameEntity { + private String mostRecentErrorClass; + private String mostRecentErrorMessage; + private long count; + private long firstOccurred; + private long lastOccurred; + + public ExceptionTraceSummaryEntity() { + } + + public String getMostRecentErrorClass() { + return mostRecentErrorClass; + } + + public void setMostRecentErrorClass(String mostRecentErrorClass) { + this.mostRecentErrorClass = mostRecentErrorClass; + } + + public String getMostRecentErrorMessage() { + return mostRecentErrorMessage; + } + + public void setMostRecentErrorMessage(String mostRecentErrorMessage) { + this.mostRecentErrorMessage = mostRecentErrorMessage; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + public long getFirstOccurred() { + return firstOccurred; + } + + public void setFirstOccurred(long firstOccurred) { + this.firstOccurred = firstOccurred; + } + + public long getLastOccurred() { + return lastOccurred; + } + + public void setLastOccurred(long lastOccurred) { + this.lastOccurred = lastOccurred; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceValueViewEntity.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceValueViewEntity.java new file mode 100644 index 000000000000..2418d2ced7d5 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/ExceptionTraceValueViewEntity.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.entity; + +/** + * @author intr3p1d + */ +public class ExceptionTraceValueViewEntity extends GroupedFieldNameEntity { + private String values; + + public ExceptionTraceValueViewEntity() { + } + + public String getValues() { + return values; + } + + public void setValues(String values) { + this.values = values; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/GroupedFieldNameEntity.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/GroupedFieldNameEntity.java new file mode 100644 index 000000000000..b2d7f329e2cb --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/entity/GroupedFieldNameEntity.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.entity; + +/** + * @author intr3p1d + */ +public class GroupedFieldNameEntity { + private String uriTemplate; + private String errorClassName; + private String errorMessage; + private String stackTraceHash; + + public GroupedFieldNameEntity() { + } + + public String getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + this.uriTemplate = uriTemplate; + } + + public String getErrorClassName() { + return errorClassName; + } + + public void setErrorClassName(String errorClassName) { + this.errorClassName = errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public void setStackTraceHash(String stackTraceHash) { + this.stackTraceHash = stackTraceHash; + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ExceptionMetaDataEntityMapper.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ExceptionMetaDataEntityMapper.java new file mode 100644 index 000000000000..a34f2dcbf966 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ExceptionMetaDataEntityMapper.java @@ -0,0 +1,57 @@ +/* + * 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.navercorp.pinpoint.common.server.mapper.MapStructUtils; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceSummaryEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceValueViewEntity; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceValueView; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * @author intr3p1d + */ +@Mapper(componentModel = "spring", uses = {StackTraceMapper.class, MapStructUtils.class}) +public interface ExceptionMetaDataEntityMapper { + + @Mappings( + @Mapping(source = ".", target = "stackTrace", qualifiedBy = StackTraceMapper.StringsToStackTrace.class) + ) + ExceptionMetaData toModel(ExceptionMetaDataEntity entity); + + @Mappings({ + @Mapping(source = "values", target = "values", qualifiedBy = MapStructUtils.JsonStrToList.class), + @Mapping(source = "uriTemplate", target = "groupedFieldName.uriTemplate"), + @Mapping(source = "errorClassName", target = "groupedFieldName.errorClassName"), + @Mapping(source = "errorMessage", target = "groupedFieldName.errorMessage"), + @Mapping(source = "stackTraceHash", target = "groupedFieldName.stackTraceHash") + }) + ExceptionTraceValueView entityToExceptionTraceValueView(ExceptionTraceValueViewEntity entity); + + @Mappings({ + @Mapping(source = "uriTemplate", target = "groupedFieldName.uriTemplate"), + @Mapping(source = "errorClassName", target = "groupedFieldName.errorClassName"), + @Mapping(source = "errorMessage", target = "groupedFieldName.errorMessage"), + @Mapping(source = "stackTraceHash", target = "groupedFieldName.stackTraceHash") + }) + ExceptionTraceSummary entityToExceptionTraceSummary(ExceptionTraceSummaryEntity entity); + +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceMapper.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceMapper.java new file mode 100644 index 000000000000..094dd188d2e1 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/StackTraceMapper.java @@ -0,0 +1,71 @@ +/* + * 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.navercorp.pinpoint.common.server.mapper.MapStructUtils; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionMetaDataEntity; +import org.mapstruct.Qualifier; +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author intr3p1d + */ +@Component +public class StackTraceMapper { + private final MapStructUtils mapStructUtils; + + public StackTraceMapper(MapStructUtils mapStructUtils) { + this.mapStructUtils = Objects.requireNonNull(mapStructUtils, "mapStructUtils"); + } + + @Qualifier + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface StringsToStackTrace { + + } + + @StringsToStackTrace + public List stackTrace(ExceptionMetaDataEntity entity) { + List classNameIterable = mapStructUtils.jsonStrToList(entity.getStackTraceClassName()); + List fileNameIterable = mapStructUtils.jsonStrToList(entity.getStackTraceFileName()); + List lineNumberIterable = mapStructUtils.jsonStrToList(entity.getStackTraceLineNumber()); + List methodNameIterable = mapStructUtils.jsonStrToList(entity.getStackTraceMethodName()); + + List wrappers = new ArrayList<>(); + for (int i = 0; i < classNameIterable.size(); i++) { + wrappers.add( + new StackTraceElementWrapper( + classNameIterable.get(i), + fileNameIterable.get(i), + lineNumberIterable.get(i), + methodNameIterable.get(i) + ) + ); + } + return wrappers; + } + +} 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 000000000000..1085ed8eacc8 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroup.java @@ -0,0 +1,67 @@ +/* + * 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.TimeseriesChartType; +import com.navercorp.pinpoint.metric.web.view.TimeseriesValueGroupView; + +import java.util.List; + +/** + * @author intr3p1d + */ +public class ExceptionTraceGroup implements TimeseriesValueGroupView { + + private static final TimeseriesChartType CHART_TYPE = TimeseriesChartType.bar; + private static final String UNIT = "COUNT"; + 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 list = (List) (List) exceptionTraceValueViews; + return new ExceptionTraceGroup(groupName, list); + } + + @Override + public String getGroupName() { + return groupName; + } + + @Override + public List getMetricValues() { + return values; + } + + @Override + public TimeseriesChartType getChartType() { + return CHART_TYPE; + } + + @Override + public String getUnit() { + return UNIT; + } +} 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 000000000000..9bb23b57fb0d --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceSummary.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.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @author intr3p1d + */ +public class ExceptionTraceSummary { + + private GroupedFieldName groupedFieldName; + private String mostRecentErrorClass; + private String mostRecentErrorMessage; + private long count; + private long firstOccurred; + private long lastOccurred; + + public ExceptionTraceSummary() { + } + + @JsonProperty("fieldName") + public GroupedFieldName getGroupedFieldName() { + return groupedFieldName; + } + + public void setGroupedFieldName(GroupedFieldName groupedFieldName) { + this.groupedFieldName = groupedFieldName; + } + + public String getMostRecentErrorClass() { + return mostRecentErrorClass; + } + + public void setMostRecentErrorClass(String mostRecentErrorClass) { + this.mostRecentErrorClass = mostRecentErrorClass; + } + + public String getMostRecentErrorMessage() { + return mostRecentErrorMessage; + } + + public void setMostRecentErrorMessage(String mostRecentErrorMessage) { + this.mostRecentErrorMessage = mostRecentErrorMessage; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + public long getFirstOccurred() { + return firstOccurred; + } + + public void setFirstOccurred(long firstOccurred) { + this.firstOccurred = firstOccurred; + } + + public long getLastOccurred() { + return lastOccurred; + } + + public void setLastOccurred(long lastOccurred) { + this.lastOccurred = lastOccurred; + } + + @Override + public String toString() { + return "ExceptionTraceSummary{" + + "groupedFieldName=" + groupedFieldName + + ", mostRecentErrorClass='" + mostRecentErrorClass + '\'' + + ", mostRecentErrorMessage='" + mostRecentErrorMessage + '\'' + + ", 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 000000000000..982638c17514 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceValueView.java @@ -0,0 +1,76 @@ +/* + * 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.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.navercorp.pinpoint.common.util.StringUtils; +import com.navercorp.pinpoint.metric.web.view.TimeSeriesValueView; + +import java.util.List; + +/** + * @author intr3p1d + */ +public class ExceptionTraceValueView implements TimeSeriesValueView { + + private static final String TOTAL_FIELDNAME = "total"; + private GroupedFieldName groupedFieldName; + private List values; + + public ExceptionTraceValueView() { + } + + public ExceptionTraceValueView(List values) { + this.values = values; + } + + @Override + public String getFieldName() { + return groupedFieldName != null ? StringUtils.defaultString(groupedFieldName.inAString(), TOTAL_FIELDNAME) : TOTAL_FIELDNAME; + } + + @JsonIgnore + public GroupedFieldName getGroupedFieldName() { + return groupedFieldName; + } + + public void setGroupedFieldName(GroupedFieldName groupedFieldName) { + this.groupedFieldName = groupedFieldName; + } + + @Override + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + @Override + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getTags() { + return null; + } + + @Override + public String toString() { + return "ExceptionTraceValueView{" + + "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 000000000000..ae50cdea7984 --- /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 000000000000..2d6bc0a436a5 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/model/GroupedFieldName.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.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.navercorp.pinpoint.common.util.StringUtils; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author intr3p1d + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupedFieldName { + + private String uriTemplate; + private String errorClassName; + private String errorMessage; + private String stackTraceHash; + + public GroupedFieldName() { + } + + public String inAString() { + return Stream.of(uriTemplate, errorClassName, errorMessage, stackTraceHash) + .filter(StringUtils::hasLength) + .collect(Collectors.joining(", ")); + } + + public String getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(String uriTemplate) { + this.uriTemplate = uriTemplate; + } + + public String getErrorClassName() { + return errorClassName; + } + + public void setErrorClassName(String errorClassName) { + this.errorClassName = errorClassName; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getStackTraceHash() { + return stackTraceHash; + } + + public void setStackTraceHash(String stackTraceHash) { + this.stackTraceHash = stackTraceHash; + } + + @Override + public String toString() { + return "GroupedFieldName{" + + "uriTemplate='" + uriTemplate + '\'' + + ", errorClassName='" + errorClassName + '\'' + + ", errorMessage='" + errorMessage + '\'' + + ", stackTraceHash='" + stackTraceHash + '\'' + + '}'; + } +} 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 000000000000..205390cb5d20 --- /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.ExceptionMetaData; +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 getSummarizedExceptionsInRange(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 000000000000..0c373961c2eb --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/service/ExceptionTraceServiceImpl.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.web.service; + +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +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 final ExceptionTraceDao exceptionTraceDao; + + public ExceptionTraceServiceImpl(ExceptionTraceDao exceptionTraceDao) { + this.exceptionTraceDao = Objects.requireNonNull(exceptionTraceDao, "exceptionTraceDao"); + } + + @Override + public List getTransactionExceptions( + ExceptionTraceQueryParameter queryParameter + ) { + return applyQueryFunction( + queryParameter, + this::getExeptionMetaDataList + ); + } + + @Override + public List getSummarizedExceptionsInRange(ExceptionTraceQueryParameter queryParameter) { + return applyQueryFunction( + queryParameter, + this::getSummarizedExeptionMetaDataList + ); + } + + @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 getExeptionMetaDataList(ExceptionTraceQueryParameter queryParameter) { + return exceptionTraceDao.getExceptions(queryParameter); + } + + private List getSummarizedExeptionMetaDataList(ExceptionTraceQueryParameter queryParameter) { + return exceptionTraceDao.getSummarizedExceptions(queryParameter); + } + + private List getExceptionTraceSummaries(ExceptionTraceQueryParameter queryParameter) { + return exceptionTraceDao.getSummaries(queryParameter); + } + + private List getExceptionTraceValueViews(ExceptionTraceQueryParameter queryParameter) { + return exceptionTraceDao.getValueViews(queryParameter); + } +} 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 000000000000..6785fc199a37 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java @@ -0,0 +1,156 @@ +/* + * 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.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.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +public class ExceptionTraceQueryParameter extends QueryParameter { + + public static final int STACKTRACE_COUNT = 3; + 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 000000000000..70a749021490 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/view/ExceptionTraceView.java @@ -0,0 +1,94 @@ +/* + * 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; + } + + @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/exceptiontrace/mapper/ExceptionTraceMapper.xml b/exceptiontrace/exceptiontrace-web/src/main/resources/exceptiontrace/mapper/ExceptionTraceMapper.xml new file mode 100644 index 000000000000..493bb8a9017f --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/main/resources/exceptiontrace/mapper/ExceptionTraceMapper.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + exceptionTrace + + + + + + ${attr.getAttributeName} + + + + + + + + + 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, + stackTraceClassName, + stackTraceFileName, + stackTraceLineNumber, + stackTraceMethodName, + 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, + arraySliceString(stackTraceClassName, 0, + ${STACKTRACE_COUNT} + ) + as + "stackTraceClassName", + arraySliceString + ( + stackTraceFileName, + 0, + ${STACKTRACE_COUNT} + ) + as + "stackTraceFileName", + arraySliceInt + ( + stackTraceLineNumber, + 0, + ${STACKTRACE_COUNT} + ) + as + "stackTraceLineNumber", + arraySliceString + ( + stackTraceMethodName, + 0, + ${STACKTRACE_COUNT} + ) + as + "stackTraceMethodName", + 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 000000000000..87b78431e4e1 --- /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 000000000000..87b78431e4e1 --- /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/ExceptionMetaDataEntityMapperTest.java b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ExceptionMetaDataEntityMapperTest.java new file mode 100644 index 000000000000..812cd56340b4 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/mapper/ExceptionMetaDataEntityMapperTest.java @@ -0,0 +1,190 @@ +package com.navercorp.pinpoint.exceptiontrace.web.mapper; + + +import com.navercorp.pinpoint.common.server.mapper.MapStructUtils; +import com.navercorp.pinpoint.exceptiontrace.common.model.ExceptionMetaData; +import com.navercorp.pinpoint.exceptiontrace.common.model.StackTraceElementWrapper; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionMetaDataEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceSummaryEntity; +import com.navercorp.pinpoint.exceptiontrace.web.entity.ExceptionTraceValueViewEntity; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceSummary; +import com.navercorp.pinpoint.exceptiontrace.web.model.ExceptionTraceValueView; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author intr3p1d + */ +@ContextConfiguration(classes = { + ExceptionMetaDataEntityMapperImpl.class, + StackTraceMapper.class, + MapStructUtils.class, + JacksonAutoConfiguration.class +}) +@ExtendWith(SpringExtension.class) +class ExceptionMetaDataEntityMapperTest { + private static final Logger logger = LogManager.getLogger(ExceptionMetaDataEntityMapper.class); + private final Random random = new Random(); + + + @Autowired + private ExceptionMetaDataEntityMapper mapper; + + @Autowired + private MapStructUtils mapStructUtils; + + + @Test + public void testEntityToModel() { + Throwable throwable = new RuntimeException(); + + ExceptionMetaDataEntity expected = newExceptionMetaDataEntity(throwable); + ExceptionMetaData actual = mapper.toModel(expected); + + Assertions.assertEquals(expected.getTimestamp(), actual.getTimestamp()); + Assertions.assertEquals(expected.getTransactionId(), actual.getTransactionId()); + Assertions.assertEquals(expected.getSpanId(), actual.getSpanId()); + Assertions.assertEquals(expected.getExceptionId(), actual.getExceptionId()); + + Assertions.assertEquals(expected.getApplicationServiceType(), actual.getApplicationServiceType()); + Assertions.assertEquals(expected.getApplicationName(), actual.getApplicationName()); + Assertions.assertEquals(expected.getAgentId(), actual.getAgentId()); + Assertions.assertEquals(expected.getUriTemplate(), actual.getUriTemplate()); + + Assertions.assertEquals(expected.getErrorClassName(), actual.getErrorClassName()); + Assertions.assertEquals(expected.getErrorMessage(), actual.getErrorMessage()); + Assertions.assertEquals(expected.getExceptionDepth(), actual.getExceptionDepth()); + + Assertions.assertEquals(expected.getStackTraceHash(), actual.getStackTraceHash()); + + + int size = throwable.getStackTrace().length; + + String classNames = expected.getStackTraceClassName(); + String fileNames = expected.getStackTraceFileName(); + String lineNumbers = expected.getStackTraceLineNumber(); + String methodNames = expected.getStackTraceMethodName(); + + List classNameIter = convertToList(classNames); + List fileNameIter = convertToList(fileNames); + List lineNumberIter = convertToList(lineNumbers); + List methodNameIter = convertToList(methodNames); + + List actualStackTrace = actual.getStackTrace(); + + for (int i = 0; i < size; i++) { + Assertions.assertEquals(classNameIter.get(i), actualStackTrace.get(i).getClassName()); + Assertions.assertEquals(fileNameIter.get(i), actualStackTrace.get(i).getFileName()); + Assertions.assertEquals(lineNumberIter.get(i), actualStackTrace.get(i).getLineNumber()); + Assertions.assertEquals(methodNameIter.get(i), actualStackTrace.get(i).getMethodName()); + } + } + + private ExceptionMetaDataEntity newExceptionMetaDataEntity(Throwable throwable) { + ExceptionMetaDataEntity dataEntity = new ExceptionMetaDataEntity(); + + dataEntity.setTimestamp(random.nextLong()); + dataEntity.setTransactionId("transactionId"); + dataEntity.setSpanId(random.nextLong()); + dataEntity.setExceptionId(random.nextLong()); + dataEntity.setApplicationServiceType("applicationServiceType"); + dataEntity.setApplicationName("applicationName"); + dataEntity.setAgentId("agentId"); + dataEntity.setUriTemplate("uriTemplate"); + dataEntity.setErrorClassName("errorClassName"); + dataEntity.setErrorMessage("errorMessage"); + dataEntity.setExceptionDepth(random.nextInt()); + dataEntity.setStackTraceHash("stackTraceHash"); + + List elements = List.of(throwable.getStackTrace()); + + dataEntity.setStackTraceClassName(toFlattenedString(elements, StackTraceElement::getClassName)); + dataEntity.setStackTraceFileName(toFlattenedString(elements, StackTraceElement::getFileName)); + dataEntity.setStackTraceLineNumber(toFlattenedString(elements, StackTraceElement::getLineNumber)); + dataEntity.setStackTraceMethodName(toFlattenedString(elements, StackTraceElement::getMethodName)); + return dataEntity; + } + + private String toFlattenedString(List elements, Function getter) { + List collect = elements.stream().map(getter).collect(Collectors.toList()); + return mapStructUtils.listToJsonStr(collect); + } + + public List convertToList(String json) { + return mapStructUtils.jsonStrToList(json); + } + + @Test + public void testEntityToValueView() { + ExceptionTraceValueViewEntity expected = newExceptionMetaDataEntity(); + + ExceptionTraceValueView actual = mapper.entityToExceptionTraceValueView(expected); + + Assertions.assertEquals(expected.getUriTemplate(), actual.getGroupedFieldName().getUriTemplate()); + Assertions.assertEquals(expected.getErrorClassName(), actual.getGroupedFieldName().getErrorClassName()); + Assertions.assertEquals(expected.getErrorMessage(), actual.getGroupedFieldName().getErrorMessage()); + Assertions.assertEquals(expected.getStackTraceHash(), actual.getGroupedFieldName().getStackTraceHash()); + + Assertions.assertNotNull(actual.getValues()); + Assertions.assertFalse(actual.getValues().isEmpty()); + } + + + private ExceptionTraceValueViewEntity newExceptionMetaDataEntity() { + ExceptionTraceValueViewEntity dataEntity = new ExceptionTraceValueViewEntity(); + + dataEntity.setUriTemplate("uriTemplate"); + dataEntity.setErrorClassName("errorClassName"); + dataEntity.setErrorMessage("errorMessage"); + dataEntity.setStackTraceHash("stackTraceHash"); + + dataEntity.setValues("[0,83,2,12]"); + return dataEntity; + } + + @Test + public void testEntityToSummary() { + ExceptionTraceSummaryEntity expected = newExceptionTraceSymmaryEntity(); + + ExceptionTraceSummary actual = mapper.entityToExceptionTraceSummary(expected); + + Assertions.assertEquals(expected.getMostRecentErrorClass(), actual.getMostRecentErrorClass()); + Assertions.assertEquals(expected.getMostRecentErrorMessage(), actual.getMostRecentErrorMessage()); + Assertions.assertEquals(expected.getCount(), actual.getCount()); + Assertions.assertEquals(expected.getFirstOccurred(), actual.getFirstOccurred()); + Assertions.assertEquals(expected.getLastOccurred(), actual.getLastOccurred()); + + Assertions.assertEquals(expected.getUriTemplate(), actual.getGroupedFieldName().getUriTemplate()); + Assertions.assertEquals(expected.getErrorClassName(), actual.getGroupedFieldName().getErrorClassName()); + Assertions.assertEquals(expected.getErrorMessage(), actual.getGroupedFieldName().getErrorMessage()); + Assertions.assertEquals(expected.getStackTraceHash(), actual.getGroupedFieldName().getStackTraceHash()); + } + + private ExceptionTraceSummaryEntity newExceptionTraceSymmaryEntity() { + ExceptionTraceSummaryEntity entity = new ExceptionTraceSummaryEntity(); + + entity.setMostRecentErrorClass("MostRecentErrorClass"); + entity.setMostRecentErrorMessage("MostRecentErrorMessage"); + entity.setCount(random.nextLong()); + entity.setFirstOccurred(random.nextLong()); + entity.setLastOccurred(random.nextLong()); + + entity.setUriTemplate("uriTemplate"); + entity.setErrorClassName("errorClassName"); + entity.setErrorMessage("errorMessage"); + entity.setStackTraceHash("stackTraceHash"); + return entity; + } +} \ No newline at end of file diff --git a/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroupTest.java b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroupTest.java new file mode 100644 index 000000000000..5c7d0bd35a66 --- /dev/null +++ b/exceptiontrace/exceptiontrace-web/src/test/java/com/navercorp/pinpoint/exceptiontrace/web/model/ExceptionTraceGroupTest.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.metric.web.view.TimeSeriesValueView; +import org.junit.jupiter.api.Test; + +import java.util.List; + + +class ExceptionTraceGroupTest { + + @Test + void newGroupFromValueViews_casting() { + List view = List.of( + new ExceptionTraceValueView(List.of(1, 2, 3)), + new ExceptionTraceValueView(List.of(4, 5, 6)) + ); + ExceptionTraceGroup group = ExceptionTraceGroup.newGroupFromValueViews("test", view); + @SuppressWarnings("unused") + List metricValues = group.getMetricValues(); + } +} \ No newline at end of file diff --git a/exceptiontrace/pom.xml b/exceptiontrace/pom.xml new file mode 100644 index 000000000000..349f8196ee3a --- /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 b19176b761e1..bc187eafd7b3 100644 --- a/metric-module/collector-starter/pom.xml +++ b/metric-module/collector-starter/pom.xml @@ -48,6 +48,10 @@ com.navercorp.pinpoint pinpoint-log-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 808481dee741..777fe42a99c1 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 @@ -8,6 +8,7 @@ import com.navercorp.pinpoint.common.server.util.ServerBootLogger; import com.navercorp.pinpoint.inspector.collector.InspectorCollectorApp; import com.navercorp.pinpoint.log.collector.LogCollectorModule; +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; @@ -58,7 +59,8 @@ public static void main(String[] args) { logger.info(String.format("Start %s collector", CollectorType.BASIC)); SpringApplicationBuilder collectorAppBuilder = createAppBuilder(builder, 15400, BasicCollectorApp.class, - UriStatCollectorConfig.class + UriStatCollectorConfig.class, + ExceptionTraceCollectorConfig.class ); collectorAppBuilder.listeners(new AdditionalProfileListener("metric")); collectorAppBuilder.listeners(new AdditionalProfileListener("uri")); @@ -70,6 +72,7 @@ public static void main(String[] args) { SpringApplicationBuilder collectorAppBuilder = createAppBuilder(builder, 15400, BasicCollectorApp.class, UriStatCollectorConfig.class, + ExceptionTraceCollectorConfig.class, InspectorCollectorApp.class ); collectorAppBuilder.build().run(args); diff --git a/metric-module/collector-starter/src/main/resources/application.yml b/metric-module/collector-starter/src/main/resources/application.yml index 27f5fbffa822..ae1c49140d63 100644 --- a/metric-module/collector-starter/src/main/resources/application.yml +++ b/metric-module/collector-starter/src/main/resources/application.yml @@ -3,4 +3,10 @@ spring: allow-bean-definition-overriding: false # web-application-type: none profiles: - active: local \ No newline at end of file + active: local + +pinpoint: + modules: + collector: + exceptiontrace: + enabled: true diff --git a/metric-module/web-starter/pom.xml b/metric-module/web-starter/pom.xml index 551e2e94d660..51b236b9c422 100644 --- a/metric-module/web-starter/pom.xml +++ b/metric-module/web-starter/pom.xml @@ -52,6 +52,10 @@ com.navercorp.pinpoint pinpoint-log-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 7b12ce254c7b..2871659e8801 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 @@ -21,6 +21,7 @@ import com.navercorp.pinpoint.inspector.web.InspectorWebApp; import com.navercorp.pinpoint.log.web.LogWebModule; 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.redis.RedisPropertySources; import com.navercorp.pinpoint.uristat.web.UriStatWebConfig; @@ -66,7 +67,8 @@ public static void main(String[] args) { MetricWebApp.class, UriStatWebConfig.class, InspectorWebApp.class, - LogWebModule.class + LogWebModule.class, + ExceptionTraceWebConfig.class ); starter.addProfiles("uri", "metric"); starter.start(args); diff --git a/metric-module/web-starter/src/main/resources/application.yml b/metric-module/web-starter/src/main/resources/application.yml index fc2a54609544..0deec9c04282 100644 --- a/metric-module/web-starter/src/main/resources/application.yml +++ b/metric-module/web-starter/src/main/resources/application.yml @@ -12,4 +12,10 @@ server: include-binding-errors: always include-stacktrace: always whitelabel: - enabled: true \ No newline at end of file + enabled: true + +pinpoint: + modules: + web: + exceptiontrace: + enabled: true diff --git a/pom.xml b/pom.xml index ae090e6d4757..9389046d14c5 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,7 @@ user batch-alarmsender channel + exceptiontrace @@ -161,6 +162,7 @@ 2.15.2 1.33 + 1.5.5.Final 4.5.13 4.4.14 @@ -525,6 +527,21 @@ pinpoint-realtime-common ${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 @@ -885,6 +902,12 @@ snakeyaml ${snakeyaml.version} + + org.mapstruct + mapstruct + ${mapstruct.version} + + org.apache.hbase