Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

New Relic in Android Studio - newrelic.properties - variants

I'm integrating New Relic in my project (with Android Studio & Gradle) which has 2 variants. Each variant has its own generated token, which I store in each variant's string.xml file.

In the New Relic documentation, it states the following:

In your project’s root directory (projectname/app), add a newrelic.properties file with the following line:

com.newrelic.application_token=generated_token

The problem is, if I do this, how can make the correct token appear for the correct variant? If this file must appear in the project root, I can't create one per variant, and so I'm forced to use the same token for both variants, which doesn't work for my requirements.

Any insight would be appreciated.

like image 807
Gil Moshayof Avatar asked May 11 '15 14:05

Gil Moshayof


2 Answers

Okay, so after contacting the support team at New Relic, there is apparently no direct solution for this as of today, although they said they've opened a feature request, and so this problem might be solved soon.

From what I managed to understand, the reason this file is needed is so that the New Relic system can display an un-obfuscated error log when an exception occurs on a production version which has been obfuscated with ProGuard.

The New Relic system, with the help of this file, will upload the ProGuard mapping.txt file to the New Relic servers and associate it with your app according to the specified token. With this, New Relic can un-obfuscate stack traces and display a descriptive stack trace with actual class & method names, rather a, b, c, etc.

As a workaround, I was told that I can forego this file all together, if I upload the mapping file manually.

The mapping file can be found at:

build/outputs/proguard/release/mapping.txt  

In order to manually upload the file, perform the following via command line:

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

This must be done for each variant which is being obfuscated with ProGuard (classically, release builds).

Source

Hope this helps someone else.

like image 58
Gil Moshayof Avatar answered Oct 23 '22 15:10

Gil Moshayof


I solved creating some Gradle tasks. Please, take a look at https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5

I following a code that worked pretty well for me.

  1. Add the New Relic application token on a string resource file. i.e.: api.xml.

  2. Create a new Gradle file. i.e: newrelic-util.gradle.

  3. Add the following content on the newly created Gradle file:

    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. On app/build.gradle file, apply the Gradle file.

    apply from: './newrelic-util.gradle'

That’s it. I created a file named newrelic-util.gradle on project app dir. If you execute the task assembleAnyVariantName, the task createNewRelicPropertyFileAnyVarianteName will be performed first. Tip: don’t track the generated file newrelic.properties file. Ignore it on your VCS.

Additionally, the task deleteNewRelicPropertyFile will be performed right before the tasks ‘clean’ and ‘createNewRelicPropertyFileAnyVarianteName’ in order to avoid a file with a wrong New Relic application token.

like image 2
Jaelson Wagner da Silva Avatar answered Oct 23 '22 15:10

Jaelson Wagner da Silva