CORDA-2586 explorer exception handling (#4957)

* Initial version of new(old) dialog that won't print a stacktrace for rpc exceptions.

* Decoupled CordaVersionProvider. Moved common files to common-logging to lower dependencies on the node explorer.

* Removed unused import and duplicate documentation comment.

* Moved error code rewrite policy in the new common/logging module according to PR review.

* Removed extra line.

* Updated log4j configurations with new package name where logging policies will be contained.

* Included common-logging module with cliutils.
This commit is contained in:
Stefan Iliev 2019-04-09 20:14:37 +01:00 committed by Anthony Keenan
parent 746fcc32e5
commit e4615f7f47
20 changed files with 167 additions and 34 deletions

View File

@ -0,0 +1,26 @@
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
dependencies {
compile group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8", version: kotlin_version
compile group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: kotlin_version
compile group: "com.typesafe", name: "config", version: typesafe_config_version
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
testCompile project(":test-utils")
}
jar {
baseName 'corda-common-logging'
}
publish {
name jar.baseName
}

View File

@ -0,0 +1,28 @@
package net.corda.common.logging
import com.jcabi.manifests.Manifests
class CordaVersion {
companion object {
private const val UNKNOWN = "Unknown"
const val current_major_release = "4.0-SNAPSHOT"
const val platformEditionCode = "OS"
private fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
val releaseVersion: String by lazy { manifestValue("Corda-Release-Version") ?: UNKNOWN }
val revision: String by lazy { manifestValue("Corda-Revision") ?: UNKNOWN }
val vendor: String by lazy { manifestValue("Corda-Vendor") ?: UNKNOWN }
val platformVersion: Int by lazy { manifestValue("Corda-Platform-Version")?.toInt() ?: 1 }
internal val semanticVersion: String by lazy { if(releaseVersion == UNKNOWN) current_major_release else releaseVersion }
}
fun getVersion(): Array<String> {
return if (Manifests.exists("Corda-Release-Version") && Manifests.exists("Corda-Revision")) {
arrayOf("Version: $releaseVersion", "Revision: $revision", "Platform Version: $platformVersion", "Vendor: $vendor")
} else {
arrayOf("No version data is available in the MANIFEST file.")
}
}
}

View File

@ -1,4 +1,4 @@
package net.corda.cliutils package net.corda.common.logging
import org.apache.logging.log4j.core.Core import org.apache.logging.log4j.core.Core
import org.apache.logging.log4j.core.LogEvent import org.apache.logging.log4j.core.LogEvent

View File

@ -1,11 +1,11 @@
package net.corda.cliutils package net.corda.common.logging
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.message.Message import org.apache.logging.log4j.message.Message
import org.apache.logging.log4j.message.SimpleMessage import org.apache.logging.log4j.message.SimpleMessage
import java.util.* import java.util.*
internal fun Message.withErrorCodeFor(error: Throwable?, level: Level): Message { fun Message.withErrorCodeFor(error: Throwable?, level: Level): Message {
return when { return when {
error != null && level.isInRange(Level.FATAL, Level.WARN) -> CompositeMessage("$formattedMessage [errorCode=${error.errorCode()}, moreInformationAt=${error.errorCodeLocationUrl()}]", format, parameters, throwable) error != null && level.isInRange(Level.FATAL, Level.WARN) -> CompositeMessage("$formattedMessage [errorCode=${error.errorCode()}, moreInformationAt=${error.errorCodeLocationUrl()}]", format, parameters, throwable)
@ -13,9 +13,9 @@ internal fun Message.withErrorCodeFor(error: Throwable?, level: Level): Message
} }
} }
private fun Throwable.errorCodeLocationUrl() = "https://errors.corda.net/${CordaVersionProvider.platformEditionCode}/${CordaVersionProvider.semanticVersion}/${errorCode()}" fun Throwable.errorCodeLocationUrl() = "https://errors.corda.net/${CordaVersion.platformEditionCode}/${CordaVersion.semanticVersion}/${errorCode()}"
private fun Throwable.errorCode(hashedFields: (Throwable) -> Array<out Any?> = Throwable::defaultHashedFields): String { fun Throwable.errorCode(hashedFields: (Throwable) -> Array<out Any?> = Throwable::defaultHashedFields): String {
val hash = staticLocationBasedHash(hashedFields) val hash = staticLocationBasedHash(hashedFields)
return hash.toBase(36) return hash.toBase(36)

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" packages="net.corda.cliutils"> <Configuration status="info" packages="net.corda.common.logging">
<Properties> <Properties>
<Property name="log-path">${sys:log-path:-logs}</Property> <Property name="log-path">${sys:log-path:-logs}</Property>

View File

@ -73,6 +73,7 @@ dependencies {
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(':common-validation') compile project(':common-validation')
compile project(':common-configuration-parsing') compile project(':common-configuration-parsing')
compile project(':common-logging')
// Backwards compatibility goo: Apps expect confidential-identities to be loaded by default. // Backwards compatibility goo: Apps expect confidential-identities to be loaded by default.
// We could eventually gate this on a target-version check. // We could eventually gate this on a target-version check.

View File

@ -4,8 +4,8 @@ import io.netty.channel.unix.Errors
import net.corda.cliutils.printError import net.corda.cliutils.printError
import net.corda.cliutils.CliWrapperBase import net.corda.cliutils.CliWrapperBase
import net.corda.cliutils.CordaCliWrapper import net.corda.cliutils.CordaCliWrapper
import net.corda.cliutils.CordaVersionProvider
import net.corda.cliutils.ExitCodes import net.corda.cliutils.ExitCodes
import net.corda.common.logging.CordaVersion
import net.corda.core.contracts.HashAttachmentConstraint import net.corda.core.contracts.HashAttachmentConstraint
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.internal.* import net.corda.core.internal.*
@ -269,9 +269,9 @@ open class NodeStartup : NodeStartupLogging {
open fun getVersionInfo(): VersionInfo { open fun getVersionInfo(): VersionInfo {
return VersionInfo( return VersionInfo(
PLATFORM_VERSION, PLATFORM_VERSION,
CordaVersionProvider.releaseVersion, CordaVersion.releaseVersion,
CordaVersionProvider.revision, CordaVersion.revision,
CordaVersionProvider.vendor CordaVersion.vendor
) )
} }

View File

@ -71,6 +71,10 @@ project(":common-validation").projectDir = new File("$settingsDir/common/validat
include 'common-configuration-parsing' include 'common-configuration-parsing'
project(":common-configuration-parsing").projectDir = new File("$settingsDir/common/configuration-parsing") project(":common-configuration-parsing").projectDir = new File("$settingsDir/common/configuration-parsing")
include 'common-logging'
project(":common-logging").projectDir = new File("$settingsDir/common/logging")
// Common libraries - end // Common libraries - end
apply from: 'buildCacheSettings.gradle' apply from: 'buildCacheSettings.gradle'

View File

@ -6,6 +6,7 @@ dependencies {
compile project(':core') compile project(':core')
compile project(':node-api') compile project(':node-api')
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(":common-logging")
// Unit testing helpers. // Unit testing helpers.
compile "junit:junit:$junit_version" compile "junit:junit:$junit_version"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" packages="net.corda.cliutils"> <Configuration status="info" packages="net.corda.common.logging">
<Properties> <Properties>
<Property name="log-path">${sys:log-path:-logs}</Property> <Property name="log-path">${sys:log-path:-logs}</Property>

View File

@ -6,6 +6,7 @@ apply plugin: 'com.jfrog.artifactory'
dependencies { dependencies {
compile project(':client:jackson') compile project(':client:jackson')
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(":common-logging")
compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"

View File

@ -7,6 +7,7 @@ description 'Network bootstrapper'
dependencies { dependencies {
compile project(':node-api') compile project(':node-api')
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(":common-logging")
compile project(':common-configuration-parsing') compile project(':common-configuration-parsing')
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"

View File

@ -7,6 +7,7 @@ description 'CLI Utilities'
dependencies { dependencies {
compile project(":core") compile project(":core")
compile project(":common-logging")
compile "info.picocli:picocli:$picocli_version" compile "info.picocli:picocli:$picocli_version"
compile "commons-io:commons-io:$commons_io_version" compile "commons-io:commons-io:$commons_io_version"

View File

@ -1,33 +1,16 @@
package net.corda.cliutils package net.corda.cliutils
import com.jcabi.manifests.Manifests
import picocli.CommandLine import picocli.CommandLine
import net.corda.common.logging.CordaVersion
/** /**
* Simple version printing when command is called with --version or -V flag. Assuming that we reuse Corda-Release-Version and Corda-Revision * Simple version printing when command is called with --version or -V flag. Assuming that we reuse Corda-Release-Version and Corda-Revision
* in the manifest file. * in the manifest file.
*/ */
class CordaVersionProvider : CommandLine.IVersionProvider { class CordaVersionProvider : CommandLine.IVersionProvider {
companion object { val version = CordaVersion()
private const val UNKNOWN = "Unknown"
const val current_major_release = "4.0-SNAPSHOT"
const val platformEditionCode = "OS"
private fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null
val releaseVersion: String by lazy { manifestValue("Corda-Release-Version") ?: UNKNOWN }
val revision: String by lazy { manifestValue("Corda-Revision") ?: UNKNOWN }
val vendor: String by lazy { manifestValue("Corda-Vendor") ?: UNKNOWN }
val platformVersion: Int by lazy { manifestValue("Corda-Platform-Version")?.toInt() ?: 1 }
internal val semanticVersion: String by lazy { if(releaseVersion == UNKNOWN) current_major_release else releaseVersion }
}
override fun getVersion(): Array<String> { override fun getVersion(): Array<String> {
return if (Manifests.exists("Corda-Release-Version") && Manifests.exists("Corda-Revision")) { return version.getVersion()
arrayOf("Version: $releaseVersion", "Revision: $revision", "Platform Version: $platformVersion", "Vendor: $vendor")
} else {
arrayOf("No version data is available in the MANIFEST file.")
}
} }
} }

View File

@ -10,6 +10,7 @@ import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.util.* import java.util.*
import net.corda.common.logging.CordaVersion
private class ShellExtensionsGenerator(val parent: CordaCliWrapper) { private class ShellExtensionsGenerator(val parent: CordaCliWrapper) {
private companion object { private companion object {
@ -72,7 +73,7 @@ private class ShellExtensionsGenerator(val parent: CordaCliWrapper) {
// If on Windows, Path.toString() returns a path with \ instead of /, but for bash Windows users we want to convert those back to /'s // If on Windows, Path.toString() returns a path with \ instead of /, but for bash Windows users we want to convert those back to /'s
private fun Path.toStringWithDeWindowsfication(): String = this.toAbsolutePath().toString().replace("\\", "/") private fun Path.toStringWithDeWindowsfication(): String = this.toAbsolutePath().toString().replace("\\", "/")
private fun jarVersion(alias: String) = "# $alias - Version: ${CordaVersionProvider.releaseVersion}, Revision: ${CordaVersionProvider.revision}" private fun jarVersion(alias: String) = "# $alias - Version: ${CordaVersion.releaseVersion}, Revision: ${CordaVersion.revision}"
private fun getAutoCompleteFileLocation(alias: String) = userHome / ".completion" / alias private fun getAutoCompleteFileLocation(alias: String) = userHome / ".completion" / alias
private fun generateAutoCompleteFile(alias: String) { private fun generateAutoCompleteFile(alias: String) {

View File

@ -19,6 +19,7 @@ dependencies {
compile project(':finance:contracts') compile project(':finance:contracts')
compile project(':finance:workflows') compile project(':finance:workflows')
compile project(':tools:worldmap') compile project(':tools:worldmap')
compile project(':common-logging')
// Log4J: logging framework (with SLF4J bindings) // Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"

View File

@ -0,0 +1,77 @@
package net.corda.explorer.ui
import impl.org.controlsfx.i18n.Localization.getString
import impl.org.controlsfx.i18n.Localization.localize
import javafx.event.ActionEvent
import javafx.event.EventHandler
import net.corda.common.logging.errorCodeLocationUrl
import org.controlsfx.dialog.ProgressDialog
import javafx.scene.control.*
import javafx.scene.layout.GridPane
import javafx.scene.layout.Priority
import javafx.scene.text.Text
import javafx.scene.text.TextFlow
import java.awt.Desktop
import java.io.PrintWriter
import java.io.StringWriter
import java.net.URI
/*
Will generate a window showing the exception message with a generated link and if requested a stacktrace.
The link opens the default browser towards the error.corda.com/ redirection pages.
*/
class AdvancedExceptionDialog(_exception: Throwable) : Dialog<ButtonType>() {
internal val exception = _exception
init {
val dialogPane = super.getDialogPane()
//Dialog title
super.setTitle(getString("exception.dlg.title"))
dialogPane.headerText = getString("exception.dlg.header")
dialogPane.styleClass.add("exception-dialog")
dialogPane.stylesheets.add(ProgressDialog::class.java.getResource("dialogs.css").toExternalForm())
dialogPane.buttonTypes.addAll(ButtonType.OK)
val hyperlink = Hyperlink(exception.errorCodeLocationUrl())
hyperlink.onAction = EventHandler<ActionEvent> {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse( URI(exception.errorCodeLocationUrl()))
} //This should be tested out on other platforms, works on my mac but the stackoverflow opinions are mixed.
}
val textFlow = TextFlow(Text("${exception.message}\n"), hyperlink)
dialogPane.content = textFlow
}
}
//Attach a stacktrace for the exception that was used in the initialization of the dialog.
fun AdvancedExceptionDialog.withStacktrace() : AdvancedExceptionDialog
{
val sw = StringWriter()
val pw = PrintWriter(sw)
exception.printStackTrace(pw)
val textArea = TextArea(sw.toString()).apply {
isEditable = false
isWrapText = false
maxWidth = Double.MAX_VALUE
maxHeight = Double.MAX_VALUE
}
GridPane.setVgrow(textArea, Priority.ALWAYS)
GridPane.setHgrow(textArea, Priority.ALWAYS)
val root = GridPane().apply {
maxWidth = Double.MAX_VALUE
add(Label(localize(getString("exception.dlg.label"))), 0, 0)
add(textArea,0 ,1)
}
dialogPane.expandableContent = root
return this
}

View File

@ -4,9 +4,11 @@ import javafx.beans.property.SimpleIntegerProperty
import javafx.scene.control.* import javafx.scene.control.*
import net.corda.client.jfx.model.NodeMonitorModel import net.corda.client.jfx.model.NodeMonitorModel
import net.corda.client.jfx.model.objectProperty import net.corda.client.jfx.model.objectProperty
import net.corda.client.rpc.RPCException
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.explorer.model.SettingsModel import net.corda.explorer.model.SettingsModel
import org.controlsfx.dialog.ExceptionDialog import net.corda.explorer.ui.AdvancedExceptionDialog
import net.corda.explorer.ui.withStacktrace
import tornadofx.* import tornadofx.*
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -50,10 +52,14 @@ class LoginView : View(WINDOW_TITLE) {
} }
getModel<SettingsModel>().commit() getModel<SettingsModel>().commit()
LoginStatus.loggedIn LoginStatus.loggedIn
} catch (e: RPCException) {
e.printStackTrace()
AdvancedExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait()
LoginStatus.exception
} catch (e: Exception) { } catch (e: Exception) {
// TODO : Handle this in a more user friendly way. // TODO : Handle this in a more user friendly way.
e.printStackTrace() e.printStackTrace()
ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait() AdvancedExceptionDialog(e).withStacktrace().apply { initOwner(root.scene.window) }.showAndWait()
LoginStatus.exception LoginStatus.exception
} finally { } finally {
root.isDisable = false root.isDisable = false

View File

@ -11,6 +11,7 @@ apply plugin: 'com.jfrog.artifactory'
dependencies { dependencies {
compile project(':tools:shell') compile project(':tools:shell')
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(":common-logging")
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.slf4j:jul-to-slf4j:$slf4j_version"

View File

@ -29,6 +29,7 @@ dependencies {
compile project(':client:rpc') compile project(':client:rpc')
compile project(':client:jackson') compile project(':client:jackson')
compile project(':tools:cliutils') compile project(':tools:cliutils')
compile project(":common-logging")
// Web stuff: for HTTP[S] servlets // Web stuff: for HTTP[S] servlets
compile "org.eclipse.jetty:jetty-servlet:$jetty_version" compile "org.eclipse.jetty:jetty-servlet:$jetty_version"