From 9143f1342d5cbdab1876d39006e7d8d5743d56a6 Mon Sep 17 00:00:00 2001 From: zhang-wei Date: Thu, 3 Sep 2020 17:12:07 +0800 Subject: [PATCH] Support HTTP api for browser recevier (#5429) * support http api --- .../en/protocols/Browser-HTTP-API-Protocol.md | 108 ++++++++++ docs/en/protocols/Browser-Protocol.md | 3 +- .../provider/BrowserModuleProvider.java | 22 +- .../{ => grpc}/BrowserPerfServiceHandler.java | 2 +- ...owserErrorLogReportBaseServletHandler.java | 96 +++++++++ ...owserErrorLogReportListServletHandler.java | 70 +++++++ ...serErrorLogReportSingleServletHandler.java | 56 +++++ .../BrowserPerfDataReportServletHandler.java | 112 ++++++++++ .../rest/BrowserReportServletHandlerTest.java | 197 ++++++++++++++++++ 9 files changed, 662 insertions(+), 4 deletions(-) create mode 100644 docs/en/protocols/Browser-HTTP-API-Protocol.md rename oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/{ => grpc}/BrowserPerfServiceHandler.java (99%) create mode 100644 oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java create mode 100644 oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java create mode 100644 oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java create mode 100644 oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java create mode 100644 oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java diff --git a/docs/en/protocols/Browser-HTTP-API-Protocol.md b/docs/en/protocols/Browser-HTTP-API-Protocol.md new file mode 100644 index 0000000000..25d2e161cc --- /dev/null +++ b/docs/en/protocols/Browser-HTTP-API-Protocol.md @@ -0,0 +1,108 @@ +# HTTP API Protocol + +HTTP API Protocol defines the API data format, including api request and response data format. +They use the HTTP1.1 wrapper of the official [SkyWalking Browser Protocol](Browser-Protocol.md). Read it for more details. + +## Performance Data Report + +Detail information about data format can be found in [BrowserPerf.proto](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto). + +### POST http://localhost:12800/browser/perfData + +Send a performance data object with JSON format. + +Input: + +```json +{ + "service": "web", + "serviceVersion": "v0.0.1", + "pagePath": "/index.html", + "redirectTime": 10, + "dnsTime": 10, + "ttfbTime": 10, + "tcpTime": 10, + "transTime": 10, + "domAnalysisTime": 10, + "fptTime": 10, + "domReadyTime": 10, + "loadPageTime": 10, + "resTime": 10, + "sslTime": 10, + "ttlTime": 10, + "firstPackTime": 10, + "fmpTime": 10 +} +``` + +OutPut: + +```json + +``` + +## Error Log Report + +Detail information about data format can be found in [BrowserPerf.proto](https://github.com/apache/skywalking-data-collect-protocol/blob/master/Browser/BrowserPerf.proto). + +### POST http://localhost:12800/browser/errorLogs + +Send an error log object list with JSON format. + +Input: + +```json +[ + { + "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b01", + "service": "web", + "serviceVersion": "v0.0.1", + "pagePath": "/index.html", + "category": "ajax", + "message": "error", + "line": 1, + "col": 1, + "stack": "error", + "errorUrl": "/index.html" + }, + { + "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b02", + "service": "web", + "serviceVersion": "v0.0.1", + "pagePath": "/index.html", + "category": "ajax", + "message": "error", + "line": 1, + "col": 1, + "stack": "error", + "errorUrl": "/index.html" + } +] +``` + +### POST http://localhost:12800/browser/errorLog + +Send a single error log object with JSON format. + +Input: + +```json +{ + "uniqueId": "55ec6178-3fb7-43ef-899c-a26944407b01", + "service": "web", + "serviceVersion": "v0.0.1", + "pagePath": "/index.html", + "category": "ajax", + "message": "error", + "line": 1, + "col": 1, + "stack": "error", + "errorUrl": "/index.html" +} +``` + +OutPut: + +```json + +``` diff --git a/docs/en/protocols/Browser-Protocol.md b/docs/en/protocols/Browser-Protocol.md index 679ecaf06a..a88a77aa76 100644 --- a/docs/en/protocols/Browser-Protocol.md +++ b/docs/en/protocols/Browser-Protocol.md @@ -4,7 +4,8 @@ Browser protocol describes the data format between [skywalking-client-js](https: ## Overview -Browser protocol is defined and provided in [gRPC format](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto). +Browser protocol is defined and provided in [gRPC format](https://github.com/apache/skywalking-data-collect-protocol/blob/master/browser/BrowserPerf.proto), +also implemented in [HTTP 1.1](Browser-HTTP-API-Protocol.md) ### Send performance data and error log diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java index bc58caf016..aa137283e7 100644 --- a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/BrowserModuleProvider.java @@ -21,13 +21,17 @@ import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule; import org.apache.skywalking.oap.server.core.CoreModule; import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService; import org.apache.skywalking.oap.server.core.server.GRPCHandlerRegister; +import org.apache.skywalking.oap.server.core.server.JettyHandlerRegister; import org.apache.skywalking.oap.server.library.module.ModuleConfig; import org.apache.skywalking.oap.server.library.module.ModuleDefine; import org.apache.skywalking.oap.server.library.module.ModuleProvider; import org.apache.skywalking.oap.server.library.module.ModuleStartException; import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException; import org.apache.skywalking.oap.server.receiver.browser.module.BrowserModule; -import org.apache.skywalking.oap.server.receiver.browser.provider.handler.BrowserPerfServiceHandler; +import org.apache.skywalking.oap.server.receiver.browser.provider.handler.grpc.BrowserPerfServiceHandler; +import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserErrorLogReportListServletHandler; +import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserErrorLogReportSingleServletHandler; +import org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest.BrowserPerfDataReportServletHandler; import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager; import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.listener.ErrorLogRecordListener; import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.listener.MultiScopesErrorLogAnalysisListener; @@ -69,10 +73,24 @@ public class BrowserModuleProvider extends ModuleProvider { GRPCHandlerRegister grpcHandlerRegister = getManager().find(SharingServerModule.NAME) .provider().getService(GRPCHandlerRegister.class); - + // grpc grpcHandlerRegister.addHandler( new BrowserPerfServiceHandler( getManager(), moduleConfig, perfDataListenerManager(), errorLogListenerManager())); + + // rest + JettyHandlerRegister jettyHandlerRegister = getManager().find(SharingServerModule.NAME) + .provider() + .getService(JettyHandlerRegister.class); + // performance + jettyHandlerRegister.addHandler( + new BrowserPerfDataReportServletHandler(getManager(), moduleConfig, perfDataListenerManager())); + // error log + ErrorLogParserListenerManager errorLogParserListenerManager = errorLogListenerManager(); + jettyHandlerRegister.addHandler( + new BrowserErrorLogReportSingleServletHandler(getManager(), moduleConfig, errorLogParserListenerManager)); + jettyHandlerRegister.addHandler( + new BrowserErrorLogReportListServletHandler(getManager(), moduleConfig, errorLogParserListenerManager)); } @Override diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java similarity index 99% rename from oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java rename to oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java index 3b1f6331fa..7be64380ed 100644 --- a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/BrowserPerfServiceHandler.java +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/grpc/BrowserPerfServiceHandler.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.skywalking.oap.server.receiver.browser.provider.handler; +package org.apache.skywalking.oap.server.receiver.browser.provider.handler.grpc; import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java new file mode 100644 index 0000000000..ccf7c80405 --- /dev/null +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportBaseServletHandler.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest; + +import java.io.IOException; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog; +import org.apache.skywalking.oap.server.library.module.ModuleManager; +import org.apache.skywalking.oap.server.library.server.jetty.JettyHandler; +import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogAnalyzer; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager; +import org.apache.skywalking.oap.server.telemetry.TelemetryModule; +import org.apache.skywalking.oap.server.telemetry.api.CounterMetrics; +import org.apache.skywalking.oap.server.telemetry.api.HistogramMetrics; +import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator; +import org.apache.skywalking.oap.server.telemetry.api.MetricsTag; + +@Slf4j +public abstract class BrowserErrorLogReportBaseServletHandler extends JettyHandler { + private final ModuleManager moduleManager; + private final BrowserServiceModuleConfig config; + private final ErrorLogParserListenerManager errorLogListenerManager; + + private final HistogramMetrics errorLogHistogram; + private final CounterMetrics logErrorCounter; + + public BrowserErrorLogReportBaseServletHandler(ModuleManager moduleManager, + BrowserServiceModuleConfig config, + ErrorLogParserListenerManager errorLogListenerManager) { + this.moduleManager = moduleManager; + this.config = config; + this.errorLogListenerManager = errorLogListenerManager; + + MetricsCreator metricsCreator = moduleManager.find(TelemetryModule.NAME) + .provider() + .getService(MetricsCreator.class); + + errorLogHistogram = metricsCreator.createHistogramMetric( + "browser_error_log_in_latency", "The process latency of browser error log", new MetricsTag.Keys("protocol"), + new MetricsTag.Values("grpc") + ); + logErrorCounter = metricsCreator.createCounter( + "browser_error_log_analysis_error_count", "The error number of browser error log analysis", + new MetricsTag.Keys("protocol"), new MetricsTag.Values("http") + ); + } + + @Override + protected void doGet(final HttpServletRequest req, + final HttpServletResponse resp) throws ServletException, IOException { + throw new UnsupportedOperationException(); + } + + @Override + protected void doPost(final HttpServletRequest req, + final HttpServletResponse resp) throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("receive browser error log"); + } + + HistogramMetrics.Timer timer = errorLogHistogram.createTimer(); + try { + for (BrowserErrorLog browserErrorLog : parseBrowserErrorLog(req)) { + ErrorLogAnalyzer analyzer = new ErrorLogAnalyzer(moduleManager, errorLogListenerManager, config); + analyzer.doAnalysis(browserErrorLog); + } + } catch (Throwable e) { + log.error(e.getMessage(), e); + logErrorCounter.inc(); + } finally { + timer.finish(); + } + } + + protected abstract List parseBrowserErrorLog(HttpServletRequest request) throws IOException; +} diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java new file mode 100644 index 0000000000..b1aae85c1d --- /dev/null +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportListServletHandler.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog; +import org.apache.skywalking.oap.server.library.module.ModuleManager; +import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils; +import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager; + +public class BrowserErrorLogReportListServletHandler extends BrowserErrorLogReportBaseServletHandler { + private final Gson gson = new Gson(); + + public BrowserErrorLogReportListServletHandler(final ModuleManager moduleManager, + final BrowserServiceModuleConfig config, + final ErrorLogParserListenerManager errorLogListenerManager) { + super(moduleManager, config, errorLogListenerManager); + } + + @Override + protected List parseBrowserErrorLog(final HttpServletRequest request) throws IOException { + BufferedReader reader = request.getReader(); + String line; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + final JsonArray array = gson.fromJson(stringBuilder.toString(), JsonArray.class); + if (array.size() == 0) { + return Collections.emptyList(); + } + + final ArrayList errorLogs = new ArrayList<>(array.size()); + for (JsonElement element : array) { + BrowserErrorLog.Builder builder = BrowserErrorLog.newBuilder(); + ProtoBufJsonUtils.fromJSON(element.toString(), builder); + errorLogs.add(builder.build()); + } + return errorLogs; + } + + @Override + public String pathSpec() { + return "/browser/errorLogs"; + } +} diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java new file mode 100644 index 0000000000..1054486e66 --- /dev/null +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserErrorLogReportSingleServletHandler.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog; +import org.apache.skywalking.oap.server.library.module.ModuleManager; +import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils; +import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.errorlog.ErrorLogParserListenerManager; + +public class BrowserErrorLogReportSingleServletHandler extends BrowserErrorLogReportBaseServletHandler { + public BrowserErrorLogReportSingleServletHandler(final ModuleManager moduleManager, + final BrowserServiceModuleConfig config, + final ErrorLogParserListenerManager errorLogListenerManager) { + super(moduleManager, config, errorLogListenerManager); + } + + @Override + protected List parseBrowserErrorLog(final HttpServletRequest request) throws IOException { + BufferedReader reader = request.getReader(); + String line; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + + BrowserErrorLog.Builder builder = BrowserErrorLog.newBuilder(); + ProtoBufJsonUtils.fromJSON(stringBuilder.toString(), builder); + return Collections.singletonList(builder.build()); + } + + @Override + public String pathSpec() { + return "/browser/errorLog"; + } +} diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java new file mode 100644 index 0000000000..e0bec011e8 --- /dev/null +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserPerfDataReportServletHandler.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest; + +import java.io.BufferedReader; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserPerfData; +import org.apache.skywalking.oap.server.library.module.ModuleManager; +import org.apache.skywalking.oap.server.library.server.jetty.JettyHandler; +import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils; +import org.apache.skywalking.oap.server.receiver.browser.provider.BrowserServiceModuleConfig; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.performance.PerfDataAnalyzer; +import org.apache.skywalking.oap.server.receiver.browser.provider.parser.performance.PerfDataParserListenerManager; +import org.apache.skywalking.oap.server.telemetry.TelemetryModule; +import org.apache.skywalking.oap.server.telemetry.api.CounterMetrics; +import org.apache.skywalking.oap.server.telemetry.api.HistogramMetrics; +import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator; +import org.apache.skywalking.oap.server.telemetry.api.MetricsTag; + +@Slf4j +public class BrowserPerfDataReportServletHandler extends JettyHandler { + private final ModuleManager moduleManager; + private final BrowserServiceModuleConfig config; + private final PerfDataParserListenerManager perfDataListenerManager; + + private final HistogramMetrics perfHistogram; + private final CounterMetrics perfErrorCounter; + + public BrowserPerfDataReportServletHandler(ModuleManager moduleManager, + BrowserServiceModuleConfig config, + PerfDataParserListenerManager perfDataListenerManager) { + this.moduleManager = moduleManager; + this.config = config; + this.perfDataListenerManager = perfDataListenerManager; + + MetricsCreator metricsCreator = moduleManager.find(TelemetryModule.NAME) + .provider() + .getService(MetricsCreator.class); + + perfHistogram = metricsCreator.createHistogramMetric( + "browser_perf_data_in_latency", "The process latency of browser performance data", + new MetricsTag.Keys("protocol"), new MetricsTag.Values("http") + ); + perfErrorCounter = metricsCreator.createCounter( + "browser_perf_data_analysis_error_count", "The error number of browser performance data analysis", + new MetricsTag.Keys("protocol"), new MetricsTag.Values("http") + ); + } + + @Override + protected void doGet(final HttpServletRequest req, + final HttpServletResponse resp) throws ServletException, IOException { + throw new UnsupportedOperationException(); + } + + @Override + protected void doPost(final HttpServletRequest req, + final HttpServletResponse resp) throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("receive browser performance data"); + } + + HistogramMetrics.Timer timer = perfHistogram.createTimer(); + try { + BrowserPerfData browserPerfData = parseBrowserPerfData(req); + PerfDataAnalyzer analyzer = new PerfDataAnalyzer(moduleManager, perfDataListenerManager, config); + analyzer.doAnalysis(browserPerfData); + } catch (Throwable e) { + log.error(e.getMessage(), e); + perfErrorCounter.inc(); + } finally { + timer.finish(); + } + } + + protected BrowserPerfData parseBrowserPerfData(HttpServletRequest request) throws IOException { + BufferedReader reader = request.getReader(); + String line; + StringBuilder stringBuilder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + + BrowserPerfData.Builder builder = BrowserPerfData.newBuilder(); + ProtoBufJsonUtils.fromJSON(stringBuilder.toString(), builder); + return builder.build(); + } + + @Override + public String pathSpec() { + return "/browser/perfData"; + } +} diff --git a/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java new file mode 100644 index 0000000000..b3dd3330c1 --- /dev/null +++ b/oap-server/server-receiver-plugin/skywalking-browser-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/browser/provider/handler/rest/BrowserReportServletHandlerTest.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.receiver.browser.provider.handler.rest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserErrorLog; +import org.apache.skywalking.apm.network.language.agent.v3.BrowserPerfData; +import org.apache.skywalking.apm.network.language.agent.v3.ErrorCategory; +import org.apache.skywalking.oap.server.library.module.ModuleManager; +import org.apache.skywalking.oap.server.telemetry.TelemetryModule; +import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator; +import org.apache.skywalking.oap.server.telemetry.none.MetricsCreatorNoop; +import org.apache.skywalking.oap.server.telemetry.none.NoneTelemetryProvider; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.internal.util.reflection.Whitebox; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"javax.management.*"}) +public class BrowserReportServletHandlerTest { + @Mock + private HttpServletRequest request; + @Mock + private ModuleManager moduleManager; + @Mock + private NoneTelemetryProvider telemetryProvider; + + @Before + public void init() { + Mockito.when(telemetryProvider.getService(MetricsCreator.class)) + .thenReturn(new MetricsCreatorNoop()); + + TelemetryModule telemetryModule = Mockito.spy(TelemetryModule.class); + Whitebox.setInternalState(telemetryModule, "loadedProvider", telemetryProvider); + Mockito.when(moduleManager.find(TelemetryModule.NAME)).thenReturn(telemetryModule); + } + + @Test + public void testPerfData() throws IOException { + final String json = "{\n" + + " \"service\": \"test\",\n" + + " \"serviceVersion\": \"v0.0.1\",\n" + + " \"pagePath\": \"/e2e-browser\",\n" + + " \"redirectTime\": 1,\n" + + " \"dnsTime\": 2,\n" + + " \"ttfbTime\": 3,\n" + + " \"tcpTime\": 4,\n" + + " \"transTime\": 5,\n" + + " \"domAnalysisTime\": 6,\n" + + " \"fptTime\": 7,\n" + + " \"domReadyTime\": 8,\n" + + " \"loadPageTime\": 9,\n" + + " \"resTime\": 10,\n" + + " \"sslTime\": 11,\n" + + " \"ttlTime\": 12,\n" + + " \"firstPackTime\": 13,\n" + + " \"fmpTime\": 14\n" + + "}"; + final BrowserPerfDataReportServletHandler reportServletHandler = new BrowserPerfDataReportServletHandler( + moduleManager, null, null); + + when(request.getReader()).thenReturn( + new BufferedReader(new StringReader(json))); + final BrowserPerfData result = reportServletHandler.parseBrowserPerfData(request); + Assert.assertEquals("test", result.getService()); + Assert.assertEquals("v0.0.1", result.getServiceVersion()); + Assert.assertEquals("/e2e-browser", result.getPagePath()); + Assert.assertEquals(1, result.getRedirectTime()); + Assert.assertEquals(2, result.getDnsTime()); + Assert.assertEquals(3, result.getTtfbTime()); + Assert.assertEquals(4, result.getTcpTime()); + Assert.assertEquals(5, result.getTransTime()); + Assert.assertEquals(6, result.getDomAnalysisTime()); + Assert.assertEquals(7, result.getFptTime()); + Assert.assertEquals(8, result.getDomReadyTime()); + Assert.assertEquals(9, result.getLoadPageTime()); + Assert.assertEquals(10, result.getResTime()); + Assert.assertEquals(11, result.getSslTime()); + Assert.assertEquals(12, result.getTtlTime()); + Assert.assertEquals(13, result.getFirstPackTime()); + Assert.assertEquals(14, result.getFmpTime()); + } + + @Test + public void testErrorLogSingle() throws IOException { + final String singleJson = "{\n" + + " \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b0e\",\n" + + " \"service\": \"test\",\n" + + " \"serviceVersion\": \"v0.0.1\",\n" + + " \"pagePath\": \"/e2e-browser\",\n" + + " \"category\": \"ajax\",\n" + + " \"message\": \"test\",\n" + + " \"line\": 1,\n" + + " \"col\": 1,\n" + + " \"stack\": \"e2e\",\n" + + " \"errorUrl\": \"/e2e-browser\"\n" + + "}"; + + final BrowserErrorLogReportSingleServletHandler singleServletHandler = new BrowserErrorLogReportSingleServletHandler( + moduleManager, null, null); + + when(request.getReader()).thenReturn( + new BufferedReader(new StringReader(singleJson))); + final List browserErrorLogs = singleServletHandler.parseBrowserErrorLog(request); + Assert.assertEquals(1, browserErrorLogs.size()); + BrowserErrorLog errorLog = browserErrorLogs.get(0); + Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b0e", errorLog.getUniqueId()); + Assert.assertEquals("test", errorLog.getService()); + Assert.assertEquals("v0.0.1", errorLog.getServiceVersion()); + Assert.assertEquals("/e2e-browser", errorLog.getPagePath()); + Assert.assertEquals(ErrorCategory.ajax, errorLog.getCategory()); + Assert.assertEquals("test", errorLog.getMessage()); + Assert.assertEquals(1, errorLog.getLine()); + Assert.assertEquals(1, errorLog.getCol()); + Assert.assertEquals("e2e", errorLog.getStack()); + Assert.assertEquals("/e2e-browser", errorLog.getErrorUrl()); + } + + @Test + public void testErrorLogList() throws IOException { + final String listJson = "[\n" + + " {\n" + + " \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b01\",\n" + + " \"service\": \"test\",\n" + + " \"serviceVersion\": \"v0.0.1\",\n" + + " \"pagePath\": \"/e2e-browser\",\n" + + " \"category\": \"ajax\",\n" + + " \"message\": \"test\",\n" + + " \"line\": 1,\n" + + " \"col\": 1,\n" + + " \"stack\": \"e2e\",\n" + + " \"errorUrl\": \"/e2e-browser\"\n" + + " },\n" + + " {\n" + + " \"uniqueId\": \"55ec6178-3fb7-43ef-899c-a26944407b02\",\n" + + " \"service\": \"test\",\n" + + " \"serviceVersion\": \"v0.0.1\",\n" + + " \"pagePath\": \"/e2e-browser\",\n" + + " \"category\": \"ajax\",\n" + + " \"message\": \"test\",\n" + + " \"line\": 1,\n" + + " \"col\": 1,\n" + + " \"stack\": \"e2e\",\n" + + " \"errorUrl\": \"/e2e-browser\"\n" + + " }\n" + + "]"; + + final BrowserErrorLogReportListServletHandler listServletHandler = new BrowserErrorLogReportListServletHandler( + moduleManager, null, null); + + when(request.getReader()).thenReturn( + new BufferedReader(new StringReader(listJson))); + + final List browserErrorLogs = listServletHandler.parseBrowserErrorLog(request); + Assert.assertEquals(2, browserErrorLogs.size()); + BrowserErrorLog errorLog1 = browserErrorLogs.get(0); + Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b01", errorLog1.getUniqueId()); + Assert.assertEquals("test", errorLog1.getService()); + Assert.assertEquals("v0.0.1", errorLog1.getServiceVersion()); + Assert.assertEquals("/e2e-browser", errorLog1.getPagePath()); + Assert.assertEquals(ErrorCategory.ajax, errorLog1.getCategory()); + Assert.assertEquals("test", errorLog1.getMessage()); + Assert.assertEquals(1, errorLog1.getLine()); + Assert.assertEquals(1, errorLog1.getCol()); + Assert.assertEquals("e2e", errorLog1.getStack()); + Assert.assertEquals("/e2e-browser", errorLog1.getErrorUrl()); + BrowserErrorLog errorLog2 = browserErrorLogs.get(1); + Assert.assertEquals("55ec6178-3fb7-43ef-899c-a26944407b02", errorLog2.getUniqueId()); + } +} -- GitLab