|
@@ -0,0 +1,279 @@
|
|
|
|
+package com.cecf.observe;
|
|
|
|
+
|
|
|
|
+import org.apache.flink.api.common.functions.MapFunction;
|
|
|
|
+import org.json.JSONArray;
|
|
|
|
+import org.json.JSONException;
|
|
|
|
+import org.json.JSONObject;
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
+
|
|
|
|
+import java.time.Instant;
|
|
|
|
+import java.util.*;
|
|
|
|
+import java.util.regex.Matcher;
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
+
|
|
|
|
+public class SpringBootSpanExtractMapFunc implements MapFunction<String, List<SpringBootSpan>> {
|
|
|
|
+ Logger logger = LoggerFactory.getLogger(SpringBootSpanExtractMapFunc.class);
|
|
|
|
+
|
|
|
|
+ // 2: SERVER, 3: CLIENT, 1: INTERNAL, 4: PRODUCER, 5: CONSUMER
|
|
|
|
+ private static final int SpanKindInternal = 1;
|
|
|
|
+ private static final int SpanKindServer = 2;
|
|
|
|
+ private static final int SpanKindClient = 3;
|
|
|
|
+ private static final int SpanKindProducer = 4;
|
|
|
|
+ private static final int SpanKindConsumer = 5;
|
|
|
|
+
|
|
|
|
+ String getRPCType(int spanKind, JSONAnyValueList attributes) {
|
|
|
|
+ // 2: SERVER, 3: CLIENT, 1: INTERNAL, 4: PRODUCER, 5: CONSUMER
|
|
|
|
+ // http check http.status_code . io.opentelemetry.apache-httpclient-4.0
|
|
|
|
+ // rpc check rpc.system. io.opentelemetry.grpc-1.6
|
|
|
|
+ if (spanKind == 1) {
|
|
|
|
+ return "local";
|
|
|
|
+ }
|
|
|
|
+ if (spanKind == 5) {
|
|
|
|
+ return "consumer";
|
|
|
|
+ }
|
|
|
|
+ if (spanKind == 4) {
|
|
|
|
+ return "producer";
|
|
|
|
+ }
|
|
|
|
+ if (spanKind == 2) {
|
|
|
|
+ // server
|
|
|
|
+ return "server";
|
|
|
|
+ }
|
|
|
|
+ // client: rpc, http
|
|
|
|
+ if (!attributes.getString("http.url").isEmpty()) {
|
|
|
|
+ return "http";
|
|
|
|
+ }
|
|
|
|
+ String rpcSys = attributes.getString("rpc.system");
|
|
|
|
+ if (rpcSys.equals("apache_dubbo")) {
|
|
|
|
+ return "dubbo";
|
|
|
|
+ } else if (!rpcSys.isEmpty()) {
|
|
|
|
+ return rpcSys;
|
|
|
|
+ }
|
|
|
|
+ String dbSys = attributes.getString("db.system");
|
|
|
|
+ if (dbSys.equals("mysql")) {
|
|
|
|
+ return "mysql";
|
|
|
|
+ } else if (!dbSys.isEmpty()) {
|
|
|
|
+ return dbSys;
|
|
|
|
+ }
|
|
|
|
+ return "client";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String getRPCName(int spanKind, String RPCType, String spanName, JSONAnyValueList attributes) {
|
|
|
|
+ if (spanKind == 3) {
|
|
|
|
+ if (RPCType.equals("dubbo")) {
|
|
|
|
+ return attributes.getString("rpc.method");
|
|
|
|
+ }
|
|
|
|
+ if (RPCType.equals("http")) {
|
|
|
|
+ return attributes.getString("http.target");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return spanName;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int getRPCResult(int spanKind, String RPCType, JSONAnyValueList attributes) {
|
|
|
|
+ if (spanKind == 3) {
|
|
|
|
+ if (RPCType.equals("http")) {
|
|
|
|
+ return attributes.getInt("http.status_code");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String getFromJSONObject(JSONObject o, String k, String de) {
|
|
|
|
+ try {
|
|
|
|
+ return o.getString(k);
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ return de;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String getStringFromJSONObjectWithDefault(JSONObject obj, String key, String defaultValue) {
|
|
|
|
+ if (!obj.has(key)) {
|
|
|
|
+ return defaultValue;
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ return obj.getString(key);
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ logger.error("get String [{}] from [{}] failed", key, obj);
|
|
|
|
+ return defaultValue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int getIntFromJSONObjectWithDefault(JSONObject obj, String key, int defaultValue) {
|
|
|
|
+ if (!obj.has(key)) {
|
|
|
|
+ return defaultValue;
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ return obj.getInt(key);
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ logger.error("get int [{}] from [{}] failed", key, obj);
|
|
|
|
+ return defaultValue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static final int StatusError = 2;
|
|
|
|
+ private static final int StatusUnset = 0;
|
|
|
|
+ private static final int StatusOK = 1;
|
|
|
|
+ Pattern pattern = Pattern.compile("-DAPP_NAME=(\\S+)");
|
|
|
|
+
|
|
|
|
+ SpringBootSpan parseSpan(JSONObject span, String serviceName, String scopeName, String serviceIP, String scopeVersion,
|
|
|
|
+ String containerID, Map<String, String> resourceAttrs) {
|
|
|
|
+ SpringBootSpan clickhouseSpan = new SpringBootSpan();
|
|
|
|
+ // appName
|
|
|
|
+ String commandLine = resourceAttrs.get("process.command_line");
|
|
|
|
+ if (commandLine != null && !commandLine.isEmpty()) {
|
|
|
|
+ Matcher matcher = pattern.matcher(commandLine);
|
|
|
|
+ if (matcher.find()) {
|
|
|
|
+ String appAlias = matcher.group(1);
|
|
|
|
+ if (appAlias != null && !appAlias.isEmpty()) {
|
|
|
|
+ clickhouseSpan.AppName = appAlias;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (!clickhouseSpan.hasAppName()) {
|
|
|
|
+ String serviceNamespace = resourceAttrs.get("service.namespace");
|
|
|
|
+ if (serviceNamespace != null && !serviceNamespace.isEmpty() && !serviceNamespace.equals("UNSET")) {
|
|
|
|
+ clickhouseSpan.AppName = serviceNamespace;
|
|
|
|
+ } else {
|
|
|
|
+ String appNameInResource = resourceAttrs.get("app.name");
|
|
|
|
+ if (appNameInResource != null && !appNameInResource.isEmpty() && !appNameInResource.equals("UNSET")) {
|
|
|
|
+ clickhouseSpan.AppName = appNameInResource;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // end appName
|
|
|
|
+ clickhouseSpan.TraceID = span.getString("traceId");
|
|
|
|
+ clickhouseSpan.ResourceAttributes = resourceAttrs;
|
|
|
|
+ clickhouseSpan.SpanID = span.getString("spanId");
|
|
|
|
+ clickhouseSpan.ParentSpanID = span.getString("parentSpanId");
|
|
|
|
+ clickhouseSpan.SpanKind = span.getInt("kind");
|
|
|
|
+ clickhouseSpan.ServiceName = serviceName;
|
|
|
|
+ clickhouseSpan.SpanName = span.getString("name");
|
|
|
|
+ clickhouseSpan.ScopeName = scopeName;
|
|
|
|
+ clickhouseSpan.ScopeVersion = scopeVersion;
|
|
|
|
+ clickhouseSpan.ContainerID = containerID;
|
|
|
|
+ clickhouseSpan.SrcIP = serviceIP;
|
|
|
|
+ clickhouseSpan.SrcPort = 0;
|
|
|
|
+ clickhouseSpan.TraceState = "";
|
|
|
|
+ clickhouseSpan.RPCRequest = new HashMap<>();
|
|
|
|
+
|
|
|
|
+ int statusCode = 0;
|
|
|
|
+ String statusMessage = "";
|
|
|
|
+ if (span.has("status")) {
|
|
|
|
+ JSONObject status = span.getJSONObject("status");
|
|
|
|
+ if (status.has("code")) {
|
|
|
|
+ statusCode = status.getInt("code");
|
|
|
|
+ }
|
|
|
|
+ if (status.has("message")) {
|
|
|
|
+ statusMessage = status.getString("message");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ long startTimeNano = Long.parseLong(span.getString("startTimeUnixNano"));
|
|
|
|
+ long endTimeNano = Long.parseLong(span.getString("endTimeUnixNano"));
|
|
|
|
+ clickhouseSpan.Duration = endTimeNano - startTimeNano;
|
|
|
|
+ clickhouseSpan.Timestamp = Date.from(Instant.ofEpochMilli((long) (startTimeNano / 1e6)));
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (span.has("attributes")) {
|
|
|
|
+ JSONArray spanAttributes = span.getJSONArray("attributes");
|
|
|
|
+ JSONAnyValueList spanAttributesMap = new JSONAnyValueList(spanAttributes);
|
|
|
|
+ clickhouseSpan.TargetIP = spanAttributesMap.getString("net.peer.name");
|
|
|
|
+ clickhouseSpan.TargetPort = spanAttributesMap.getInt("net.peer.port");
|
|
|
|
+ clickhouseSpan.FuncName = spanAttributesMap.getString("code.function");
|
|
|
|
+ clickhouseSpan.FuncNamespace = spanAttributesMap.getString("code.namespace");
|
|
|
|
+ clickhouseSpan.SpanAttributes = spanAttributesMap.toStringMap();
|
|
|
|
+
|
|
|
|
+ if (statusCode == StatusError && statusMessage.isEmpty() && clickhouseSpan.SpanKind == SpanKindClient) {
|
|
|
|
+ // fill status message with http code
|
|
|
|
+ statusMessage = "status code:" + String.valueOf(spanAttributesMap.getInt("http.status_code"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // http check http.status_code . io.opentelemetry.apache-httpclient-4.0
|
|
|
|
+ // rpc check rpc.system. io.opentelemetry.grpc-1.6
|
|
|
|
+ clickhouseSpan.RPCType = this.getRPCType(clickhouseSpan.SpanKind, spanAttributesMap);
|
|
|
|
+ clickhouseSpan.RPCName = this.getRPCName(clickhouseSpan.SpanKind, clickhouseSpan.RPCType,
|
|
|
|
+ clickhouseSpan.SpanName, spanAttributesMap);
|
|
|
|
+ clickhouseSpan.RPCResult = this.getRPCResult(clickhouseSpan.SpanKind, clickhouseSpan.RPCType,
|
|
|
|
+ spanAttributesMap);
|
|
|
|
+ clickhouseSpan.FuncLineNO = spanAttributesMap.getInt("code.lineno");
|
|
|
|
+ clickhouseSpan.DBStatement = spanAttributesMap.getString("db.statement");
|
|
|
|
+ clickhouseSpan.DBConnectionString = spanAttributesMap.getString("db.connection_string");
|
|
|
|
+ }
|
|
|
|
+ if (span.has("events")) {
|
|
|
|
+ JSONArray events = span.getJSONArray("events");
|
|
|
|
+ if (events != null && events.length() > 0) {
|
|
|
|
+ SpanEvents spanEvents = new SpanEvents(events);
|
|
|
|
+ List<SpanEvent> exEvents = spanEvents.getEventsByName("exception");
|
|
|
|
+ for (SpanEvent ev : exEvents) {
|
|
|
|
+ clickhouseSpan.Exceptions.add(ev.getException());
|
|
|
|
+ }
|
|
|
|
+ for (int i = 0; i < events.length(); i++) {
|
|
|
|
+ JSONObject e = events.getJSONObject(i);
|
|
|
|
+ String evTsStr = e.getString("timeUnixNano");
|
|
|
|
+ if (evTsStr == null) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ long evTs = Long.parseLong(evTsStr);
|
|
|
|
+ String evName = e.getString("name");
|
|
|
|
+ if (evTs > 0 && evName != null && !evName.isEmpty()) {
|
|
|
|
+ clickhouseSpan.Events.add(new TraceEvent(evTs, evName));
|
|
|
|
+ }
|
|
|
|
+ } catch (NumberFormatException exception) {
|
|
|
|
+ logger.warn("timeUnixNano:{} not a valid long", evTsStr, exception);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ clickhouseSpan.FuncResult = 0;
|
|
|
|
+ clickhouseSpan.StatusCode = statusCode;
|
|
|
|
+ clickhouseSpan.StatusMessage = statusMessage;
|
|
|
|
+ return clickhouseSpan;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public List<SpringBootSpan> map(String value) {
|
|
|
|
+ try {
|
|
|
|
+ JSONObject msg = new JSONObject(value);
|
|
|
|
+ JSONArray resourceSpans = msg.getJSONArray("resourceSpans");
|
|
|
|
+ List<SpringBootSpan> ret = new ArrayList<>();
|
|
|
|
+ for (int i = 0; i < resourceSpans.length(); i++) {
|
|
|
|
+ JSONObject resourceSpan = resourceSpans.getJSONObject(i);
|
|
|
|
+ JSONArray resourceAttrs = resourceSpan
|
|
|
|
+ .getJSONObject("resource")
|
|
|
|
+ .getJSONArray("attributes");
|
|
|
|
+ JSONAnyValueList resourceAttributesAnyValue = new JSONAnyValueList(resourceAttrs);
|
|
|
|
+ String serviceName = resourceAttributesAnyValue.getString("service.name");
|
|
|
|
+ String containerID = resourceAttributesAnyValue.getString("container.id");
|
|
|
|
+ String serviceIP = resourceAttributesAnyValue.getString("pod.ip");
|
|
|
|
+ JSONArray scopeSpans = resourceSpan.getJSONArray("scopeSpans");
|
|
|
|
+ for (int j = 0; j < scopeSpans.length(); j++) {
|
|
|
|
+ JSONObject scopeSpan = scopeSpans.getJSONObject(j);
|
|
|
|
+ JSONObject scope = scopeSpan.getJSONObject("scope");
|
|
|
|
+ String scopeName = getFromJSONObject(scope, "name", "UNKNOWN");
|
|
|
|
+ String scopeVersion = this.getFromJSONObject(scope, "version", "");
|
|
|
|
+ JSONArray spans = scopeSpan.getJSONArray("spans");
|
|
|
|
+ for (int k = 0; k < spans.length(); k++) {
|
|
|
|
+ JSONObject span = spans.getJSONObject(k);
|
|
|
|
+ try {
|
|
|
|
+ SpringBootSpan clickhouseSpan = this.parseSpan(span, serviceName, scopeName, serviceIP,
|
|
|
|
+ scopeVersion, containerID, resourceAttributesAnyValue.toStringMap());
|
|
|
|
+ ret.add(clickhouseSpan);
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ logger.error("parse span failed", e);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return ret;
|
|
|
|
+ } catch (JSONException e) {
|
|
|
|
+ logger.error("[TraceSpringBoot]: parse json failed", e);
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|