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一般来说,发布版本)执行此操作。
希望这对其他人有帮助。
我解决了创建一些 Gradle 任务的问题。请看一下 https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5
我遵循的代码非常适合我。
在字符串资源文件上添加 New Relic 应用程序令牌。即:api.xml.
创建一个新的 Gradle 文件。即:newrelic-util.gradle.
在新建的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>
}
在 app/build.gradle 文件上,应用 Gradle 文件。
申请自:'./newrelic-util.gradle'
就是这样。我在项目应用程序目录中创建了一个名为 newrelic-util.gradle 的文件。如果执行任务 assembleAnyVariantName,将首先执行任务 createNewRelicPropertyFileAnyVariantName。提示:不要跟踪生成的文件 newrelic.properties 文件。在你的 VCS 上忽略它。
此外,任务 deleteNewRelicPropertyFile 将在任务“clean”和“createNewRelicPropertyFileAnyVarianteName”之前执行,以避免文件带有错误的 New Relic 应用程序令牌。
我正在将 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一般来说,发布版本)执行此操作。
希望这对其他人有帮助。
我解决了创建一些 Gradle 任务的问题。请看一下 https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5
我遵循的代码非常适合我。
在字符串资源文件上添加 New Relic 应用程序令牌。即:api.xml.
创建一个新的 Gradle 文件。即:newrelic-util.gradle.
在新建的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>
}
在 app/build.gradle 文件上,应用 Gradle 文件。
申请自:'./newrelic-util.gradle'
就是这样。我在项目应用程序目录中创建了一个名为 newrelic-util.gradle 的文件。如果执行任务 assembleAnyVariantName,将首先执行任务 createNewRelicPropertyFileAnyVariantName。提示:不要跟踪生成的文件 newrelic.properties 文件。在你的 VCS 上忽略它。
此外,任务 deleteNewRelicPropertyFile 将在任务“clean”和“createNewRelicPropertyFileAnyVarianteName”之前执行,以避免文件带有错误的 New Relic 应用程序令牌。