Elasticsearch:使用 Elastic APM 监控 Android 应用程序

Posted Elastic 中国社区官方博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Elasticsearch:使用 Elastic APM 监控 Android 应用程序相关的知识,希望对你有一定的参考价值。

作者:Alexander WertCesar Munoz

 

人们通过私人和专业的移动应用程序在智能手机上处理越来越多的事情。 拥有成千上万甚至数百万的用户,确保出色的性能和可靠性是移动应用程序和相关后端服务的提供商和运营商面临的主要挑战。 了解移动应用程序的行为、崩溃的发生和类型、响应时间慢的根本原因以及后端问题对用户的实际影响是管理移动应用程序和相关后端服务性能的关键。

Elastic 推出了适用于 android 应用程序的应用程序性能监控 (APM) 代理,允许开发人员跟踪其应用程序的关键方面,从崩溃和 HTTP 请求到屏幕渲染时间和端到端分布式跟踪。 所有这些都有助于解决移动应用程序、相应后端服务及其交互的问题和性能缺陷。 Elastic APM Android 代理会自动检测你的应用程序及其依赖项,因此你可以简单地将代理 “即插即用” 到你的应用程序中,而不必担心对代码库进行太多更改。

Elastic APM Android 代理是在 OpenTelemetry 之上从头开发的,OpenTelemetry 是一种开放标准和可观察性框架。 开发人员将能够充分利用其功能,以及庞大而活跃的社区提供的支持。 如果你熟悉 OpenTelemetry 并且你的应用程序已经使用 OpenTelemetry 进行检测,那么你可以在切换到 Elastic APM Android Agent 时简单地重用它。 但如果情况并非如此,请不要担心 —— 代理被配置为自动处理常见的可追踪场景,而无需深入研究 OpenTelemetry API 的细节。

[相关文章:添加免费和开放的 Elastic APM 作为 Elastic 可观察性部署的一部分]

怎么运行的

Elastic APM Android Agent 是 SDK 和 Gradle 插件的组合。 SDK 包含实用程序,可让你初始化和配置代理的行为,以及准备和初始化 OpenTelemetry SDK。 你可以使用 SDK 以编程方式配置和初始化代理,特别是高级和特殊用例。

在大多数情况下,不需要编程配置和初始化。 相反,你可以使用提供的 Gradle 插件来配置代理并自动检测你的应用程序。 Gradle 插件在后台使用 Byte Buddy 和官方 Android Gradle 插件 API,通过应用程序及其依赖项类的编译时转换,自动将检测代码注入你的应用程序。

在配置并启用 Elastic Android APM 代理 Gradle 插件的情况下编译你的应用程序将使你的 Android 应用程序在运行时报告跟踪数据、指标以及不同的事件和日志。

在 Android 应用程序中使用 Elastic APM 代理

通过一个简单的演示应用程序,我们将按照 “设置代理” 指南中提到的步骤来设置 Elastic Android APM 代理。

先决条件

对于此示例,你将需要以下内容:

  • 启用了 APM 的 Elastic Stack(我们推荐使用 Elastic 的 Cloud 产品。免费试用。)
  • Java 11+
  • Android Studio
  • Android 模拟器、AVD 设备

你还需要一种方法将应用程序的信号推送到 Elastic。 因此,你将需要 Elastic APM 的秘密令牌(secret token),稍后你将在我们的示例应用程序中配置该令牌。

我们示例的测试项目

为了展示包括分布式跟踪在内的端到端场景,在此示例中,我们将检测一个简单的天气应用程序,该应用程序包含两个 Android UI 片段和一个基于 Spring Boot 的简单本地后端服务。

第一个片段将有一个包含一些城市名称的下拉列表以及一个将你带到第二个片段的按钮,你将在其中看到所选城市的当前温度。 如果你在第一个屏幕上选择了一个非欧洲城市,那么当你前往第二个屏幕时,你会从(本地)后端收到一个错误。 这是为了演示如何在 Elastic APM 中捕获和关联网络和后端错误。

应用 Elastic APM 代理插件

在下文中,我们将解释从头开始为 Android 应用程序设置 Elastic APM Android 代理所需的所有步骤。 如果你想跳过这些说明并立即查看代理的运行情况,请使用该存储库的主分支并仅应用步骤 (3.b),然后再继续下一节(“设置本地后端服务”)。

1. 克隆示例应用程序库并在 Android Studio 中打开它。
2. 切换到 uninstrumented 的 repo 分支,从一个空白的、未检测的 Android 应用程序开始。 你可以运行此命令切换到未检测的分支:

git checkout uninstrumented

3. 按照 Elastic APM Android Agent 的设置指南进行操作:

将 co.elastic.apm.android 插件添加到 app/build.gradle 文件(请确保使用该插件的最新版本,你可以在此处找到该插件)。

通过在 app/build.gradle 文件的 “elasticAPM” 部分中提供 “serverUrl” 和 “secretToken” 来配置代理与 Elastic APM 后端的连接。

// Android app's build.gradle file
plugins 
    //...
    id "co.elastic.apm.android" version "[latest_version]"


//...

elasticApm 
    // Minimal configuration
    serverUrl = "https://your.elastic.apm.endpoint"

    // Optional
    serviceName = "weather-sample-app" 
    serviceVersion = "0.0.1" 
    secretToken = "your Elastic APM secret token" 

4. 唯一实际需要更改的代码是一行代码,用于在 Application.onCreate 方法中初始化 Elastic APM Android 代理。 此示例应用程序的应用程序类位于 app/src/main/java/co/elastic/apm/android/sample/MyApp.kt。

package co.elastic.apm.android.sample

import android.app.Application
import co.elastic.apm.android.sdk.ElasticApmAgent

class MyApp : Application() 

    override fun onCreate() 
        super.onCreate()
        ElasticApmAgent.initialize(this)
    

请记住,对于此示例,我们不会更改代理的默认配置 —— 如果你想了解有关如何更改的更多信息,请查看代理的运行时配置指南。

在启动我们的 Android 天气应用程序之前,我们需要配置并启动本地天气后端服务,如下一节所述。

设置本地后端服务

代理提供的关键功能之一是分布式跟踪,它允许你查看 HTTP 事务的完整端到端故事,从我们的移动应用程序开始并遍历应用程序使用的检测后端服务。 Elastic APM 将以一个分布式跟踪的形式向你展示全貌,这对于解决问题非常方便,尤其是与高延迟和后端错误相关的问题。

作为示例应用程序的一部分,我们将启动一个简单的本地后端服务来处理我们应用程序的 HTTP 请求。 后端服务使用 Elastic APM Java 代理进行检测,以收集自己的 APM 数据并将其发送到 Elastic APM,使其能够将移动交互与后端请求的处理相关联。

为了配置本地服务器,我们需要在 backend/src/main/resources/elasticapm.properties 文件中设置我们的 Elastic APM 端点和秘密令牌(与上一步中我们的 Android 应用程序使用的相同):

service_name=weather-backend
application_packages=co.elastic.apm.android.sample
server_url=YOUR_ELASTIC_APM_URL
secret_token=YOUR_ELASTIC_APM_SECRET_TOKEN

启动演示

我们的示例应用程序将为代理当前支持的框架获得自动检测,这意味着我们将看到屏幕渲染跨度以及开箱即用的 OkHttp 请求。 对于当前不支持的框架,你可以应用手动检测来丰富你的 APM 数据(请参阅下面的 “手动检测”)。

我们准备启动演示。 (该demo旨在使用Android模拟器在本地环境中执行。)因此,我们需要:

  1. 在位于示例项目根目录的终端中使用此命令启动后端服务:./gradlew bootRun(如果你使用的是 Windows,则为 gradlew.bat bootRun)。 或者,你可以从 Android Studio 启动后端服务。
  2. 在 Android 模拟器(来自 Android Studio)中启动天气示例应用程序。

一切运行后,我们需要在应用程序中四处导航以生成一些我们希望在 Elastic APM 中观察到的负载。 因此,选择一个城市,单击 “Next” 并重复多次。 请确保至少选择 New York 一次。 你会看到天气预报对纽约这个城市不起作用。 下面,我们将使用 Elastic APM 来找出选择纽约时出现的问题。

 

初看 APM 结果

让我们打开 Kibana 并导航到 Observability 解决方案。

在 Services 导航项下,你应该看到两个服务的列表:我们的 Android 应用 weather-sample-app 和相应的后端服务 weather-backend。 单击 Service map 选项卡以查看这些服务与任何外部服务之间依赖关系的可视化。

单击 weather-sample-app 以深入了解 Android 应用程序的仪表板。 移动应用程序的服务视图在发布此博文时处于技术预览(technical preview)阶段,但你已经可以在该屏幕上看到有关该应用程序的深入信息。 你会看到所选时间范围内的活动会话数量、weather-sample-app 发出的 HTTP 请求数量、请求的地理分布以及设备型号、操作系统版本、网络连接类型和应用程序的细分等信息 版本。 (有关崩溃和应用程序加载时间的信息正在开发中。)

为了演示的目的,我们保持这个演示简单,所以数据不那么多样化,也相当有限。 但是,当你监控使用次数较多且设备型号、操作系统版本等方面更加多样化的移动应用程序时,此类数据特别有用。当你可以使用这些属性进行过滤和分组你的 APM 数据时,故障排除和性能问题变得更加容易 。 你可以使用顶部的快速过滤器来执行此操作,并查看指标如何根据你的选择被采用。

 

现在,让我们看看如何处理单个用户交互,包括对后端服务的下游调用。 在 Transactions 选项卡(顶部)下,我们看到不同的端到端 transaction 组,包括 FirstFragment 和 SecondFragment 的两个 transactions。

让我们深入研究 SecondFragment - 查看出现的 transaction,以查看此 transaction 组的指标(例如,延迟、吞吐量)以及单个用户交互的调用瀑布视图。 正如我们在下面的屏幕截图中看到的,在视图创建之后,该片段向 10.0.2.2 执行 HTTP GET 请求,这需要大约 130 毫秒。 在同一个瀑布中,我们看到 HTTP 调用由 weather-backend 服务处理,它本身对 api.open-meteo.com 进行 HTTP 调用。

 现在,当查看纽约被选为城市的请求的瀑布视图时,我们看到后端服务发生错误,这解释了为什么预测对 New York 不起作用。 通过单击红色的查看相关错误标记,你将获得错误的详细信息和问题的实际根本原因。

天气后端的异常消息指出 “This service can only retrieve geo locations for European cities!” 这就是选择 New York 作为城市的问题。

 

手动检测

如前所述,Elastic APM Android Agent 代表你为受支持的框架执行一系列自动检测; 但是,在某些情况下,你可能希望根据应用的用例获得额外的检测。 对于这些情况,你已经了解了 OpenTelemetry API,它是 Elastic APM Android 代理的基础。 OpenTelemetry Java SDK 包含用于创建自定义 spans、 metrics 和 logs 的工具,并且由于它是 Elastic APM Android 代理的基础,因此你无需在项目中添加任何额外依赖项也无需配置任何内容即可使用它及将你的自定义信号连接到你自己的 Elastic 环境,就像代理为你做的那样。

开始的方法是像这样获取 OpenTelemetry 的实例:

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();

然后你可以按照 OpenTelemetry Java 文档中的说明创建自定义信号。 有关创建自定义 span 的示例,请参见以下示例:

OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
Tracer tracer = openTelemetry.getTracer("instrumentation-library-name", "1.0.0");
Span span = tracer.spanBuilder("my span").startSpan();

// Make the span the current span
try (Scope ss = span.makeCurrent()) 
  // In this scope, the span is the current/active span
 finally 
    span.end();

结论

在这篇博文中,我们演示了如何使用 Elastic APM Android 代理在基于 Android 的移动应用程序中实现端到端的可观察性。 设置代理只需几分钟,所提供的见解可让你分析应用的性能及其对后端服务的依赖性。 有了 Elastic APM Android Agent,你可以利用 Elastic 丰富的 APM 功能以及各种可能性,通过自定义检测和自定义仪表板来自定义你的分析工作流程。

你好奇吗? 然后自己试试。 在 Elastic Cloud 上注册免费试用,使用本博客中所述的 Elastic APM Android 代理丰富你的 Android 应用程序,并探索 Elastic 的可观察性解决方案中的数据。

原文:Monitoring Android applications with Elastic APM | Elastic Blog

golang elasticsearch7的使用

Golang操作elasticsearch7

  • 包:github.com/olivere/elastic/v7
  • elasticsearch版本7.6
    首先可以定义一个全量,构建连接池

    var esCli *elastic.Client
    func init() {
          var err error
          esCli, err = elastic.NewClient(elastic.SetSniff(false), elastic.SetURL("http://127.0.0.1:9200")
          if err != nil {
              panic(err)
          }
       }

使用方法
1、添加

 func EsAdd(es_index string, es_body interface{}) string {
    var id string
    rsAdd, err := esCli.Index().
        Index(es_index).
        BodyJson(es_body).
        Do(context.Background())
    if err != nil {
        panic(err)
    } else {
        id = rsAdd.Id
    }
    return id
 }
PS:id是指ES自动生成的id,如果想自定义id可以在Index()后面加.Id(string)。目前用的是es7.6,所以可以不用加Type默认_doc,
也可以指定方法Type()。

2、批量添加

func EsAddBulk(es_index string, es_body []interface{}) {
    bulkRequest := esCli.Bulk()
    for _, v := range es_body {
        tmp := v
        req := elastic.NewBulkIndexRequest().Index(es_index).Doc(tmp)
        bulkRequest = bulkRequest.Add(req)
    }
    _, err := bulkRequest.Do(context.Background())
    if err != nil {
        panic(err)
    }
}

3、添加

func EsUpdate(es_index string, es_id string, es_body interface{}) {
    _, err := esCli.Update().
        Index(es_index).
        Id(es_id).
        Doc(es_body).
        Do(context.Background())
    if err != nil {
        panic(err)
    }
}

4、批量修改

func EsUpdateBulk(es_index string, es_body []map[string]string) {
    bulkRequest := esCli.Bulk()
    for _, v := range es_body {
        tmp := v
        tmps := make(map[string]string)
        for is, vs := range tmp {
            if is != "id" {
                tmps[is] = vs
            }
        }
        req := elastic.NewBulkUpdateRequest().Index(es_index).Id(tmp["id"]).Doc(tmps).DocAsUpsert(true)
        bulkRequest = bulkRequest.Add(req)
    }
    _, err := bulkRequest.Do(context.Background())
    if err != nil {
       panic(err)
    }
}
PS:批量修改和批量增加方法是不同的,批量修改的方法NewBulkUpdateRequest,要调用这个才有DocAsUpsert。
之前在NewBulkIndexRequest下一直没有找到DocAsUpsert。
另外用[]map[string]string是因为每个数据要修改的key可能不同,如果要修改的key相同可以用interface 或 struct。

5、删除文档

func Delete(es_index string, id string) {
    _, err := esCli.Delete().Index(es_index).Id(id).Do(context.Background())
    if err != nil {
        panic(err)
    }
}

6、查询当前index是否存在

func ExistsIndex(es_index string) bool {
    exists, err := esCli.IndexExists(es_index).Do(context.Background())
    if err != nil {
        panic(err)
    }
    return exists
}

7、查询

func Search(es_index string,page int ,li int) interface{} {
    p := (page - 1) * li
    collapsedata := elastic.NewCollapseBuilder("company")
    esq := elastic.NewBoolQuery()
    esq.Must(elastic.NewMatchPhraseQuery("company", "CO LTD."))
    esq.Must(elastic.NewMatchQuery("country", "US"))
    search := esCli.Search().
        Index(es_index).
        From(p).Size(li).
        Query(esq).
        Collapse(collapsedata).
        Pretty(true)
    searchResult, err := search.Do(context.Background())
    if err != nil {
        panic(err)
    } else {
        return searchResult
    }
}
PS:NewMatchPhraseQuery是代表短语的匹配,Collapse是指定字段相同结果的折叠

查找延伸:
像上面的结果返回的是es所有的参数,有时候只想用_Souce那要在把里面的结果抽取出来。官方也配有方法:

func PrintTradedata(res *elastic.SearchResult, err error) tradedata {
    if err != nil {
        panic(err)
    }
    var typ tradedata
    var re tradedata
    for _, item := range res.Each(reflect.TypeOf(typ)) { 
        te := item.(tradedata)
        jsonValue, _ := json.Marshal(te)
        json.Unmarshal(jsonValue, &re)
    }
    return re
}

但是这种的话,也有一定的限制性就是如果我还想要_id的话可能又要自己在循环一边结构获取。所以我去看来官方的方法,然后自己重新写一遍:

func PrintTradedata(res *elastic.SearchResult, err error) tradedata {
    if err != nil {
        panic(err)
    }
    var re tradedata
    for _, item := range res.Hits.Hits {
        if item.Source != nil {
            jsonValue, _ := json.Marshal(item.Source)
            json.Unmarshal(jsonValue, &re)
            re.ID = item.Id
        }
    }
    return re
}
这样就能提取出es的_id和_source的结合,可能觉得就这样几行为什么官方要封装多一个方法,
其实官方封装的方法还做结构的验证安全性要比我的高,如果只要_source推荐还是用官方的。

以上就是一些golang使用es7的基本方法,低的版本也适用不过要加上type。es最强大的还是它的搜索功能我只是写了基础的,使用bool查询,还有term、math、fuzzy、wildcard等等,还有翻页scroll和search after,大家都可以去elastic包中查看。如果有需要,等有空我也可以写一篇专门关于搜索的用法。
上述内容欢迎大家指正和交流。

以上是关于Elasticsearch:使用 Elastic APM 监控 Android 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

elastic search使用总结

golang elasticsearch7的使用

解决golang使用elastic连接elasticsearch时自动转换连接地址

解决golang使用elastic连接elasticsearch时自动转换连接地址

Nodejs中@elastic/elasticsearch的使用

错误 [内部] 为 docker.elastic.co/elasticsearch/elasticsearch:7.12.0 加载元数据