Skip to content

Commit 7333c22

Browse files
authored
include error chain when logging bucket loading errors (#8380)
* include error chain when logging bucket loading errors * unify formatFailureChain methods. re-activate editable mappings in application.conf * unused imports * format * snapshots
1 parent 7c2975f commit 7333c22

File tree

12 files changed

+74
-71
lines changed

12 files changed

+74
-71
lines changed

app/controllers/Controller.scala

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import scala.concurrent.ExecutionContext
1515

1616
trait Controller extends InjectedController with ExtendedController with UserAwareRequestLogging with LazyLogging {
1717

18-
final val badRequestLabel = "Operation could not be performed. See JSON body for more information."
19-
2018
private def jsonErrorWrites(errors: JsError)(implicit m: MessagesProvider): JsObject =
2119
Json.obj(
2220
"errors" -> errors.errors.map(error =>

app/models/annotation/AnnotationUploadService.scala

+7-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.scalableminds.util.accesscontext.DBAccessContext
55
import java.io.{File, FileInputStream, InputStream}
66
import java.nio.file.{Files, Path, StandardCopyOption}
77
import com.scalableminds.util.io.ZipIO
8+
import com.scalableminds.util.mvc.Formatter
89
import com.scalableminds.util.tools.Fox
910
import com.scalableminds.webknossos.datastore.SkeletonTracing.{SkeletonTracing, TreeGroup}
1011
import com.scalableminds.webknossos.datastore.VolumeTracing.{SegmentGroup, VolumeTracing}
@@ -14,7 +15,7 @@ import files.TempFileService
1415
import javax.inject.Inject
1516
import models.annotation.nml.NmlResults._
1617
import models.annotation.nml.{NmlParseSuccessWithoutFile, NmlParser, NmlResults}
17-
import net.liftweb.common.{Box, Empty, Failure, Full}
18+
import net.liftweb.common.{Empty, Failure, Full}
1819
import net.liftweb.common.Box.tryo
1920
import play.api.i18n.MessagesProvider
2021

@@ -30,20 +31,16 @@ case class SharedParsingParameters(useZipName: Boolean,
3031
userOrganizationId: String,
3132
isTaskUpload: Boolean = false)
3233

33-
class AnnotationUploadService @Inject()(tempFileService: TempFileService, nmlParser: NmlParser) extends LazyLogging {
34+
class AnnotationUploadService @Inject()(tempFileService: TempFileService, nmlParser: NmlParser)
35+
extends LazyLogging
36+
with Formatter {
3437

3538
private def extractFromNmlFile(file: File, name: String, sharedParsingParameters: SharedParsingParameters)(
3639
implicit m: MessagesProvider,
3740
ec: ExecutionContext,
3841
ctx: DBAccessContext): Fox[NmlParseResult] =
3942
extractFromNml(new FileInputStream(file), name, sharedParsingParameters)
4043

41-
private def formatChain(chain: Box[Failure]): String = chain match {
42-
case Full(failure) =>
43-
" <~ " + failure.msg + formatChain(failure.chain)
44-
case _ => ""
45-
}
46-
4744
private def extractFromNml(inputStream: InputStream,
4845
name: String,
4946
sharedParsingParameters: SharedParsingParameters,
@@ -60,8 +57,8 @@ class AnnotationUploadService @Inject()(tempFileService: TempFileService, nmlPar
6057
parserOutput.futureBox.map {
6158
case Full(NmlParseSuccessWithoutFile(skeletonTracingOpt, uploadedVolumeLayers, datasetId, description, wkUrl)) =>
6259
NmlParseSuccess(name, skeletonTracingOpt, uploadedVolumeLayers, datasetId, description, wkUrl)
63-
case Failure(msg, _, chain) => NmlParseFailure(name, msg + chain.map(_ => formatChain(chain)).getOrElse(""))
64-
case Empty => NmlParseEmpty(name)
60+
case f: Failure => NmlParseFailure(name, formatFailureChain(f, messagesProviderOpt = Some(m)))
61+
case Empty => NmlParseEmpty(name)
6562
}
6663
}
6764

conf/application.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ features {
161161
exportTiffMaxVolumeMVx = 1024
162162
exportTiffMaxEdgeLengthVx = 8192
163163
defaultToLegacyBindings = false
164-
editableMappingsEnabled = false
164+
editableMappingsEnabled = true
165165
# The only valid item value is currently "ConnectomeView":
166166
optInTabs = []
167167
openIdConnectEnabled = false

frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/misc.e2e.js.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Generated by [AVA](https://avajs.dev).
7272
defaultToLegacyBindings: false,
7373
discussionBoard: 'https://forum.image.sc/tag/webknossos',
7474
discussionBoardRequiresAdmin: false,
75-
editableMappingsEnabled: false,
75+
editableMappingsEnabled: true,
7676
exportTiffMaxEdgeLengthVx: 8192,
7777
exportTiffMaxVolumeMVx: 1024,
7878
hideNavbarLogin: false,

util/src/main/scala/com/scalableminds/util/mvc/ExtendedController.scala

+4-18
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.scalableminds.util.mvc
22

33
import com.google.protobuf.CodedInputStream
44
import com.scalableminds.util.accesscontext.TokenContext
5-
import com.scalableminds.util.time.Instant
65
import com.scalableminds.util.tools.{BoxImplicits, Fox, FoxImplicits}
76
import com.typesafe.scalalogging.LazyLogging
87
import net.liftweb.common._
@@ -40,23 +39,10 @@ trait BoxToResultHelpers extends I18nSupport with Formatter with RemoteOriginHel
4039
allowRemoteOriginIfSelected(addNoCacheHeaderFallback(result))
4140
}
4241

43-
private def formatChainOpt(chain: Box[Failure])(implicit messages: MessagesProvider): Option[String] = chain match {
44-
case Full(_) => Some(formatChain(chain))
45-
case _ => None
46-
}
47-
48-
private def formatChain(chain: Box[Failure], includeTime: Boolean = true)(
49-
implicit messages: MessagesProvider): String = chain match {
50-
case Full(failure) =>
51-
val serverTimeMsg = if (includeTime) s"[Server Time ${Instant.now}] " else ""
52-
serverTimeMsg + " <~ " + formatFailure(failure) + formatChain(failure.chain, includeTime = false)
53-
case _ => ""
54-
}
55-
56-
private def formatFailure(failure: Failure)(implicit messages: MessagesProvider): String =
57-
failure match {
58-
case ParamFailure(msg, _, _, param) => Messages(msg) + " " + param.toString
59-
case Failure(msg, _, _) => Messages(msg)
42+
private def formatChainOpt(chainBox: Box[Failure])(implicit messages: MessagesProvider): Option[String] =
43+
chainBox match {
44+
case Full(chain) => Some(formatFailureChain(chain, includeTime = true, messagesProviderOpt = Some(messages)))
45+
case _ => None
6046
}
6147

6248
private def jsonMessages(msgs: JsArray): JsObject =

util/src/main/scala/com/scalableminds/util/mvc/Formatter.scala

+33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.scalableminds.util.mvc
22

3+
import com.scalableminds.util.time.Instant
34
import com.scalableminds.util.tools.TextUtils
5+
import net.liftweb.common.{Box, Failure, Full, ParamFailure}
6+
import play.api.i18n.{Messages, MessagesProvider}
47

58
import java.text.SimpleDateFormat
69
import java.util.Date
@@ -78,4 +81,34 @@ trait Formatter {
7881
}
7982
}
8083

84+
protected def formatFailureChain(failure: Failure,
85+
includeStackTraces: Boolean = false,
86+
includeTime: Boolean = false,
87+
messagesProviderOpt: Option[MessagesProvider] = None): String = {
88+
89+
def formatStackTrace(failure: Failure) =
90+
failure.exception match {
91+
case Full(exception) if includeStackTraces => s" Stack trace: ${TextUtils.stackTraceAsString(exception)} "
92+
case _ => ""
93+
}
94+
95+
def formatNextChain(chainBox: Box[Failure]): String = chainBox match {
96+
case Full(chainFailure) =>
97+
" <~ " + formatFailureChain(chainFailure, includeStackTraces, includeTime = false, messagesProviderOpt)
98+
case _ => ""
99+
}
100+
101+
def formatMsg(msg: String): String =
102+
messagesProviderOpt.map(mp => Messages(msg)(mp)).getOrElse(msg)
103+
104+
def formatOneFailure(failure: Failure): String =
105+
failure match {
106+
case ParamFailure(msg, _, _, param) => formatMsg(msg) + " " + param.toString
107+
case Failure(msg, _, _) => formatMsg(msg)
108+
}
109+
110+
val serverTimeMsg = if (includeTime) s"[Server Time ${Instant.now}] " else ""
111+
serverTimeMsg + formatOneFailure(failure) + formatStackTrace(failure) + formatNextChain(failure.chain)
112+
}
113+
81114
}

util/src/main/scala/com/scalableminds/util/tools/Fox.scala

-16
Original file line numberDiff line numberDiff line change
@@ -257,22 +257,6 @@ object Fox extends FoxImplicits {
257257
runNext(functions, t)
258258
}
259259

260-
def failureChainAsString(failure: Failure, includeStackTraces: Boolean = false): String = {
261-
def formatStackTrace(failure: Failure) =
262-
failure.exception match {
263-
case Full(exception) if includeStackTraces => s" Stack trace: ${TextUtils.stackTraceAsString(exception)} "
264-
case _ => ""
265-
}
266-
267-
def formatChain(chain: Box[Failure]): String = chain match {
268-
case Full(failure) =>
269-
" <~ " + failure.msg + formatStackTrace(failure) + formatChain(failure.chain)
270-
case _ => ""
271-
}
272-
273-
failure.msg + formatStackTrace(failure) + formatChain(failure.chain)
274-
}
275-
276260
def firstSuccess[T](foxes: Seq[Fox[T]])(implicit ec: ExecutionContext): Fox[T] = {
277261
def runNext(remainingFoxes: Seq[Fox[T]]): Fox[T] =
278262
remainingFoxes match {

webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/DataSourceController.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ class DataSourceController @Inject()(
745745
reportMutable += "Error when exploring as layer set: Resulted in zero layers."
746746
None
747747
case f: Failure =>
748-
reportMutable += s"Error when exploring as layer set: ${Fox.failureChainAsString(f)}"
748+
reportMutable += s"Error when exploring as layer set: ${formatFailureChain(f, includeStackTraces = true, messagesProviderOpt = Some(request.messages))}"
749749
None
750750
case Empty =>
751751
reportMutable += "Error when exploring as layer set: Empty"

webknossos-datastore/app/com/scalableminds/webknossos/datastore/explore/ExploreRemoteLayerService.scala

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.scalableminds.webknossos.datastore.explore
22

33
import com.scalableminds.util.geometry.Vec3Int
4+
import com.scalableminds.util.mvc.Formatter
45
import com.scalableminds.util.tools.{Fox, FoxImplicits}
56
import com.scalableminds.webknossos.datastore.DataStoreConfig
67
import com.scalableminds.webknossos.datastore.datavault.VaultPath
@@ -16,6 +17,7 @@ import com.scalableminds.webknossos.datastore.storage.{DataVaultCredential, Data
1617
import com.typesafe.scalalogging.LazyLogging
1718
import net.liftweb.common.Box.tryo
1819
import net.liftweb.common.{Box, Empty, Failure, Full}
20+
import play.api.i18n.MessagesProvider
1921
import play.api.libs.json.{Json, OFormat}
2022

2123
import java.net.URI
@@ -49,10 +51,12 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
4951
dataStoreConfig: DataStoreConfig)
5052
extends ExploreLayerUtils
5153
with FoxImplicits
54+
with Formatter
5255
with LazyLogging {
5356

5457
def exploreRemoteDatasource(parameters: List[ExploreRemoteLayerParameters], reportMutable: ListBuffer[String])(
55-
implicit ec: ExecutionContext): Fox[GenericDataSource[DataLayer]] =
58+
implicit ec: ExecutionContext,
59+
mp: MessagesProvider): Fox[GenericDataSource[DataLayer]] =
5660
for {
5761
exploredLayersNested <- Fox.serialCombined(parameters)(
5862
parameters =>
@@ -75,7 +79,8 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
7579
private def exploreRemoteLayersForOneUri(layerUri: String,
7680
credentialId: Option[String],
7781
reportMutable: ListBuffer[String])(
78-
implicit ec: ExecutionContext): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
82+
implicit ec: ExecutionContext,
83+
mp: MessagesProvider): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
7984
for {
8085
uri <- tryo(new URI(removeNeuroglancerPrefixesFromUri(removeHeaderFileNamesFromUriSuffix(layerUri)))) ?~> s"Received invalid URI: $layerUri"
8186
_ <- bool2Fox(uri.getScheme != null) ?~> s"Received invalid URI: $layerUri"
@@ -116,7 +121,8 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
116121
credentialId: Option[String],
117122
explorers: List[RemoteLayerExplorer],
118123
reportMutable: ListBuffer[String])(
119-
implicit ec: ExecutionContext): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
124+
implicit ec: ExecutionContext,
125+
mp: MessagesProvider): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
120126
remotePathsWithDepth match {
121127
case Nil =>
122128
Fox.empty
@@ -139,7 +145,8 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
139145
explorers: List[RemoteLayerExplorer],
140146
credentialId: Option[String],
141147
reportMutable: ListBuffer[String])(
142-
implicit ec: ExecutionContext): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
148+
implicit ec: ExecutionContext,
149+
mp: MessagesProvider): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
143150
Fox
144151
.sequence(explorers.map { explorer =>
145152
{
@@ -160,12 +167,13 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
160167
explorer: RemoteLayerExplorer,
161168
path: VaultPath,
162169
reportMutable: ListBuffer[String])(
163-
implicit ec: ExecutionContext): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] = explorationResult match {
170+
implicit ec: ExecutionContext,
171+
mp: MessagesProvider): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] = explorationResult match {
164172
case Full(layersWithVoxelSizes) =>
165173
reportMutable += s"Found ${layersWithVoxelSizes.length} ${explorer.name} layers at $path."
166174
Fox.successful(layersWithVoxelSizes)
167175
case f: Failure =>
168-
reportMutable += s"Error when reading $path as ${explorer.name}: ${Fox.failureChainAsString(f)}"
176+
reportMutable += s"Error when reading $path as ${explorer.name}: ${formatFailureChain(f, messagesProviderOpt = Some(mp))}"
169177
Fox.empty
170178
case Empty =>
171179
reportMutable += s"Error when reading $path as ${explorer.name}: Empty"
@@ -179,7 +187,8 @@ class ExploreRemoteLayerService @Inject()(dataVaultService: DataVaultService,
179187
credentialId: Option[String],
180188
explorers: List[RemoteLayerExplorer],
181189
reportMutable: ListBuffer[String])(
182-
implicit ec: ExecutionContext): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
190+
implicit ec: ExecutionContext,
191+
mp: MessagesProvider): Fox[List[(DataLayerWithMagLocators, VoxelSize)]] =
183192
explorationResultOfPath match {
184193
case Full(layersWithVoxelSizes) =>
185194
Fox.successful(layersWithVoxelSizes)

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AdHocMeshServiceHolder.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.scalableminds.webknossos.datastore.services
22

33
import org.apache.pekko.actor.ActorSystem
4-
import javax.inject.Inject
54

5+
import javax.inject.Inject
66
import scala.concurrent.ExecutionContext
77
import scala.concurrent.duration.FiniteDuration
88

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/DatasetErrorLoggingService.scala

+10-14
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package com.scalableminds.webknossos.datastore.services
22

33
import org.apache.pekko.actor.ActorSystem
44
import com.google.inject.name.Named
5-
import com.scalableminds.util.tools.{Fox, TextUtils}
5+
import com.scalableminds.util.mvc.Formatter
6+
import com.scalableminds.util.tools.Fox
67
import com.scalableminds.util.tools.Fox.box2Fox
78
import com.scalableminds.webknossos.datastore.helpers.IntervalScheduler
89
import com.scalableminds.webknossos.datastore.models.datasource.DataSourceId
910
import com.typesafe.scalalogging.LazyLogging
10-
import net.liftweb.common.{Empty, Failure, Full}
11+
import net.liftweb.common.{Failure, Full}
1112
import play.api.inject.ApplicationLifecycle
1213

1314
import javax.inject.Inject
@@ -19,6 +20,7 @@ class DatasetErrorLoggingService @Inject()(
1920
val applicationHealthService: ApplicationHealthService,
2021
@Named("webknossos-datastore") val system: ActorSystem)(implicit val ec: ExecutionContext)
2122
extends IntervalScheduler
23+
with Formatter
2224
with LazyLogging {
2325

2426
private val errorCountThresholdPerDataset = 5
@@ -30,10 +32,10 @@ class DatasetErrorLoggingService @Inject()(
3032
// Not doing any synchronization here since wrong counts don’t do much harm, and synchronizing would be slow
3133
private val recentErrors: scala.collection.mutable.Map[(String, String), Int] = scala.collection.mutable.Map()
3234

33-
def shouldLog(organizationId: String, datasetName: String): Boolean =
35+
private def shouldLog(organizationId: String, datasetName: String): Boolean =
3436
recentErrors.getOrElse((organizationId, datasetName), 0) < errorCountThresholdPerDataset
3537

36-
def registerLogged(organizationId: String, datasetName: String): Unit = {
38+
private def registerLogged(organizationId: String, datasetName: String): Unit = {
3739
val previousErrorCount = recentErrors.getOrElse((organizationId, datasetName), 0)
3840
if (previousErrorCount >= errorCountThresholdPerDataset - 1) {
3941
logger.info(
@@ -64,19 +66,13 @@ class DatasetErrorLoggingService @Inject()(
6466
logger.error(s"Caught internal error ($msg) while $label for $dataSourceId:", e)
6567
applicationHealthService.pushError(e)
6668
Fox.failure(msg, Full(e))
67-
case Failure(msg, Full(exception), _) =>
69+
case f: Failure =>
6870
if (shouldLog(dataSourceId.organizationId, dataSourceId.directoryName)) {
69-
logger.error(
70-
s"Error while $label for $dataSourceId: $msg – Stack trace: ${TextUtils.stackTraceAsString(exception)} ")
71+
logger.error(s"Error while $label for $dataSourceId: ${formatFailureChain(f, includeStackTraces = true)}")
7172
registerLogged(dataSourceId.organizationId, dataSourceId.directoryName)
7273
}
73-
Fox.failure(msg, Full(exception))
74-
case Failure(msg, Empty, _) =>
75-
if (shouldLog(dataSourceId.organizationId, dataSourceId.directoryName)) {
76-
logger.error(s"Error while $label for $dataSourceId, Failure without exception: $msg")
77-
registerLogged(dataSourceId.organizationId, dataSourceId.directoryName)
78-
}
79-
Fox.failure(msg)
74+
f.toFox
8075
case other => other.toFox
8176
}
77+
8278
}

0 commit comments

Comments
 (0)