Skip to content

Commit

Permalink
Merge pull request #337 from broadinstitute/bug
Browse files Browse the repository at this point in the history
[IA-1857] add error reporting
  • Loading branch information
Qi77Qi committed Aug 7, 2020
2 parents 7eb4ba9 + 1a585cb commit 92fcd96
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 3 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-util"
Contains utility functions and classes. Util2 is added because util needs to support 2.11 for `firecloud-orchestration`,
but many libraries start to drop 2.11 support. Util2 doesn't support 2.11.

Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-util2" % "0.1-df50246"`
Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-util2" % "0.1-TRAVIS-REPLACE-ME"`

[Changelog](util2/CHANGELOG.md)

Expand Down Expand Up @@ -82,7 +82,15 @@ Contains utility functions for publishing custom metrics using openTelemetry (op

Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-opentelemetry" % "0.1-e66171c"`

[Changelog](newrelic/CHANGELOG.md)
[Changelog](openTelemetry/CHANGELOG.md)

## workbench-error-reporting

Contains utility functions for publishing custom metrics using openTelemetry (openCensus and openTracing).

Latest SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-error-reporting" % "0.1-TRAVIS-REPLACE-ME"`

[Changelog](errorReporting/CHANGELOG.md)

## workbench-service-test

Expand Down
7 changes: 7 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ lazy val workbenchOpenTelemetry = project
.dependsOn(workbenchUtil2 % testAndCompile)
.withTestSettings

lazy val workbenchErrorReporting = project
.in(file("errorReporting"))
.settings(errorReportingSettings: _*)
.dependsOn(workbenchUtil2 % testAndCompile)
.withTestSettings

lazy val workbenchServiceTest = project
.in(file("serviceTest"))
.settings(serviceTestSettings: _*)
Expand Down Expand Up @@ -82,6 +88,7 @@ lazy val workbenchLibs = project
.aggregate(workbenchMetrics)
.aggregate(workbenchNewrelic)
.aggregate(workbenchOpenTelemetry)
.aggregate(workbenchErrorReporting)
.aggregate(workbenchGoogle)
.aggregate(workbenchGoogle2)
.aggregate(workbenchServiceTest)
Expand Down
10 changes: 10 additions & 0 deletions errorReporting/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

This file documents changes to the `workbench-error-reporting` library, including notes on how to upgrade to new versions.

## 0.1

### Added
- Add `ErrorReporting`

SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-error-reporting" % "0.1-TRAVIS-REPLACE-ME"`
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.broadinstitute.dsde.workbench.errorReporting

import java.nio.file.Path

import cats.effect.{Resource, Sync}
import com.google.api.gax.core.FixedCredentialsProvider
import com.google.auth.oauth2.{GoogleCredentials, ServiceAccountCredentials}
import com.google.cloud.errorreporting.v1beta1.{ReportErrorsServiceClient, ReportErrorsServiceSettings}
import com.google.devtools.clouderrorreporting.v1beta1.{ProjectName, SourceLocation}

import scala.collection.JavaConverters._

trait ErrorReporting[F[_]] {
def reportError(msg: String, sourceLocation: SourceLocation): F[Unit]

/**
* @param t This throwable can not be NoStackTrace
* @return
*/
def reportError(t: Throwable): F[Unit]
}

object ErrorReporting {
def fromPath[F[_]](pathToCredential: Path, appName: String, projectName: ProjectName)(
implicit F: Sync[F]
): Resource[F, ErrorReporting[F]] =
for {
crendtialFile <- org.broadinstitute.dsde.workbench.util2.readPath(pathToCredential)
credential = ServiceAccountCredentials
.fromStream(crendtialFile)
.createScoped(
Set("https://www.googleapis.com/auth/cloud-platform").asJava
)
client <- fromCredential(credential, appName, projectName)
} yield client

def fromCredential[F[_]](credentials: GoogleCredentials, appName: String, projectName: ProjectName)(
implicit F: Sync[F]
): Resource[F, ErrorReporting[F]] = {
val settings = ReportErrorsServiceSettings
.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
.build()
Resource
.make[F, ReportErrorsServiceClient](F.delay(ReportErrorsServiceClient.create(settings)))(
c => F.delay(c.close())
)
.map(c => new ErrorReportingInterpreter[F](appName, projectName, c))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.broadinstitute.dsde.workbench.errorReporting

import cats.effect.{Resource, Sync}
import cats.implicits._
import com.google.cloud.errorreporting.v1beta1.ReportErrorsServiceClient
import com.google.devtools.clouderrorreporting.v1beta1._
import java.io.PrintWriter
import java.io.StringWriter

class ErrorReportingInterpreter[F[_]](appName: String, projectName: ProjectName, client: ReportErrorsServiceClient)(
implicit F: Sync[F]
) extends ErrorReporting[F] {
val serviceContext = ServiceContext.newBuilder().setService(appName)

override def reportError(msg: String, sourceLocation: SourceLocation): F[Unit] = {
val errorEvent = ReportedErrorEvent
.newBuilder()
.setMessage(msg)
.setServiceContext(serviceContext)
.setContext(ErrorContext.newBuilder().setReportLocation(sourceLocation))
.build()

F.delay(client.reportErrorEvent(projectName, errorEvent))
}

override def reportError(t: Throwable): F[Unit] = {
val stackTraceWriter = Resource.make {
val sw = new StringWriter
F.delay(StackTraceWriter(sw, new PrintWriter(sw)))
}(sw => F.delay(sw.printWriter.close()) >> F.delay(sw.stringWriter.close()))

stackTraceWriter.use { w =>
for {
_ <- F.delay(t.printStackTrace(w.printWriter))
errorEvent = ReportedErrorEvent
.newBuilder()
.setMessage(w.stringWriter.toString)
.setServiceContext(serviceContext)
.build()
_ <- F.delay(client.reportErrorEvent(projectName, errorEvent))
} yield ()
}
}
}

final private case class StackTraceWriter(stringWriter: StringWriter, printWriter: PrintWriter)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.broadinstitute.dsde.workbench.errorReporting

/**
* Defines a ReportWorthy type class which has `def isReportWorthy(a: A): Boolean` function
*/
trait ReportWorthy[A] {
def isReportWorthy(a: A): Boolean
}

final case class ReportWorthyOps[A](a: A)(implicit ev: ReportWorthy[A]) {
def isReportWorthy: Boolean = ev.isReportWorthy(a)
}

object ReportWorthySyntax {
implicit def reportWorthySyntax[A: ReportWorthy](a: A): ReportWorthyOps[A] = ReportWorthyOps(a)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.broadinstitute.dsde.workbench.errorReporting

import java.nio.file.Paths

import cats.effect.IO
import com.google.devtools.clouderrorreporting.v1beta1.{ProjectName, SourceLocation}

import scala.concurrent.ExecutionContext.global
import scala.util.control.NoStackTrace

object ErrorReportingManualTest {
implicit val cs = IO.contextShift(global)

private def test(reporting: ErrorReporting[IO]): IO[Unit] =
for {
_ <- reporting.reportError(new Exception("eeee2"))
_ <- reporting.reportError(
"error2",
SourceLocation
.newBuilder()
.setFunctionName("qi-function")
.setFilePath(this.getClass.getName)
// .setLineNumber(10)
.build()
)
} yield ()

def run(): Unit = {
val res = ErrorReporting
.fromPath[IO](Paths.get("/Users/qi/.google/qi-billing-90828dd5e7b8.json"),
"qi-test-app",
ProjectName.of("qi-billing"))
.use(c => test(c))

res.unsafeRunSync()
}
}

final case class CustomException(msg: String) extends NoStackTrace
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.broadinstitute.dsde.workbench.errorReporting

import cats.effect._
import com.google.devtools.clouderrorreporting.v1beta1.SourceLocation

object FakeErrorReporting extends ErrorReporting[IO] {
override def reportError(msg: String, sourceLocation: SourceLocation): IO[Unit] = IO.unit

override def reportError(t: Throwable): IO[Unit] = IO.unit
}
5 changes: 5 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ object Dependencies {
openCensusTraceStackDriver
)

val errorReportingDependencies = List(
catsEffect,
"com.google.cloud" % "google-cloud-errorreporting" % "0.120.0-beta"
)

val util2Dependencies = commonDependencies ++ List(
catsEffect,
log4cats,
Expand Down
6 changes: 6 additions & 0 deletions project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ object Settings {
version := createVersion("0.1")
) ++ publishSettings

val errorReportingSettings = cross212and213 ++ commonSettings ++ List(
name := "workbench-error-reporting",
libraryDependencies ++= errorReportingDependencies,
version := createVersion("0.1")
) ++ publishSettings

val serviceTestSettings = only212 ++ commonSettings ++ List(
name := "workbench-service-test",
libraryDependencies ++= serviceTestDependencies,
Expand Down
2 changes: 1 addition & 1 deletion util2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ This file documents changes to the `workbench-util2` library, including notes on

- Add `ConsoleLogger`

SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-util2" % "0.1-df50246"`
SBT dependency: `"org.broadinstitute.dsde.workbench" %% "workbench-util2" % "0.1-TRAVIS-REPLACE-ME"`

Moved a few utilities that depends on `circe`, `fs2` from `util` to `util2`
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ package object util2 {
def readFile[F[_]](path: String)(implicit sf: Sync[F]): Resource[F, FileInputStream] =
Resource.make(sf.delay(new FileInputStream(path)))(f => sf.delay(f.close()))

def readPath[F[_]](path: Path)(implicit sf: Sync[F]): Resource[F, FileInputStream] =
Resource.make(sf.delay(new FileInputStream(path.toString)))(f => sf.delay(f.close()))

/*
* Example:
* scala> org.broadinstitute.dsde.workbench.util.readJsonFileToA[IO, List[String]](java.nio.file.Paths.get("/tmp/list"), None).compile.lastOrError.unsafeRunSync
Expand Down

0 comments on commit 92fcd96

Please sign in to comment.