Skip to content

Commit

Permalink
Remove source code sanitizer, switch to tapir, use sttp as client, ad…
Browse files Browse the repository at this point in the history
…d throttling, deduplicate sent reports
  • Loading branch information
rochala committed Mar 29, 2024
1 parent ebd43f7 commit cca5308
Show file tree
Hide file tree
Showing 25 changed files with 155 additions and 946 deletions.
9 changes: 6 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ lazy val telemetryInterfaces = project
else Nil
},
libraryDependencies := List(
"com.softwaremill.sttp.tapir" %% "tapir-core" % "1.10.0",
"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.10.0",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.27.7",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.27.7" % "compile-internal",
),
Expand Down Expand Up @@ -346,7 +348,6 @@ val mtagsSettings = List(
Compile / doc / sources := Seq.empty,
libraryDependencies ++= Seq(
"com.lihaoyi" %% "geny" % V.genyVersion,
"com.lihaoyi" %% "requests" % V.requests,
"com.thoughtworks.qdox" % "qdox" % V.qdox, // for java mtags
"org.scala-lang.modules" %% "scala-java8-compat" % V.java8Compat,
"org.jsoup" % "jsoup" % V.jsoup, // for extracting HTML from javadocs
Expand Down Expand Up @@ -528,8 +529,9 @@ lazy val metals = project
// for JSON formatted doctor
"com.lihaoyi" %% "ujson" % "3.1.5",
// For fetching projects' templates
// For remote language server
"com.lihaoyi" %% "requests" % V.requests,
// telemetry client
"com.softwaremill.sttp.client3" %% "core" % "3.9.5",
"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % "1.10.0",
// for producing SemanticDB from Scala source files, to be sure we want the same version of scalameta
"org.scalameta" %% "scalameta" % V.semanticdb(scalaVersion.value),
"org.scalameta" % "semanticdb-scalac-core" % V.semanticdb(
Expand Down Expand Up @@ -832,6 +834,7 @@ lazy val unit = project
"io.get-coursier" %% "coursier" % V.coursier, // for jars
"ch.epfl.scala" %% "bloop-config" % V.bloopConfig,
"org.scalameta" %% "munit" % V.munit,
"com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.10.0",
),
buildInfoPackage := "tests",
Compile / resourceGenerators += InputProperties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import scala.meta.internal.process.ExitCodes
import scala.meta.io.AbsolutePath

import coursierapi._
import sttp.client3._

class NewProjectProvider(
client: MetalsLanguageClient,
Expand All @@ -34,13 +35,15 @@ class NewProjectProvider(
)(implicit context: ExecutionContext) {

private val templatesUrl =
"https://github.com/foundweekends/giter8/wiki/giter8-templates.md"
uri"https://github.com/foundweekends/giter8/wiki/giter8-templates.md"
private val giterDependency = Dependency
.of("org.foundweekends.giter8", "giter8_2.12", BuildInfo.gitter8Version)
// equal to cmd's: g8 playframework/play-scala-seed.g8 --name=../<<name>>
private val giterMain = "giter8.Giter8"

val backend = HttpClientSyncBackend()
private var allTemplates = Seq.empty[MetalsQuickPickItem]

def allTemplatesFromWeb: Seq[MetalsQuickPickItem] =
synchronized {
if (allTemplates.nonEmpty) {
Expand All @@ -53,14 +56,17 @@ class NewProjectProvider(
// - [jimschubert/finatra.g8](https://github.com/jimschubert/finatra.g8)
// (A simple Finatra 2.5 template with sbt-revolver and sbt-native-packager)
val all = for {
result <- Try(requests.get(templatesUrl)).toOption.toIterable
_ = if (result.statusCode != 200)
result <- Try(
basicRequest.get(templatesUrl).send(backend)
).toOption.toIterable
_ = if (!result.is200)
client.showMessage(
NewScalaProject.templateDownloadFailed(result.statusMessage)
NewScalaProject.templateDownloadFailed(result.statusText)
)
if result.statusCode == 200
if result.is200
text <- result.body.toOption
} yield {
NewProjectProvider.templatesFromText(result.text(), icons.github)
NewProjectProvider.templatesFromText(text, icons.github)
}
allTemplates = all.flatten.toSeq
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import scala.meta.internal.metals.BuildTargets
import scala.meta.internal.metals.Compilers
import scala.meta.internal.metals.DefinitionProvider
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.Report
import scala.meta.internal.metals.ScalaVersionSelector
import scala.meta.internal.metals.ScalaVersions
import scala.meta.internal.metals.SemanticdbFeatureProvider
Expand Down Expand Up @@ -46,6 +45,7 @@ import scala.meta.pc.ReportContext
import ch.epfl.scala.bsp4j.BuildTargetIdentifier
import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.TextDocumentPositionParams
import scala.meta.internal.pc.StandardReport

final class ImplementationProvider(
semanticdbs: Semanticdbs,
Expand Down Expand Up @@ -176,7 +176,7 @@ final class ImplementationProvider(

if (sourceFiles.isEmpty) {
rc.unsanitized.create(
Report(
StandardReport(
"missing-definition",
s"""|Missing definition symbol for:
|$dealisedSymbol
Expand Down
21 changes: 11 additions & 10 deletions metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1225,19 +1225,20 @@ class Compilers(
): ReportContext = {
val logger =
ju.logging.Logger.getLogger(classOf[TelemetryReportContext].getName)

val loggerAccess = LoggerAccess(
debug = logger.fine(_),
info = logger.info(_),
warning = logger.warning(_),
error = logger.severe(_),
)

new TelemetryReportContext(
telemetryLevel = () => userConfig().telemetryLevel,
reporterContext = createTelemetryReporterContext,
sanitizers = new TelemetryReportContext.Sanitizers(
workspace = Some(workspace.toNIO),
sourceCodeTransformer = Some(ScalametaSourceCodeTransformer),
),
logger = LoggerAccess(
debug = logger.fine(_),
info = logger.info(_),
warning = logger.warning(_),
error = logger.severe(_),
),
workspaceSanitizer = new WorkspaceSanitizer(Some(workspace.toNIO)),
telemetryClient = new telemetry.TelemetryClient(logger = loggerAccess),
logger = loggerAccess,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import org.eclipse.lsp4j.InlayHint
import org.eclipse.lsp4j.InlayHintLabelPart
import org.eclipse.lsp4j.TextDocumentIdentifier
import org.eclipse.{lsp4j => l}
import scala.meta.pc.ReportContext
import scala.meta.internal.pc.StandardReport

final class InlayHintResolveProvider(
definitionProvider: DefinitionProvider,
Expand All @@ -30,7 +32,7 @@ final class InlayHintResolveProvider(
resolve(inlayHint, labelParts, path, token)
case Left(error) =>
scribe.warn(s"Failed to resolve inlay hint: $error")
rc.unsanitized.create(report(inlayHint, path, error), ifVerbose = true)
rc.unsanitized.create(report(inlayHint, path, error), true)
Future.successful(inlayHint)
}
}
Expand Down Expand Up @@ -109,7 +111,7 @@ final class InlayHintResolveProvider(
error: Throwable,
) = {
val pos = inlayHint.getPosition()
Report(
StandardReport(
"inlayHint-resolve",
s"""|pos: $pos
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,22 +195,24 @@ class MetalsLspService(
},
ReportLevel.fromString(MetalsServerConfig.default.loglevel),
)
private val logger = logging.MetalsLogger.default

private val loggerAccess =
LoggerAccess(
debug = logger.debug(_),
info = logger.info(_),
warning = logger.warn(_),
error = logger.error(_),
)

val client = new telemetry.TelemetryClient(logger = loggerAccess)

private val remoteTelemetryReports = new TelemetryReportContext(
telemetryLevel = () => userConfig.telemetryLevel,
reporterContext = createTelemetryReporterContext,
sanitizers = new TelemetryReportContext.Sanitizers(
workspace = Some(folder.toNIO),
sourceCodeTransformer = Some(ScalametaSourceCodeTransformer),
),
logger = {
val logger = logging.MetalsLogger.default
LoggerAccess(
debug = logger.debug(_),
info = logger.info(_),
warning = logger.warn(_),
error = logger.error(_),
)
},
workspaceSanitizer = new WorkspaceSanitizer(Some(folder.toNIO)),
telemetryClient = client,
logger = loggerAccess,
)

implicit val reports: ReportContext =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import com.google.common.hash.BloomFilter
import com.google.common.hash.Funnels
import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.ReferenceParams
import scala.meta.internal.pc.StandardReport
import scala.meta.pc.ReportContext

final class ReferenceProvider(
workspace: AbsolutePath,
Expand Down Expand Up @@ -174,7 +176,7 @@ final class ReferenceProvider(
s"No references found, index size ${index.size}\n" + fileInIndex
)
report.unsanitized.create(
Report(
StandardReport(
"empty-references",
index
.map { case (path, entry) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ case class UserConfiguration(
verboseCompilation: Boolean = false,
automaticImportBuild: AutoImportBuildKind = AutoImportBuildKind.Off,
scalaCliLauncher: Option[String] = None,
defaultBspToBuildTool: Boolean = false,
telemetryLevel: TelemetryLevel = TelemetryLevel.default,
) {

Expand Down Expand Up @@ -362,8 +363,8 @@ object UserConfiguration {
"off",
"all",
"Import build when changes detected without prompting",
"""|Automatically import builds rather than prompting the user to choose. "initial" will
|only automatically import a build when a project is first opened, "all" will automate
"""|Automatically import builds rather than prompting the user to choose. "initial" will
|only automatically import a build when a project is first opened, "all" will automate
|build imports after subsequent changes as well.""".stripMargin,
),
UserConfigurationOption(
Expand All @@ -380,8 +381,8 @@ object UserConfiguration {
"off",
"all",
"Import build when changes detected without prompting",
"""|Automatically import builds rather than prompting the user to choose. "initial" will
|only automatically import a build when a project is first opened, "all" will automate
"""|Automatically import builds rather than prompting the user to choose. "initial" will
|only automatically import a build when a project is first opened, "all" will automate
|build imports after subsequent changes as well.""".stripMargin,
),
UserConfigurationOption(
Expand Down Expand Up @@ -654,10 +655,9 @@ object UserConfiguration {
scalafixRulesDependencies = scalafixRulesDependencies,
customProjectRoot = customProjectRoot,
verboseCompilation = verboseCompilation,
scalaCliLauncher = None,
telemetryLevel = telemetryLevel,
autoImportBuilds = autoImportBuilds,
scalaCliLauncher = scalaCliLauncher,
automaticImportBuild = autoImportBuilds,
defaultBspToBuildTool = defaultBspToBuildTool,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import scala.meta.internal.metals.Compilers
import scala.meta.internal.metals.DefinitionProvider
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.ReferenceProvider
import scala.meta.internal.metals.ReportContext
import scala.meta.internal.metals.TextEdits
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.parsing.Trees
Expand All @@ -30,6 +29,7 @@ import scala.meta.internal.semanticdb.TextDocument
import scala.meta.internal.{semanticdb => s}
import scala.meta.io.AbsolutePath
import scala.meta.pc.CancelToken
import scala.meta.pc.ReportContext

import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.MessageParams
Expand Down
Original file line number Diff line number Diff line change
@@ -1,84 +1,46 @@
package scala.meta.internal.telemetry

import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.util.Random
import scala.util.Success
import sttp.client3._

import scala.meta.internal.metals.LoggerAccess
import scala.meta.internal.metals.TelemetryLevel
import scala.meta.internal.telemetry

import requests.Response
import sttp.tapir.client.sttp.SttpClientInterpreter
import sttp.model.Uri
import com.google.common.util.concurrent.RateLimiter

object TelemetryClient {

case class Config(serverHost: String)
case class Config(serverHost: Uri)
object Config {
// private final val DefaultTelemetryEndpoint =
// "https://scala3.westeurope.cloudapp.azure.com/telemetry"
private final val DefaultTelemetryEndpoint = "http://localhost:8081"
private final val DefaultTelemetryEndpoint = uri"http://localhost:8081"
val default: Config = Config(DefaultTelemetryEndpoint)
}

private class TelemetryRequest[In](
endpoint: telemetry.FireAndForgetEndpoint[In],
logger: LoggerAccess,
)(implicit config: Config, ec: ExecutionContext) {
private val endpointURL = s"${config.serverHost}${endpoint.uri}"
private val requester = requests.send(endpoint.method)
println(s"TelemetryClient: sending $endpointURL")
private val interpreter = SttpClientInterpreter()

def apply(data: In): Unit = {
val json = endpoint.encodeInput(data)
val response = execute(json)
acknowledgeResponse(response)
}

private def execute(
data: String,
retries: Int = 3,
backoffMillis: Int = 100,
): Future[Response] = Future {
requester(
url = endpointURL,
data = data,
keepAlive = false,
check = false,
)
}.recoverWith {
case _: requests.TimeoutException | _: requests.UnknownHostException
if retries > 0 =>
Thread.sleep(backoffMillis)
execute(data, retries - 1, backoffMillis + Random.nextInt(1000))
}

private def acknowledgeResponse(response: Future[Response]): Unit =
response.onComplete {
case Success(value) if value.is2xx =>
case _ =>
logger.debug(
s"${endpoint.method}:${endpoint.uri} should never result in error, got ${response}"
)
}
}
}

private[meta] class TelemetryClient(
telemetryLevel: () => TelemetryLevel,
class TelemetryClient(
config: TelemetryClient.Config = TelemetryClient.Config.default,
logger: LoggerAccess = LoggerAccess.system,
)(implicit ec: ExecutionContext)
extends telemetry.TelemetryService {
) {
import TelemetryClient._
import telemetry.TelemetryService._

implicit private def clientConfig: Config = config

private val sendErrorReport0 =
new TelemetryRequest(sendErrorReportEndpoint, logger)

def sendErrorReport(report: telemetry.ErrorReport): Unit =
if (telemetryLevel().enabled) sendErrorReport0(report)

val rateLimiter = RateLimiter.create(1.0 / 5.0)

val backend = HttpClientFutureBackend()
val sendReport: ErrorReport => Unit = report => {
if (rateLimiter.tryAcquire()) {
logger.debug("Sending telemetry report.")
interpreter
.toClient(
TelemetryEndpoints.sendReport,
baseUri = Some(config.serverHost),
backend = backend,
)
.apply(report)
} else logger.debug("Report was omitted, because of quota")
()
}
}
Loading

0 comments on commit cca5308

Please sign in to comment.