Android Studio 中的新 Relic - newrelic.properties - 变体

New Relic in Android Studio - newrelic.properties - variants

我正在将 New Relic 集成到我的项目中(使用 Android Studio 和 Gradle),它有 2 个变体。每个变体都有自己生成的令牌,我将其存储在每个变体的 string.xml 文件中。

New Relic documentation 中,它声明如下:

在项目的根目录 (projectname/app) 中,添加一个包含以下行的 newrelic.properties 文件:

com.newrelic.application_token=generated_token

问题是,如果我这样做,如何才能使正确的标记出现在正确的变体中?如果此文件必须出现在项目根目录中,我无法为每个变体创建一个,因此我不得不对两个变体使用相同的标记,这不符合我的要求。

如有任何见解,我们将不胜感激。

好的,所以在联系了 New Relic 的支持团队之后,到目前为止显然还没有直接的解决方案,尽管他们说他们已经提出了一个功能请求,所以这个问题可能很快就会得到解决。

根据我的理解,需要这个文件的原因是当生产版本发生异常时,New Relic 系统可以显示一个未混淆的错误日志,该异常已被 ProGuard 混淆。

New Relic 系统借助此文件将 ProGuard mapping.txt 文件上传到 New Relic 服务器,并根据指定的令牌将其与您的应用相关联。有了这个,New Relic 可以取消混淆堆栈跟踪并显示具有实际 class 和方法名称的描述性堆栈跟踪,而不是 a、b、c 等

作为解决方法,我被告知如果我手动上传映射文件,我可以放弃这个文件。

映射文件位于:

build/outputs/proguard/release/mapping.txt  

要手动上传文件,请通过命令行执行以下操作:

curl -v -F proguard=@"<path_to_mapping.txt>" -H "X-APP-LICENSE-KEY:<APPLICATION_TOKEN>" https://mobile-symbol-upload.newrelic.com/symbol

必须对使用 ProGuard 混淆的每个变体(class一般来说,发布版本)执行此操作。

Source

希望这对其他人有帮助。

我解决了创建一些 Gradle 任务的问题。请看一下 https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5

我遵循的代码非常适合我。

  1. 在字符串资源文件上添加 New Relic 应用程序令牌。即:api.xml.

  2. 创建一个新的 Gradle 文件。即:newrelic-util.gradle.

  3. 在新建的Gradle文件中添加以下内容:

    apply plugin: 'com.android.application'
    
    android.applicationVariants.all { variant ->
    
    //<editor-fold desc="Setup New Relic property file">
    
    def variantName = variant.name.capitalize()
    def newRelicTasksGroup = "newrelic"
    def projectDirPath = project.getProjectDir().absolutePath
    def newRelicPropertyFileName = "newrelic.properties"
    def newRelicPropertyFilePath = "${projectDirPath}/${newRelicPropertyFileName}"
    
    // Cleanup task for New Relic property file creation process.
    def deleteNewRelicPropertyFile = "deleteNewRelicPropertyFile"
    def taskDeleteNewRelicPropertyFile = project.tasks.findByName(deleteNewRelicPropertyFile)
    
    if (!taskDeleteNewRelicPropertyFile) {
        taskDeleteNewRelicPropertyFile = tasks.create(name: deleteNewRelicPropertyFile) {
            group = newRelicTasksGroup
            description = "Delete the newrelic.properties file on project dir."
    
            doLast {
                new File("${newRelicPropertyFilePath}").with {
                    if (exists()) {
                        logger.lifecycle("Deleting file ${absolutePath}.")
                        delete()
                    } else {
                        logger.lifecycle("Nothing to do. File ${absolutePath} not found.")
                    }
                }
            }
        }
    }
    
    /*
     * Fix for warning message reported by task "newRelicMapUploadVariantName"
     * Message:
     * [newrelic] newrelic.properties was not found! Mapping file for variant [variantName] not uploaded.
     * New Relic discussion:
     * https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176
     */
    
    def requiredTaskName = "assemble${variantName}"
    def taskAssembleByVariant = project.tasks.findByName(requiredTaskName)
    def createNewRelicPropertyFileVariantName = "createNewRelicPropertyFile${variantName}"
    
    // 0. Searching task candidate to be dependent.
    if (taskAssembleByVariant) {
        logger.debug("Candidate task to be dependent found: ${taskAssembleByVariant.name}")
    
        // 1. Task creation
        def taskCreateNewRelicPropertyFile = tasks.create(name: createNewRelicPropertyFileVariantName) {
            group = newRelicTasksGroup
            description = "Generate the newrelic.properties file on project dir.\nA key/value propety " +
                    "will be written in file to be consumed by newRelicMapUploadVariantName task."
    
            logger.debug("Creating task: ${name}")
    
            doLast {
                def newRelicPropertyKey = "com.newrelic.application_token"
                def newRelicStringResourceKey = "new_relic_key"
                def targetResourceFileName = "api.xml"
                def variantXmlResourceFilePath = "${projectDirPath}/src/${variant.name}/res/values/${targetResourceFileName}"
                def mainXmlResourceFilePath = "${projectDirPath}/src/main/res/values/${targetResourceFileName}"
                def xmlResourceFilesPaths = [variantXmlResourceFilePath, mainXmlResourceFilePath]
    
                xmlResourceFilesPaths.any { xmlResourceFilePath ->
                    // 1.1. Searching xml resource file.
                    def xmlResourceFile = new File(xmlResourceFilePath)
                    if (xmlResourceFile.exists()) {
                        logger.lifecycle("Reading property from xml resource file: ${xmlResourceFilePath}.")
    
                        // 1.2. Searching for string name new_relic_key api.xml resource file.
                        def nodeResources = new XmlParser().parse(xmlResourceFile)
                        def nodeString = nodeResources.find {
                            Node nodeString -> nodeString.'@name'.toString() == newRelicStringResourceKey
                        }
    
                        // 1.3. Checking if string name new_relic_key was found.
                        if (nodeString != null) {
                            def newRelicApplicationToken = "${nodeString.value()[0]}"
                            logger.lifecycle("name:${nodeString.'@name'.toString()};" +
                                    "value:${newRelicApplicationToken}")
    
                            // 1.4 Checking the content of newRelicApplicationToken
                            if (newRelicApplicationToken == 'null' || newRelicApplicationToken.allWhitespace) {
                                logger.warn("Invalid value for key ${newRelicStringResourceKey}. " +
                                        "Please, consider configuring a value for key ${newRelicStringResourceKey}" +
                                        " on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}. " +
                                        "The ${newRelicPropertyFileName} will be not created.")
                                return true // break the loop
                            }
    
                            // 1.5. File creation.
                            File fileProperties = new File(newRelicPropertyFilePath)
                            fileProperties.createNewFile()
                            logger.lifecycle("File ${fileProperties.absolutePath} created.")
    
                            // 1.6. Writing content on properties file.
                            def fileComments = "File generated dynamically by gradle task ${createNewRelicPropertyFileVariantName}.\n" +
                                    "Don't change it manually.\n" +
                                    "Don't track it on VCS."
                            new Properties().with {
                                load(fileProperties.newDataInputStream())
                                setProperty(newRelicPropertyKey, newRelicApplicationToken.toString())
                                store(fileProperties.newWriter(), fileComments)
                            }
    
                            logger.lifecycle("Properties saved on file ${fileProperties.absolutePath}.")
                            return true // break the loop
                        } else {
                            logger.warn("The key ${newRelicStringResourceKey} was not found on ${xmlResourceFile.absolutePath}.\n" +
                                    "Please, consider configuring a key/value on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}.")
                            return // continue to next xmlResourceFilePath
                        }
                    } else {
                        logger.error("Resource file not found: ${xmlResourceFile.absolutePath}")
                        return // continue next xmlResourceFilePath
                    }
                }
            }
        }
    
        // 2. Task dependency setup
        // To check the task dependencies, use:
        // logger.lifecycle("Task ${name} now depends on tasks:")
        // dependsOn.forEach { dep -> logger.lifecycle("\tTask: ${dep}") }
        tasks['clean'].dependsOn taskDeleteNewRelicPropertyFile
        taskCreateNewRelicPropertyFile.dependsOn taskDeleteNewRelicPropertyFile
        taskAssembleByVariant.dependsOn taskCreateNewRelicPropertyFile
    } else {
        logger.error("Required task ${requiredTaskName} was not found. " +
                "The task ${createNewRelicPropertyFileVariantName} will be not created.")
    }
    
    //</editor-fold>
    

    }

  4. 在 app/build.gradle 文件上,应用 Gradle 文件。

    申请自:'./newrelic-util.gradle'

就是这样。我在项目应用程序目录中创建了一个名为 newrelic-util.gradle 的文件。如果执行任务 assembleAnyVariantName,将首先执行任务 createNewRelicPropertyFileAnyVariantName。提示:不要跟踪生成的文件 newrelic.properties 文件。在你的 VCS 上忽略它。

此外,任务 deleteNewRelicPropertyFile 将在任务“clean”和“createNewRelicPropertyFileAnyVarianteName”之前执行,以避免文件带有错误的 New Relic 应用程序令牌。