Skip to content

Commit c67bcc3

Browse files
authored
feat: Implement SentryModule and PureConfig integration (#192)
1 parent 84ef56e commit c67bcc3

File tree

9 files changed

+137
-0
lines changed

9 files changed

+137
-0
lines changed

build.sbt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ lazy val root = project
4444
monixCatnapMicrometer,
4545
monixCatnapPureConfig,
4646
pureConfig,
47+
sentry,
48+
sentryPureConfig,
4749
sslConfig
4850
)
4951
.settings(
@@ -374,6 +376,23 @@ lazy val pureConfig = project
374376
libraryDependencies += Dependencies.pureConfig
375377
)
376378

379+
lazy val sentry = project
380+
.in(file("sentry"))
381+
.settings(BuildSettings.common)
382+
.settings(
383+
name := "sst-sentry",
384+
libraryDependencies += Dependencies.sentry
385+
)
386+
387+
lazy val sentryPureConfig = project
388+
.in(file("sentry-pureconfig"))
389+
.dependsOn(sentry)
390+
.settings(BuildSettings.common)
391+
.settings(
392+
name := "sst-sentry-pureconfig",
393+
libraryDependencies += Dependencies.pureConfig
394+
)
395+
377396
lazy val site = project
378397
.in(file("site"))
379398
.enablePlugins(
@@ -395,6 +414,7 @@ lazy val site = project
395414
http4sClientMonixCatnap,
396415
monixCatnapPureConfig,
397416
micrometerJmxPureConfig,
417+
sentry,
398418
sslConfig
399419
)
400420
.settings(BuildSettings.common)

http4s-server-blaze/src/main/scala/com/avast/sst/http4s/server/Http4sBlazeServerModule.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.http4s.server.blaze.BlazeServerBuilder
99

1010
import scala.concurrent.ExecutionContext
1111
import scala.concurrent.duration.Duration
12+
1213
object Http4sBlazeServerModule {
1314

1415
/** Makes [[org.http4s.server.Server]] (Blaze) initialized with the given config and [[org.http4s.HttpApp]].

project/Dependencies.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ object Dependencies {
2929
val scalafixScaluzzi = "com.github.vovapolu" %% "scaluzzi" % "0.1.3"
3030
val scalafixSortImports = "com.nequissimus" %% "sort-imports" % "0.3.2"
3131
val scalaTest = "org.scalatest" %% "scalatest" % "3.1.1"
32+
val sentry = "io.sentry" % "sentry" % "1.7.30"
3233
val silencer = "com.github.ghik" % "silencer-plugin" % Versions.silencer cross CrossVersion.full
3334
val silencerLib = "com.github.ghik" % "silencer-lib" % Versions.silencer cross CrossVersion.full
3435
val slf4jApi = "org.slf4j" % "slf4j-api" % "1.7.30"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.avast.sst.sentry.pureconfig
2+
3+
import com.avast.sst.sentry.SentryConfig
4+
import pureconfig.ConfigReader
5+
import pureconfig.generic.ProductHint
6+
import pureconfig.generic.semiauto.deriveReader
7+
8+
trait ConfigReaders {
9+
10+
implicit protected def hint[T]: ProductHint[T] = ProductHint.default
11+
12+
implicit val sentryConfigReader: ConfigReader[SentryConfig] = deriveReader
13+
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.avast.sst.sentry.pureconfig
2+
3+
import pureconfig.ConfigFieldMapping
4+
import pureconfig.generic.ProductHint
5+
6+
/** Contains [[pureconfig.ConfigReader]] instances with default "kebab-case" naming convention. */
7+
object implicits extends ConfigReaders {
8+
9+
/** Contains [[pureconfig.ConfigReader]] instances with "kebab-case" naming convention.
10+
*
11+
* This is alias for the default `implicits._` import.
12+
*/
13+
object KebabCase extends ConfigReaders
14+
15+
/** Contains [[pureconfig.ConfigReader]] instances with "camelCase" naming convention. */
16+
object CamelCase extends ConfigReaders {
17+
implicit override protected def hint[T]: ProductHint[T] = ProductHint(ConfigFieldMapping(pureconfig.CamelCase, pureconfig.CamelCase))
18+
}
19+
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.avast.sst.sentry
2+
3+
final case class SentryConfig(
4+
dsn: String,
5+
release: Option[String] = None,
6+
environment: Option[String] = None,
7+
distribution: Option[String] = None,
8+
serverName: Option[String] = None,
9+
tags: Map[String, String] = Map.empty,
10+
stacktraceAppPackages: List[String] = List.empty
11+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.avast.sst.sentry
2+
3+
import cats.effect.{Resource, Sync}
4+
import io.sentry.{SentryClient, SentryClientFactory}
5+
6+
import scala.jdk.CollectionConverters._
7+
import scala.reflect.ClassTag
8+
9+
object SentryModule {
10+
11+
/** Makes [[io.sentry.SentryClient]] initialized with the given config. */
12+
def make[F[_]: Sync](config: SentryConfig): Resource[F, SentryClient] = {
13+
Resource.make {
14+
Sync[F].delay {
15+
val dsnCustomizations = s"${config.stacktraceAppPackages.mkString("stacktrace.app.packages=", ",", "")}"
16+
val finalDsn = if (dsnCustomizations.nonEmpty) s"${config.dsn}?$dsnCustomizations" else config.dsn
17+
val sentryClient = SentryClientFactory.sentryClient(finalDsn)
18+
config.release.foreach(sentryClient.setRelease)
19+
config.environment.foreach(sentryClient.setEnvironment)
20+
config.distribution.foreach(sentryClient.setDist)
21+
config.serverName.foreach(sentryClient.setServerName)
22+
sentryClient.setTags(config.tags.asJava)
23+
sentryClient
24+
}
25+
}(sentry => Sync[F].delay(sentry.closeConnection()))
26+
}
27+
28+
/** Makes [[io.sentry.SentryClient]] initialized with the given config and overrides the `release` property
29+
* with `Implementation-Version` from the `MANIFEST.MF` file inside the same JAR (package) as the `Main` class.
30+
*/
31+
def makeWithReleaseFromPackage[F[_]: Sync, Main: ClassTag](config: SentryConfig): Resource[F, SentryClient] = {
32+
for {
33+
customizedConfig <- Resource.liftF {
34+
Sync[F].delay {
35+
for {
36+
pkg <- Option(implicitly[ClassTag[Main]].runtimeClass.getPackage)
37+
v <- Option(pkg.getImplementationVersion)
38+
} yield config.copy(release = Some(v))
39+
}
40+
}
41+
sentryClient <- make[F](customizedConfig.getOrElse(config))
42+
} yield sentryClient
43+
}
44+
45+
}

site/docs/subprojects/sentry.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
layout: docs
3+
title: "Sentry"
4+
---
5+
6+
# Sentry
7+
8+
`libraryDependencies += "com.avast" %% "sst-sentry" % "@VERSION@"`
9+
10+
This subproject allows you to initialize `SentryClient` from a configuration case class. There are two `make*` methods. The one called `make`
11+
does everything according to the configuration. The one called `makeWithReleaseFromPackage` adds a bit of clever behavior because it reads
12+
the `Implementation-Version` property from the `MANIFEST.MF` file from the JAR (package) of the respective `Main` class and uses it to override
13+
the `release` property of Sentry. This allows you to automatically propage the version of your application to Sentry.
14+
15+
Initialization of the `SentryClient` is side-effectful so it is wrapped in `Resource[F, SentryClient]` and `F` is `Sync`.
16+
17+
```scala mdoc:silent
18+
import com.avast.sst.sentry._
19+
import zio.interop.catz._
20+
import zio.Task
21+
22+
val sentry = SentryModule.make[Task](SentryConfig("<dsn>"))
23+
```

site/menu.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ options:
2828
url: subprojects/flyway
2929
- title: monix-catnap - Circuit Breaker
3030
url: subprojects/monix-catnap
31+
- title: Sentry
32+
url: subprojects/sentry

0 commit comments

Comments
 (0)